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