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