summaryrefslogtreecommitdiff
path: root/workers/branch/test
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-07-22 16:32:18 +0300
committerYuriy Andamasov <yuriy@vyos.io>2026-07-22 16:32:18 +0300
commitc3fca8bd8e67b66a53ff4900759670b2bc4720d9 (patch)
tree5db048e489ea373c6f5eb82efd9b87047d82649e /workers/branch/test
parentf1fc5d8d45242c3b259ce216c0705e828a51fb2a (diff)
downloadvyos-documentation-c3fca8bd8e67b66a53ff4900759670b2bc4720d9.tar.gz
vyos-documentation-c3fca8bd8e67b66a53ff4900759670b2bc4720d9.zip
workers: bare-directory 301 via asset probe + asset-class images + config congruence test (review round 1)
Diffstat (limited to 'workers/branch/test')
-rw-r--r--workers/branch/test/content.test.ts114
1 files changed, 107 insertions, 7 deletions
diff --git a/workers/branch/test/content.test.ts b/workers/branch/test/content.test.ts
index 807b1dd1..94a0d5a6 100644
--- a/workers/branch/test/content.test.ts
+++ b/workers/branch/test/content.test.ts
@@ -1,5 +1,16 @@
import { describe, it, expect } from "vitest";
import worker, { classifyPath, cacheHeaderFor, withDocsHeaders, type Env } from "../src/index";
+// The workers pool has no real filesystem; import the branch wrangler configs as Vite `?raw`
+// assets (pattern from apex/test/manifest.test.ts) so their content is inlined at bundle time
+// for the html_handling congruence pin at the bottom of this file.
+// eslint-disable-next-line import/no-unresolved
+import wranglerRolling from "../wrangler.rolling.jsonc?raw";
+// eslint-disable-next-line import/no-unresolved
+import wranglerV15 from "../wrangler.v15.jsonc?raw";
+// eslint-disable-next-line import/no-unresolved
+import wranglerV14 from "../wrangler.v14.jsonc?raw";
+// eslint-disable-next-line import/no-unresolved
+import wranglerLegacy from "../wrangler.legacy.jsonc?raw";
describe("cache classes (§3.3)", () => {
it("HTML + config class → max-age=0, s-maxage=300", () => {
@@ -80,14 +91,21 @@ describe("default fetch entrypoint", () => {
});
describe("directory-index mapping (html_handling \"none\", §3.2.3 amended 2026-07-22)", () => {
- // Assets binding stub emulating html_handling:"none": exact-path lookups only —
- // no extension inference and no directory auto-index. A bare "/foo/" therefore only
- // resolves because the worker rewrites it to "/foo/index.html" before this runs, which
- // is exactly the RTD-parity behavior under test.
+ // NOTE: these tests mock the ASSETS binding — they exercise the WORKER's directory
+ // mapping + bare-directory probe logic, NOT the wrangler `html_handling` config (which
+ // only exists at deploy time). The config-level backstop is the smoke gate,
+ // scripts/docs_gates/smoke.py; the raw-string congruence test at the bottom of this file
+ // is the unit-level pin against a silent revert.
+ //
+ // Assets binding stub emulating html_handling:"none": exact-path lookups only — no
+ // extension inference and no directory auto-index. A bare "/foo/" resolves only because
+ // the worker rewrites it to "/foo/index.html" first; a bare "/foo" 301s only because the
+ // worker probes "/foo/index.html" and finds it — exactly the behavior under test.
const ASSET_MAP: Record<string, string> = {
"/en/rolling/index.html": "<html>root index</html>",
"/en/rolling/cli.html": "<html>cli page</html>",
"/en/rolling/guide/index.html": "<html>guide index</html>",
+ "/en/rolling/installation/index.html": "<html>installation index</html>",
};
const makeAssetsEnv = (docsEnv: Env["DOCS_ENV"], seen: string[]): Env => ({
@@ -150,15 +168,19 @@ describe("directory-index mapping (html_handling \"none\", §3.2.3 amended 2026-
.toBe("public, max-age=0, s-maxage=300, must-revalidate");
});
- it("extensionless page path (no trailing slash) is not mapped → 404", async () => {
+ it("extensionless file-like path (no dir behind it) probes, misses, then 404s", async () => {
const seen: string[] = [];
const resp = await worker.fetch(
new Request("https://docs.vyos.io/en/rolling/cli"),
makeAssetsEnv("production", seen),
);
expect(resp.status).toBe(404);
- // passed through unmodified; html_handling:"none" does not infer ".html"
- expect(seen).toEqual(["https://docs.vyos.io/en/rolling/cli"]);
+ // probe "/cli/index.html" misses (no such dir) → fall through to the exact-path fetch of
+ // "/cli", which also misses (real asset is cli.html); html_handling:"none" never infers it.
+ expect(seen).toEqual([
+ "https://docs.vyos.io/en/rolling/cli/index.html",
+ "https://docs.vyos.io/en/rolling/cli",
+ ]);
// a 404 must never carry a cacheable page/asset class
expect(resp.headers.get("Cache-Control")).toBe("no-store");
});
@@ -172,4 +194,82 @@ describe("directory-index mapping (html_handling \"none\", §3.2.3 amended 2026-
expect(resp.status).toBe(200);
expect(seen).toEqual(["https://docs.vyos.io/en/rolling/index.html?q=foo"]);
});
+
+ it("bare directory path (real dir behind it) → 301 to the slashed form, query preserved", async () => {
+ const seen: string[] = [];
+ const resp = await worker.fetch(
+ new Request("https://docs.vyos.io/en/rolling/installation?q=x"),
+ makeAssetsEnv("production", seen),
+ );
+ expect(resp.status).toBe(301);
+ expect(resp.headers.get("Location")).toBe("/en/rolling/installation/?q=x");
+ expect(resp.headers.get("X-Docs-Build")).toBe("testsha");
+ // only the index.html probe reached ASSETS — the redirect short-circuits the passthrough
+ expect(seen).toEqual(["https://docs.vyos.io/en/rolling/installation/index.html?q=x"]);
+ });
+
+ it("trailing-slash mapping preserves request method + conditional/range headers", async () => {
+ // Codex: pins that `new Request(mapped, request)` carries method + headers to ASSETS,
+ // so conditional-GET (304) and Range (206) still work on directory URLs.
+ let captured: Request | undefined;
+ const env: Env = {
+ ASSETS: {
+ fetch: async (req: Request) => {
+ captured = req;
+ return new Response(null, { headers: { "content-type": "text/html" } });
+ },
+ } as unknown as Fetcher,
+ DOCS_BUILD_SHA: "testsha",
+ DOCS_ENV: "production",
+ };
+ await worker.fetch(
+ new Request("https://docs.vyos.io/en/rolling/", {
+ method: "HEAD",
+ headers: { "If-None-Match": '"abc"', Range: "bytes=0-0" },
+ }),
+ env,
+ );
+ expect(captured?.url).toBe("https://docs.vyos.io/en/rolling/index.html");
+ expect(captured?.method).toBe("HEAD");
+ expect(captured?.headers.get("If-None-Match")).toBe('"abc"');
+ expect(captured?.headers.get("Range")).toBe("bytes=0-0");
+ });
+});
+
+describe("asset cache-class classification (§3.3, extended)", () => {
+ it("images, fonts, and the /_images/ + /_static/ trees get the asset class", () => {
+ for (const p of [
+ "/en/rolling/_images/diagram.png",
+ "/en/rolling/_static/fonts/roboto.woff2",
+ "/en/rolling/logo.svg",
+ "/en/rolling/photo.jpeg",
+ "/en/rolling/icon.ico",
+ "/en/rolling/vyos-documentation.pdf",
+ ]) {
+ expect(classifyPath(p)).toBe("asset");
+ }
+ });
+ it("HTML pages and data files stay the page class", () => {
+ for (const p of [
+ "/en/rolling/index.html",
+ "/en/rolling/cli.html",
+ "/en/rolling/versions.json",
+ "/en/rolling/sitemap.xml",
+ ]) {
+ expect(classifyPath(p)).toBe("page");
+ }
+ });
+});
+
+describe("wrangler config congruence — html_handling pinned to \"none\"", () => {
+ // Unit tests mock ASSETS and never exercise the real wrangler html_handling config; this
+ // raw-string assertion is the unit-level pin against a silent revert to
+ // "auto-trailing-slash" (which reintroduces the 307-on-explicit-.html smoke failure). The
+ // deploy-time smoke gate (scripts/docs_gates/smoke.py) is the runtime-level backstop.
+ it("all four branch wrangler envs set html_handling \"none\"", () => {
+ for (const raw of [wranglerRolling, wranglerV15, wranglerV14, wranglerLegacy]) {
+ expect(raw).toContain('"html_handling": "none"');
+ expect(raw).not.toContain("auto-trailing-slash");
+ }
+ });
});