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.tranquil.dev:u:testuser',
89 handle: 'testuser.test.tranquil.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.tranquil.dev:u:testuser',
106 createdBy: 'did:web:test.tranquil.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.tranquil.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.tranquil.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.tranquil.account.getNotificationPrefs', () =>
177 jsonResponse(mockData.notificationPrefs())
178 )
179 mockEndpoint('com.tranquil.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}