Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333import { describe, expect, it, vi } from "vitest";import type { Did } from "@atcute/lexicons/syntax";import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
const agent = { sub: "did:plc:alice"} as unknown as OAuthUserAgent;
interface HarnessOptions { createError?: Error; recordError?: Error; putError?: Error; readError?: Error; existing?: { value: Record<string, unknown>; cid?: string | null } | null; mirrorMisses?: number; emptyAnswers?: number; upstreamBranches?: string[];}
// mocks have to be installed before the module is loadedconst load = async ({ createError, recordError, putError, readError, existing = null, mirrorMisses = 0, emptyAnswers = 0, upstreamBranches = ["main"]}: HarnessOptions = {}) => { vi.resetModules(); const events: string[] = []; const records: unknown[] = []; const knotInputs: Record<string, unknown>[] = []; const knotAgents: OAuthUserAgent[] = []; const createRecord = vi.fn(async (_agent, _collection, record) => { events.push("record:create"); if (recordError) throw recordError; records.push(record); return { uri: "at://did:plc:alice/sh.tangled.repo/demo", cid: "initial-cid" }; }); const runCalls: Record<string, unknown>[] = []; const putRecord = vi.fn(async (_agent, _collection, _rkey, record, swapRecord) => { events.push("record:put"); if (putError) throw putError; records.push(record); return { uri: "at://did:plc:alice/sh.tangled.repo/demo", cid: "committed-cid", swapRecord }; }); const deleteRecord = vi.fn(async () => { events.push("record:delete"); });
const readRecord = vi.fn(async () => { if (readError) throw readError; return existing === null ? null : { value: existing.value, written: existing.cid === null ? null : { uri: "at://did:plc:alice/sh.tangled.repo/demo", cid: existing.cid ?? "existing-cid" } }; });
let misses = mirrorMisses; let empties = emptyAnswers; const listBranches = vi.fn(async (_ctx, { repo }: { repo: string }) => { events.push("mirror:list"); if (repo === "did:plc:upstream") return { branches: upstreamBranches.map((name) => ({ name })) }; if (misses-- > 0) throw new Error("RepoNotFound"); return { branches: empties-- > 0 ? [] : [{ name: "main" }] }; });
vi.doMock("./knotmirror", () => ({ branches: listBranches })); vi.doMock("./write", () => ({ createRecord, putRecord, deleteRecord, readRecord })); vi.doMock("./client", () => ({ createBobbinClient: ({ serviceUrl, agent: caller }: { serviceUrl: string; agent?: OAuthUserAgent; }) => ({ xrpc: { call: async (_schema: unknown, options: { input?: Record<string, unknown> }) => { if (serviceUrl === "https://bobbin.test") { events.push("bobbin:await"); return { ok: true, data: { status: "indexed" } }; } if (caller) knotAgents.push(caller); if (options.input) { knotInputs.push(options.input); runCalls.push(options.input); } if (options.input && "repo" in options.input) { events.push("knot:delete"); return { ok: true, data: null }; } events.push("knot:create"); if (createError) throw createError; return { ok: true, data: { did: "did:plc:repo" } }; } } }) }));
return { api: await import("./repoCreate"), events, records, runCalls, knotInputs, knotAgents, createRecord, putRecord, deleteRecord, readRecord, listBranches };};
const input = { ownerDid: "did:plc:alice" as Did, ownerHandle: "alice.test", name: "Demo.git", description: "a repo", defaultBranch: "main", knot: "knot.test", spindle: "spindle.test"};
describe("repository creation", () => { it("uses the organization agent for the PDS record and knot creation", async () => { const { api, createRecord, knotAgents } = await load(); const org = { sub: "did:plc:org" } as unknown as OAuthUserAgent; const result = await api.createRepo(org, "https://bobbin.test", { ...input, ownerDid: "did:plc:org", ownerHandle: "org.test" }); expect(createRecord.mock.calls[0][0]).toBe(org); expect(knotAgents).toEqual([org]); expect(result.ownerHandle).toBe("org.test"); }); it("rejects an organization owner paired with the controller's session", async () => { const { api, createRecord } = await load(); await expect( api.createRepo(agent, "https://bobbin.test", { ...input, ownerDid: "did:plc:org", ownerHandle: "org.test" }) ).rejects.toThrow("active account changed"); expect(createRecord).not.toHaveBeenCalled(); });
it("creates the knot repo, then declares its DID under the lowercased name", async () => { const { api, events, records, knotInputs, createRecord } = await load();
const creation = await api.createRepo(agent, "https://bobbin.test", input);
expect(creation.name).toBe("Demo"); expect(events).toEqual(["knot:create", "record:create", "bobbin:await", "mirror:list"]); expect(knotInputs[0]).toMatchObject({ name: "Demo", spindle: "did:web:spindle.test" }); expect(records[0]).toMatchObject({ $type: "org.tangled.repo.declaration", repo: "did:plc:repo" }); expect(createRecord).toHaveBeenCalledWith( agent, "org.tangled.repo.declaration", expect.anything(), "demo" ); });
it("waits for the mirror to know the repo before handing it back", async () => { const { api, listBranches } = await load({ mirrorMisses: 2 });
const creation = await api.createRepo(agent, "https://bobbin.test", input);
expect(listBranches).toHaveBeenCalledTimes(3); expect(listBranches.mock.calls[0]).toEqual([ expect.anything(), { repo: "did:plc:repo", limit: 1 } ]); expect(creation.repoDid).toBe("did:plc:repo"); });
it("waits for a fork to have the upstream's branches, not just to exist", async () => { const { api, listBranches } = await load({ emptyAnswers: 2 });
await api.createRepo(agent, "https://bobbin.test", { ...input, forkOf: "did:plc:upstream" });
const forkReads = listBranches.mock.calls.filter(([, { repo }]) => repo === "did:plc:repo"); expect(forkReads).toHaveLength(3); });
it("doesn't wait for branches on a fork of an empty repo", async () => { const { api, listBranches } = await load({ emptyAnswers: Infinity, upstreamBranches: [] });
await api.createRepo(agent, "https://bobbin.test", { ...input, forkOf: "did:plc:upstream" });
const forkReads = listBranches.mock.calls.filter(([, { repo }]) => repo === "did:plc:repo"); expect(forkReads).toHaveLength(1); });
it("hands the repo back anyway when the mirror never catches up", async () => { vi.useFakeTimers(); try { const { api, events, listBranches } = await load({ mirrorMisses: Infinity });
const creating = api.createRepo(agent, "https://bobbin.test", input); await vi.advanceTimersByTimeAsync(60_000);
expect((await creating).repoDid).toBe("did:plc:repo"); expect(listBranches.mock.calls.length).toBeGreaterThan(3); expect(events).not.toContain("knot:delete"); expect(events).not.toContain("record:delete"); } finally { vi.useRealTimers(); } });
it("cleans up the knot when the declaration cannot be written", async () => { const { api, events } = await load({ recordError: new Error("pds refused") });
await expect(api.createRepo(agent, "https://bobbin.test", input)).rejects.toThrow( "pds refused" );
expect(events.slice(-1)).toEqual(["knot:delete"]); });
it("leaves the pds untouched when the knot rejects creation", async () => { const { api, events } = await load({ createError: new Error("no capacity") });
await expect(api.createRepo(agent, "https://bobbin.test", input)).rejects.toThrow( "no capacity" );
expect(events).not.toContain("record:create"); expect(events).not.toContain("knot:delete"); });
it("refuses a name the account already has instead of failing inside the pds", async () => { const { api, events, readRecord } = await load({ existing: { value: { $type: "org.tangled.repo.declaration", repo: "did:plc:theirs" } } });
await expect(api.createRepo(agent, "https://bobbin.test", input)).rejects.toThrow( 'A repository named "Demo" already exists in your account. Choose another name.' );
expect(readRecord).toHaveBeenCalledWith(agent, "org.tangled.repo.declaration", "demo"); expect(events).toEqual([]); });
it("fails immediately when readRecord throws rather than proceeding with blind create", async () => { const { api } = await load({ readError: new Error("PDS connection dropped") }); await expect(api.createRepo(agent, "https://bobbin.test", input)).rejects.toThrow( "PDS connection dropped" ); });
it("passes the fork upstream as a tangled source", async () => { const { api, knotInputs } = await load(); await api.createRepo(agent, "https://bobbin.test", { ...input, name: "fork", forkOf: "did:plc:upstream" });
expect(knotInputs[0]).toMatchObject({ source: { $type: "org.tangled.repo.defs#sourceTangled", did: "did:plc:upstream" } }); });});
describe("repository names", () => { it.each(["", ".hidden", "trail.", "a/b", "a..b", "self", "bad name"])( "rejects %j", async (name) => { const { api } = await load(); expect(() => api.validateRepoName(name)).toThrow(); } );});
describe("default branch names", () => { it.each(["main", "release/2026.10", "feat/qa-fixes", "v1"])("accepts %j", async (branch) => { const { api } = await load(); expect(api.branchNameProblem(branch)).toBeUndefined(); });
it.each([ "", "a".repeat(245), "é".repeat(123), "has space", "a..b", "-lead", "trail/", "a//b", "end.", "x.lock", ".hidden", "a@{b", "@", "what?" ])("rejects %j", async (branch) => { const { api } = await load(); expect(api.branchNameProblem(branch)).toBeTypeOf("string"); });});