import { describe, expect, mock, test } from "bun:test"; // Fake app records keyed by ID const compiledApp = { id: "app-1", name: "Compiled App", htmlDefinition: "", schemaJson: "{}", createdAt: 0, updatedAt: 0, formatVersion: 2, }; const legacyApp = { id: "legacy-1", name: "Legacy App", htmlDefinition: "
Hello
", schemaJson: "{}", createdAt: 0, updatedAt: 0, }; const apps = new Map([ ["app-1", compiledApp], ["legacy-1", legacyApp], ]); const UNSUPPORTED_HTML = `

This app uses the retired single-file format and can no longer be opened.

`; mock.module("../apps/app-store.js", () => ({ resolveAppSource: (id: string) => { const app = apps.get(id); if (!app) { return null; } return { id, name: app.name, dirName: id, sourceDir: `/fake/apps/${id}`, origin: { kind: "workspace" as const }, }; }, readAppFileBytesFromDir: () => Buffer.from(""), // Only the legacy app's directory has a root index.html and no src/. isLegacySingleFileDir: (sourceDir: string) => sourceDir === "/fake/apps/legacy-1", UNSUPPORTED_LEGACY_APP_HTML: UNSUPPORTED_HTML, })); // Mock shared-app-links-store (imported by app-routes but unused here) mock.module("../apps/shared-app-links-store.js", () => ({ createSharedAppLink: () => ({ shareToken: "tok" }), getSharedAppLink: () => null, incrementDownloadCount: () => {}, deleteSharedAppLinkByToken: () => false, })); // Stub fs so the serve path finds a fake dist/index.html for the compiled app mock.module("node:fs", () => ({ existsSync: (p: string) => p === "/fake/apps/app-1/dist/index.html", readFileSync: (p: string, _enc?: string) => { if (p === "/fake/apps/app-1/dist/index.html") { return ''; } return ""; }, })); import { ROUTES } from "../runtime/routes/app-routes.js"; import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js"; import type { ResponseHeaderArgs } from "../runtime/routes/types.js"; /** Find a route by operationId. */ function getRoute(operationId: string) { const route = ROUTES.find((r) => r.operationId === operationId); if (!route) { throw new Error(`Route not found: ${operationId}`); } return route; } /** Resolve responseHeaders from a route definition for the given args. */ function getResponseHeaders( operationId: string, args: ResponseHeaderArgs, ): Record { const route = getRoute(operationId); if (!route.responseHeaders) { return {}; } if (typeof route.responseHeaders === "function") { return route.responseHeaders(args); } return route.responseHeaders; } /** Parse CSP header into a directive map. */ function parseCsp(header: string): Record { const directives: Record = {}; for (const part of header.split(";")) { const trimmed = part.trim(); const spaceIdx = trimmed.indexOf(" "); if (spaceIdx === -1) { continue; } directives[trimmed.slice(0, spaceIdx)] = trimmed.slice(spaceIdx + 1); } return directives; } describe("app-routes CSP headers", () => { test("does NOT include 'unsafe-inline' in script-src", () => { const headers = getResponseHeaders("pages_serve", { pathParams: { appId: "app-1" }, }); const directives = parseCsp(headers["Content-Security-Policy"]); expect(directives["script-src"]).not.toContain("'unsafe-inline'"); }); test("includes 'self' in script-src for external main.js", () => { const headers = getResponseHeaders("pages_serve", { pathParams: { appId: "app-1" }, }); const directives = parseCsp(headers["Content-Security-Policy"]); expect(directives["script-src"]).toContain("'self'"); }); test("includes 'unsafe-inline' in style-src", () => { const headers = getResponseHeaders("pages_serve", { pathParams: { appId: "app-1" }, }); const directives = parseCsp(headers["Content-Security-Policy"]); expect(directives["style-src"]).toContain("'unsafe-inline'"); }); test("has img-src with self, data, and https", () => { const headers = getResponseHeaders("pages_serve", { pathParams: { appId: "app-1" }, }); const directives = parseCsp(headers["Content-Security-Policy"]); expect(directives["img-src"]).toContain("'self'"); expect(directives["img-src"]).toContain("data:"); expect(directives["img-src"]).toContain("https:"); }); }); describe("handleServePage", () => { const pageHandler = getRoute("pages_serve").handler; test("serves compiled dist/index.html for compiled apps", () => { const html = pageHandler({ pathParams: { appId: "app-1" } }) as string; expect(html).toContain("/v1/apps/app-1/dist/main.js"); }); test("serves the unsupported-format message for legacy single-file apps", () => { const html = pageHandler({ pathParams: { appId: "legacy-1" } }) as string; expect(html).toContain(UNSUPPORTED_HTML); expect(html).not.toContain("
Hello
"); }); test("throws NotFoundError for unknown apps", () => { expect(() => pageHandler({ pathParams: { appId: "nope" } })).toThrow( NotFoundError, ); }); }); describe("handleServeDistFile appId validation", () => { const distHandler = getRoute("apps_dist_file").handler; test("rejects appId with encoded path traversal (..)", () => { expect(() => distHandler({ pathParams: { appId: "..", filename: "main.js" } }), ).toThrow(BadRequestError); }); test("rejects appId with forward slash", () => { expect(() => distHandler({ pathParams: { appId: "../../etc", filename: "main.js" }, }), ).toThrow(BadRequestError); }); test("rejects appId with backslash", () => { expect(() => distHandler({ pathParams: { appId: "foo\\bar", filename: "main.js" }, }), ).toThrow(BadRequestError); }); test("rejects empty appId", () => { expect(() => distHandler({ pathParams: { appId: "", filename: "main.js" } }), ).toThrow(BadRequestError); }); test("rejects appId with leading whitespace", () => { expect(() => distHandler({ pathParams: { appId: " app-1", filename: "main.js" }, }), ).toThrow(BadRequestError); }); test("rejects appId with trailing whitespace", () => { expect(() => distHandler({ pathParams: { appId: "app-1 ", filename: "main.js" }, }), ).toThrow(BadRequestError); }); test("rejects appId containing .. in the middle", () => { expect(() => distHandler({ pathParams: { appId: "foo..bar", filename: "main.js" }, }), ).toThrow(BadRequestError); }); test("allows valid appId and filename (file not found throws NotFoundError)", () => { expect(() => distHandler({ pathParams: { appId: "app-1", filename: "main.js" }, }), ).toThrow(NotFoundError); }); });