diff --git a/web/src/lib/api/repoSettings.test.ts b/web/src/lib/api/repoSettings.test.ts index 22667f50c..cbad26c9b 100644 --- a/web/src/lib/api/repoSettings.test.ts +++ b/web/src/lib/api/repoSettings.test.ts @@ -19,13 +19,19 @@ vi.mock("./write", () => ({ createRecord, putRecord, deleteRecord })); vi.mock("./awaitRecord", () => ({ awaitIndexedRecord, awaitDeletedRecord })); const call = vi.fn(async (_schema: unknown, _options: unknown) => ({ ok: true, data: undefined })); +const post = vi.fn( + async (_name: unknown, _options: { as: null; input: Record }) => ({ + ok: true, + data: undefined + }) +); vi.mock("./client", async (importOriginal) => ({ ...(await importOriginal()), createBobbinClient: () => ({ xrpc: { call } }) as unknown as BobbinContext })); vi.mock("./_request", async (importOriginal) => ({ ...(await importOriginal()), - serviceClient: () => ({ xrpc: { call } }) as unknown as BobbinContext + serviceClient: () => ({ xrpc: { call, post } }) as unknown as BobbinContext })); const api = await import("./repoSettings"); @@ -97,18 +103,18 @@ describe("deleteRepo", () => { const location = { uri: view.uri, knot: "knot.test", + name: "core", ownerDid: "did:plc:owner", repoDid: "did:plc:repo", cid: view.cid }; - it("drops the record before the knot repository", async () => { const order: string[] = []; deleteRecord.mockImplementationOnce(async () => { order.push("record"); return undefined; }); - call.mockImplementationOnce(async () => { + post.mockImplementationOnce(async () => { order.push("knot"); return { ok: true, data: undefined }; }); @@ -117,7 +123,9 @@ describe("deleteRepo", () => { expect(order).toEqual(["record", "knot"]); expect(deleteRecord).toHaveBeenCalledWith(agent, "sh.tangled.repo", "core", "bafyold"); - expect(call.mock.calls.at(-1)?.[1]).toEqual({ + expect(post.mock.calls.at(-1)?.[0]).toBe("sh.tangled.repo.delete"); + expect(post.mock.calls.at(-1)?.[1]).toEqual({ + as: null, input: { repo: "did:plc:repo", did: "did:plc:owner", @@ -128,11 +136,21 @@ describe("deleteRepo", () => { expect(awaitDeletedRecord).toHaveBeenCalled(); }); + it("names a repository whose record predates repo DIDs", async () => { + await api.deleteRepo(agent, bobbin, { ...location, repoDid: undefined }); + + expect(post.mock.calls.at(-1)?.[1]?.input).toEqual({ + did: "did:plc:owner", + name: "core", + rkey: "core" + }); + }); + it("reports a knot that refuses after the record is gone", async () => { - call.mockRejectedValueOnce(new Error("repo already exists: core")); + post.mockRejectedValueOnce(new Error("repo already exists: core")); await expect(api.deleteRepo(agent, bobbin, location)).rejects.toThrow( - /did not delete the repository/ + /did not delete core/ ); expect(awaitDeletedRecord).not.toHaveBeenCalled(); }); @@ -142,14 +160,4 @@ describe("deleteRepo", () => { await expect(api.deleteRepo(agent, bobbin, location)).rejects.toThrow(/appview/); }); - - it("refuses a repository that has no DID on its knot", async () => { - await expect( - api.deleteRepo(agent, bobbin, { - uri: view.uri, - knot: "knot.test", - ownerDid: "did:plc:owner" - }) - ).rejects.toThrow(/repo DIDs/); - }); }); diff --git a/web/src/lib/api/repoSettings.ts b/web/src/lib/api/repoSettings.ts index 712a26027..1bb6dbb50 100644 --- a/web/src/lib/api/repoSettings.ts +++ b/web/src/lib/api/repoSettings.ts @@ -5,7 +5,10 @@ import { awaitDeletedRecord, awaitIndexedRecord } from "$lib/api/awaitRecord"; import { serviceClient } from "$lib/api/_request"; import { createBobbinClient } from "$lib/api/client"; import { mainSchema as addCollaboratorSchema } from "$lib/api/lexicons/types/sh/tangled/repo/addCollaborator"; -import { mainSchema as deleteRepoSchema } from "$lib/api/lexicons/types/sh/tangled/repo/delete"; +import { + mainSchema as deleteRepoSchema, + type $input as DeleteRepoInput +} from "$lib/api/lexicons/types/sh/tangled/repo/delete"; import { mainSchema as removeCollaboratorSchema } from "$lib/api/lexicons/types/sh/tangled/repo/removeCollaborator"; import { mainSchema as setDefaultBranchSchema } from "$lib/api/lexicons/types/sh/tangled/repo/setDefaultBranch"; import type { RecordView, RepoRecord } from "$lib/api/records"; @@ -93,6 +96,7 @@ export const removeCollaborator = async ( export interface RepoLocation { uri: string; knot: string; + name: string; ownerDid: string; repoDid?: string; cid?: Cid; @@ -103,26 +107,24 @@ export const deleteRepo = async ( bobbinUrl: string, repo: RepoLocation ): Promise => { - if (!repo.repoDid) { - throw new Error("This repository predates repo DIDs and cannot be deleted here."); - } const rkey = rkeyFromUri(repo.uri); await deleteRecord(agent, REPO_COLLECTION, rkey, repo.cid); try { await ok( - serviceClient(agent, repo.knot).xrpc.call(deleteRepoSchema, { + serviceClient(agent, repo.knot).xrpc.post(deleteRepoSchema.nsid, { + as: null, input: { - repo: didOf(repo.repoDid), did: didOf(repo.ownerDid), - name: rkey, - rkey: recordKeyOf(rkey) - } + name: repo.name, + rkey: recordKeyOf(rkey), + ...(repo.repoDid ? { repo: didOf(repo.repoDid) } : {}) + } as DeleteRepoInput }) ); } catch (cause) { const detail = cause instanceof Error ? cause.message : String(cause); throw new Error( - `The record is gone, but ${repo.knot} did not delete the repository: ${detail}`, + `The record is gone, but ${repo.knot} did not delete ${repo.name}: ${detail}`, { cause } ); }