clone

push a directory of html from the terminal and get a four-character url back. a deno cli on cloudflare

feat/source-stable-push
nzip / worker / test / index.runtime.test.ts
index.runtime.test.ts 20.5K
1import {2 createExecutionContext,3 env,4 SELF,5 waitOnExecutionContext,6} from "cloudflare:test";7import { describe, expect, it } from "vitest";8import worker from "../src/index.ts";9import { hashPassword, verifyPassword } from "../src/password.ts";1011describe("Worker runtime", () => {12 it("serves the landing page with public cache headers", async () => {13 const response = await SELF.fetch("https://share.demo.dev/");1415 expect(response.status).toBe(200);16 expect(response.headers.get("cache-control")).toBe("public, max-age=3600");17 expect(response.headers.get("cloudflare-cdn-cache-control")).toBe(18 "no-store",19 );20 expect(response.headers.get("content-security-policy")).toBe(21 "frame-ancestors 'none'",22 );23 expect(response.headers.get("x-frame-options")).toBe("DENY");24 expect(await response.text()).toContain("nzip");25 });2627 it("rejects unknown wildcard hosts without exposing the control plane", async () => {28 const invalid = await SELF.fetch("https://attacker.demo.dev/api/status", {29 headers: { authorization: "Bearer runtime-test-token" },30 });31 const validArtifactHost = await SELF.fetch(32 "https://0123.demo.dev/api/status",33 { headers: { authorization: "Bearer runtime-test-token" } },34 );3536 expect(invalid.status).toBe(404);37 expect(validArtifactHost.status).toBe(404);38 expect(await invalid.json()).toEqual({ error: "not found" });39 });4041 it("keeps the root shell claim-independent and hides pairing by default", async () => {42 const response = await SELF.fetch("https://share.demo.dev/");43 const html = await response.text();4445 expect(response.headers.get("set-cookie")).toBeNull();46 expect(html).not.toContain('id="pair"');47 expect(html).toContain("maximum-scale=1, user-scalable=no");48 expect(html).not.toContain('<link rel="manifest"');49 const count = await env.DB.prepare(50 "SELECT COUNT(*) AS count FROM notification_devices",51 ).first<{ count: number }>();52 expect(count?.count).toBe(0);53 });5455 it("rejects enrollment until the owner opens a pairing window", async () => {56 const browserHeaders = {57 origin: "https://share.demo.dev",58 "sec-fetch-site": "same-origin",59 "content-type": "application/json",60 };61 const status = await SELF.fetch("https://share.demo.dev/_notify/pairing", {62 headers: { "sec-fetch-site": "same-origin" },63 });64 expect(status.status).toBe(200);65 expect(await status.json()).toEqual({ enabled: false, expiresAt: null });6667 const enrolled = await SELF.fetch(68 "https://share.demo.dev/_notify/enrollments",69 { method: "POST", headers: browserHeaders, body: "{}" },70 );71 expect(enrolled.status).toBe(404);72 expect(await enrolled.json()).toEqual({ error: "pairing unavailable" });7374 const crossOrigin = await SELF.fetch(75 "https://share.demo.dev/_notify/pairing",76 {77 headers: {78 origin: "https://attacker.example",79 "sec-fetch-site": "cross-site",80 },81 },82 );83 expect(crossOrigin.status).toBe(403);8485 await SELF.fetch("https://share.demo.dev/api/notify/pairing", {86 method: "POST",87 headers: {88 authorization: "Bearer runtime-test-token",89 "content-type": "application/json",90 },91 body: "{}",92 });93 await env.DB.prepare(94 "UPDATE notification_pairing_window SET enabled_until = 0 WHERE id = 1",95 ).run();96 const expired = await SELF.fetch(97 "https://share.demo.dev/_notify/enrollments",98 { method: "POST", headers: browserHeaders, body: "{}" },99 );100 expect(expired.status).toBe(404);101 });102103 it("enrolls, previews, approves, and activates while delivery is disabled", async () => {104 const browserHeaders = {105 origin: "https://share.demo.dev",106 "sec-fetch-site": "same-origin",107 "content-type": "application/json",108 "user-agent": "runtime mobile browser",109 };110 const ownerHeaders = {111 authorization: "Bearer runtime-test-token",112 "content-type": "application/json",113 };114 const opened = await SELF.fetch(115 "https://share.demo.dev/api/notify/pairing",116 { method: "POST", headers: ownerHeaders, body: "{}" },117 );118 expect(opened.status).toBe(200);119 expect(await opened.json()).toMatchObject({ enabled: true });120 const pairing = await SELF.fetch("https://share.demo.dev/_notify/pairing", {121 headers: { "sec-fetch-site": "same-origin" },122 });123 expect(await pairing.json()).toMatchObject({ enabled: true });124125 const enrolled = await SELF.fetch(126 "https://share.demo.dev/_notify/enrollments",127 { method: "POST", headers: browserHeaders, body: "{}" },128 );129 expect(enrolled.status).toBe(201);130 expect(enrolled.headers.get("cache-control")).toBe("no-store");131 expect(enrolled.headers.get("vary")).toBe("Cookie");132 const cookie = enrolled.headers.get("set-cookie")?.split(";", 1)[0];133 const enrollment = await enrolled.json<{ code: string }>();134 expect(cookie).toMatch(/^__Host-nzip-notify=/);135 expect(enrollment.code).toMatch(/^[2-9A-HJ-NP-Z]{4}-[2-9A-HJ-NP-Z]{4}$/);136137 const preview = await SELF.fetch(138 `https://share.demo.dev/api/notify/approvals/${enrollment.code}`,139 { headers: ownerHeaders },140 );141 expect(preview.status).toBe(200);142 expect(await preview.json()).toMatchObject({143 userAgentSummary: "runtime mobile browser",144 deviceClass: "mobile",145 });146147 const approved = await SELF.fetch(148 "https://share.demo.dev/api/notify/approvals",149 {150 method: "POST",151 headers: ownerHeaders,152 body: JSON.stringify({ code: enrollment.code, name: "runtime device" }),153 },154 );155 expect(approved.status).toBe(200);156 expect(await approved.json()).toMatchObject({157 name: "runtime device",158 status: "approved",159 });160161 const activated = await SELF.fetch(162 "https://share.demo.dev/_notify/enrollments/activate",163 {164 method: "POST",165 headers: { ...browserHeaders, cookie: cookie! },166 body: "{}",167 },168 );169 expect(activated.status).toBe(200);170 expect(await activated.json()).toMatchObject({171 paired: true,172 notifications: "off",173 });174175 const disabledSend = await SELF.fetch(176 "https://share.demo.dev/api/notify",177 {178 method: "POST",179 headers: ownerHeaders,180 body: JSON.stringify({ body: "runtime test" }),181 },182 );183 expect(disabledSend.status).toBe(503);184 });185186 it("serves install assets with explicit content and cache policies", async () => {187 const manifest = await SELF.fetch(188 "https://share.demo.dev/_notify/app.webmanifest",189 );190 const serviceWorker = await SELF.fetch(191 "https://share.demo.dev/_notify/sw.js",192 );193 const icon = await SELF.fetch("https://share.demo.dev/_notify/icon.svg");194195 expect(manifest.headers.get("content-type")).toContain(196 "application/manifest+json",197 );198 expect(serviceWorker.headers.get("content-type")).toContain(199 "text/javascript",200 );201 expect(serviceWorker.headers.get("cache-control")).toBe("no-cache");202 expect(serviceWorker.headers.get("service-worker-allowed")).toBe("/");203 expect(icon.headers.get("content-type")).toContain("image/svg+xml");204 });205206 it("rejects unauthenticated API requests before touching storage", async () => {207 const response = await SELF.fetch("https://share.demo.dev/api/status");208209 expect(response.status).toBe(401);210 expect(await response.json()).toEqual({ error: "unauthorized" });211 });212213 it("applies the complete D1 migration set", async () => {214 const table = await env.DB.prepare(215 "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'security_incidents'",216 ).first<{ name: string }>();217218 expect(table?.name).toBe("security_incidents");219 });220221 it("routes authenticated status requests with the shared release version", async () => {222 const response = await SELF.fetch("https://share.demo.dev/api/status", {223 headers: { authorization: "Bearer runtime-test-token" },224 });225226 expect(response.status).toBe(200);227 expect(await response.json()).toMatchObject({228 version: "0.10.0",229 defaultVaults: { temporary: "personal", permanent: "public" },230 globalDefaultTtl: 14,231 vaults: expect.arrayContaining([232 expect.objectContaining({233 slot: 0,234 name: "personal",235 defaultTtl: 14,236 effectiveDefaultTtl: 14,237 defaultFor: ["temporary"],238 }),239 expect.objectContaining({240 slot: 15,241 name: "public",242 defaultTtl: "forever",243 effectiveDefaultTtl: "forever",244 defaultFor: ["permanent"],245 }),246 ]),247 });248 });249250 it("preserves TTL and password settings when repushing without policy flags", async () => {251 const address = 0xeffe;252 const expiresAt = Math.floor(Date.now() / 1000) + 90 * 86_400;253 const emptyHash =254 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";255 const oldManifest = "a".repeat(64);256 await env.DB.batch([257 env.DB.prepare(258 "INSERT INTO vaults (slot, name, created_at) VALUES (?, ?, ?)",259 ).bind(14, "repush-policy", Math.floor(Date.now() / 1000)),260 env.DB.prepare(261 `INSERT INTO sites262 (address, vault_slot, alias, current_manifest, created_at, updated_at, expires_at, password_hash)263 VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,264 ).bind(265 address,266 14,267 "keep-settings",268 oldManifest,269 Math.floor(Date.now() / 1000),270 Math.floor(Date.now() / 1000),271 expiresAt,272 "existing-password-hash",273 ),274 ]);275 await env.CONTENT.put(`blob/${emptyHash}`, new Uint8Array());276277 const response = await SELF.fetch(278 "https://share.demo.dev/api/push/commit",279 {280 method: "POST",281 headers: {282 authorization: "Bearer runtime-test-token",283 "content-type": "application/json",284 },285 body: JSON.stringify({286 manifest: {287 v: 1,288 files: {289 "index.html": {290 h: emptyHash,291 s: 0,292 ct: "text/html; charset=utf-8",293 },294 },295 },296 target: { address },297 }),298 },299 );300301 expect(response.status).toBe(200);302 expect(await response.json()).toMatchObject({303 address: "effe",304 url: "https://effe.demo.dev/",305 expiresAt,306 ttlSource: "existing-site",307 protected: true,308 });309310 const legacy = await SELF.fetch("https://share.demo.dev/effe/index.html", {311 redirect: "manual",312 });313 expect(legacy.status).toBe(308);314 expect(legacy.headers.get("location")).toBe(315 "https://effe.demo.dev/index.html",316 );317318 const isolated = await SELF.fetch("https://effe.demo.dev/", {319 headers: { cookie: "nzip_aeffe=not-a-valid-unlock" },320 });321 expect(isolated.status).toBe(401);322 expect(isolated.headers.get("cloudflare-cdn-cache-control")).toBe(323 "no-store",324 );325 expect(isolated.headers.get("permissions-policy")).toBeNull();326 expect(isolated.headers.get("origin-agent-cluster")).toBe("?1");327 const stored = await env.DB.prepare(328 "SELECT expires_at, password_hash FROM sites WHERE address = ?",329 ).bind(address).first<330 { expires_at: number | null; password_hash: string | null }331 >();332 expect(stored).toEqual({333 expires_at: expiresAt,334 password_hash: "existing-password-hash",335 });336 });337338 it("resolves new-site TTL from explicit, vault, and global policy layers", async () => {339 const headers = {340 authorization: "Bearer runtime-test-token",341 "content-type": "application/json",342 };343 const emptyHash =344 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";345 await env.CONTENT.put(`blob/${emptyHash}`, new Uint8Array());346 await env.DB.prepare(347 `INSERT INTO vaults348 (slot, name, description, default_ttl, created_at)349 VALUES (13, 'inherited', NULL, NULL, ?)`,350 ).bind(Math.floor(Date.now() / 1000)).run();351 const manifest = {352 v: 1,353 files: {354 "index.html": { h: emptyHash, s: 0, ct: "text/html; charset=utf-8" },355 },356 };357 const commit = async (target: unknown, ttl?: number) => {358 const response = await SELF.fetch(359 "https://share.demo.dev/api/push/commit",360 {361 method: "POST",362 headers,363 body: JSON.stringify({364 manifest,365 target,366 ...(ttl === undefined ? {} : { ttl }),367 }),368 },369 );370 expect(response.status).toBe(200);371 return await response.json<Record<string, unknown>>();372 };373374 expect(await commit({ vault: "personal", alias: "vault-policy" }))375 .toMatchObject({376 ttl: 14,377 ttlSource: "vault",378 });379 expect(await commit({ vault: "inherited", alias: "global-policy" }))380 .toMatchObject({381 ttl: 14,382 ttlSource: "global",383 });384 expect(await commit({ vault: "inherited", alias: "explicit-policy" }, 30))385 .toMatchObject({386 ttl: 30,387 ttlSource: "explicit",388 });389 });390391 it("reserves app origins idempotently, mirrors lofi CSP, and keeps tombstones", async () => {392 const headers = {393 authorization: "Bearer runtime-test-token",394 "content-type": "application/json",395 };396 const init = async () => {397 const response = await SELF.fetch("https://share.demo.dev/api/apps", {398 method: "POST",399 headers,400 body: JSON.stringify({401 target: { vault: "public", alias: "runtime-app" },402 }),403 });404 expect([200, 201]).toContain(response.status);405 return await response.json<{406 address: string;407 url: string;408 deployed: boolean;409 }>();410 };411412 const first = await init();413 const second = await init();414 expect(second.address).toBe(first.address);415 expect(first.deployed).toBe(false);416417 const placeholder = await SELF.fetch(first.url);418 expect(placeholder.status).toBe(200);419 expect(await placeholder.text()).toContain("not deployed yet");420421 const emptyHash =422 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";423 await env.CONTENT.put(`blob/${emptyHash}`, new Uint8Array());424 const policy = "default-src 'self'; frame-ancestors 'none'";425 const deployed = await SELF.fetch(426 "https://share.demo.dev/api/push/commit",427 {428 method: "POST",429 headers,430 body: JSON.stringify({431 manifest: {432 v: 1,433 files: {434 "index.html": {435 h: emptyHash,436 s: 0,437 ct: "text/html; charset=utf-8",438 },439 },440 },441 target: { vault: "public", alias: "runtime-app" },442 app: { contentSecurityPolicy: policy },443 }),444 },445 );446 expect(deployed.status).toBe(200);447 expect(await deployed.json()).toMatchObject({448 address: first.address,449 ttl: "forever",450 ttlSource: "vault",451 });452453 const hosted = await SELF.fetch(first.url);454 expect(hosted.status).toBe(200);455 expect(hosted.headers.get("content-security-policy")).toBe(policy);456457 const removed = await SELF.fetch(458 "https://share.demo.dev/api/sites/public%3Aruntime-app",459 { method: "DELETE", headers },460 );461 expect(removed.status).toBe(200);462 const afterDelete = await SELF.fetch(first.url);463 expect(afterDelete.status).toBe(200);464 expect(await afterDelete.text()).toContain("not deployed yet");465 expect(await init()).toMatchObject({466 address: first.address,467 deployed: false,468 });469 });470471 it("creates, lists, renames, and redescribes vaults", async () => {472 const headers = {473 authorization: "Bearer runtime-test-token",474 "content-type": "application/json",475 };476 const created = await SELF.fetch("https://share.demo.dev/api/vaults", {477 method: "POST",478 headers,479 body: JSON.stringify({480 name: "agent-work",481 description: "Scratch space for agent-generated review artifacts",482 }),483 });484 expect(created.status).toBe(201);485 expect(await created.json()).toMatchObject({486 name: "agent-work",487 description: "Scratch space for agent-generated review artifacts",488 defaultTtl: null,489 effectiveDefaultTtl: 14,490 defaultFor: [],491 siteCount: 0,492 });493494 const updated = await SELF.fetch(495 "https://share.demo.dev/api/vaults/agent-work",496 {497 method: "PATCH",498 headers,499 body: JSON.stringify({500 name: "reviews",501 description: "Human review links; safe to share with collaborators",502 defaultTtl: "forever",503 }),504 },505 );506 expect(updated.status).toBe(200);507 expect(await updated.json()).toMatchObject({508 name: "reviews",509 description: "Human review links; safe to share with collaborators",510 defaultTtl: "forever",511 effectiveDefaultTtl: "forever",512 });513514 for (const lifecycle of ["temporary", "permanent"]) {515 const selected = await SELF.fetch(516 `https://share.demo.dev/api/default-vaults/${lifecycle}`,517 { method: "PUT", headers, body: JSON.stringify({ name: "reviews" }) },518 );519 expect(selected.status).toBe(200);520 }521522 const listed = await SELF.fetch("https://share.demo.dev/api/vaults", {523 headers,524 });525 expect(await listed.json()).toContainEqual(expect.objectContaining({526 name: "reviews",527 description: "Human review links; safe to share with collaborators",528 defaultFor: expect.arrayContaining(["temporary", "permanent"]),529 }));530531 for (const [lifecycle, name] of [["temporary", "personal"], ["permanent", "public"]]) {532 const restored = await SELF.fetch(533 `https://share.demo.dev/api/default-vaults/${lifecycle}`,534 { method: "PUT", headers, body: JSON.stringify({ name }) },535 );536 expect(restored.status).toBe(200);537 }538539 const cleared = await SELF.fetch(540 "https://share.demo.dev/api/vaults/reviews",541 {542 method: "PATCH",543 headers,544 body: JSON.stringify({ description: "" }),545 },546 );547 expect(cleared.status).toBe(200);548 expect(await cleared.json()).toMatchObject({549 name: "reviews",550 description: null,551 });552553 const clearedWithNull = await SELF.fetch(554 "https://share.demo.dev/api/vaults/reviews",555 {556 method: "PATCH",557 headers,558 body: JSON.stringify({ description: null }),559 },560 );561 expect(clearedWithNull.status).toBe(200);562 expect(await clearedWithNull.json()).toMatchObject({563 name: "reviews",564 description: null,565 });566567 const multiline = await SELF.fetch(568 "https://share.demo.dev/api/vaults/reviews",569 {570 method: "PATCH",571 headers,572 body: JSON.stringify({ description: "line1\nline2" }),573 },574 );575 expect(multiline.status).toBe(400);576577 const badEncoding = await SELF.fetch(578 "https://share.demo.dev/api/vaults/%zz",579 {580 method: "PATCH",581 headers,582 body: JSON.stringify({ description: "x" }),583 },584 );585 expect(badEncoding.status).toBe(400);586 });587588 it("evaluates a real D1 probe window through the scheduled handler", async () => {589 const now = Math.floor(Date.now() / 300_000) * 300;590 const bucket = now - 300;591 await env.DB.batch(592 Array.from({ length: 20 }, (_, address) =>593 env.DB.prepare(594 `INSERT INTO security_probes595 (bucket, scanner_id, address, vault_slot, is_live, country, asn)596 VALUES (?, 'runtime-scanner', ?, 0, 0, 'US', 64500)`,597 ).bind(bucket, address)),598 );599 const ctx = createExecutionContext();600601 await worker.scheduled(602 { cron: "*/5 * * * *", scheduledTime: Date.now(), noRetry() {} },603 env,604 ctx,605 );606 await waitOnExecutionContext(ctx);607608 const incident = await env.DB.prepare(609 "SELECT status, severity FROM security_incidents WHERE name = 'enumeration'",610 ).first<{ status: string; severity: number }>();611 const notification = await env.DB.prepare(612 "SELECT attempts, sent_at FROM security_notifications WHERE action = 'open'",613 ).first<{ attempts: number; sent_at: number | null }>();614 expect(incident).toMatchObject({ status: "open", severity: 1 });615 expect(notification?.attempts).toBe(1);616 expect(notification?.sent_at).not.toBeNull();617 });618619 it("runs password hashing and verification inside workerd", async () => {620 const started = performance.now();621 const encoded = await hashPassword("runtime-password");622 const elapsedMs = performance.now() - started;623624 expect(await verifyPassword("runtime-password", encoded)).toBe(true);625 expect(elapsedMs).toBeLessThan(1_000);626 console.log({ event: "test.pbkdf2_runtime", elapsedMs });627 });628});