refactor: serve static frontend with Go APIs
This commit is contained in:
1 parent
763d2f0648
commit
b14b4fced7
101 files changed
+963
-3928
No files matched your search
@@ -0,0 +1,106 @@
|
||||
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");
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user