an app to share curated trails sidetrail.app
1

Configure Feed

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

at main 4.5 kB View raw
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 refresh: vi.fn(), 66})); 67 68// Mock next/headers (no cookies in tests) 69vi.mock("next/headers", () => ({ 70 cookies: vi.fn(async () => ({ 71 get: vi.fn(() => null), 72 set: vi.fn(), 73 delete: vi.fn(), 74 })), 75})); 76 77// Mock next/navigation 78class NotFoundError extends Error { 79 constructor() { 80 super("NEXT_NOT_FOUND"); 81 this.name = "NotFoundError"; 82 } 83} 84 85vi.mock("next/navigation", () => ({ 86 notFound: () => { 87 throw new NotFoundError(); 88 }, 89})); 90 91// Mock @/auth to use test users 92vi.mock("@/auth", () => ({ 93 getCurrentDid: vi.fn(() => getCurrentTestDid()), 94 getCurrentUser: vi.fn(async () => { 95 const did = getCurrentTestDid(); 96 if (!did) return null; 97 const handle = resolveDid(did); 98 const avatar = getAvatar(did); 99 return handle ? { did, handle, avatar } : null; 100 }), 101 getSessionAgent: vi.fn(async () => null), 102})); 103 104// Mock @/data/lex-client (PDS operations are not used in integration tests) 105// Actions that would write to PDS are tested via event emission instead 106vi.mock("@/data/lex-client", () => ({ 107 getLexClient: vi.fn(async () => { 108 throw new Error( 109 "getLexClient should not be called in integration tests. " + 110 "Use event emission (emit.trail.create, etc.) to simulate PDS writes.", 111 ); 112 }), 113})); 114 115// Mock @/data/db to use the test database 116vi.mock("@/data/db", async () => { 117 const { getTestDb, trails, walks, completions, drafts, accounts } = 118 await import("./helpers/test-db"); 119 120 return { 121 getDb: () => getTestDb(), 122 trails, 123 walks, 124 completions, 125 drafts, 126 accounts, 127 // Re-export types from schema 128 type: {} as any, 129 }; 130}); 131 132// Re-export types for the db mock 133vi.mock("@sidetrail/db", async (importOriginal) => { 134 const original = await importOriginal<typeof import("@sidetrail/db")>(); 135 return { 136 ...original, 137 // These are already in original, just re-exporting 138 }; 139}); 140 141// Mock @atproto/identity to use test users 142vi.mock("@atproto/identity", () => { 143 class IdResolver { 144 handle = { 145 resolve: async (handle: string) => resolveHandle(handle), 146 }; 147 did = { 148 resolve: async (did: string) => { 149 const handle = resolveDid(did); 150 if (!handle) return null; 151 return { 152 id: did, 153 alsoKnownAs: [`at://${handle}`], 154 }; 155 }, 156 }; 157 } 158 159 return { IdResolver }; 160}); 161 162// Mock fetch for avatar lookups 163const mockFetch = vi.fn(async (url: string) => { 164 // Handle bsky avatar requests 165 if (url.includes("app.bsky.actor.getProfile")) { 166 const match = url.match(/actor=([^&]+)/); 167 if (match) { 168 const did = decodeURIComponent(match[1]); 169 const avatar = getAvatar(did); 170 const handle = resolveDid(did); 171 return { 172 ok: true, 173 json: async () => ({ 174 did, 175 handle: handle || did, 176 avatar, 177 }), 178 }; 179 } 180 } 181 182 // Default: return empty response 183 return { 184 ok: false, 185 status: 404, 186 json: async () => ({}), 187 }; 188}); 189 190global.fetch = mockFetch as any;