WIP! A BB-style forum, on the ATmosphere!
We're still working... we'll be back soon when we have something to show off!
node
typescript
hono
htmx
atproto
1import { describe, it, expect, vi } from "vitest";
2import { createForumRecord } from "../lib/steps/create-forum.js";
3
4describe("createForumRecord", () => {
5 const forumDid = "did:plc:testforum";
6
7 function mockDb() {
8 return {
9 insert: vi.fn().mockReturnValue({
10 values: vi.fn().mockResolvedValue(undefined),
11 }),
12 } as any;
13 }
14
15 // XRPC "RecordNotFound" error mimics @atproto/api behavior
16 function recordNotFoundError() {
17 const err = Object.assign(new Error("Record not found"), {
18 status: 400,
19 error: "RecordNotFound",
20 });
21 return err;
22 }
23
24 function mockAgent(overrides: Record<string, any> = {}) {
25 return {
26 com: {
27 atproto: {
28 repo: {
29 getRecord: vi.fn().mockRejectedValue(recordNotFoundError()),
30 createRecord: vi.fn().mockResolvedValue({
31 data: { uri: `at://${forumDid}/space.atbb.forum.forum/self`, cid: "bafytest" },
32 }),
33 ...overrides,
34 },
35 },
36 },
37 } as any;
38 }
39
40 it("creates forum record and inserts into DB when it does not exist", async () => {
41 const db = mockDb();
42 const agent = mockAgent();
43
44 const result = await createForumRecord(db, agent, forumDid, {
45 name: "My Forum",
46 description: "A test forum",
47 });
48
49 expect(result.created).toBe(true);
50 expect(result.cid).toBe("bafytest");
51 expect(result.uri).toContain("space.atbb.forum.forum/self");
52 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledWith(
53 expect.objectContaining({
54 repo: forumDid,
55 collection: "space.atbb.forum.forum",
56 rkey: "self",
57 record: expect.objectContaining({
58 $type: "space.atbb.forum.forum",
59 name: "My Forum",
60 description: "A test forum",
61 }),
62 })
63 );
64 // Verify DB insertion
65 expect(db.insert).toHaveBeenCalled();
66 });
67
68 it("skips creation when forum record already exists", async () => {
69 const db = mockDb();
70 const agent = mockAgent({
71 getRecord: vi.fn().mockResolvedValue({
72 data: {
73 uri: `at://${forumDid}/space.atbb.forum.forum/self`,
74 cid: "bafyexisting",
75 value: { name: "Existing Forum" },
76 },
77 }),
78 });
79
80 const result = await createForumRecord(db, agent, forumDid, {
81 name: "My Forum",
82 });
83
84 expect(result.created).toBe(false);
85 expect(result.skipped).toBe(true);
86 expect(result.cid).toBe("bafyexisting");
87 expect(agent.com.atproto.repo.createRecord).not.toHaveBeenCalled();
88 // No DB insertion when skipped
89 expect(db.insert).not.toHaveBeenCalled();
90 });
91
92 it("throws when PDS write fails", async () => {
93 const db = mockDb();
94 const agent = mockAgent({
95 createRecord: vi.fn().mockRejectedValue(new Error("PDS write failed")),
96 });
97
98 await expect(
99 createForumRecord(db, agent, forumDid, { name: "My Forum" })
100 ).rejects.toThrow("PDS write failed");
101 });
102
103 it("re-throws non-RecordNotFound errors from getRecord", async () => {
104 const db = mockDb();
105 const agent = mockAgent({
106 getRecord: vi.fn().mockRejectedValue(new Error("Network timeout")),
107 });
108
109 await expect(
110 createForumRecord(db, agent, forumDid, { name: "My Forum" })
111 ).rejects.toThrow("Network timeout");
112
113 // Should never attempt createRecord
114 expect(agent.com.atproto.repo.createRecord).not.toHaveBeenCalled();
115 });
116});