import { describe, expect, it, vi } from "vitest"; import { blobRawUrl, branches, gitTarget, knotMirrorTarget, log, requireGit, tag, tags, tree } from "#lib/api/gitclient.ts"; const config = { bobbinUrl: "https://bobbin.example" }; const repo = { uri: "at://did:plc:owner/sh.tangled.repo/core", repoDid: "did:plc:reporepo", git: { defaultBranch: "main", isEmpty: false } }; const jsonResponse = (body: unknown): Response => new Response(JSON.stringify(body), { status: 200, headers: { "content-type": "application/json" } }); describe("gitTarget", () => { it("routes git ops to the mirror surface on bobbin keyed by repo DID", () => { const target = gitTarget(config, repo, globalThis.fetch); expect(target.ctx.serviceUrl).toBe("https://bobbin.example"); expect(target.repo).toBe("did:plc:reporepo"); }); it("404s when the repo DID is unknown", () => { expect(() => gitTarget(config, { uri: repo.uri }, globalThis.fetch)).toThrow( expect.objectContaining({ status: 404 }) ); }); it("keys the mirror target by repo DID", () => { expect(knotMirrorTarget(config, repo, globalThis.fetch).repo).toBe("did:plc:reporepo"); }); }); describe("requireGit", () => { it("passes a repo that supports git through", () => { expect(requireGit(repo)).toBe(repo); }); it("404s a repo without git", () => { expect(() => requireGit({ git: undefined })).toThrow( expect.objectContaining({ status: 404 }) ); }); }); describe("endpoint routing", () => { const mirrorTarget = (fetchMock: typeof globalThis.fetch) => gitTarget(config, repo, fetchMock); const calledUrl = (fetchMock: ReturnType>, call = 0) => new URL(String(fetchMock.mock.calls[call][0])); it("sends log to the mirror's listCommitsByRef keyed by DID, skip intact", async () => { const fetchMock = vi .fn() .mockResolvedValue(jsonResponse({ commits: [] })); await log(mirrorTarget(fetchMock), { ref: "main", limit: 10, skip: 20 }); const url = calledUrl(fetchMock); expect(url.origin).toBe("https://bobbin.example"); expect(url.pathname).toBe("/xrpc/org.tangled.temp.git.listCommitsByRef"); expect(url.searchParams.get("repo")).toBe(repo.repoDid); expect(url.searchParams.get("ranges")).toBe("main"); expect(url.searchParams.get("skip")).toBe("20"); }); it("sends tree to bobbin's getTree with the path param", async () => { const fetchMock = vi .fn() .mockResolvedValue(jsonResponse({ entries: [] })); await tree(mirrorTarget(fetchMock), { ref: "main", path: "src/lib" }); const url = calledUrl(fetchMock); expect(url.origin).toBe("https://bobbin.example"); expect(url.pathname).toBe("/xrpc/org.tangled.temp.git.getTree"); expect(url.searchParams.get("repo")).toBe(repo.repoDid); expect(url.searchParams.get("path")).toBe("src/lib"); }); it("sends branches and tags to the list endpoints on the mirror", async () => { const fetchMock = vi .fn() .mockImplementation(() => Promise.resolve(jsonResponse({ branches: [], tags: [] }))); const target = mirrorTarget(fetchMock); await branches(target, 5); await tags(target, 5); expect(calledUrl(fetchMock).pathname).toBe("/xrpc/org.tangled.temp.git.listBranches"); expect(calledUrl(fetchMock, 1).pathname).toBe("/xrpc/org.tangled.temp.git.listTags"); }); it("routes single-tag reads to getRef on the mirror", async () => { const fetchMock = vi .fn() .mockImplementation(() => Promise.resolve(jsonResponse({}))); await tag(mirrorTarget(fetchMock), "v1.0.0"); expect(calledUrl(fetchMock).pathname).toBe("/xrpc/org.tangled.temp.git.getRef"); }); it("builds the raw url against the mirror without a raw flag", () => { const url = new URL( blobRawUrl(mirrorTarget(globalThis.fetch), { ref: "main", path: "README.md" }) ); expect(url.origin).toBe("https://bobbin.example"); expect(url.pathname).toBe("/xrpc/org.tangled.temp.git.getBlob"); expect(url.searchParams.get("repo")).toBe(repo.repoDid); expect(url.searchParams.has("raw")).toBe(false); expect(url.searchParams.has("download")).toBe(false); }); it("asks the mirror to name the file when downloading", () => { const url = new URL( blobRawUrl(mirrorTarget(globalThis.fetch), { ref: "main", path: "a/b.txt", download: true }) ); expect(url.searchParams.get("download")).toBe("true"); }); });