import test from "node:test"; import assert from "node:assert/strict"; import {buildConfiguration, cloudflareRuleFromSpec, definitionsFromLabels, syncCloudflareRule} from "./controller.mjs"; const labels = { "com.docker.compose.project": "authentik", "com.docker.compose.service": "authentik", "traefik.enable": "true", "traefik.http.routers.authentik.rule": "Host(`auth.xiteng.site`)", "traefik.http.routers.authentik.entrypoints": "websecure", "traefik.http.routers.authentik.service": "authentik", "traefik.http.routers.authentik.middlewares": "force-https-headers", "traefik.http.routers.authentik.tls": "true", "traefik.http.routers.authentik.tls.certresolver": "cfresolver", "traefik.http.routers.authentik-http.rule": "Host(`auth.xiteng.site`)", "traefik.http.routers.authentik-http.entrypoints": "web", "traefik.http.routers.authentik-http.service": "authentik", "xiteng.site.cache.frontend.enabled": "true", "xiteng.site.cache.frontend.routers": "authentik,authentik-http", "xiteng.site.cache.frontend.paths": "/static/dist/,/assets/", "xiteng.site.cache.frontend.edge-ttl": "604800", "xiteng.site.cache.frontend.browser-ttl": "3600" }; function reverseObjectKeys(value) { if (Array.isArray(value)) { return value.map(reverseObjectKeys); } if (value && typeof value === "object") { return Object.fromEntries( Object.entries(value).reverse().map(([key, nested]) => [key, reverseObjectKeys(nested)]) ); } return value; } test("parses cache definitions without exposing unrelated labels", () => { const definitions = definitionsFromLabels(labels); assert.equal(definitions.size, 1); assert.equal(definitions.get("frontend").paths, "/static/dist/,/assets/"); assert.equal(definitions.get("frontend").routers, "authentik,authentik-http"); }); test("builds higher-priority GET/HEAD routers and preserves base middleware", () => { const built = buildConfiguration([{Id: "one", Labels: labels}], {zoneName: "xiteng.site"}); assert.deepEqual(built.errors, []); assert.equal(built.policyCount, 1); assert.equal(built.routerCount, 2); const generated = Object.values(built.traefik.http.routers); assert.ok(generated.every((router) => router.rule.includes("Method(`GET`)"))); assert.ok(generated.every((router) => router.rule.includes("PathPrefix(`/static/dist/`)"))); assert.ok(generated.some((router) => router.middlewares.includes("force-https-headers@docker"))); assert.ok(generated.every((router) => router.middlewares[0].startsWith("xiteng-static-"))); assert.ok(generated.some((router) => router.tls?.certResolver === "cfresolver")); const middleware = Object.values(built.traefik.http.middlewares)[0]; assert.equal(middleware.headers.customResponseHeaders["Cache-Control"], "public, max-age=3600, stale-while-revalidate=86400"); assert.match(middleware.headers.customResponseHeaders["Cloudflare-CDN-Cache-Control"], /s-maxage=604800/); }); test("creates one Cloudflare expression from exact hosts and safe path prefixes", () => { const built = buildConfiguration([{Id: "one", Labels: labels}], {zoneName: "xiteng.site"}); assert.match(built.cloudflare.expression, /http\.host eq "auth\.xiteng\.site"/); assert.match(built.cloudflare.expression, /starts_with\(http\.request\.uri\.path, "\/assets\/"\)/); const rule = cloudflareRuleFromSpec(built.cloudflare); assert.equal(rule.action_parameters.cache, true); assert.equal(rule.action_parameters.edge_ttl.status_code_ttl[1].value, 0); assert.equal(rule.enabled, true); }); test("does not update a semantically identical Cloudflare rule when API reorders fields", async () => { const built = buildConfiguration([{Id: "one", Labels: labels}], {zoneName: "xiteng.site"}); const current = {id: "managed-rule", ...reverseObjectKeys(cloudflareRuleFromSpec(built.cloudflare))}; const originalFetch = globalThis.fetch; globalThis.fetch = async () => new Response(JSON.stringify({ success: true, result: {id: "cache-ruleset", rules: [current]} }), {status: 200, headers: {"Content-Type": "application/json"}}); try { const result = await syncCloudflareRule(built.cloudflare, {zoneId: "zone", token: "token", dryRun: true}); assert.deepEqual(result, {changed: false, action: "unchanged", ruleId: "managed-rule"}); } finally { globalThis.fetch = originalFetch; } }); test("does not override browser Cache-Control unless browser-ttl is explicit", () => { const edgeOnly = {...labels}; delete edgeOnly["xiteng.site.cache.frontend.browser-ttl"]; const built = buildConfiguration([{Id: "edge-only", Labels: edgeOnly}], {zoneName: "xiteng.site"}); const headers = Object.values(built.traefik.http.middlewares)[0].headers.customResponseHeaders; assert.equal(headers["Cache-Control"], undefined); assert.match(headers["Cloudflare-CDN-Cache-Control"], /s-maxage=604800/); }); test("rejects root and traversal-like cache paths", () => { for (const paths of ["/", "/static/../admin/", "/static//nested/"]) { const broken = {...labels, "xiteng.site.cache.frontend.paths": paths}; const built = buildConfiguration([{Id: paths, Labels: broken}], {zoneName: "xiteng.site"}); assert.equal(built.policyCount, 0); assert.equal(built.errors.length, 1); assert.equal(Object.keys(built.traefik.http.routers).length, 0); assert.equal(Object.keys(built.traefik.http.middlewares).length, 0); } }); test("does not let labels cache a hostname outside the managed zone", () => { const broken = { ...labels, "traefik.http.routers.authentik.rule": "Host(`example.com`)", "traefik.http.routers.authentik-http.rule": "Host(`example.com`)" }; const built = buildConfiguration([{Id: "external", Labels: broken}], {zoneName: "xiteng.site"}); assert.equal(built.policyCount, 0); assert.match(built.errors[0], /outside xiteng\.site/); });