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
at root/atb-56-theme-caching-layer 141 lines 4.3 kB view raw
1import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; 2import { seedDefaultRoles } from "../seed-roles.js"; 3import { createTestContext, type TestContext } from "./test-context.js"; 4import { roles } from "@atbb/db"; 5 6describe("seedDefaultRoles", () => { 7 let ctx: TestContext; 8 9 beforeEach(async () => { 10 ctx = await createTestContext(); 11 }); 12 13 afterEach(async () => { 14 await ctx.cleanup(); 15 }); 16 17 function mockAgent(opts: { 18 failForRole?: string; 19 alwaysFail?: boolean; 20 } = {}) { 21 let callIndex = 0; 22 return { 23 com: { 24 atproto: { 25 repo: { 26 createRecord: vi.fn().mockImplementation(({ record }: { record: { name: string } }) => { 27 callIndex++; 28 if (opts.alwaysFail || record.name === opts.failForRole) { 29 throw new Error(`PDS write failed for role "${record.name}"`); 30 } 31 return Promise.resolve({ 32 data: { 33 uri: `at://${ctx.config.forumDid}/space.atbb.forum.role/tid${callIndex}`, 34 cid: `bafynew${callIndex}`, 35 }, 36 }); 37 }), 38 }, 39 }, 40 }, 41 } as any; 42 } 43 44 function withAgent(agent: ReturnType<typeof mockAgent>) { 45 return { 46 ...ctx, 47 forumAgent: { 48 getAgent: () => agent, 49 } as any, 50 }; 51 } 52 53 it("creates all roles when none exist", async () => { 54 const agent = mockAgent(); 55 const result = await seedDefaultRoles(withAgent(agent)); 56 57 expect(result.created).toBe(4); 58 expect(result.skipped).toBe(0); 59 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledTimes(4); 60 }); 61 62 it("skips roles that already exist in the database", async () => { 63 // Pre-insert Owner role so it gets skipped 64 await ctx.db.insert(roles).values({ 65 did: ctx.config.forumDid, 66 rkey: "owner-rkey", 67 cid: "bafyexisting", 68 name: "Owner", 69 description: "Forum owner", 70 priority: 0, 71 createdAt: new Date(), 72 indexedAt: new Date(), 73 }); 74 75 const agent = mockAgent(); 76 const result = await seedDefaultRoles(withAgent(agent)); 77 78 expect(result.skipped).toBe(1); 79 expect(result.created).toBe(3); 80 // Only 3 PDS writes — Owner was skipped 81 expect(agent.com.atproto.repo.createRecord).toHaveBeenCalledTimes(3); 82 }); 83 84 it("throws when ForumAgent is null", async () => { 85 await expect(seedDefaultRoles({ ...ctx, forumAgent: null })).rejects.toThrow( 86 "ForumAgent not available" 87 ); 88 }); 89 90 it("throws when ForumAgent is not authenticated (getAgent returns null)", async () => { 91 const unauthenticatedAgent = { getAgent: () => null } as any; 92 await expect( 93 seedDefaultRoles({ ...ctx, forumAgent: unauthenticatedAgent }) 94 ).rejects.toThrow("ForumAgent not authenticated"); 95 }); 96 97 it("throws when the Member role (critical) fails to seed", async () => { 98 const agent = mockAgent({ failForRole: "Member" }); 99 100 await expect(seedDefaultRoles(withAgent(agent))).rejects.toThrow( 101 'Failed to seed critical role "Member"' 102 ); 103 }); 104 105 it("throws when any other role fails to seed", async () => { 106 const agent = mockAgent({ failForRole: "Owner" }); 107 108 await expect(seedDefaultRoles(withAgent(agent))).rejects.toThrow( 109 'Failed to seed critical role "Owner"' 110 ); 111 }); 112 113 it("does not partially create roles when a critical role fails", async () => { 114 // Owner and Admin succeed, then Member fails 115 let callCount = 0; 116 const failingAgent = { 117 com: { 118 atproto: { 119 repo: { 120 createRecord: vi.fn().mockImplementation(({ record }: { record: { name: string } }) => { 121 callCount++; 122 if (record.name === "Member") { 123 throw new Error("PDS timeout"); 124 } 125 return Promise.resolve({ 126 data: { 127 uri: `at://${ctx.config.forumDid}/space.atbb.forum.role/tid${callCount}`, 128 cid: `bafynew${callCount}`, 129 }, 130 }); 131 }), 132 }, 133 }, 134 }, 135 } as any; 136 137 await expect(seedDefaultRoles(withAgent(failingAgent))).rejects.toThrow( 138 'Failed to seed critical role "Member"' 139 ); 140 }); 141});