diff --git a/web/__tests__/connections-actions.test.ts b/web/__tests__/connections-actions.test.ts new file mode 100644 index 0000000..9d9e2d8 --- /dev/null +++ b/web/__tests__/connections-actions.test.ts @@ -0,0 +1,45 @@ +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { revalidatePathMock, syncRecentStravaMock } = vi.hoisted(() => ({ + revalidatePathMock: vi.fn(), + syncRecentStravaMock: vi.fn(), +})) + +vi.mock("next/cache", () => ({ + revalidatePath: revalidatePathMock, +})) + +vi.mock("@/lib/server/strava/sync", () => ({ + importAllStravaHistory: vi.fn(), + importStravaHistory: vi.fn(), + syncRecentStrava: syncRecentStravaMock, +})) + +vi.mock("@/lib/server/garmin/sync", () => ({ + syncGarminMetrics: vi.fn(), +})) + +vi.mock("@/lib/supabase/server", () => ({ + createClient: async () => ({ + auth: { + getUser: async () => ({ data: { user: { id: "user-1" } } }), + }, + }), +})) + +import { syncStrava } from "@/app/(app)/connections/actions" + +describe("connections actions", () => { + beforeEach(() => { + vi.clearAllMocks() + syncRecentStravaMock.mockResolvedValue({ imported: 1, skipped: 0 }) + }) + + it("revalidates progression after a Strava sync", async () => { + await expect(syncStrava()).resolves.toEqual({ synced: 1 }) + + expect(revalidatePathMock).toHaveBeenCalledWith("/connections") + expect(revalidatePathMock).toHaveBeenCalledWith("/dashboard") + expect(revalidatePathMock).toHaveBeenCalledWith("/progression") + }) +}) diff --git a/web/__tests__/middleware.test.ts b/web/__tests__/middleware.test.ts new file mode 100644 index 0000000..27b1432 --- /dev/null +++ b/web/__tests__/middleware.test.ts @@ -0,0 +1,46 @@ +import { NextRequest } from "next/server" +import { beforeEach, describe, expect, it, vi } from "vitest" + +const { getUserMock } = vi.hoisted(() => ({ + getUserMock: vi.fn(), +})) + +vi.mock("@supabase/ssr", () => ({ + createServerClient: () => ({ + auth: { + getUser: getUserMock, + }, + }), +})) + +import { updateSession } from "@/lib/supabase/middleware" + +function request(path: string) { + return new NextRequest(`https://sporttrack.test${path}`) +} + +describe("updateSession", () => { + beforeEach(() => { + vi.clearAllMocks() + vi.stubEnv("NEXT_PUBLIC_SUPABASE_URL", "https://supabase.test") + vi.stubEnv("NEXT_PUBLIC_SUPABASE_ANON_KEY", "anon-key") + getUserMock.mockResolvedValue({ data: { user: null } }) + }) + + it.each(["/api/cron/garmin", "/api/cron/polar"])( + "allows unauthenticated cron route %s to reach its handler", + async (path) => { + const response = await updateSession(request(path)) + + expect(response.status).toBe(200) + expect(response.headers.get("location")).toBeNull() + }, + ) + + it("redirects unauthenticated app routes to login", async () => { + const response = await updateSession(request("/dashboard")) + + expect(response.status).toBe(307) + expect(response.headers.get("location")).toBe("https://sporttrack.test/login?redirect=%2Fdashboard") + }) +}) diff --git a/web/app/(app)/connections/actions.ts b/web/app/(app)/connections/actions.ts index 6799704..0dbfd14 100644 --- a/web/app/(app)/connections/actions.ts +++ b/web/app/(app)/connections/actions.ts @@ -8,6 +8,12 @@ import { createClient } from "@/lib/supabase/server" const POLAR_FULL_HISTORY_DAYS = 3650 +function revalidateStravaViews() { + revalidatePath("/connections") + revalidatePath("/dashboard") + revalidatePath("/progression") +} + export async function syncStrava(): Promise<{ synced?: number; error?: string }> { const supabase = await createClient() const { @@ -18,8 +24,7 @@ export async function syncStrava(): Promise<{ synced?: number; error?: string }> try { const { imported } = await syncRecentStrava(user.id) - revalidatePath("/connections") - revalidatePath("/dashboard") + revalidateStravaViews() return { synced: imported } } catch (e) { return { error: e instanceof Error ? e.message : "Synchronisation échouée" } @@ -38,8 +43,7 @@ export async function syncStravaHistory( try { const { imported } = await importStravaHistory(user.id, days) - revalidatePath("/connections") - revalidatePath("/dashboard") + revalidateStravaViews() return { synced: imported } } catch (e) { return { error: e instanceof Error ? e.message : "Import historique échoué" } @@ -56,8 +60,7 @@ export async function syncAllStravaHistory(): Promise<{ synced?: number; error?: try { const { imported } = await importAllStravaHistory(user.id) - revalidatePath("/connections") - revalidatePath("/dashboard") + revalidateStravaViews() return { synced: imported } } catch (e) { return { error: e instanceof Error ? e.message : "Import complet échoué" } @@ -119,6 +122,7 @@ export async function disconnectStrava(): Promise<{ success?: boolean; error?: s if (error) return { error: error.message } revalidatePath("/connections") + revalidatePath("/progression") return { success: true } } diff --git a/web/app/(app)/health/page.tsx b/web/app/(app)/health/page.tsx index 69fe09d..50d0c39 100644 --- a/web/app/(app)/health/page.tsx +++ b/web/app/(app)/health/page.tsx @@ -209,7 +209,7 @@ export default async function HealthPage() { {/* Main recovery metrics grid */}