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 global.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: () => ({ 141 availableUserDomains: ["test.tranquil.dev"], 142 inviteCodeRequired: false, 143 links: { 144 privacyPolicy: "https://example.com/privacy", 145 termsOfService: "https://example.com/tos", 146 }, 147 }), 148 describeRepo: (did: string) => ({ 149 handle: "testuser.test.tranquil.dev", 150 did, 151 didDoc: {}, 152 collections: [ 153 "app.bsky.feed.post", 154 "app.bsky.feed.like", 155 "app.bsky.graph.follow", 156 ], 157 handleIsCorrect: true, 158 }), 159}; 160export function setupDefaultMocks(): void { 161 setupFetchMock(); 162 mockEndpoint( 163 "com.atproto.server.getSession", 164 () => jsonResponse(mockData.session()), 165 ); 166 mockEndpoint("com.atproto.server.createSession", (_url, options) => { 167 const body = JSON.parse((options?.body as string) || "{}"); 168 if (body.identifier && body.password === "correctpassword") { 169 return jsonResponse( 170 mockData.session({ handle: body.identifier.replace("@", "") }), 171 ); 172 } 173 return errorResponse( 174 "AuthenticationRequired", 175 "Invalid identifier or password", 176 401, 177 ); 178 }); 179 mockEndpoint( 180 "com.atproto.server.refreshSession", 181 () => jsonResponse(mockData.session()), 182 ); 183 mockEndpoint("com.atproto.server.deleteSession", () => jsonResponse({})); 184 mockEndpoint( 185 "com.atproto.server.listAppPasswords", 186 () => jsonResponse({ passwords: [mockData.appPassword()] }), 187 ); 188 mockEndpoint("com.atproto.server.createAppPassword", (_url, options) => { 189 const body = JSON.parse((options?.body as string) || "{}"); 190 return jsonResponse({ 191 name: body.name, 192 password: "xxxx-xxxx-xxxx-xxxx", 193 createdAt: new Date().toISOString(), 194 }); 195 }); 196 mockEndpoint("com.atproto.server.revokeAppPassword", () => jsonResponse({})); 197 mockEndpoint( 198 "com.atproto.server.getAccountInviteCodes", 199 () => jsonResponse({ codes: [mockData.inviteCode()] }), 200 ); 201 mockEndpoint( 202 "com.atproto.server.createInviteCode", 203 () => jsonResponse({ code: "new-invite-" + Date.now() }), 204 ); 205 mockEndpoint( 206 "com.tranquil.account.getNotificationPrefs", 207 () => jsonResponse(mockData.notificationPrefs()), 208 ); 209 mockEndpoint( 210 "com.tranquil.account.updateNotificationPrefs", 211 () => jsonResponse({ success: true }), 212 ); 213 mockEndpoint( 214 "com.atproto.server.requestEmailUpdate", 215 () => jsonResponse({ tokenRequired: true }), 216 ); 217 mockEndpoint("com.atproto.server.updateEmail", () => jsonResponse({})); 218 mockEndpoint("com.atproto.identity.updateHandle", () => jsonResponse({})); 219 mockEndpoint( 220 "com.atproto.server.requestAccountDelete", 221 () => jsonResponse({}), 222 ); 223 mockEndpoint("com.atproto.server.deleteAccount", () => jsonResponse({})); 224 mockEndpoint( 225 "com.atproto.server.describeServer", 226 () => jsonResponse(mockData.describeServer()), 227 ); 228 mockEndpoint("com.atproto.repo.describeRepo", (url) => { 229 const params = new URLSearchParams(url.split("?")[1]); 230 const repo = params.get("repo") || "did:web:test"; 231 return jsonResponse(mockData.describeRepo(repo)); 232 }); 233 mockEndpoint( 234 "com.atproto.repo.listRecords", 235 () => jsonResponse({ records: [] }), 236 ); 237} 238export function setupAuthenticatedUser( 239 sessionOverrides?: Partial<Session>, 240): Session { 241 const session = mockData.session(sessionOverrides); 242 _testSetState({ 243 session, 244 loading: false, 245 error: null, 246 }); 247 return session; 248} 249export function setupUnauthenticatedUser(): void { 250 _testSetState({ 251 session: null, 252 loading: false, 253 error: null, 254 }); 255}