diff --git a/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx b/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx index 167a870fe..5578b6a12 100644 --- a/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx +++ b/frontend/app/chat/[chatId]/components/ChatInput/__tests__/ChatInput.test.tsx @@ -19,9 +19,7 @@ afterEach(() => { describe("ChatInput", () => { it("should render correctly", () => { // Rendering the ChatInput component - const { getByTestId, baseElement } = render(); - - console.log({ baseElement }); + const { getByTestId } = render(); const chatInputForm = getByTestId("chat-input-form"); expect(chatInputForm).toBeDefined(); diff --git a/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts b/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts new file mode 100644 index 000000000..ae755f11f --- /dev/null +++ b/frontend/app/config/components/ApiKeyConfig/hooks/__tests__/useApiKeyConfig.test.ts @@ -0,0 +1,67 @@ +import { act, renderHook } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { useApiKeyConfig } from "../useApiKeyConfig"; + +const createApiKeyMock = vi.fn(() => "dummyApiKey"); +const trackMock = vi.fn((props: unknown) => ({ props })); + +const useAuthApiMock = vi.fn(() => ({ + createApiKey: () => createApiKeyMock(), +})); + +const useEventTrackingMock = vi.fn(() => ({ + track: (props: unknown) => trackMock(props), +})); + +vi.mock("@/lib/api/auth/useAuthApi", () => ({ + useAuthApi: () => useAuthApiMock(), +})); +vi.mock("@/services/analytics/useEventTracking", () => ({ + useEventTracking: () => useEventTrackingMock(), +})); + +describe("useApiKeyConfig", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should set the apiKey when handleCreateClick is called", async () => { + const { result } = renderHook(() => useApiKeyConfig()); + + await act(async () => { + await result.current.handleCreateClick(); + }); + + expect(createApiKeyMock).toHaveBeenCalledTimes(1); + expect(trackMock).toHaveBeenCalledWith("CREATE_API_KEY"); + expect(result.current.apiKey).toBe("dummyApiKey"); + }); + + it("should call copyToClipboard when handleCopyClick is called with a non-empty apiKey", () => { + vi.mock("react", async () => { + const actual = await vi.importActual("react"); + + return { + ...actual, + useState: () => ["dummyApiKey", vi.fn()], + }; + }); + //@ts-ignore - clipboard is not actually readonly + global.navigator.clipboard = { + writeText: vi.fn(), + }; + + const { result } = renderHook(() => useApiKeyConfig()); + + act(() => result.current.handleCopyClick()); + + expect(trackMock).toHaveBeenCalledTimes(1); + expect(trackMock).toHaveBeenCalledWith("COPY_API_KEY"); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(global.navigator.clipboard.writeText).toHaveBeenCalledWith( + "dummyApiKey" + ); + }); +}); diff --git a/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts b/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts index d4d81fb29..6d9b3d064 100644 --- a/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts +++ b/frontend/app/config/components/ApiKeyConfig/hooks/useApiKeyConfig.ts @@ -1,21 +1,19 @@ import { useState } from "react"; -import { useAxios } from "@/lib/hooks"; +import { useAuthApi } from "@/lib/api/auth/useAuthApi"; import { useEventTracking } from "@/services/analytics/useEventTracking"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useApiKeyConfig = () => { const [apiKey, setApiKey] = useState(""); - const { axiosInstance } = useAxios(); const { track } = useEventTracking(); + const { createApiKey } = useAuthApi(); const handleCreateClick = async () => { try { void track("CREATE_API_KEY"); - const response = await axiosInstance.post<{ api_key: string }>( - "/api-key" - ); // replace with your api-key endpoint URL - setApiKey(response.data.api_key); + const createdApiKey = await createApiKey(); + setApiKey(createdApiKey); } catch (error) { console.error("Error creating API key: ", error); } diff --git a/frontend/app/config/components/BackendConfig.tsx b/frontend/app/config/components/BackendConfig/BackendConfig.tsx similarity index 100% rename from frontend/app/config/components/BackendConfig.tsx rename to frontend/app/config/components/BackendConfig/BackendConfig.tsx diff --git a/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx b/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx new file mode 100644 index 000000000..481fe7499 --- /dev/null +++ b/frontend/app/config/components/BackendConfig/__tests__/BackendConfig.test.tsx @@ -0,0 +1,24 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { BackendConfig } from "../BackendConfig"; + +const registerMock = vi.fn(() => void 0); + +describe("BackendConfig", () => { + it("renders the component with fields and labels", () => { + //@ts-expect-error we don't need registerMock to return all `register` keys + const { getByText } = render(); + + expect(getByText("Backend config")).toBeDefined(); + expect(getByText("Backend URL")).toBeDefined(); + expect(getByText("Supabase URL")).toBeDefined(); + expect(getByText("Supabase key")).toBeDefined(); + expect(getByText("Keep in local")).toBeDefined(); + expect(getByText("Keep in local")).toBeDefined(); + expect(registerMock).toHaveBeenCalledWith("backendUrl"); + expect(registerMock).toHaveBeenCalledWith("supabaseUrl"); + expect(registerMock).toHaveBeenCalledWith("supabaseKey"); + expect(registerMock).toHaveBeenCalledWith("backendUrl"); + }); +}); diff --git a/frontend/app/config/components/ConfigForm.tsx b/frontend/app/config/components/ConfigForm.tsx index 2d06f2b3a..fc499954e 100644 --- a/frontend/app/config/components/ConfigForm.tsx +++ b/frontend/app/config/components/ConfigForm.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/navigation"; import Button from "@/lib/components/ui/Button"; import { useConfig } from "../hooks/useConfig"; -import { BackendConfig } from "./BackendConfig"; +import { BackendConfig } from "./BackendConfig/BackendConfig"; import { ModelConfig } from "./ModelConfig"; import { UserAccountSection } from "./UserAccountSection"; diff --git a/frontend/lib/api/auth/__tests__/useAuthApi.test.ts b/frontend/lib/api/auth/__tests__/useAuthApi.test.ts new file mode 100644 index 000000000..36d1cd022 --- /dev/null +++ b/frontend/lib/api/auth/__tests__/useAuthApi.test.ts @@ -0,0 +1,31 @@ +import { renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { useAuthApi } from "../useAuthApi"; + +const axiosPostMock = vi.fn(() => ({ + data: { + api_key: "", + }, +})); + +vi.mock("@/lib/hooks", () => ({ + useAxios: () => ({ + axiosInstance: { + post: axiosPostMock, + }, + }), +})); + +describe("useAuthApi", () => { + it("should call createApiKey with the correct parameters", async () => { + const { + result: { + current: { createApiKey }, + }, + } = renderHook(() => useAuthApi()); + await createApiKey(); + expect(axiosPostMock).toHaveBeenCalledTimes(1); + expect(axiosPostMock).toHaveBeenCalledWith("/api-key"); + }); +}); diff --git a/frontend/lib/api/auth/auth.ts b/frontend/lib/api/auth/auth.ts new file mode 100644 index 000000000..4c170aa5f --- /dev/null +++ b/frontend/lib/api/auth/auth.ts @@ -0,0 +1,9 @@ +import { AxiosInstance } from "axios"; + +export const createApiKey = async ( + axiosInstance: AxiosInstance +): Promise => { + const response = await axiosInstance.post<{ api_key: string }>("/api-key"); + + return response.data.api_key; +}; diff --git a/frontend/lib/api/auth/useAuthApi.ts b/frontend/lib/api/auth/useAuthApi.ts new file mode 100644 index 000000000..68a4ed0bd --- /dev/null +++ b/frontend/lib/api/auth/useAuthApi.ts @@ -0,0 +1,12 @@ +import { useAxios } from "@/lib/hooks"; + +import { createApiKey } from "./auth"; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const useAuthApi = () => { + const { axiosInstance } = useAxios(); + + return { + createApiKey: async () => createApiKey(axiosInstance), + }; +};