import { isHttpError } from "@sveltejs/kit"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { ClientResponseError } from "#lib/api/client.ts"; import { redirectRepoDidToKnot, redirectToKnot } from "#lib/server/gitHttp.ts"; const REPO_DID = "did:plc:j5hmlfdrwkvtxm7cjmu7j2is"; const OWNER_DID = "did:plc:wshs7t2adsemcrrd4snkeqli"; const mocks = vi.hoisted(() => ({ resolveRepoByName: vi.fn(), getRepoByRepoDid: vi.fn() })); vi.mock("#lib/server/config.ts", () => ({ getConfig: () => ({ bobbinUrl: "https://bobbin.test", posthogApiKey: "phc_test", posthogHost: "https://posthog.test", posthogEnv: "test" }) })); vi.mock("#lib/api/identity.ts", () => ({ resolveMiniDoc: async () => ({ did: OWNER_DID, handle: "tangled.org" }) })); vi.mock("#lib/api/repo.ts", () => ({ resolveRepoByName: mocks.resolveRepoByName })); vi.mock("#lib/api/records.ts", () => ({ getRepoByRepoDid: mocks.getRepoByRepoDid })); const repoView = (knot?: string) => ({ uri: `at://${REPO_DID}/org.tangled.repo.manifest/self`, knot, value: { declaration: { owner: OWNER_DID, name: "core" } } }); const event =

(params: P, path: string) => ({ params, url: new URL(`https://tangled.test${path}`), fetch: vi.fn() }); const statusOf = (promise: Promise) => promise.then( () => "resolved", (cause) => (isHttpError(cause) ? cause.status : cause) ); const captured = vi.fn(); beforeEach(() => { mocks.resolveRepoByName.mockReset().mockResolvedValue(repoView("knot1.tangled.sh")); mocks.getRepoByRepoDid.mockReset().mockResolvedValue(repoView("knot1.tangled.sh")); captured.mockReset().mockResolvedValue(new Response("{}")); vi.stubGlobal("fetch", captured); }); const sentEvents = () => captured.mock.calls.map( ([, init]) => JSON.parse(String(init?.body)) as Record ); describe("clone tracking", () => { it.each([ [ "by name", () => redirectToKnot("info/refs")( event( { handle: "tangled.org", repo: "core" }, "/tangled.org/core/info/refs?service=git-upload-pack" ) ) ], [ "by repo did", () => redirectRepoDidToKnot("info/refs")( event({ handle: REPO_DID }, `/${REPO_DID}/info/refs?service=git-upload-pack`) ) ] ])("counts an upload-pack ref advertisement %s against the owner", async (_, run) => { await run(); expect(sentEvents()).toEqual([ expect.objectContaining({ event: "clone", distinct_id: OWNER_DID, properties: { repo: "core", repo_at: `at://${REPO_DID}/org.tangled.repo.manifest/self`, environment: "test" } }) ]); }); it.each([ ["a push", "info/refs", "?service=git-receive-pack"], ["the pack itself", "git-upload-pack", ""], ["an archive", "info/refs", "?service=git-upload-archive"] ])("leaves %s uncounted", async (_, service, search) => { await redirectToKnot(service)( event({ handle: "tangled.org", repo: "core" }, `/tangled.org/core/${service}${search}`) ); expect(captured).not.toHaveBeenCalled(); }); }); describe("redirectToKnot", () => { it.each([ ["info/refs", "?service=git-upload-pack"], ["info/refs", "?service=git-receive-pack"], ["git-upload-pack", ""], ["git-receive-pack", ""] ])("sends %s%s to the repo did on its knot", async (service, search) => { const res = await redirectToKnot(service)( event({ handle: "tangled.org", repo: "core" }, `/tangled.org/core/${service}${search}`) ); expect(res.status).toBe(307); expect(res.headers.get("location")).toBe( `https://knot1.tangled.sh/${REPO_DID}/${service}${search}` ); }); it("drops a .git suffix from the repo name", async () => { await redirectToKnot("info/refs")( event({ handle: "tangled.org", repo: "core.git" }, "/tangled.org/core.git/info/refs") ); expect(mocks.resolveRepoByName).toHaveBeenCalledWith(expect.anything(), OWNER_DID, "core"); }); it("follows a did:web knot to its host", async () => { mocks.resolveRepoByName.mockResolvedValue(repoView("did:web:knot.example.com")); const res = await redirectToKnot("info/refs")( event({ handle: "tangled.org", repo: "core" }, "/tangled.org/core/info/refs") ); expect(res.headers.get("location")).toBe(`https://knot.example.com/${REPO_DID}/info/refs`); }); it.each([ ["the repo does not exist", null], ["the repo has no knot", repoView()] ])("answers 404 when %s", async (_, view) => { mocks.resolveRepoByName.mockResolvedValue(view); const handler = redirectToKnot("info/refs"); await expect( statusOf( handler( event({ handle: "tangled.org", repo: "nope" }, "/tangled.org/nope/info/refs") ) ) ).resolves.toBe(404); }); }); describe("redirectRepoDidToKnot", () => { it("sends the repo did permalink to its knot", async () => { const res = await redirectRepoDidToKnot("info/refs")( event({ handle: REPO_DID }, `/${REPO_DID}/info/refs?service=git-upload-pack`) ); expect(res.status).toBe(307); expect(res.headers.get("location")).toBe( `https://knot1.tangled.sh/${REPO_DID}/info/refs?service=git-upload-pack` ); }); it("answers 404 for a handle without looking it up", async () => { const handler = redirectRepoDidToKnot("info/refs"); await expect( statusOf(handler(event({ handle: "tangled.org" }, "/tangled.org/info/refs"))) ).resolves.toBe(404); expect(mocks.getRepoByRepoDid).not.toHaveBeenCalled(); }); it.each([ [404, "RepoNotFound"], [503, "Overloaded"] ])("answers %i when looking up the did fails with %s", async (status, error) => { mocks.getRepoByRepoDid.mockRejectedValue( new ClientResponseError({ status, data: { error } }) ); const handler = redirectRepoDidToKnot("info/refs"); await expect( statusOf(handler(event({ handle: OWNER_DID }, `/${OWNER_DID}/info/refs`))) ).resolves.toBe(status); }); });