an app to share curated trails sidetrail.app
1

Configure Feed

Select the types of activity you want to include in your feed.

1/** 2 * Test setup for action tests with eventual consistency verification 3 * 4 * This setup allows actions to run (unlike the main setup which mocks them out) 5 * and automatically verifies that optimistic writes match ingester behavior. 6 * 7 * Usage: Create test files in data/__tests__/actions/ that import this setup. 8 */ 9 10import { beforeAll, beforeEach, afterAll, afterEach, vi } from "vitest"; 11import { 12 initTestDb, 13 clearTestDb, 14 closeTestDb, 15 resetCounters, 16 resetSeqCounter, 17 resetCurrentUser, 18 resetTidCounter, 19 getCurrentTestDid, 20 resolveHandle, 21 resolveDid, 22 getAvatar, 23} from "./helpers"; 24import { 25 createAfterMock, 26 createCapturingLexClient, 27 verifyEventualConsistency, 28 reset as resetEventualConsistency, 29} from "./helpers/eventual-consistency"; 30 31// ============================================================================ 32// Global Setup 33// ============================================================================ 34 35beforeAll(async () => { 36 await initTestDb(); 37}); 38 39afterAll(async () => { 40 await closeTestDb(); 41}); 42 43beforeEach(async () => { 44 await clearTestDb(); 45 resetCounters(); 46 resetSeqCounter(); 47 resetTidCounter(); 48 resetCurrentUser(); 49 resetEventualConsistency(); 50 vi.clearAllMocks(); 51}); 52 53afterEach(async () => { 54 // Verify eventual consistency after each test 55 await verifyEventualConsistency(); 56}); 57 58// ============================================================================ 59// Module Mocks 60// ============================================================================ 61 62// Mock server-only 63vi.mock("server-only", () => ({})); 64 65// Mock next/cache 66vi.mock("next/cache", () => ({ 67 revalidatePath: vi.fn(), 68 revalidateTag: vi.fn(), 69 updateTag: vi.fn(), 70 cacheTag: vi.fn(), 71 cacheLife: vi.fn(), 72 refresh: vi.fn(), 73})); 74 75// Mock next/headers 76vi.mock("next/headers", () => ({ 77 cookies: vi.fn(async () => ({ 78 get: vi.fn(() => null), 79 set: vi.fn(), 80 delete: vi.fn(), 81 })), 82})); 83 84// Mock next/navigation 85class NotFoundError extends Error { 86 constructor() { 87 super("NEXT_NOT_FOUND"); 88 this.name = "NotFoundError"; 89 } 90} 91 92vi.mock("next/navigation", () => ({ 93 notFound: () => { 94 throw new NotFoundError(); 95 }, 96})); 97 98// Mock next/server - capture after() callbacks 99vi.mock("next/server", () => ({ 100 after: createAfterMock(), 101})); 102 103// Mock @/auth 104vi.mock("@/auth", () => ({ 105 getCurrentDid: vi.fn(() => getCurrentTestDid()), 106 getCurrentUser: vi.fn(async () => { 107 const did = getCurrentTestDid(); 108 if (!did) return null; 109 const handle = resolveDid(did); 110 const avatar = getAvatar(did); 111 return handle ? { did, handle, avatar } : null; 112 }), 113 getSessionAgent: vi.fn(async () => null), 114})); 115 116// Mock @/data/lex-client - use capturing client that tracks PDS operations 117const capturingClient = createCapturingLexClient(() => getCurrentTestDid() || "did:plc:test"); 118 119vi.mock("@/data/lex-client", () => ({ 120 getLexClient: vi.fn(async () => capturingClient), 121})); 122 123// Mock @/data/db to use test database 124vi.mock("@/data/db", async () => { 125 const { getTestDb, trails, walks, completions, drafts, accounts } = 126 await import("./helpers/test-db"); 127 128 return { 129 getDb: () => getTestDb(), 130 trails, 131 walks, 132 completions, 133 drafts, 134 accounts, 135 type: {} as any, 136 }; 137}); 138 139vi.mock("@sidetrail/db", async (importOriginal) => { 140 const original = await importOriginal<typeof import("@sidetrail/db")>(); 141 return { ...original }; 142}); 143 144// Mock @atproto/identity 145vi.mock("@atproto/identity", () => { 146 class IdResolver { 147 handle = { 148 resolve: async (handle: string) => resolveHandle(handle), 149 }; 150 did = { 151 resolve: async (did: string) => { 152 const handle = resolveDid(did); 153 if (!handle) return null; 154 return { 155 id: did, 156 alsoKnownAs: [`at://${handle}`], 157 }; 158 }, 159 }; 160 } 161 162 return { IdResolver }; 163}); 164 165// Mock fetch 166const mockFetch = vi.fn(async (url: string) => { 167 if (url.includes("app.bsky.actor.getProfile")) { 168 const match = url.match(/actor=([^&]+)/); 169 if (match) { 170 const did = decodeURIComponent(match[1]); 171 const avatar = getAvatar(did); 172 const handle = resolveDid(did); 173 return { 174 ok: true, 175 json: async () => ({ did, handle: handle || did, avatar }), 176 }; 177 } 178 } 179 return { ok: false, status: 404, json: async () => ({}) }; 180}); 181 182global.fetch = mockFetch as any;