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