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 { createBoard } from "../lib/steps/create-board.js";
3
4describe("createBoard", () => {
5 const forumDid = "did:plc:testforum";
6 const categoryUri = `at://${forumDid}/space.atbb.forum.category/cattid`;
7 const categoryId = BigInt(42);
8 const categoryCid = "bafycategory";
9
10 function mockDb(options: { existingBoard?: any } = {}) {
11 return {
12 select: vi.fn().mockReturnValue({
13 from: vi.fn().mockReturnValue({
14 where: vi.fn().mockReturnValue({
15 limit: vi.fn().mockResolvedValue(
16 options.existingBoard ? [options.existingBoard] : []
17 ),
18 }),
19 }),
20 }),
21 insert: vi.fn().mockReturnValue({
22 values: vi.fn().mockResolvedValue(undefined),
23 }),
24 } as any;
25 }
26
27 function mockAgent(overrides: Record<string, any> = {}) {
28 return {
29 com: {
30 atproto: {
31 repo: {
32 createRecord: vi.fn().mockResolvedValue({
33 data: {
34 uri: `at://${forumDid}/space.atbb.forum.board/tid456`,
35 cid: "bafyboard",
36 },
37 }),
38 ...overrides,
39 },
40 },
41 },
42 } as any;
43 }
44
45 const baseInput = {
46 name: "General Discussion",
47 categoryUri,
48 categoryId,
49 categoryCid,
50 };
51
52 it("creates board on PDS and inserts into DB", async () => {
53 const db = mockDb();
54 const agent = mockAgent();
55
56 const result = await createBoard(db, agent, forumDid, baseInput);
57
58 expect(result.created).toBe(true);
59 expect(result.skipped).toBe(false);
60 expect(result.uri).toContain("space.atbb.forum.board/tid456");
61 expect(result.cid).toBe("bafyboard");
62 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledWith(
63 expect.objectContaining({
64 repo: forumDid,
65 collection: "space.atbb.forum.board",
66 record: expect.objectContaining({
67 $type: "space.atbb.forum.board",
68 name: "General Discussion",
69 // Board record includes the category ref nested under "category"
70 category: {
71 category: { uri: categoryUri, cid: categoryCid },
72 },
73 }),
74 })
75 );
76 expect(db.insert).toHaveBeenCalled();
77 });
78
79 it("derives slug from name", async () => {
80 const db = mockDb();
81 const agent = mockAgent();
82
83 await createBoard(db, agent, forumDid, {
84 ...baseInput,
85 name: "Off Topic Chat",
86 });
87
88 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledWith(
89 expect.objectContaining({
90 record: expect.objectContaining({ slug: "off-topic-chat" }),
91 })
92 );
93 });
94
95 it("skips when board with same name exists in the same category", async () => {
96 const db = mockDb({
97 existingBoard: {
98 did: forumDid,
99 rkey: "existingtid",
100 cid: "bafyexisting",
101 name: "General Discussion",
102 },
103 });
104 const agent = mockAgent();
105
106 const result = await createBoard(db, agent, forumDid, baseInput);
107
108 expect(result.created).toBe(false);
109 expect(result.skipped).toBe(true);
110 expect(result.existingName).toBe("General Discussion");
111 expect(agent.com.atproto.repo.createRecord).not.toHaveBeenCalled();
112 expect(db.insert).not.toHaveBeenCalled();
113 });
114
115 it("throws when PDS write fails", async () => {
116 const db = mockDb();
117 const agent = mockAgent({
118 createRecord: vi.fn().mockRejectedValue(new Error("PDS write failed")),
119 });
120
121 await expect(createBoard(db, agent, forumDid, baseInput)).rejects.toThrow(
122 "PDS write failed"
123 );
124 });
125
126 it("throws when DB insert fails after successful PDS write", async () => {
127 const db = {
128 select: vi.fn().mockReturnValue({
129 from: vi.fn().mockReturnValue({
130 where: vi.fn().mockReturnValue({
131 limit: vi.fn().mockResolvedValue([]), // no existing board
132 }),
133 }),
134 }),
135 insert: vi.fn().mockReturnValue({
136 values: vi.fn().mockRejectedValue(new Error("DB insert failed")),
137 }),
138 } as any;
139 const agent = mockAgent();
140
141 await expect(createBoard(db, agent, forumDid, baseInput)).rejects.toThrow(
142 "DB insert failed"
143 );
144 // PDS write happened before the failing DB insert
145 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalled();
146 });
147
148 it("includes sortOrder in PDS record and DB row when provided", async () => {
149 const db = mockDb();
150 const agent = mockAgent();
151
152 await createBoard(db, agent, forumDid, { ...baseInput, sortOrder: 3 });
153
154 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledWith(
155 expect.objectContaining({
156 record: expect.objectContaining({ sortOrder: 3 }),
157 })
158 );
159 expect(db.insert().values).toHaveBeenCalledWith(
160 expect.objectContaining({ sortOrder: 3 })
161 );
162 });
163
164 it("omits sortOrder from PDS record and DB row when not provided", async () => {
165 const db = mockDb();
166 const agent = mockAgent();
167
168 await createBoard(db, agent, forumDid, baseInput);
169
170 const record = (agent.com.atproto.repo.createRecord as ReturnType<typeof vi.fn>).mock.calls[0][0].record;
171 expect(record).not.toHaveProperty("sortOrder");
172 expect(db.insert().values).toHaveBeenCalledWith(
173 expect.objectContaining({ sortOrder: null })
174 );
175 });
176});