summaryrefslogtreecommitdiff
path: root/workers/preview/test
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-07-10 17:14:13 +0300
committerGitHub <noreply@github.com>2026-07-10 16:14:13 +0200
commit21689ef59b0eb34b1a29eda739dc10d33d25b44a (patch)
tree95883c2ee70d4c09562ae3e0289e18f34999240c /workers/preview/test
parent6d2d28d4411ff52ad0f9314cf3696336748ab89d (diff)
downloadvyos-documentation-21689ef59b0eb34b1a29eda739dc10d33d25b44a.tar.gz
vyos-documentation-21689ef59b0eb34b1a29eda739dc10d33d25b44a.zip
docs: Cloudflare Workers hosting pipeline (apex, content workers, CI, previews) (#2140)
* docs-infra: scaffold Cloudflare workers workspace (versions.json v2, matrix, toolchain) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: record full Phase-0 plan decision in workers/PLAN.md πŸ€– Generated by [robots](https://vyos.io) * docs-infra: shared content worker β€” asset serving, cache classes, X-Docs-Build, canary no-store πŸ€– Generated by [robots](https://vyos.io) * docs-infra: run worker script before assets; test fetch entrypoint πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex manifest loader + dispatch map + runtime binding guard (TDD) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex redirects (aliases, PDF, trailing-slash) + special paths (TDD) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: PDF redirect honors pdf:null and preserves query πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex UA gate β€” allowlist-wins, log-only AI crawlers, empty block list at launch πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex router (pipeline Β§3.2), themed 404/503, /kb seam, env configs + congruence test πŸ€– Generated by [robots](https://vyos.io) * docs-infra: add missing-User-Agent regression test for apex UA gate πŸ€– Generated by [robots](https://vyos.io) * docs-infra: R2-streaming preview worker β€” MIME map, noindex, no-store (TDD) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: preview 404 no-store + fetch handler tests πŸ€– Generated by [robots](https://vyos.io) * docs-infra: bootstrap script β€” binding-target workers must exist before apex deploys πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex run_worker_first, lockfile for npm ci, PDF Location from manifest πŸ€– Generated by [robots](https://vyos.io) * docs-infra: derive html_baseurl from DOCS_VERSION_SLUG with RTD fallback (canonical gate prereq) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: version picker + status banner + language scaffold (vanilla JS, TDD pure core) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: picker preserves query+hash across switch; valid breadcrumb markup πŸ€– Generated by [robots](https://vyos.io) * docs-infra: Pagefind search wrapper with runtime base-path + preview prefix handling (TDD) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: pagefind wrapper β€” asset-failure notice + UI stylesheet load πŸ€– Generated by [robots](https://vyos.io) * docs-infra: gate Pagefind searchbox to CF builds (RTD keeps stock search until cutover) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: deploy sanity gates β€” limits, critical pages, count-delta, canonical (TDD) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: hermetic gate tests via fixture versions.json πŸ€– Generated by [robots](https://vyos.io) * docs-infra: docs-build workflow β€” candidate/smoke/promote two-stage deploy + registry + rollback Two-stage CF Workers pipeline: build in pinned container, assemble artifact, sanity gates, deploy candidate, scoped pre-traffic smoke via canary apex, promote (rollback-id capture, hostname purge, registry upload), post-promote probe + auto-rollback. DOCS_CF_LIVE repo variable gates every docs.vyos.io production interaction pre-cutover. scripts/docs_gates/smoke.py adds one authorized check beyond the spec: the version's index.html probe asserts the #vyos-search mount div is present in the response body, guarding CI silently forgetting DOCS_VERSION_SLUG (which would otherwise ship stock RTD search without the Pagefind gate noticing). πŸ€– Generated by [robots](https://vyos.io) * docs-infra: build docs image in-workflow with buildx cache (v4.1 β€” digest pin dropped) Plan v4.1 amendment: the ghcr.io digest-pinned image does not exist (workflow would hard-fail at the first docker step on every push). Replace the BUILD_IMAGE env placeholder with an in-workflow docker build from docker/Dockerfile via docker/setup-buildx-action@v3 + docker/build-push-action@v6 (context: docker/, load: true, tags: docs-build:local, GHA cache from/to). The checked-out commit is the pin; buildx GHA cache keeps repeat builds cheap. Sphinx-build step swaps to docs-build:local; inner script unchanged. πŸ€– Generated by [robots](https://vyos.io) * docs-infra: apex/preview deploy workflow β€” canary auto, production behind environment approval * docs-infra: apex-deploy concurrency guard (per-ref, cancel-in-progress) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: fork-safe PR preview pipeline β€” approval record, R2 prefixes, label consumption, cleanup * docs-infra: nightly preview sweep β€” pipefail + per-prefix failure isolation πŸ€– Generated by [robots](https://vyos.io) * docs-infra: nightly canary QA β€” per-entry sweep + URL-parity corpus vs RTD πŸ€– Generated by [robots](https://vyos.io) * docs-infra: parity sweep scoped to CF-built versions; transport-error resilience πŸ€– Generated by [robots](https://vyos.io) * docs-infra: one-off bootstrap workflow (binding targets β€” runs once on this push) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: remove one-off bootstrap workflow (bootstrap complete) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: one-off canary apex + preview deploy (route targets for Task 3.6 step 2c) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: remove one-off canary deploy workflow (targets live) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: address Phase-0 CodeRabbit findings (canonical gate, error caching, registry pointer, validation) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: strengthen manifest tests (full dispatch iteration, mutation-free validate) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: address GitHub CodeRabbit review (pointer-after-probe, fail-closed sweeps, block-precedence UA gate, preview hardening) πŸ€– Generated by [robots](https://vyos.io) * docs-infra: adversarial review fixes β€” error no-store, probe retry, PR-list membership, preview dotted-segment πŸ€– Generated by [robots](https://vyos.io) * docs-infra: serve oversized legacy PDF from R2 via apex (spec Β§5 fallback) The 1.3 PDF (29.2 MiB) exceeds the 25 MiB static-asset cap and is absent from the legacy content Worker's build, so /_/downloads/en/1.3/pdf/ (and the picker's PDF link) 301'd into a dead-end 404 post-cutover. Add the R2 object fallback spec Β§5 already documented but never implemented: a DOCS_PDFS R2 bucket binding on the apex Worker, a manifest pdf_r2_key field (1.3 only), and a router step ahead of version dispatch that streams the object with its own cache class (canary/error still force no-store). πŸ€– Generated by [robots](https://vyos.io) * docs-infra: PDF R2 fallback honors Range + If-None-Match, preserves ETag πŸ€– Generated by [robots](https://vyos.io)
Diffstat (limited to 'workers/preview/test')
-rw-r--r--workers/preview/test/preview.test.ts93
1 files changed, 93 insertions, 0 deletions
diff --git a/workers/preview/test/preview.test.ts b/workers/preview/test/preview.test.ts
new file mode 100644
index 00000000..90530690
--- /dev/null
+++ b/workers/preview/test/preview.test.ts
@@ -0,0 +1,93 @@
+import { describe, it, expect } from "vitest";
+import worker, { mimeFor, keyFor } from "../src/index";
+import type { Env } from "../src/index";
+
+describe("preview worker helpers (Β§10)", () => {
+ it("derives R2 key from path, defaulting directory to index.html", () => {
+ expect(keyFor("/pr-42/en/rolling/")).toBe("pr-42/en/rolling/index.html");
+ expect(keyFor("/pr-42/en/rolling/cli/index.html")).toBe("pr-42/en/rolling/cli/index.html");
+ });
+ it("extension→MIME fallback map (nosniff-safe)", () => {
+ expect(mimeFor("a.html")).toBe("text/html; charset=utf-8");
+ expect(mimeFor("a.css")).toBe("text/css");
+ expect(mimeFor("a.js")).toBe("text/javascript");
+ expect(mimeFor("a.json")).toBe("application/json");
+ expect(mimeFor("a.svg")).toBe("image/svg+xml");
+ expect(mimeFor("a.woff2")).toBe("font/woff2");
+ expect(mimeFor("a.unknown")).toBe("application/octet-stream");
+ });
+});
+
+// Fake R2Bucket mock following the makeEnv() precedent in apex/test/router.test.ts β€”
+// only the `get` surface the handler consumes; miniflare's real R2 not needed here.
+function makeEnv(objects: Record<string, { body: string; contentType?: string }>): Env {
+ return {
+ PREVIEWS: {
+ get: async (key: string) => {
+ const hit = objects[key];
+ if (!hit) return null;
+ return {
+ body: hit.body,
+ httpMetadata: hit.contentType ? { contentType: hit.contentType } : undefined,
+ };
+ },
+ } as unknown as R2Bucket,
+ };
+}
+
+const get = (path: string, env: Env) =>
+ worker.fetch(new Request(`https://docs-preview.vyos.io${path}`), env);
+
+describe("preview worker fetch entrypoint (Β§10)", () => {
+ it("serves a found object with uploader contentType + noindex/no-store/nosniff headers", async () => {
+ const env = makeEnv({
+ "pr-42/en/rolling/index.html": { body: "<h1>preview</h1>", contentType: "text/html; charset=utf-8" },
+ });
+ const r = await get("/pr-42/en/rolling/", env);
+ expect(r.status).toBe(200);
+ expect(await r.text()).toBe("<h1>preview</h1>");
+ expect(r.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ expect(r.headers.get("X-Robots-Tag")).toBe("noindex");
+ expect(r.headers.get("Cache-Control")).toBe("no-store");
+ expect(r.headers.get("X-Content-Type-Options")).toBe("nosniff");
+ });
+ it("missing object β†’ 404 with noindex AND no-store (404s must never be cached)", async () => {
+ const r = await get("/pr-42/en/rolling/missing.html", makeEnv({}));
+ expect(r.status).toBe(404);
+ expect(r.headers.get("X-Robots-Tag")).toBe("noindex");
+ expect(r.headers.get("Cache-Control")).toBe("no-store");
+ });
+ it("object without httpMetadata.contentType falls back to mimeFor(key)", async () => {
+ const env = makeEnv({ "pr-42/en/rolling/style.css": { body: "body{}" } });
+ const r = await get("/pr-42/en/rolling/style.css", env);
+ expect(r.status).toBe(200);
+ expect(r.headers.get("content-type")).toBe("text/css");
+ });
+ it("extensionless directory URL with no trailing slash serves the directory's index.html", async () => {
+ const env = makeEnv({
+ "pr-42/en/rolling/cli/index.html": { body: "<h1>cli</h1>", contentType: "text/html; charset=utf-8" },
+ });
+ const r = await get("/pr-42/en/rolling/cli", env);
+ expect(r.status).toBe(200);
+ expect(await r.text()).toBe("<h1>cli</h1>");
+ expect(r.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ });
+ it("extensionless directory URL with a dotted version segment earlier in the path still probes index.html (dot-check is last-segment-only)", async () => {
+ const env = makeEnv({
+ "pr-42/en/1.4/cli/index.html": { body: "<h1>cli 1.4</h1>", contentType: "text/html; charset=utf-8" },
+ });
+ const r = await get("/pr-42/en/1.4/cli", env);
+ expect(r.status).toBe(200);
+ expect(await r.text()).toBe("<h1>cli 1.4</h1>");
+ expect(r.headers.get("content-type")).toBe("text/html; charset=utf-8");
+ });
+ it("a transient R2/binding error on either probe degrades to a controlled 503 (never an unhandled exception)", async () => {
+ const throwingEnv: Env = {
+ PREVIEWS: { get: async () => { throw new Error("R2 unavailable"); } } as unknown as R2Bucket,
+ };
+ const r = await get("/pr-42/en/rolling/cli", throwingEnv);
+ expect(r.status).toBe(503);
+ expect(r.headers.get("X-Robots-Tag")).toBe("noindex");
+ expect(r.headers.get("Cache-Control")).toBe("no-store");
+ });
+});