import { access, readFile, readdir } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; import { safeBrowserLocationNext, safeBrowserNext } from "@/lib/client/browser-auth"; import nextConfig from "../next.config"; const repositoryRoot = dirname(dirname(fileURLToPath(import.meta.url))); const appRoot = join(repositoryRoot, "app"); afterEach(() => vi.unstubAllGlobals()); async function findProductionEntryFiles(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }); const nested = await Promise.all(entries.map(async (entry) => { const path = join(directory, entry.name); if (entry.isDirectory()) return findProductionEntryFiles(path); return entry.isFile() && /^(?:layout|page|route)\.tsx?$/.test(entry.name) ? [path] : []; })); return nested.flat(); } async function findFilesOrEmpty(directory: string): Promise { const entries = await readdir(directory, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => { if (error.code === "ENOENT") return []; throw error; }); const nested = await Promise.all(entries.map(async (entry) => { const path = join(directory, entry.name); return entry.isDirectory() ? findFilesOrEmpty(path) : [path]; })); return nested.flat(); } describe("static frontend architecture", () => { it("exports an image-optimizer-independent static site", () => { expect(nextConfig.output).toBe("export"); expect(nextConfig.images?.unoptimized).toBe(true); }); it("has no Next server runtime entry points", async () => { expect(await findFilesOrEmpty(join(appRoot, "api"))).toEqual([]); expect(await findFilesOrEmpty(join(appRoot, "uploads"))).toEqual([]); expect(await findFilesOrEmpty(join(appRoot, "generated-results"))).toEqual([]); await expect(access(join(repositoryRoot, "middleware.ts"))).rejects.toMatchObject({ code: "ENOENT" }); const entryFiles = await findProductionEntryFiles(appRoot); expect(entryFiles.filter((path) => path.endsWith("route.ts"))).toEqual([]); for (const entryFile of entryFiles) { const source = await readFile(entryFile, "utf8"); expect(source, entryFile).not.toMatch(/(?:from\s+|import\s*\()?["']@\/lib\/server(?:\/|["'])/); expect(source, entryFile).not.toMatch(/(?:from\s+|import\s*\()?["']next\/(?:headers|server)["']/); } }); it("loads current identity from the same-origin Go API", async () => { const responseBody = { authenticated: false, authRequired: true, authConfigured: true, authMode: null, user: null }; const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify(responseBody), { status: 200, headers: { "content-type": "application/json" } }) ); vi.stubGlobal("fetch", fetchMock); const { fetchBrowserAuthState } = await import("@/lib/client/browser-auth"); await expect(fetchBrowserAuthState()).resolves.toEqual(responseBody); expect(fetchMock).toHaveBeenCalledOnce(); expect(fetchMock).toHaveBeenCalledWith("/api/auth/me", { method: "GET", cache: "no-store", credentials: "same-origin", headers: { Accept: "application/json" }, signal: undefined }); }); it("keeps safe same-origin redirects away from auth endpoints", () => { expect(safeBrowserNext("/billing?tab=wallet#history")).toBe("/billing?tab=wallet#history"); expect(safeBrowserLocationNext({ pathname: "/create", search: "?mode=video", hash: "#x" })).toBe("/create?mode=video#x"); expect(safeBrowserNext("//evil.example/path", "/create?mode=video#x")).toBe("/create?mode=video#x"); for (const unsafe of [ "//evil.example/path", "/\\evil.example/path", "/api/auth/logout", "/auth/login", "/auth/admin-login" ]) { expect(safeBrowserNext(unsafe), unsafe).toBe("/create"); } }); });