import { describe, expect, it, vi } from "vitest";
import { cidOf, nsidOf, recordKeyOf } from "#lib/api/syntax.ts";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import type * as Write from "#lib/api/write.ts";
const agent = { sub: "did:plc:writer" } as unknown as OAuthUserAgent;
const collection = nsidOf("sh.tangled.repo.issue");
const record = { $type: "sh.tangled.repo.issue", title: "hi" };
const mintedCid = "bafyreiminted" + "a".repeat(46);
const readCid = "bafyreiread" + "a".repeat(48);
const createdCid = "bafyreicreated" + "a".repeat(45);
// doMock needs the modules loaded after it, so the imports are dynamic
const load = async () => {
vi.resetModules();
const inputs: Record<string, unknown>[] = [];
vi.doMock("$app/env", () => ({ browser: true }));
vi.doMock("#lib/auth/agent.ts", () => ({
createClient: () => ({
call: async (_schema: unknown, options: { input: Record<string, unknown> }) => {
inputs.push(options.input);
return {
ok: true,
data: { uri: "at://did:plc:writer/sh.tangled.repo.issue/1", cid: mintedCid }
};
}
})
}));
return { cache: await import("./cache"), write: await import("#lib/api/write.ts"), inputs };
};
describe("record writes", () => {
it("drops cached reads so a write is visible on the next read", async () => {
const { cache, write } = await load();
const read = vi.fn().mockResolvedValue("stale");
await cache.cached("issues:did:plc:repo:open:", 30_000, read);
await write.putRecord(agent, collection, recordKeyOf("3lk"), record);
await cache.cached("issues:did:plc:repo:open:", 30_000, read);
expect(read).toHaveBeenCalledTimes(2);
});
it.each([
[
"putRecord",
(w: typeof Write) =>
w.putRecord(agent, collection, recordKeyOf("3lk"), record, cidOf(readCid)),
readCid
],
[
"deleteRecord",
(w: typeof Write) =>
w.deleteRecord(agent, collection, recordKeyOf("3lk"), cidOf(createdCid)),
createdCid
]
])("forwards the swap guard on %s", async (_name, op, swapRecord) => {
const { write, inputs } = await load();
await op(write);
expect(inputs[0]).toMatchObject({ rkey: "3lk", swapRecord });
});
it("takes a minted rkey instead of letting the pds assign one", async () => {
const { write, inputs } = await load();
await write.createRecord(agent, collection, record, recordKeyOf("3lkminted"));
expect(inputs[0]).toMatchObject({ rkey: "3lkminted" });
});
it("keeps the cache when the write fails", async () => {
vi.resetModules();
vi.doMock("$app/env", () => ({ browser: true }));
vi.doMock("#lib/auth/agent.ts", () => ({
createClient: () => ({
call: async () => ({
ok: false,
data: { error: "InvalidRequest", message: "nope" }
})
})
}));
const cache = await import("./cache");
const write = await import("#lib/api/write.ts");
const read = vi.fn().mockResolvedValue("fresh");
await cache.cached("issues:did:plc:repo:open:", 30_000, read);
await expect(write.deleteRecord(agent, collection, recordKeyOf("3lk"))).rejects.toThrow();
await cache.cached("issues:did:plc:repo:open:", 30_000, read);
expect(read).toHaveBeenCalledTimes(1);
});
});
describe("readRecord", () => {
it("returns record value and written metadata on 200", async () => {
vi.resetModules();
vi.doMock("$app/env", () => ({ browser: true }));
vi.doMock("#lib/auth/agent.ts", () => ({
createClient: () => ({
call: async () => ({
ok: true,
data: {
uri: "at://did:plc:writer/sh.tangled.repo.issue/1",
cid: mintedCid,
value: { title: "found" }
}
})
})
}));
const write = await import("#lib/api/write.ts");
const result = await write.readRecord<{ title: string }>(
agent,
collection,
recordKeyOf("3lk")
);
expect(result).toEqual({
value: { title: "found" },
written: { uri: "at://did:plc:writer/sh.tangled.repo.issue/1", cid: mintedCid }
});
});
it.each(["RecordNotFound", "CouldNotFindRecord"])("returns null on %s", async (error) => {
vi.resetModules();
vi.doMock("$app/env", () => ({ browser: true }));
vi.doMock("#lib/auth/agent.ts", () => ({
createClient: () => ({
call: async () => ({
ok: false,
data: { error, message: "not found" }
})
})
}));
const write = await import("#lib/api/write.ts");
const result = await write.readRecord(agent, collection, recordKeyOf("3lk"));
expect(result).toBeNull();
});
it("rethrows non-404 errors instead of swallowing into null", async () => {
vi.resetModules();
vi.doMock("$app/env", () => ({ browser: true }));
vi.doMock("#lib/auth/agent.ts", () => ({
createClient: () => ({
call: async () => ({
ok: false,
status: 500,
data: { error: "InternalServerError", message: "pds down" }
})
})
}));
const write = await import("#lib/api/write.ts");
await expect(write.readRecord(agent, collection, recordKeyOf("3lk"))).rejects.toThrow();
});
});