Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

65 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([
21 new Response(null, { status: 200 }),
22 new Response("hello", { status: 200, headers: { "content-type": "image/png", "content-length": "5", etag: '"e"' } }),
23 new Response(null, { status: 204 }),
24 ]);
25 const store = s3FileStore(config, fetcher);
26 await store.put("docs/ab cd", new TextEncoder().encode("hello"), "image/png");
27 const got = await store.get("docs/ab cd");
28 await store.delete("docs/ab cd");
29 assert.deepEqual(
30 calls.map((c) => [c.method, c.url]),
31 [
32 ["PUT", "https://s3.example.com/g1t-docs/docs/ab%20cd"],
33 ["GET", "https://s3.example.com/g1t-docs/docs/ab%20cd"],
34 ["DELETE", "https://s3.example.com/g1t-docs/docs/ab%20cd"],
35 ],
36 );
37 assert.match(
38 calls[0]!.headers.authorization!,
39 /^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}$/,
40 );
41 assert.equal(calls[0]!.headers["x-amz-content-sha256"], "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824");
42 assert.equal(calls[0]!.headers.host, undefined);
43 assert.equal(got?.content_type, "image/png");
44 assert.equal(got?.bytes, 5);
45 assert.equal(await new Response(got!.body).text(), "hello");
46});
47
48test("a missing file is null", async () => {
49 const { fetcher } = fake([new Response("", { status: 404 })]);
50 assert.equal(await s3FileStore(config, fetcher).get("docs/x"), null);
51});
52
53test("virtual-hosted addresses put the bucket in the host", async () => {
54 const { calls, fetcher } = fake();
55 await s3FileStore({ ...config, virtual_hosted: true }, fetcher).delete("docs/x");
56 assert.equal(calls[0]!.url, "https://g1t-docs.s3.example.com/docs/x");
57});
58
59test("the store follows the settings, and says what is missing", () => {
60 assert.throws(() => fileStore({ DOCS_FILES: "s3", DOCS_S3_BUCKET: "b" }), /DOCS_S3_ENDPOINT, DOCS_S3_ACCESS_KEY_ID, DOCS_S3_SECRET_ACCESS_KEY/);
61 assert.throws(() => fileStore({}), /No file store/);
62 assert.ok(
63 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" }),
64 );
65});