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 integration tests 3 * 4 * This setup uses a real PostgreSQL database and simulates Jetstream events 5 * by directly inserting into the database. The queries run against real data. 6 */ 7 8import { beforeAll, beforeEach, afterAll, vi } from "vitest"; 9import { 10 initTestDb, 11 clearTestDb, 12 closeTestDb, 13 resetCounters, 14 resetSeqCounter, 15 resetCurrentUser, 16 resetTidCounter, 17 getCurrentTestDid, 18 resolveHandle, 19 resolveDid, 20 getAvatar, 21} from "./helpers"; 22 23// ============================================================================ 24// Global Setup 25// ============================================================================ 26 27beforeAll(async () => { 28 // Initialize test database with fresh schema 29 await initTestDb(); 30}); 31 32afterAll(async () => { 33 // Close database connection 34 await closeTestDb(); 35}); 36 37beforeEach(async () => { 38 // Clear all data between tests 39 await clearTestDb(); 40 41 // Reset counters 42 resetCounters(); 43 resetSeqCounter(); 44 resetTidCounter(); 45 resetCurrentUser(); 46 47 // Clear mocks 48 vi.clearAllMocks(); 49}); 50 51// ============================================================================ 52// Module Mocks 53// ============================================================================ 54 55// Mock server-only (allow server code to run in tests) 56vi.mock("server-only", () => ({})); 57 58// Mock next/cache (cache operations are no-ops in tests) 59vi.mock("next/cache", () => ({ 60 revalidatePath: vi.fn(), 61 revalidateTag: vi.fn(), 62 updateTag: vi.fn(), 63 cacheTag: vi.fn(), 64 cacheLife: vi.fn(), 65})); 66 67// Mock next/headers (no cookies in tests) 68vi.mock("next/headers", () => ({ 69 cookies: vi.fn(async () => ({ 70 get: vi.fn(() => null), 71 set: vi.fn(), 72 delete: vi.fn(), 73 })), 74})); 75 76// Mock next/navigation 77class NotFoundError extends Error { 78 constructor() { 79 super("NEXT_NOT_FOUND"); 80 this.name = "NotFoundError"; 81 } 82} 83 84vi.mock("next/navigation", () => ({ 85 notFound: () => { 86 throw new NotFoundError(); 87 }, 88})); 89 90// Mock @/auth to use test users 91vi.mock("@/auth", () => ({ 92 getCurrentDid: vi.fn(() => getCurrentTestDid()), 93 getCurrentUser: vi.fn(async () => { 94 const did = getCurrentTestDid(); 95 if (!did) return null; 96 const handle = resolveDid(did); 97 const avatar = getAvatar(did); 98 return handle ? { did, handle, avatar } : null; 99 }), 100 getSessionAgent: vi.fn(async () => null), 101})); 102 103// Mock @/data/lex-client (PDS operations are not used in integration tests) 104// Actions that would write to PDS are tested via event emission instead 105vi.mock("@/data/lex-client", () => ({ 106 getLexClient: vi.fn(async () => { 107 throw new Error( 108 "getLexClient should not be called in integration tests. " + 109 "Use event emission (emit.trail.create, etc.) to simulate PDS writes.", 110 ); 111 }), 112})); 113 114// Mock @/data/db to use the test database 115vi.mock("@/data/db", async () => { 116 const { getTestDb, trails, walks, completions, drafts, accounts } = 117 await import("./helpers/test-db"); 118 119 return { 120 getDb: () => getTestDb(), 121 trails, 122 walks, 123 completions, 124 drafts, 125 accounts, 126 // Re-export types from schema 127 type: {} as any, 128 }; 129}); 130 131// Re-export types for the db mock 132vi.mock("@sidetrail/db", async (importOriginal) => { 133 const original = await importOriginal<typeof import("@sidetrail/db")>(); 134 return { 135 ...original, 136 // These are already in original, just re-exporting 137 }; 138}); 139 140// Mock @atproto/identity to use test users 141vi.mock("@atproto/identity", () => { 142 class IdResolver { 143 handle = { 144 resolve: async (handle: string) => resolveHandle(handle), 145 }; 146 did = { 147 resolve: async (did: string) => { 148 const handle = resolveDid(did); 149 if (!handle) return null; 150 return { 151 id: did, 152 alsoKnownAs: [`at://${handle}`], 153 }; 154 }, 155 }; 156 } 157 158 return { IdResolver }; 159}); 160 161// Mock fetch for avatar lookups 162const mockFetch = vi.fn(async (url: string) => { 163 // Handle bsky avatar requests 164 if (url.includes("app.bsky.actor.getProfile")) { 165 const match = url.match(/actor=([^&]+)/); 166 if (match) { 167 const did = decodeURIComponent(match[1]); 168 const avatar = getAvatar(did); 169 const handle = resolveDid(did); 170 return { 171 ok: true, 172 json: async () => ({ 173 did, 174 handle: handle || did, 175 avatar, 176 }), 177 }; 178 } 179 } 180 181 // Default: return empty response 182 return { 183 ok: false, 184 status: 404, 185 json: async () => ({}), 186 }; 187}); 188 189global.fetch = mockFetch as any;