Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Docs know what code they describe; a project's docs folder in Docs; Docs events; files on any S3 store | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { fileStore, s3FileStore } from "./files.ts"; | |
| 5 | ||
| 6 | type Call = { url: string; method: string; headers: Record<string, string>; body: Uint8Array | null }; | |
| 7 | ||
| 8 | function fake(responses: Response[] = []) { | |
| 9 | const calls: Call[] = []; | |
| 10 | const fetcher = (async (url: string, init: RequestInit) => { | |
| 11 | calls.push({ url, method: String(init.method), headers: init.headers as Record<string, string>, body: (init.body as Uint8Array | null) ?? null }); | |
| 12 | return responses.shift() ?? new Response(null, { status: 200 }); | |
| 13 | }) as unknown as typeof fetch; | |
| 14 | return { calls, fetcher }; | |
| 15 | } | |
| 16 | ||
| 17 | const config = { endpoint: "https://s3.example.com", bucket: "g1t-docs", region: "us-east-1", access_key_id: "AKID", secret_access_key: "secret" }; | |
| 18 | ||
| 19 | test("puts, gets and deletes a file by its key, path style, signed", async () => { | |
| 20 | const { calls, fetcher } = fake([new Response(null, { status: 200 }), new Response("hello", { status: 200, headers: { "content-type": "image/png", "content-length": "5", etag: '"e"' } }), new Response(null, { status: 204 })]); | |
| 21 | const store = s3FileStore(config, fetcher); | |
| 22 | await store.put("docs/ab cd", new TextEncoder().encode("hello"), "image/png"); | |
| 23 | const got = await store.get("docs/ab cd"); | |
| 24 | await store.delete("docs/ab cd"); | |
| 25 | assert.deepEqual( | |
| 26 | calls.map((c) => [c.method, c.url]), | |
| 27 | [ | |
| 28 | ["PUT", "https://s3.example.com/g1t-docs/docs/ab%20cd"], | |
| 29 | ["GET", "https://s3.example.com/g1t-docs/docs/ab%20cd"], | |
| 30 | ["DELETE", "https://s3.example.com/g1t-docs/docs/ab%20cd"], | |
| 31 | ], | |
| 32 | ); | |
| 33 | assert.match(calls[0]!.headers.authorization!, /^AWS4-HMAC-SHA256 Credential=AKID\/\d{8}\/us-east-1\/s3\/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=[0-9a-f]{64}$/); | |
| 34 | assert.equal(calls[0]!.headers["x-amz-content-sha256"], "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"); | |
| 35 | assert.equal(calls[0]!.headers.host, undefined); | |
| 36 | assert.equal(got?.content_type, "image/png"); | |
| 37 | assert.equal(got?.bytes, 5); | |
| 38 | assert.equal(await new Response(got!.body).text(), "hello"); | |
| 39 | }); | |
| 40 | ||
| 41 | test("a missing file is null", async () => { | |
| 42 | const { fetcher } = fake([new Response("", { status: 404 })]); | |
| 43 | assert.equal(await s3FileStore(config, fetcher).get("docs/x"), null); | |
| 44 | }); | |
| 45 | ||
| 46 | test("virtual-hosted addresses put the bucket in the host", async () => { | |
| 47 | const { calls, fetcher } = fake(); | |
| 48 | await s3FileStore({ ...config, virtual_hosted: true }, fetcher).delete("docs/x"); | |
| 49 | assert.equal(calls[0]!.url, "https://g1t-docs.s3.example.com/docs/x"); | |
| 50 | }); | |
| 51 | ||
| 52 | test("the store follows the settings, and says what is missing", () => { | |
| 53 | assert.throws(() => fileStore({ DOCS_FILES: "s3", DOCS_S3_BUCKET: "b" }), /DOCS_S3_ENDPOINT, DOCS_S3_ACCESS_KEY_ID, DOCS_S3_SECRET_ACCESS_KEY/); | |
| 54 | assert.throws(() => fileStore({}), /No file store/); | |
| 55 | assert.ok(fileStore({ DOCS_FILES: "s3", DOCS_S3_ENDPOINT: "http://minio:9000", DOCS_S3_BUCKET: "b", DOCS_S3_ACCESS_KEY_ID: "k", DOCS_S3_SECRET_ACCESS_KEY: "s" })); | |
| 56 | }); |