Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344import { describe, expect, it, vi } from "vitest";import { createBobbinClient } from "./client";import { gitTarget } from "./gitclient";import { fetchSides, loadDiff, resolveChange } from "./pullDiff";import type { MirrorFileDiff } from "./gitmirror";
const src = (path: string, extra: Partial<MirrorFileDiff["lhsSrc"]> = {}) => ({ path, oid: `oid-${path}`, size: 10, isBinary: false, isSubmodule: false, ...extra});
// gitmirror names both sides of an add or delete after the same path and marks the// side that doesn't exist with a null oidconst absent = (path: string) => src(path, { oid: "0".repeat(40), size: 0 });
const json = (body: unknown) => new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" } });
const REPO = "did:plc:repo" as never;
const diffSpec = { kind: "diff", repo: REPO, version: { base: "base", head: "head" } } as const;
// contents keyed by `<ref>:<path>`, so each side can differconst harness = (diffs: MirrorFileDiff[], contents: Record<string, string>) => { const calls: string[] = []; const fetchMock = vi.fn<typeof globalThis.fetch>().mockImplementation(async (input) => { const url = new URL(String(input)); calls.push(url.pathname); if (url.pathname.endsWith("temp2.getDiff") || url.pathname.endsWith("temp2.getInterdiff")) { return json({ diffs }); } const key = `${url.searchParams.get("ref")}:${url.searchParams.get("path")}`; const content = contents[key]; if (content === undefined) return new Response("nope", { status: 404 }); return json({ path: url.searchParams.get("path"), content, encoding: "utf-8", size: 1 }); });
const deps = { ctx: createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock }) }; const git = gitTarget( { bobbinUrl: "https://bobbin.test", knotMirrorUrl: "" } as never, { uri: "at://did:plc:alice/sh.tangled.repo/core", repoDid: "did:plc:repo" }, fetchMock ); return { deps: { ...deps, git }, calls };};
const interdiffSpec = { kind: "interdiff", repo: REPO, oldVersion: { base: "v1base", head: "v1head" }, version: { base: "v2base", head: "v2head" }} as const;
const blobCalls = (calls: string[]) => calls.filter((path) => path.endsWith("repo.blob"));
describe("loadDiff", () => { it("describes every file without reading a single blob", async () => { const { deps, calls } = harness( [ { lhsSrc: src("a.ts"), rhsSrc: src("a.ts"), hunks: [ { novelLhs: [0], novelRhs: [0, 1], lines: [{ lhs: 0, rhs: 0 }, { rhs: 1 }] } ], hasSyntacticChanges: false }, { lhsSrc: absent("new.ts"), rhsSrc: src("new.ts"), hunks: [{ novelLhs: [], novelRhs: [0], lines: [{ rhs: 0 }] }], hasSyntacticChanges: false }, { lhsSrc: src("gone.ts"), rhsSrc: absent("gone.ts"), hunks: [{ novelLhs: [0], novelRhs: [], lines: [{ lhs: 0 }] }], hasSyntacticChanges: false }, { lhsSrc: src("old.ts"), rhsSrc: src("moved.ts"), hunks: [], hasSyntacticChanges: false } ], {} );
const diff = await loadDiff(deps, diffSpec);
expect(diff.stat).toEqual({ insertions: 3, deletions: 2, files_changed: 4 }); expect(diff.truncated).toBe(0); expect(diff.contents).toEqual({ oldRef: "base", newRef: "head" }); expect(diff.files.map((file) => file.kind)).toEqual([ "changed", "new", "deleted", "renamed" ]); expect(diff.files[3].oldName).toBe("old.ts"); // every renderable file carries the two sides a later fetch needs expect(diff.files.every((file) => file.sides !== undefined)).toBe(true); // the whole point: metadata costs one request, no blob reads at all expect(blobCalls(calls)).toHaveLength(0); expect(calls).toHaveLength(1); });
it("reserves height from the hunk line count", async () => { const { deps } = harness( [ { lhsSrc: src("a.ts"), rhsSrc: src("a.ts"), hunks: [ { novelLhs: [], novelRhs: [], lines: [{ lhs: 0, rhs: 0 }, { rhs: 1 }] }, { novelLhs: [], novelRhs: [], lines: [{ lhs: 5, rhs: 6 }] } ], hasSyntacticChanges: false } ], {} );
const diff = await loadDiff(deps, diffSpec);
// three line pairs plus two lines of per-hunk chrome expect(diff.files[0].lines).toBe(7); });
it("notes the files it will never fetch, and leaves them no sides", async () => { const { deps } = harness( [ { lhsSrc: src("logo.png", { isBinary: true }), rhsSrc: src("logo.png", { isBinary: true }), hunks: [], hasSyntacticChanges: false }, { lhsSrc: src("vendor", { isSubmodule: true }), rhsSrc: src("vendor", { isSubmodule: true }), hunks: [], hasSyntacticChanges: false }, // the payload carries each side's size, so an oversize file is noted // without ever paying a request the blob endpoint would refuse { lhsSrc: src("huge.ts"), rhsSrc: src("huge.ts", { size: (1 << 20) + 1 }), hunks: [], hasSyntacticChanges: false } ], {} );
const diff = await loadDiff(deps, diffSpec);
expect(diff.files.map((file) => file.note)).toEqual([ "This is a binary file and will not be displayed.", "Submodule pointer, not shown.", "This file is too large to display." ]); expect(diff.files.every((file) => file.sides === undefined)).toBe(true); });
it("marks an interdiff's left side as arriving inline", async () => { const { deps, calls } = harness([], {});
const diff = await loadDiff(deps, interdiffSpec);
expect(diff.contents).toEqual({ inlineOld: true, newRef: "v2head" }); expect(blobCalls(calls)).toHaveLength(0); });});
describe("fetchSides", () => { it("reads both sides of a plain diff, and only the ones that exist", async () => { const { deps, calls } = harness( [ { lhsSrc: src("a.ts"), rhsSrc: src("a.ts"), hunks: [{ novelLhs: [0], novelRhs: [0], lines: [{ lhs: 0, rhs: 0 }] }], hasSyntacticChanges: false }, { lhsSrc: absent("new.ts"), rhsSrc: src("new.ts"), hunks: [{ novelLhs: [], novelRhs: [0], lines: [{ rhs: 0 }] }], hasSyntacticChanges: false } ], { "base:a.ts": "one\n", "head:a.ts": "two\nthree\n", "head:new.ts": "hello\n" } );
const diff = await loadDiff(deps, diffSpec); const changed = await fetchSides(deps, diff.contents, diff.files[0]); const added = await fetchSides(deps, diff.contents, diff.files[1]);
expect(changed).toEqual({ oldFile: { name: "a.ts", contents: "one\n" }, newFile: { name: "a.ts", contents: "two\nthree\n" } }); // a file with no left side gets an empty left side, not a failed fetch expect(added).toEqual({ oldFile: { name: "new.ts", contents: "" }, newFile: { name: "new.ts", contents: "hello\n" } }); // two for the changed file, one for the added file's right side: the absent // side is never fetched, it would 404 expect(blobCalls(calls)).toHaveLength(3); });
it("takes an interdiff's left side inline and fetches only the right", async () => { const { deps, calls } = harness( [ { lhsSrc: src("a.ts", { content: "one\n" }), rhsSrc: src("a.ts"), hunks: [{ novelLhs: [0], novelRhs: [0], lines: [{ lhs: 0, rhs: 0 }] }], hasSyntacticChanges: false }, // the rebased tree only exists inside one gitmirror request, so a left // side with no inline text can never be recovered { lhsSrc: src("b.ts"), rhsSrc: src("b.ts"), hunks: [], hasSyntacticChanges: false } ], { "v2head:a.ts": "two\n", "v2head:b.ts": "two\n" } );
const diff = await loadDiff(deps, interdiffSpec);
expect(await fetchSides(deps, diff.contents, diff.files[0])).toEqual({ oldFile: { name: "a.ts", contents: "one\n" }, newFile: { name: "a.ts", contents: "two\n" } }); expect(await fetchSides(deps, diff.contents, diff.files[1])).toEqual({ note: "Contents could not be loaded." }); // one blob per file, the right side only expect(blobCalls(calls)).toHaveLength(2); });
it("notes a side the blob endpoint cannot serve", async () => { const { deps } = harness( [ { lhsSrc: src("a.ts"), rhsSrc: src("a.ts"), hunks: [], hasSyntacticChanges: false } ], {} );
const diff = await loadDiff(deps, diffSpec);
expect(await fetchSides(deps, diff.contents, diff.files[0])).toEqual({ note: "Contents could not be loaded." }); });
it("hands back a noted file's note rather than fetching it", async () => { const { deps, calls } = harness( [ { lhsSrc: src("logo.png", { isBinary: true }), rhsSrc: src("logo.png", { isBinary: true }), hunks: [], hasSyntacticChanges: false } ], {} );
const diff = await loadDiff(deps, diffSpec);
expect(await fetchSides(deps, diff.contents, diff.files[0])).toEqual({ note: "This is a binary file and will not be displayed." }); expect(blobCalls(calls)).toHaveLength(0); });});
describe("resolveChange", () => { const bases = { oldVersion: { base: "v1base", head: "v1head" }, version: { base: "v2base", head: "v2head" } }; const older = [{ hash: "aaa", parent: "aaaparent", changeId: "kmzwvxqo" }] as never[]; const newer = [ { hash: "bbb", parent: "bbbparent", changeId: "kmzwvxqo" }, { hash: "ccc", parent: "cccparent", changeId: "nvtsyrpq" } ] as never[];
it("interdiffs the two commits carrying the change", () => { expect(resolveChange("kmzwvxqo", older, newer, bases)).toEqual({ kind: "interdiff", oldVersion: { base: "aaaparent", head: "aaa" }, version: { base: "bbbparent", head: "bbb" } }); });
it("plain-diffs a change that only exists in the newer version", () => { expect(resolveChange("nvtsyrpq", older, newer, bases)).toEqual({ kind: "diff", version: { base: "cccparent", head: "ccc" } }); });
it("reports a change the newer version does not have", () => { expect(resolveChange("zzzzzzzz", older, newer, bases)).toEqual({ kind: "missing" }); // a repo without jj change ids can never match expect(resolveChange("kmzwvxqo", older, [{ hash: "bbb" }] as never[], bases)).toEqual({ kind: "missing" }); });
it("falls back to the version base for a root commit", () => { const rootless = [{ hash: "aaa", changeId: "kmzwvxqo" }] as never[]; expect(resolveChange("kmzwvxqo", rootless, newer, bases)).toEqual({ kind: "interdiff", oldVersion: { base: "v1base", head: "aaa" }, version: { base: "bbbparent", head: "bbb" } }); });});