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}