import { describe, expect, it, vi } from "vitest";
import {
blobRawUrl,
branches,
gitTarget,
knotMirrorTarget,
log,
requireGit,
tag,
tags,
tree
} from "#lib/api/gitclient.ts";
import type { Fetch } from "#lib/fetch.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: Fetch) => gitTarget(config, repo, fetchMock);
const calledUrl = (fetchMock: ReturnType<typeof vi.fn<Fetch>>, 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<Fetch>().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<Fetch>().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<Fetch>()
.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<Fetch>()
.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");
});
});