quill/oracle Public Public: anyone can read this; nobody can change it.
workbenchthis machine
Files cd91eed001e72e1896fd7b6b076c7f0fc935a518
| 1 | import { expect, test } from "bun:test"; |
| 2 | import { mkdtempSync } from "node:fs"; |
| 3 | import { tmpdir } from "node:os"; |
| 4 | import { join } from "node:path"; |
| 5 | |
| 6 | process.env.FORGE_APP_DATA = mkdtempSync(join(tmpdir(), "oracle-")); |
| 7 | process.env.PORT = "0"; |
| 8 | const { server } = await import("./server.ts"); |
| 9 | const base = `http://${server.hostname}:${server.port}`; |
| 10 | |
| 11 | const ask = async (question: string): Promise<Response> => { |
| 12 | const form = new FormData(); |
| 13 | form.set("question", question); |
| 14 | return fetch(`${base}/ask`, { method: "POST", body: form, redirect: "manual" }); |
| 15 | }; |
| 16 | |
| 17 | test("serves the door", async () => { |
| 18 | const response = await fetch(`${base}/`); |
| 19 | expect(response.status).toBe(200); |
| 20 | const text = await response.text(); |
| 21 | expect(text).toContain("the oracle"); |
| 22 | expect(text).toContain("Ask once"); |
| 23 | }); |
| 24 | |
| 25 | test("asking lands on the verdict's own page", async () => { |
| 26 | const posted = await ask("Should I ship it on Friday?"); |
| 27 | expect(posted.status).toBe(303); |
| 28 | const location = posted.headers.get("location") ?? ""; |
| 29 | expect(location).toMatch(/^\/v\/\d+$/); |
| 30 | const page = await (await fetch(base + location)).text(); |
| 31 | expect(page).toContain("Should I ship it on Friday?"); |
| 32 | expect(page).toContain("% sure"); |
| 33 | expect(page).toContain("the sign it read"); |
| 34 | }); |
| 35 | |
| 36 | test("asking the same thing twice does not get a second opinion", async () => { |
| 37 | const first = (await ask("Is the milk still good")).headers.get("location"); |
| 38 | const second = (await ask("is the MILK still good?")).headers.get("location"); |
| 39 | expect(second).toBe(first); |
| 40 | const page = await (await fetch(base + first)).text(); |
| 41 | expect(page).toContain("asked 2×"); |
| 42 | }); |
| 43 | |
| 44 | test("an empty question is not a question", async () => { |
| 45 | const posted = await ask(" "); |
| 46 | expect(posted.status).toBe(303); |
| 47 | expect(posted.headers.get("location")).toBe("/"); |
| 48 | }); |
| 49 | |
| 50 | test("a judged verdict moves the oracle's record", async () => { |
| 51 | const location = (await ask("Will the deploy hold overnight?")).headers.get("location") ?? ""; |
| 52 | const id = location.split("/").pop(); |
| 53 | const form = new FormData(); |
| 54 | form.set("outcome", "right"); |
| 55 | const judged = await fetch(`${base}/v/${id}/judge`, { |
| 56 | method: "POST", |
| 57 | body: form, |
| 58 | redirect: "manual", |
| 59 | }); |
| 60 | expect(judged.status).toBe(303); |
| 61 | const page = await (await fetch(base + location)).text(); |
| 62 | expect(page).toContain("judged right"); |
| 63 | expect(await (await fetch(`${base}/`)).text()).toContain("right of"); |
| 64 | expect(await (await fetch(`${base}/ledger?show=right`)).text()).toContain( |
| 65 | "Will the deploy hold overnight?", |
| 66 | ); |
| 67 | }); |
| 68 | |
| 69 | test("the ledger only holds the three words it knows", async () => { |
| 70 | const location = (await ask("Is this outcome inventable?")).headers.get("location") ?? ""; |
| 71 | const id = location.split("/").pop(); |
| 72 | const form = new FormData(); |
| 73 | form.set("outcome", "brilliant"); |
| 74 | const judged = await fetch(`${base}/v/${id}/judge`, { method: "POST", body: form }); |
| 75 | expect(judged.status).toBe(404); |
| 76 | expect(await (await fetch(base + location)).text()).toContain("unjudged"); |
| 77 | }); |
| 78 | |
| 79 | test("a verdict that was never asked for is not found", async () => { |
| 80 | const response = await fetch(`${base}/v/999999`); |
| 81 | expect(response.status).toBe(404); |
| 82 | expect(await response.text()).toContain("no such verdict"); |
| 83 | }); |
| 84 | |
| 85 | test("stands on the security floor", async () => { |
| 86 | const response = await fetch(`${base}/`); |
| 87 | expect(response.headers.get("content-security-policy")).toContain("default-src 'self'"); |
| 88 | expect(response.headers.get("x-content-type-options")).toBe("nosniff"); |
| 89 | }); |
| 90 | |
| 91 | test("a bug answers with the floor, not the runtime's page", async () => { |
| 92 | const response = await fetch(`${base}/ask`, { |
| 93 | method: "POST", |
| 94 | headers: { "content-type": "application/x-www-form-urlencoded" }, |
| 95 | body: "question=%E0%A4%A", |
| 96 | }); |
| 97 | expect(response.headers.get("content-security-policy")).toContain("default-src 'self'"); |
| 98 | }); |
| 99 | |
| 100 | test("a body is capped by the bytes read, whatever it claims", async () => { |
| 101 | const response = await fetch(`${base}/ask`, { |
| 102 | method: "POST", |
| 103 | headers: { "content-type": "application/x-www-form-urlencoded" }, |
| 104 | body: "question=" + "x".repeat(1024 * 1024), |
| 105 | }); |
| 106 | expect(response.status).toBe(413); |
| 107 | }); |
| 108 | |
| 109 | test("HEAD sees the app up", async () => { |
| 110 | const response = await server.fetch(new Request(`${base}/`, { method: "HEAD" })); |
| 111 | expect(response.status).toBe(200); |
| 112 | expect(await response.text()).toBe(""); |
| 113 | }); |
| 114 | |
| 115 | test("refuses a cross-site ask", async () => { |
| 116 | const form = new FormData(); |
| 117 | form.set("question", "did this come from another site?"); |
| 118 | const posted = await fetch(`${base}/ask`, { |
| 119 | method: "POST", |
| 120 | body: form, |
| 121 | redirect: "manual", |
| 122 | headers: { origin: "https://evil.example", "sec-fetch-site": "cross-site" }, |
| 123 | }); |
| 124 | expect(posted.status).toBe(403); |
| 125 | expect(await (await fetch(`${base}/ledger`)).text()).not.toContain("another site"); |
| 126 | }); |
| 127 | |
| 128 | test("a question is escaped on the way out", async () => { |
| 129 | const location = (await ask('<img src=x onerror="alert(1)">')).headers.get("location") ?? ""; |
| 130 | const page = await (await fetch(base + location)).text(); |
| 131 | expect(page).toContain("<img"); |
| 132 | expect(page).not.toContain("<img src=x"); |
| 133 | }); |