this repo has no description
1import { vi } from "vitest"; 2import type { AppPassword, InviteCode, Session } from "../lib/api"; 3import { _testSetState } from "../lib/auth.svelte"; 4 5const originalPushState = globalThis.history.pushState.bind(globalThis.history); 6const originalReplaceState = globalThis.history.replaceState.bind( 7 globalThis.history, 8); 9 10globalThis.history.pushState = ( 11 data: unknown, 12 unused: string, 13 url?: string | URL | null, 14) => { 15 originalPushState(data, unused, url); 16 if (url) { 17 const urlStr = typeof url === "string" ? url : url.toString(); 18 Object.defineProperty(globalThis.location, "pathname", { 19 value: urlStr.split("?")[0], 20 writable: true, 21 configurable: true, 22 }); 23 } 24}; 25 26globalThis.history.replaceState = ( 27 data: unknown, 28 unused: string, 29 url?: string | URL | null, 30) => { 31 originalReplaceState(data, unused, url); 32 if (url) { 33 const urlStr = typeof url === "string" ? url : url.toString(); 34 Object.defineProperty(globalThis.location, "pathname", { 35 value: urlStr.split("?")[0], 36 writable: true, 37 configurable: true, 38 }); 39 } 40}; 41 42export interface MockResponse { 43 ok: boolean; 44 status: number; 45 json: () => Promise<unknown>; 46} 47export type MockHandler = ( 48 url: string, 49 options?: RequestInit, 50) => MockResponse | Promise<MockResponse>; 51const mockHandlers: Map<string, MockHandler> = new Map(); 52export function mockEndpoint(endpoint: string, handler: MockHandler): void { 53 mockHandlers.set(endpoint, handler); 54} 55export function mockEndpointOnce(endpoint: string, handler: MockHandler): void { 56 const originalHandler = mockHandlers.get(endpoint); 57 mockHandlers.set(endpoint, (url, options) => { 58 mockHandlers.set(endpoint, originalHandler!); 59 return handler(url, options); 60 }); 61} 62export function clearMocks(): void { 63 mockHandlers.clear(); 64} 65function extractEndpoint(url: string): string { 66 const match = url.match(/\/xrpc\/([^?]+)/); 67 return match ? match[1] : url; 68} 69export function setupFetchMock(): void { 70 globalThis.fetch = vi.fn( 71 async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => { 72 const url = typeof input === "string" ? input : input.toString(); 73 const endpoint = extractEndpoint(url); 74 const handler = mockHandlers.get(endpoint); 75 if (handler) { 76 const result = await handler(url, init); 77 return { 78 ok: result.ok, 79 status: result.status, 80 json: result.json, 81 text: async () => JSON.stringify(await result.json()), 82 headers: new Headers(), 83 redirected: false, 84 statusText: result.ok ? "OK" : "Error", 85 type: "basic", 86 url, 87 clone: () => ({ ...result }) as Response, 88 body: null, 89 bodyUsed: false, 90 arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), 91 blob: () => Promise.resolve(new Blob()), 92 formData: () => Promise.resolve(new FormData()), 93 } as Response; 94 } 95 return { 96 ok: false, 97 status: 404, 98 json: () => 99 Promise.resolve({ 100 error: "NotFound", 101 message: `No mock for ${endpoint}`, 102 }), 103 text: () => 104 Promise.resolve( 105 JSON.stringify({ 106 error: "NotFound", 107 message: `No mock for ${endpoint}`, 108 }), 109 ), 110 headers: new Headers(), 111 redirected: false, 112 statusText: "Not Found", 113 type: "basic", 114 url, 115 clone: function () { 116 return this; 117 }, 118 body: null, 119 bodyUsed: false, 120 arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), 121 blob: () => Promise.resolve(new Blob()), 122 formData: () => Promise.resolve(new FormData()), 123 } as Response; 124 }, 125 ); 126} 127export function jsonResponse<T>(data: T, status = 200): MockResponse { 128 return { 129 ok: status >= 200 && status < 300, 130 status, 131 json: () => Promise.resolve(data), 132 }; 133} 134export function errorResponse( 135 error: string, 136 message: string, 137 status = 400, 138): MockResponse { 139 return { 140 ok: false, 141 status, 142 json: () => Promise.resolve({ error, message }), 143 }; 144} 145export const mockData = { 146 session: (overrides?: Partial<Session>): Session => ({ 147 did: "did:web:test.tranquil.dev:u:testuser", 148 handle: "testuser.test.tranquil.dev", 149 email: "test@example.com", 150 emailConfirmed: true, 151 accessJwt: "mock-access-jwt-token", 152 refreshJwt: "mock-refresh-jwt-token", 153 ...overrides, 154 }), 155 appPassword: (overrides?: Partial<AppPassword>): AppPassword => ({ 156 name: "Test App", 157 createdAt: new Date().toISOString(), 158 ...overrides, 159 }), 160 inviteCode: (overrides?: Partial<InviteCode>): InviteCode => ({ 161 code: "test-invite-123", 162 available: 1, 163 disabled: false, 164 forAccount: "did:web:test.tranquil.dev:u:testuser", 165 createdBy: "did:web:test.tranquil.dev:u:testuser", 166 createdAt: new Date().toISOString(), 167 uses: [], 168 ...overrides, 169 }), 170 notificationPrefs: (overrides?: Record<string, unknown>) => ({ 171 preferredChannel: "email", 172 email: "test@example.com", 173 discordId: null, 174 discordVerified: false, 175 telegramUsername: null, 176 telegramVerified: false, 177 signalNumber: null, 178 signalVerified: false, 179 ...overrides, 180 }), 181 describeServer: (overrides?: Record<string, unknown>) => ({ 182 availableUserDomains: ["test.tranquil.dev"], 183 inviteCodeRequired: false, 184 links: { 185 privacyPolicy: "https://example.com/privacy", 186 termsOfService: "https://example.com/tos", 187 }, 188 selfHostedDidWebEnabled: true, 189 availableCommsChannels: ["email", "discord", "telegram", "signal"], 190 ...overrides, 191 }), 192 describeRepo: (did: string) => ({ 193 handle: "testuser.test.tranquil.dev", 194 did, 195 didDoc: {}, 196 collections: [ 197 "app.bsky.feed.post", 198 "app.bsky.feed.like", 199 "app.bsky.graph.follow", 200 ], 201 handleIsCorrect: true, 202 }), 203}; 204export function setupDefaultMocks(): void { 205 setupFetchMock(); 206 mockEndpoint( 207 "com.atproto.server.getSession", 208 () => jsonResponse(mockData.session()), 209 ); 210 mockEndpoint("com.atproto.server.createSession", (_url, options) => { 211 const body = JSON.parse((options?.body as string) || "{}"); 212 if (body.identifier && body.password === "correctpassword") { 213 return jsonResponse( 214 mockData.session({ handle: body.identifier.replace("@", "") }), 215 ); 216 } 217 return errorResponse( 218 "AuthenticationRequired", 219 "Invalid identifier or password", 220 401, 221 ); 222 }); 223 mockEndpoint( 224 "com.atproto.server.refreshSession", 225 () => jsonResponse(mockData.session()), 226 ); 227 mockEndpoint("com.atproto.server.deleteSession", () => jsonResponse({})); 228 mockEndpoint( 229 "com.atproto.server.listAppPasswords", 230 () => jsonResponse({ passwords: [mockData.appPassword()] }), 231 ); 232 mockEndpoint("com.atproto.server.createAppPassword", (_url, options) => { 233 const body = JSON.parse((options?.body as string) || "{}"); 234 return jsonResponse({ 235 name: body.name, 236 password: "xxxx-xxxx-xxxx-xxxx", 237 createdAt: new Date().toISOString(), 238 }); 239 }); 240 mockEndpoint("com.atproto.server.revokeAppPassword", () => jsonResponse({})); 241 mockEndpoint( 242 "com.atproto.server.getAccountInviteCodes", 243 () => jsonResponse({ codes: [mockData.inviteCode()] }), 244 ); 245 mockEndpoint( 246 "com.atproto.server.createInviteCode", 247 () => jsonResponse({ code: "new-invite-" + Date.now() }), 248 ); 249 mockEndpoint( 250 "_account.getNotificationPrefs", 251 () => jsonResponse(mockData.notificationPrefs()), 252 ); 253 mockEndpoint( 254 "_account.updateNotificationPrefs", 255 () => jsonResponse({ success: true }), 256 ); 257 mockEndpoint( 258 "_account.getNotificationHistory", 259 () => jsonResponse({ notifications: [] }), 260 ); 261 mockEndpoint( 262 "com.atproto.server.requestEmailUpdate", 263 () => jsonResponse({ tokenRequired: true }), 264 ); 265 mockEndpoint("com.atproto.server.updateEmail", () => jsonResponse({})); 266 mockEndpoint("com.atproto.identity.updateHandle", () => jsonResponse({})); 267 mockEndpoint( 268 "com.atproto.server.requestAccountDelete", 269 () => jsonResponse({}), 270 ); 271 mockEndpoint("com.atproto.server.deleteAccount", () => jsonResponse({})); 272 mockEndpoint( 273 "com.atproto.server.describeServer", 274 () => jsonResponse(mockData.describeServer()), 275 ); 276 mockEndpoint("com.atproto.repo.describeRepo", (url) => { 277 const params = new URLSearchParams(url.split("?")[1]); 278 const repo = params.get("repo") || "did:web:test"; 279 return jsonResponse(mockData.describeRepo(repo)); 280 }); 281 mockEndpoint( 282 "com.atproto.repo.listRecords", 283 () => jsonResponse({ records: [] }), 284 ); 285 mockEndpoint( 286 "_backup.listBackups", 287 () => jsonResponse({ backups: [] }), 288 ); 289} 290export function setupAuthenticatedUser( 291 sessionOverrides?: Partial<Session>, 292): Session { 293 const session = mockData.session(sessionOverrides); 294 _testSetState({ 295 session, 296 loading: false, 297 error: null, 298 }); 299 return session; 300} 301export function setupUnauthenticatedUser(): void { 302 _testSetState({ 303 session: null, 304 loading: false, 305 error: null, 306 }); 307}