Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161import { 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; putError?: Error;}
// mocks have to be installed before the module is loadedconst load = async ({ createError, putError }: HarnessOptions = {}) => { vi.resetModules(); const events: string[] = []; const records: unknown[] = []; const knotInputs: Record<string, unknown>[] = []; const createRecord = vi.fn(async (_agent, _collection, record) => { events.push("record:create"); records.push(record); return { uri: "at://did:plc:alice/sh.tangled.repo/demo", cid: "initial-cid" }; }); 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"); });
vi.doMock("./write", () => ({ createRecord, putRecord, deleteRecord })); vi.doMock("./client", () => ({ createBobbinClient: ({ serviceUrl }: { serviceUrl: string }) => ({ 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 (options.input) knotInputs.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: { repoDid: "did:plc:repo" } }; } } }) }));
return { api: await import("./repoCreate"), events, records, knotInputs, createRecord, putRecord, deleteRecord };};
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("announces the record before creating the knot repo, then commits the repo DID", async () => { const { api, events, records, putRecord } = await load();
const creation = await api.createRepo(agent, "https://bobbin.test", input);
expect(creation.name).toBe("Demo"); expect(events).toEqual([ "record:create", "bobbin:await", "knot:create", "record:put", "bobbin:await" ]); expect(records[0]).toMatchObject({ $type: "sh.tangled.repo", knot: "knot.test", name: "Demo", spindle: "spindle.test" }); expect(records[1]).toMatchObject({ repoDid: "did:plc:repo" }); expect(putRecord).toHaveBeenCalledWith( agent, "sh.tangled.repo", "demo", expect.anything(), "initial-cid" ); });
it("removes the pds record before cleaning up the knot when commit fails", async () => { const { api, events, deleteRecord } = await load({ putError: new Error("pds refused") });
await expect(api.createRepo(agent, "https://bobbin.test", input)).rejects.toThrow( "pds refused" );
expect(events.slice(-2)).toEqual(["record:delete", "knot:delete"]); expect(deleteRecord).toHaveBeenCalledWith(agent, "sh.tangled.repo", "demo", "initial-cid"); });
it("removes the announced record 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).toContain("record:delete"); expect(events).not.toContain("knot:delete"); });
it("passes fork identity and clone source to both halves", async () => { const { api, records, knotInputs } = await load(); await api.createRepo(agent, "https://bobbin.test", { ...input, name: "fork", source: { record: "did:plc:upstream", cloneUrl: "https://knot.source/did:plc:upstream" } });
expect(records[0]).toMatchObject({ source: "did:plc:upstream" }); expect(knotInputs[0]).toMatchObject({ source: "https://knot.source/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(); } );});