import { afterEach, describe, expect, it, vi } from "vitest"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { cidOf, didOf, knotOf, recordKeyOf } from "$lib/api/syntax"; vi.mock("$app/environment", () => ({ browser: false })); const agent = { sub: "did:plc:writer" } as unknown as OAuthUserAgent; const repoDid = didOf("did:web:knot.test:repo"); const knot = knotOf("knot.test"); const rkey = recordKeyOf("3labc"); const liveCid = cidOf("bafyreilive" + "a".repeat(48)); const load = async () => { vi.resetModules(); const knotWrites: { record?: Record }[] = []; const legacy: { record: Record; swap?: string }[] = []; const attested: unknown[] = []; const awaited: string[] = []; vi.doMock("$lib/api/repo", () => ({ resolveRepoKnot: vi.fn(async () => knot) })); vi.doMock("$lib/auth/agent", () => ({ mintServiceAuth: vi.fn(async () => "jwt") })); vi.doMock("$lib/api/cache", () => ({ clear: () => {} })); vi.doMock("$lib/api/attestation", () => ({ attest: vi.fn((_a: unknown, subject: unknown) => { attested.push(subject); }) })); vi.doMock("$lib/api/awaitRecord", () => ({ awaitReadable: vi.fn(async (_b: string | undefined, saved: { uri: string }) => { awaited.push(saved.uri); }), awaitRemoved: vi.fn(async () => {}) })); vi.doMock("$lib/api/write", () => ({ putRecord: vi.fn( async (_a: unknown, _c: string, _r: string, record: unknown, swap?: string) => { legacy.push({ record: record as Record, swap }); return { uri: `at://${agent.sub}/sh.tangled.repo.issue/3lnew`, cid: "bafylegacy" }; } ), deleteRecord: vi.fn(async () => {}) })); vi.doMock("$lib/api/cob", () => ({ withRkey: (saved: { uri: string; cid: string }) => ({ uri: saved.uri, cid: saved.cid, rkey: recordKeyOf("3lnew") }), createCob: vi.fn( async (_a: unknown, _k: unknown, _d: unknown, _c: string, record: unknown) => { knotWrites.push({ record: record as Record }); return { uri: `at://${repoDid}/org.tangled.track.ticket/${rkey}`, cid: liveCid }; } ), updateCob: vi.fn( async ( _a: unknown, _k: unknown, _uri: string, mutate: (current: Record) => Record ) => { const record = mutate({ $type: "org.tangled.track.ticket", title: { $type: "org.tangled.markup.markdown#inline", text: "old" }, state: "open", tangledLabels: [ { $type: "org.tangled.label.defs#labelToken", key: "reviewer" } ], targetBranch: "release", createdAt: "2026-10-01T00:00:00Z" }); knotWrites.push({ record }); return { uri: `at://${repoDid}/org.tangled.track.ticket/${rkey}`, cid: liveCid, value: record }; } ), eraseCob: vi.fn(async () => {}) })); const issue = await import("./issue"); return { issue, knotWrites, legacy, attested, awaited }; }; const capabilities = (list: string[]) => vi.stubGlobal( "fetch", vi.fn(async () => ({ ok: true, json: async () => ({ capabilities: list }) }) as Response) ); afterEach(() => { vi.unstubAllGlobals(); }); describe("ticket write targets", () => { it("writes lexicon ticket records on knots and restates live labels and branch on edits", async () => { capabilities(["knot-social-writes"]); const { issue, knotWrites } = await load(); await issue.openTicket(agent, "https://bobbin.test", repoDid, { title: "new", body: "prose" }); expect(knotWrites[0]?.record).toMatchObject({ $type: "org.tangled.track.ticket", title: { $type: "org.tangled.markup.markdown#inline", text: "new" }, body: { $type: "org.tangled.markup.markdown", text: "prose" }, state: "open" }); await issue.editTicket(agent, "https://bobbin.test", repoDid, { rkey, swapRecord: liveCid, title: "newer" }); expect(knotWrites[1]?.record).toMatchObject({ title: { text: "newer" }, state: "open", tangledLabels: [{ key: "reviewer" }], targetBranch: "release" }); }); it("writes legacy issue collections when knots don't advertise social writes", async () => { capabilities(["knot-acl"]); const { issue, knotWrites, legacy, attested, awaited } = await load(); await issue.openTicket(agent, "https://bobbin.test", repoDid, { title: "new" }); expect(knotWrites).toEqual([]); expect(attested).toEqual([]); expect(legacy[0]?.record).toEqual({ $type: "sh.tangled.repo.issue", repo: repoDid, title: "new", body: "", createdAt: expect.any(String) }); expect(awaited).toHaveLength(1); }); });