| 1 | import assert from "node:assert/strict"; |
| 2 | import { afterEach, test } from "node:test"; |
| 3 | |
| 4 | import type { FileDiff } from "@g1t/contracts"; |
| 5 | |
| 6 | import { forgetFiles, loadFile, peekFile } from "./diff-files.ts"; |
| 7 | |
| 8 | const SOURCE = "/acme/web/diff-files.json?base=&head=" + "b".repeat(40); |
| 9 | |
| 10 | const file = (path: string): FileDiff => ({ path, status: "modified", additions: 1, deletions: 0, binary: false, hunks: [] }); |
| 11 | |
| 12 | /** A stand-in for fetch that answers with the paths asked for, and notes each address. */ |
| 13 | function server(fail = false) { |
| 14 | const asked: string[] = []; |
| 15 | const fetcher = (async (url: string) => { |
| 16 | asked.push(url); |
| 17 | if (fail) return new Response("no", { status: 500 }); |
| 18 | const paths = new URL(url, "https://g1t.test").searchParams.getAll("path").filter((path) => path !== "gone.ts"); |
| 19 | return Response.json({ files: paths.map(file) }); |
| 20 | }) as typeof fetch; |
| 21 | return { asked, fetcher }; |
| 22 | } |
| 23 | |
| 24 | afterEach(forgetFiles); |
| 25 | |
| 26 | test("files asked for together are fetched in one request, and once", async () => { |
| 27 | const { asked, fetcher } = server(); |
| 28 | const [a, b, gone] = await Promise.all([ |
| 29 | loadFile(SOURCE, "src/a.ts", { fetcher }), |
| 30 | loadFile(SOURCE, "src/b c.ts", { fetcher }), |
| 31 | loadFile(SOURCE, "gone.ts", { fetcher }), |
| 32 | ]); |
| 33 | assert.equal(asked.length, 1); |
| 34 | assert.ok(asked[0]!.startsWith(SOURCE + "&path=src%2Fa.ts&path=src%2Fb%20c.ts")); |
| 35 | assert.equal(a?.path, "src/a.ts"); |
| 36 | assert.equal(b?.path, "src/b c.ts"); |
| 37 | // No longer in the change: null, not an error. |
| 38 | assert.equal(gone, null); |
| 39 | // Asked again, or drawn again after a refresh: from what was fetched. |
| 40 | assert.equal(await loadFile(SOURCE, "src/a.ts", { fetcher }), a); |
| 41 | assert.equal(peekFile(SOURCE, "src/a.ts"), a); |
| 42 | assert.equal(peekFile(SOURCE, "never.ts"), undefined); |
| 43 | assert.equal(asked.length, 1); |
| 44 | }); |
| 45 | |
| 46 | test("many files at once go 20 to a request", async () => { |
| 47 | const { asked, fetcher } = server(); |
| 48 | await Promise.all(Array.from({ length: 45 }, (_, n) => loadFile(SOURCE, `f${n}`, { fetcher }))); |
| 49 | assert.equal(asked.length, 3); |
| 50 | }); |
| 51 | |
| 52 | test("the files after one come with it, and are not asked for again", async () => { |
| 53 | const { asked, fetcher } = server(); |
| 54 | await loadFile(SOURCE, "a", { also: ["b", "c"], fetcher }); |
| 55 | assert.equal(asked.length, 1); |
| 56 | assert.ok(asked[0]!.endsWith("&path=b&path=c&path=a")); |
| 57 | assert.equal(peekFile(SOURCE, "c")?.path, "c"); |
| 58 | // Reached later, they are there; one already fetched is not asked for again. |
| 59 | assert.equal((await loadFile(SOURCE, "c", { also: ["a", "d"], fetcher }))?.path, "c"); |
| 60 | await loadFile(SOURCE, "d", { fetcher }); |
| 61 | assert.equal(asked.length, 2); |
| 62 | assert.ok(asked[1]!.endsWith("?base=&head=" + "b".repeat(40) + "&path=d")); |
| 63 | }); |
| 64 | |
| 65 | test("a failed fetch is an error, and is tried again when asked again", async () => { |
| 66 | const failing = server(true); |
| 67 | await assert.rejects(loadFile(SOURCE, "src/a.ts", { fetcher: failing.fetcher })); |
| 68 | assert.equal(peekFile(SOURCE, "src/a.ts"), undefined); |
| 69 | const working = server(); |
| 70 | assert.equal((await loadFile(SOURCE, "src/a.ts", { fetcher: working.fetcher }))?.path, "src/a.ts"); |
| 71 | assert.equal(working.asked.length, 1); |
| 72 | }); |