Skip to content
56 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { fileStore, s3FileStore } from "./files.ts";
5
6type Call = { url: string; method: string; headers: Record<string, string>; body: Uint8Array | null };
7
8function 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
17const config = { endpoint: "https://s3.example.com", bucket: "g1t-docs", region: "us-east-1", access_key_id: "AKID", secret_access_key: "secret" };
18
19test("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
41test("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
46test("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
52test("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});