Files
NianAIGC/tests/static-frontend-architecture.test.ts

107 lines
4.0 KiB
TypeScript

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<string[]> {
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<string[]> {
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<typeof fetch>().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");
}
});
});