Merge pull request #22698 from BerriAI/litellm_test_projects_hooks
[Test] UI - Projects: Add unit tests for project hooks
This commit is contained in:
commit
2d26209d80
@ -0,0 +1,111 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useCreateProject, ProjectCreateParams } from "./useCreateProject";
|
||||
import { projectKeys, ProjectResponse } from "./useProjects";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => ""),
|
||||
getGlobalLitellmHeaderName: vi.fn(() => "Authorization"),
|
||||
deriveErrorMessage: vi.fn((data: any) => data?.error || "Error"),
|
||||
handleError: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
const mockProject: ProjectResponse = {
|
||||
project_id: "proj-1",
|
||||
project_alias: "Test Project",
|
||||
description: "A test project",
|
||||
team_id: "team-1",
|
||||
budget_id: null,
|
||||
metadata: null,
|
||||
models: ["gpt-4"],
|
||||
spend: 25.0,
|
||||
model_spend: null,
|
||||
model_rpm_limit: null,
|
||||
model_tpm_limit: null,
|
||||
blocked: false,
|
||||
object_permission_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_at: "2024-01-02T00:00:00Z",
|
||||
updated_by: "user-1",
|
||||
litellm_budget_table: null,
|
||||
};
|
||||
|
||||
function makeWrapper(queryClient: QueryClient) {
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
}
|
||||
|
||||
describe("useCreateProject", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { result } = renderHook(() => useCreateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.mutate).toBeDefined();
|
||||
});
|
||||
|
||||
it("should POST to /project/new and return the created project", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
const { result } = renderHook(() => useCreateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
const params: ProjectCreateParams = { team_id: "team-1", project_alias: "New Project" };
|
||||
const data = await result.current.mutateAsync(params);
|
||||
expect(data).toEqual(mockProject);
|
||||
const [url, init] = (global.fetch as any).mock.calls[0];
|
||||
expect(url).toContain("/project/new");
|
||||
expect(init.method).toBe("POST");
|
||||
expect(JSON.parse(init.body)).toMatchObject(params);
|
||||
});
|
||||
|
||||
it("should invalidate project queries on success", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
const { result } = renderHook(() => useCreateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await result.current.mutateAsync({ team_id: "team-1" });
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: projectKeys.all });
|
||||
});
|
||||
|
||||
it("should set isError when the request fails", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ error: "Server error" }),
|
||||
});
|
||||
const { result } = renderHook(() => useCreateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
result.current.mutateAsync({ team_id: "team-1" }).catch(() => {});
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
});
|
||||
|
||||
it("should throw when accessToken is missing", async () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: null, userRole: "Admin" });
|
||||
const { result } = renderHook(() => useCreateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await expect(result.current.mutateAsync({ team_id: "team-1" })).rejects.toThrow(
|
||||
"Access token is required"
|
||||
);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,88 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useDeleteProject } from "./useDeleteProject";
|
||||
import { projectKeys } from "./useProjects";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => ""),
|
||||
getGlobalLitellmHeaderName: vi.fn(() => "Authorization"),
|
||||
deriveErrorMessage: vi.fn((data: any) => data?.error || "Error"),
|
||||
handleError: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
function makeWrapper(queryClient: QueryClient) {
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
}
|
||||
|
||||
describe("useDeleteProject", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { result } = renderHook(() => useDeleteProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.mutate).toBeDefined();
|
||||
});
|
||||
|
||||
it("should send DELETE to /project/delete with the given project IDs", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => ({}) });
|
||||
const { result } = renderHook(() => useDeleteProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await result.current.mutateAsync(["proj-1", "proj-2"]);
|
||||
const [url, init] = (global.fetch as any).mock.calls[0];
|
||||
expect(url).toContain("/project/delete");
|
||||
expect(init.method).toBe("DELETE");
|
||||
expect(JSON.parse(init.body)).toEqual({ project_ids: ["proj-1", "proj-2"] });
|
||||
});
|
||||
|
||||
it("should invalidate project queries on success", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => ({}) });
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
const { result } = renderHook(() => useDeleteProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await result.current.mutateAsync(["proj-1"]);
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: projectKeys.all });
|
||||
});
|
||||
|
||||
it("should set isError when the request fails", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ error: "Not found" }),
|
||||
});
|
||||
const { result } = renderHook(() => useDeleteProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
result.current.mutateAsync(["proj-1"]).catch(() => {});
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
});
|
||||
|
||||
it("should throw when accessToken is missing", async () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: null, userRole: "Admin" });
|
||||
const { result } = renderHook(() => useDeleteProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await expect(result.current.mutateAsync(["proj-1"])).rejects.toThrow(
|
||||
"Access token is required"
|
||||
);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,144 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useProjectDetails } from "./useProjectDetails";
|
||||
import { projectKeys, ProjectResponse } from "./useProjects";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => ""),
|
||||
getGlobalLitellmHeaderName: vi.fn(() => "Authorization"),
|
||||
deriveErrorMessage: vi.fn((data: any) => data?.error || "Error"),
|
||||
handleError: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
const mockProject: ProjectResponse = {
|
||||
project_id: "proj-1",
|
||||
project_alias: "Test Project",
|
||||
description: "A test project",
|
||||
team_id: "team-1",
|
||||
budget_id: null,
|
||||
metadata: null,
|
||||
models: ["gpt-4"],
|
||||
spend: 25.0,
|
||||
model_spend: null,
|
||||
model_rpm_limit: null,
|
||||
model_tpm_limit: null,
|
||||
blocked: false,
|
||||
object_permission_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_at: "2024-01-02T00:00:00Z",
|
||||
updated_by: "user-1",
|
||||
litellm_budget_table: null,
|
||||
};
|
||||
|
||||
const mockProjects: ProjectResponse[] = [
|
||||
mockProject,
|
||||
{ ...mockProject, project_id: "proj-2", project_alias: "Test Project 2" },
|
||||
];
|
||||
|
||||
function makeWrapper(queryClient: QueryClient) {
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
}
|
||||
|
||||
describe("useProjectDetails", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current).toBeDefined();
|
||||
});
|
||||
|
||||
it("should return project details when the request succeeds", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
expect(result.current.data).toEqual(mockProject);
|
||||
});
|
||||
|
||||
it("should call /project/info with the projectId encoded as a query param", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
renderHook(() => useProjectDetails("proj-1"), { wrapper: makeWrapper(queryClient) });
|
||||
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
|
||||
const [url] = (global.fetch as any).mock.calls[0];
|
||||
expect(url).toContain("/project/info");
|
||||
expect(url).toContain("project_id=proj-1");
|
||||
});
|
||||
|
||||
it("should not fetch when projectId is missing", () => {
|
||||
const { result } = renderHook(() => useProjectDetails(undefined), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not fetch when accessToken is missing", () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: null, userRole: "Admin" });
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not fetch when userRole is not an admin role", () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Internal User" });
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should seed initialData from the projects list cache", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
queryClient.setQueryData(projectKeys.list({}), mockProjects);
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.data).toEqual(mockProject);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
await waitFor(() => expect(result.current.isFetching).toBe(false));
|
||||
});
|
||||
|
||||
it("should return undefined initialData when projectId is not in the cache", () => {
|
||||
queryClient.setQueryData(projectKeys.list({}), mockProjects);
|
||||
const { result } = renderHook(() => useProjectDetails("non-existent"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should set isError when the request fails", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ error: "Not found" }),
|
||||
});
|
||||
const { result } = renderHook(() => useProjectDetails("proj-1"), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,124 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useProjects, ProjectResponse } from "./useProjects";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => ""),
|
||||
getGlobalLitellmHeaderName: vi.fn(() => "Authorization"),
|
||||
deriveErrorMessage: vi.fn((data: any) => data?.error || "Error"),
|
||||
handleError: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
const mockProjects: ProjectResponse[] = [
|
||||
{
|
||||
project_id: "proj-1",
|
||||
project_alias: "Test Project",
|
||||
description: "A test project",
|
||||
team_id: "team-1",
|
||||
budget_id: null,
|
||||
metadata: null,
|
||||
models: ["gpt-4"],
|
||||
spend: 25.0,
|
||||
model_spend: null,
|
||||
model_rpm_limit: null,
|
||||
model_tpm_limit: null,
|
||||
blocked: false,
|
||||
object_permission_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_at: "2024-01-02T00:00:00Z",
|
||||
updated_by: "user-1",
|
||||
litellm_budget_table: null,
|
||||
},
|
||||
{
|
||||
project_id: "proj-2",
|
||||
project_alias: "Test Project 2",
|
||||
description: null,
|
||||
team_id: "team-1",
|
||||
budget_id: null,
|
||||
metadata: null,
|
||||
models: [],
|
||||
spend: 0,
|
||||
model_spend: null,
|
||||
model_rpm_limit: null,
|
||||
model_tpm_limit: null,
|
||||
blocked: false,
|
||||
object_permission_id: null,
|
||||
created_at: "2024-01-03T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_at: "2024-01-03T00:00:00Z",
|
||||
updated_by: "user-1",
|
||||
litellm_budget_table: null,
|
||||
},
|
||||
];
|
||||
|
||||
function makeWrapper(queryClient: QueryClient) {
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
}
|
||||
|
||||
describe("useProjects", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProjects });
|
||||
const { result } = renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
expect(result.current).toBeDefined();
|
||||
});
|
||||
|
||||
it("should return projects when the request succeeds", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProjects });
|
||||
const { result } = renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
expect(result.current.data).toEqual(mockProjects);
|
||||
});
|
||||
|
||||
it("should call GET /project/list with the auth header", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProjects });
|
||||
renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
|
||||
const [url, init] = (global.fetch as any).mock.calls[0];
|
||||
expect(url).toContain("/project/list");
|
||||
expect(init.headers["Authorization"]).toBe("Bearer test-token");
|
||||
});
|
||||
|
||||
it("should set isError when the request fails", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ error: "Not authorized" }),
|
||||
});
|
||||
const { result } = renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not fetch when accessToken is missing", () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: null, userRole: "Admin" });
|
||||
const { result } = renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not fetch when userRole is not an admin role", () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Internal User" });
|
||||
const { result } = renderHook(() => useProjects(), { wrapper: makeWrapper(queryClient) });
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,116 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useUpdateProject } from "./useUpdateProject";
|
||||
import { projectKeys, ProjectResponse } from "./useProjects";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => ""),
|
||||
getGlobalLitellmHeaderName: vi.fn(() => "Authorization"),
|
||||
deriveErrorMessage: vi.fn((data: any) => data?.error || "Error"),
|
||||
handleError: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
const mockProject: ProjectResponse = {
|
||||
project_id: "proj-1",
|
||||
project_alias: "Test Project",
|
||||
description: "A test project",
|
||||
team_id: "team-1",
|
||||
budget_id: null,
|
||||
metadata: null,
|
||||
models: ["gpt-4"],
|
||||
spend: 25.0,
|
||||
model_spend: null,
|
||||
model_rpm_limit: null,
|
||||
model_tpm_limit: null,
|
||||
blocked: false,
|
||||
object_permission_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_at: "2024-01-02T00:00:00Z",
|
||||
updated_by: "user-1",
|
||||
litellm_budget_table: null,
|
||||
};
|
||||
|
||||
function makeWrapper(queryClient: QueryClient) {
|
||||
return ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
}
|
||||
|
||||
describe("useUpdateProject", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
global.fetch = vi.fn();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token", userRole: "Admin" });
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const { result } = renderHook(() => useUpdateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
expect(result.current.mutate).toBeDefined();
|
||||
});
|
||||
|
||||
it("should POST to /project/update and return the updated project", async () => {
|
||||
const updated = { ...mockProject, project_alias: "Updated Name" };
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => updated });
|
||||
const { result } = renderHook(() => useUpdateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
const data = await result.current.mutateAsync({
|
||||
projectId: "proj-1",
|
||||
params: { project_alias: "Updated Name" },
|
||||
});
|
||||
expect(data).toEqual(updated);
|
||||
const [url, init] = (global.fetch as any).mock.calls[0];
|
||||
expect(url).toContain("/project/update");
|
||||
expect(JSON.parse(init.body)).toMatchObject({
|
||||
project_id: "proj-1",
|
||||
project_alias: "Updated Name",
|
||||
});
|
||||
});
|
||||
|
||||
it("should invalidate project queries on success", async () => {
|
||||
(global.fetch as any).mockResolvedValue({ ok: true, json: async () => mockProject });
|
||||
const invalidateSpy = vi.spyOn(queryClient, "invalidateQueries");
|
||||
const { result } = renderHook(() => useUpdateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await result.current.mutateAsync({ projectId: "proj-1", params: {} });
|
||||
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: projectKeys.all });
|
||||
});
|
||||
|
||||
it("should set isError when the request fails", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
json: async () => ({ error: "Server error" }),
|
||||
});
|
||||
const { result } = renderHook(() => useUpdateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
result.current.mutateAsync({ projectId: "proj-1", params: {} }).catch(() => {});
|
||||
await waitFor(() => expect(result.current.isError).toBe(true));
|
||||
});
|
||||
|
||||
it("should throw when accessToken is missing", async () => {
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: null, userRole: "Admin" });
|
||||
const { result } = renderHook(() => useUpdateProject(), {
|
||||
wrapper: makeWrapper(queryClient),
|
||||
});
|
||||
await expect(
|
||||
result.current.mutateAsync({ projectId: "proj-1", params: {} })
|
||||
).rejects.toThrow("Access token is required");
|
||||
expect(global.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user