diff --git a/apps/frontend/src/app/(dashboard)/layout.test.tsx b/apps/frontend/src/app/dashboard/layout.test.tsx similarity index 100% rename from apps/frontend/src/app/(dashboard)/layout.test.tsx rename to apps/frontend/src/app/dashboard/layout.test.tsx diff --git a/apps/frontend/src/app/(dashboard)/layout.tsx b/apps/frontend/src/app/dashboard/layout.tsx similarity index 100% rename from apps/frontend/src/app/(dashboard)/layout.tsx rename to apps/frontend/src/app/dashboard/layout.tsx diff --git a/apps/frontend/src/app/(dashboard)/page.tsx b/apps/frontend/src/app/dashboard/page.tsx similarity index 99% rename from apps/frontend/src/app/(dashboard)/page.tsx rename to apps/frontend/src/app/dashboard/page.tsx index 5318b58..3d8a268 100644 --- a/apps/frontend/src/app/(dashboard)/page.tsx +++ b/apps/frontend/src/app/dashboard/page.tsx @@ -185,7 +185,7 @@ export default function DashboardPage() { >

{recipe.title}

- {recipe.calories ? `${Math.round(recipe.calories)} cal` : "N/A"} ·{" "} + {recipe.calories ? `${Math.round(recipe.calories)} cal` : "N/A"} ·{" "} {recipe.readyInMinutes || "N/A"} min

@@ -205,4 +205,3 @@ export default function DashboardPage() { ); } - diff --git a/apps/frontend/src/app/(dashboard)/recipes/page.tsx b/apps/frontend/src/app/dashboard/recipes/page.tsx similarity index 100% rename from apps/frontend/src/app/(dashboard)/recipes/page.tsx rename to apps/frontend/src/app/dashboard/recipes/page.tsx diff --git a/apps/frontend/src/lib/api/auth.api.ts b/apps/frontend/src/lib/api/auth.api.ts index a3a666b..57e03cf 100644 --- a/apps/frontend/src/lib/api/auth.api.ts +++ b/apps/frontend/src/lib/api/auth.api.ts @@ -16,10 +16,11 @@ interface AuthResponse { status: string; data: { user: User; - session: { - access_token: string; - refresh_token: string; - expires_at: number; + tokens: { + accessToken: string; + refreshToken: string; + expiresIn: number; + expiresAt: number; }; }; error: null; diff --git a/apps/frontend/src/lib/hooks/use-auth.test.tsx b/apps/frontend/src/lib/hooks/use-auth.test.tsx new file mode 100644 index 0000000..8cf1eed --- /dev/null +++ b/apps/frontend/src/lib/hooks/use-auth.test.tsx @@ -0,0 +1,129 @@ +import React, { type ReactNode } from "react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { renderHook, waitFor } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { useAuth } from "./use-auth"; + +const { + clearAuthMock, + loginMock, + pushMock, + setAuthMock, + signupMock, + toastSuccessMock, +} = vi.hoisted(() => ({ + clearAuthMock: vi.fn(), + loginMock: vi.fn(), + pushMock: vi.fn(), + setAuthMock: vi.fn(), + signupMock: vi.fn(), + toastSuccessMock: vi.fn(), +})); + +vi.mock("../api/auth.api", () => ({ + authApi: { + login: loginMock, + signup: signupMock, + logout: vi.fn(), + getCurrentUser: vi.fn(), + }, +})); + +vi.mock("../store/auth-store", () => ({ + useAuthStore: () => ({ + user: null, + setAuth: setAuthMock, + clearAuth: clearAuthMock, + }), +})); + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: pushMock, + }), +})); + +vi.mock("sonner", () => ({ + toast: { + success: toastSuccessMock, + }, +})); + +function createWrapper() { + const queryClient = new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, + }); + + return function Wrapper({ children }: { children: ReactNode }) { + return ( + {children} + ); + }; +} + +describe("useAuth", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("stores backend tokens and navigates after login", async () => { + const user = { id: "user-1", email: "test@example.com" }; + loginMock.mockResolvedValue({ + user, + tokens: { + accessToken: "access-token", + refreshToken: "refresh-token", + expiresIn: 3600, + expiresAt: 123456, + }, + }); + + const { result } = renderHook(() => useAuth(), { wrapper: createWrapper() }); + + result.current.login({ email: "test@example.com", password: "password123" }); + + await waitFor(() => + expect(setAuthMock).toHaveBeenCalledWith( + user, + "access-token", + "refresh-token" + ) + ); + expect(pushMock).toHaveBeenCalledWith("/dashboard"); + expect(toastSuccessMock).toHaveBeenCalledWith("Welcome back!"); + }); + + it("stores backend tokens and navigates after signup", async () => { + const user = { id: "user-2", email: "new@example.com" }; + signupMock.mockResolvedValue({ + user, + tokens: { + accessToken: "new-access-token", + refreshToken: "new-refresh-token", + expiresIn: 3600, + expiresAt: 123456, + }, + }); + + const { result } = renderHook(() => useAuth(), { wrapper: createWrapper() }); + + result.current.signup({ + email: "new@example.com", + password: "password123", + displayName: "New User", + }); + + await waitFor(() => + expect(setAuthMock).toHaveBeenCalledWith( + user, + "new-access-token", + "new-refresh-token" + ) + ); + expect(pushMock).toHaveBeenCalledWith("/dashboard"); + expect(toastSuccessMock).toHaveBeenCalledWith("Account created successfully!"); + }); +}); diff --git a/apps/frontend/src/lib/hooks/use-auth.ts b/apps/frontend/src/lib/hooks/use-auth.ts index 9eb8826..ed07111 100644 --- a/apps/frontend/src/lib/hooks/use-auth.ts +++ b/apps/frontend/src/lib/hooks/use-auth.ts @@ -12,7 +12,7 @@ export function useAuth() { const loginMutation = useMutation({ mutationFn: authApi.login, onSuccess: (data) => { - setAuth(data.user, data.session.access_token, data.session.refresh_token); + setAuth(data.user, data.tokens.accessToken, data.tokens.refreshToken); toast.success("Welcome back!"); router.push("/dashboard"); }, @@ -21,7 +21,7 @@ export function useAuth() { const signupMutation = useMutation({ mutationFn: authApi.signup, onSuccess: (data) => { - setAuth(data.user, data.session.access_token, data.session.refresh_token); + setAuth(data.user, data.tokens.accessToken, data.tokens.refreshToken); toast.success("Account created successfully!"); router.push("/dashboard"); },