@classic-homes/auth 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2033 @@
1
+ // src/testing/fixtures/users.ts
2
+ var mockUser = {
3
+ id: "user-123",
4
+ username: "testuser",
5
+ email: "test@example.com",
6
+ firstName: "Test",
7
+ lastName: "User",
8
+ phone: "+1234567890",
9
+ role: "user",
10
+ roles: ["user"],
11
+ permissions: ["read:profile", "write:profile"],
12
+ isActive: true,
13
+ emailVerified: true,
14
+ authMethod: "password",
15
+ createdAt: "2024-01-01T00:00:00.000Z",
16
+ lastLoginAt: "2024-06-01T12:00:00.000Z"
17
+ };
18
+ var mockAdminUser = {
19
+ id: "admin-123",
20
+ username: "adminuser",
21
+ email: "admin@example.com",
22
+ firstName: "Admin",
23
+ lastName: "User",
24
+ phone: "+1234567891",
25
+ role: "admin",
26
+ roles: ["admin", "user"],
27
+ permissions: [
28
+ "read:profile",
29
+ "write:profile",
30
+ "read:users",
31
+ "write:users",
32
+ "delete:users",
33
+ "read:admin",
34
+ "write:admin",
35
+ "manage:system"
36
+ ],
37
+ isActive: true,
38
+ emailVerified: true,
39
+ authMethod: "password",
40
+ createdAt: "2024-01-01T00:00:00.000Z",
41
+ lastLoginAt: "2024-06-01T12:00:00.000Z"
42
+ };
43
+ var mockSSOUser = {
44
+ id: "sso-123",
45
+ username: "ssouser",
46
+ email: "sso@example.com",
47
+ firstName: "SSO",
48
+ lastName: "User",
49
+ role: "user",
50
+ roles: ["user"],
51
+ permissions: ["read:profile", "write:profile"],
52
+ isActive: true,
53
+ emailVerified: true,
54
+ authMethod: "oauth",
55
+ ssoProfileUrl: "https://sso.example.com/profile/sso-123",
56
+ createdAt: "2024-01-01T00:00:00.000Z",
57
+ lastLoginAt: "2024-06-01T12:00:00.000Z"
58
+ };
59
+ var mockMFAUser = {
60
+ id: "mfa-123",
61
+ username: "mfauser",
62
+ email: "mfa@example.com",
63
+ firstName: "MFA",
64
+ lastName: "User",
65
+ role: "user",
66
+ roles: ["user"],
67
+ permissions: ["read:profile", "write:profile"],
68
+ isActive: true,
69
+ emailVerified: true,
70
+ authMethod: "password",
71
+ createdAt: "2024-01-01T00:00:00.000Z",
72
+ lastLoginAt: "2024-06-01T12:00:00.000Z"
73
+ };
74
+ var mockUnverifiedUser = {
75
+ id: "unverified-123",
76
+ username: "unverifieduser",
77
+ email: "unverified@example.com",
78
+ firstName: "Unverified",
79
+ lastName: "User",
80
+ role: "user",
81
+ roles: ["user"],
82
+ permissions: [],
83
+ isActive: true,
84
+ emailVerified: false,
85
+ authMethod: "password",
86
+ createdAt: "2024-01-01T00:00:00.000Z"
87
+ };
88
+ var mockInactiveUser = {
89
+ id: "inactive-123",
90
+ username: "inactiveuser",
91
+ email: "inactive@example.com",
92
+ firstName: "Inactive",
93
+ lastName: "User",
94
+ role: "user",
95
+ roles: ["user"],
96
+ permissions: [],
97
+ isActive: false,
98
+ emailVerified: true,
99
+ authMethod: "password",
100
+ createdAt: "2024-01-01T00:00:00.000Z"
101
+ };
102
+ function createMockUser(overrides = {}) {
103
+ return {
104
+ ...mockUser,
105
+ id: overrides.id ?? `user-${Date.now()}`,
106
+ ...overrides
107
+ };
108
+ }
109
+ function createMockUserWithRoles(roles, permissions, overrides = {}) {
110
+ return createMockUser({
111
+ role: roles[0] ?? "user",
112
+ roles,
113
+ permissions,
114
+ ...overrides
115
+ });
116
+ }
117
+ function createMockUserWithAuthMethod(authMethod, overrides = {}) {
118
+ const baseOverrides = { authMethod };
119
+ if (authMethod === "oauth" || authMethod === "both") {
120
+ baseOverrides.ssoProfileUrl = `https://sso.example.com/profile/${overrides.id ?? "user-123"}`;
121
+ }
122
+ return createMockUser({
123
+ ...baseOverrides,
124
+ ...overrides
125
+ });
126
+ }
127
+
128
+ // src/testing/fixtures/tokens.ts
129
+ var BASE64_URL_ENCODE = (str) => {
130
+ return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
131
+ };
132
+ var createFakeJWT = (payload, expiresIn = 3600) => {
133
+ const header = { alg: "HS256", typ: "JWT" };
134
+ const now = Math.floor(Date.now() / 1e3);
135
+ const fullPayload = {
136
+ iat: now,
137
+ exp: now + expiresIn,
138
+ ...payload
139
+ };
140
+ const headerB64 = BASE64_URL_ENCODE(JSON.stringify(header));
141
+ const payloadB64 = BASE64_URL_ENCODE(JSON.stringify(fullPayload));
142
+ const signature = BASE64_URL_ENCODE("fake-signature");
143
+ return `${headerB64}.${payloadB64}.${signature}`;
144
+ };
145
+ var mockAccessToken = createFakeJWT(
146
+ {
147
+ sub: "user-123",
148
+ username: "testuser",
149
+ email: "test@example.com",
150
+ roles: ["user"],
151
+ permissions: ["read:profile", "write:profile"],
152
+ type: "access"
153
+ },
154
+ 3600
155
+ );
156
+ var mockRefreshToken = createFakeJWT(
157
+ {
158
+ sub: "user-123",
159
+ type: "refresh"
160
+ },
161
+ 604800
162
+ );
163
+ var mockExpiredToken = createFakeJWT(
164
+ {
165
+ sub: "user-123",
166
+ username: "testuser",
167
+ email: "test@example.com",
168
+ roles: ["user"],
169
+ permissions: ["read:profile", "write:profile"],
170
+ type: "access"
171
+ },
172
+ -3600
173
+ // Already expired
174
+ );
175
+ var mockAdminToken = createFakeJWT(
176
+ {
177
+ sub: "admin-123",
178
+ username: "adminuser",
179
+ email: "admin@example.com",
180
+ roles: ["admin", "user"],
181
+ permissions: [
182
+ "read:profile",
183
+ "write:profile",
184
+ "read:users",
185
+ "write:users",
186
+ "delete:users",
187
+ "read:admin",
188
+ "write:admin",
189
+ "manage:system"
190
+ ],
191
+ type: "access"
192
+ },
193
+ 3600
194
+ );
195
+ var mockMFAToken = createFakeJWT(
196
+ {
197
+ sub: "mfa-123",
198
+ type: "mfa_challenge",
199
+ username: "mfauser"
200
+ },
201
+ 300
202
+ // 5 minutes
203
+ );
204
+ var mockSessionToken = createFakeJWT(
205
+ {
206
+ sub: "user-123",
207
+ type: "session",
208
+ deviceId: "device-123",
209
+ trusted: true
210
+ },
211
+ 86400
212
+ // 24 hours
213
+ );
214
+ function createMockJWT(payload = {}, expiresIn = 3600) {
215
+ const defaultPayload = {
216
+ sub: "user-123",
217
+ type: "access",
218
+ ...payload
219
+ };
220
+ return createFakeJWT(defaultPayload, expiresIn);
221
+ }
222
+ function createExpiredMockJWT(payload = {}, expiredSecondsAgo = 3600) {
223
+ return createMockJWT(payload, -expiredSecondsAgo);
224
+ }
225
+ function createMockTokenPair(userId = "user-123", options = {}) {
226
+ const accessToken = createMockJWT(
227
+ {
228
+ sub: userId,
229
+ type: "access",
230
+ roles: options.roles ?? ["user"],
231
+ permissions: options.permissions ?? ["read:profile", "write:profile"]
232
+ },
233
+ options.accessExpiresIn ?? 3600
234
+ );
235
+ const refreshToken = createMockJWT(
236
+ {
237
+ sub: userId,
238
+ type: "refresh"
239
+ },
240
+ options.refreshExpiresIn ?? 604800
241
+ );
242
+ return { accessToken, refreshToken };
243
+ }
244
+ function createMockMFAToken(userId = "mfa-123", expiresIn = 300) {
245
+ return createMockJWT(
246
+ {
247
+ sub: userId,
248
+ type: "mfa_challenge"
249
+ },
250
+ expiresIn
251
+ );
252
+ }
253
+
254
+ // src/testing/fixtures/responses.ts
255
+ var mockLoginSuccess = {
256
+ accessToken: mockAccessToken,
257
+ refreshToken: mockRefreshToken,
258
+ sessionToken: mockSessionToken,
259
+ user: mockUser,
260
+ expiresIn: 3600
261
+ };
262
+ var mockMFARequired = {
263
+ accessToken: "",
264
+ refreshToken: "",
265
+ user: mockMFAUser,
266
+ expiresIn: 0,
267
+ requiresMFA: true,
268
+ mfaToken: mockMFAToken,
269
+ availableMethods: ["totp", "backup"]
270
+ };
271
+ var mockMFARequiredLegacy = {
272
+ accessToken: "",
273
+ refreshToken: "",
274
+ user: mockMFAUser,
275
+ expiresIn: 0,
276
+ mfaRequired: true,
277
+ mfaChallengeToken: mockMFAToken
278
+ };
279
+ var mockAdminLoginSuccess = {
280
+ ...mockLoginSuccess,
281
+ user: mockAdminUser,
282
+ ...createMockTokenPair("admin-123", {
283
+ roles: ["admin", "user"],
284
+ permissions: mockAdminUser.permissions
285
+ })
286
+ };
287
+ var mockSSOLoginSuccess = {
288
+ ...mockLoginSuccess,
289
+ user: mockSSOUser
290
+ };
291
+ var mockLogoutSuccess = {
292
+ success: true,
293
+ message: "Logged out successfully"
294
+ };
295
+ var mockSSOLogoutResponse = {
296
+ success: true,
297
+ message: "Logged out successfully",
298
+ ssoLogout: true,
299
+ logoutUrl: "https://sso.example.com/logout?redirect=https://app.example.com"
300
+ };
301
+ var mockRegisterSuccess = {
302
+ message: "Registration successful",
303
+ user: mockUser,
304
+ requiresEmailVerification: false
305
+ };
306
+ var mockRegisterRequiresVerification = {
307
+ message: "Registration successful. Please verify your email.",
308
+ user: mockUnverifiedUser,
309
+ requiresEmailVerification: true
310
+ };
311
+ var mockMFASetup = {
312
+ qrCodeUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
313
+ manualEntryKey: "JBSWY3DPEHPK3PXP",
314
+ backupCodes: [
315
+ "AAAA-BBBB-CCCC",
316
+ "DDDD-EEEE-FFFF",
317
+ "GGGG-HHHH-IIII",
318
+ "JJJJ-KKKK-LLLL",
319
+ "MMMM-NNNN-OOOO",
320
+ "PPPP-QQQQ-RRRR",
321
+ "SSSS-TTTT-UUUU",
322
+ "VVVV-WWWW-XXXX"
323
+ ],
324
+ instructions: {
325
+ step1: "Download an authenticator app (e.g., Google Authenticator, Authy)",
326
+ step2: "Scan the QR code or manually enter the key",
327
+ step3: "Enter the 6-digit code from your authenticator app",
328
+ step4: "Store your backup codes in a safe place"
329
+ }
330
+ };
331
+ var mockMFAStatusEnabled = {
332
+ enabled: true,
333
+ methods: ["totp"]
334
+ };
335
+ var mockMFAStatusDisabled = {
336
+ enabled: false,
337
+ methods: []
338
+ };
339
+ var mockCurrentSession = {
340
+ id: "session-current",
341
+ deviceName: "Chrome on macOS",
342
+ browser: "Chrome 120",
343
+ location: "San Francisco, CA",
344
+ ipAddress: "192.168.1.1",
345
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
346
+ isCurrent: true,
347
+ isTrusted: true,
348
+ deviceFingerprint: "fp-abc123",
349
+ trustedDeviceId: "device-123",
350
+ createdAt: new Date(Date.now() - 864e5).toISOString(),
351
+ expiresAt: new Date(Date.now() + 6048e5).toISOString()
352
+ };
353
+ var mockOtherSession = {
354
+ id: "session-other",
355
+ deviceName: "Firefox on Windows",
356
+ browser: "Firefox 121",
357
+ location: "New York, NY",
358
+ ipAddress: "10.0.0.1",
359
+ lastActivity: new Date(Date.now() - 36e5).toISOString(),
360
+ isCurrent: false,
361
+ isTrusted: false,
362
+ deviceFingerprint: "fp-def456",
363
+ createdAt: new Date(Date.now() - 1728e5).toISOString(),
364
+ expiresAt: new Date(Date.now() + 5184e5).toISOString()
365
+ };
366
+ var mockSessions = [mockCurrentSession, mockOtherSession];
367
+ var mockTrustedDevice = {
368
+ id: "device-trusted",
369
+ deviceFingerprint: "fp-trusted-123",
370
+ deviceName: "MacBook Pro",
371
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
372
+ ipAddress: "192.168.1.1",
373
+ trusted: true,
374
+ status: "active",
375
+ lastSeen: (/* @__PURE__ */ new Date()).toISOString(),
376
+ createdAt: new Date(Date.now() - 2592e6).toISOString()
377
+ };
378
+ var mockUntrustedDevice = {
379
+ id: "device-untrusted",
380
+ deviceFingerprint: "fp-untrusted-456",
381
+ deviceName: "Unknown Device",
382
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
383
+ ipAddress: "10.0.0.100",
384
+ trusted: false,
385
+ status: "active",
386
+ lastSeen: new Date(Date.now() - 864e5).toISOString(),
387
+ createdAt: new Date(Date.now() - 6048e5).toISOString()
388
+ };
389
+ var mockDevices = [mockTrustedDevice, mockUntrustedDevice];
390
+ var mockApiKey = {
391
+ id: "key-123",
392
+ name: "Production API Key",
393
+ description: "Main API key for production environment",
394
+ keyPreview: "sk_live_xxxx...xxxx",
395
+ permissions: ["read:data", "write:data"],
396
+ role: "api",
397
+ isActive: true,
398
+ expiresAt: new Date(Date.now() + 31536e6).toISOString(),
399
+ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString(),
400
+ createdAt: new Date(Date.now() - 2592e6).toISOString(),
401
+ usageCount: 1234
402
+ };
403
+ var mockApiKeys = [
404
+ mockApiKey,
405
+ {
406
+ ...mockApiKey,
407
+ id: "key-456",
408
+ name: "Development API Key",
409
+ description: "API key for development",
410
+ keyPreview: "sk_test_xxxx...xxxx",
411
+ usageCount: 56
412
+ }
413
+ ];
414
+ var mockLinkedAccount = {
415
+ id: "link-123",
416
+ provider: "authentik",
417
+ providerAccountId: "auth-user-123",
418
+ providerEmail: "user@sso.example.com",
419
+ providerUsername: "ssouser",
420
+ providerAvatarUrl: "https://sso.example.com/avatar/123.png",
421
+ isPrimary: true,
422
+ isVerified: true,
423
+ createdAt: new Date(Date.now() - 2592e6).toISOString(),
424
+ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
425
+ };
426
+ var mockSecurityEventLogin = {
427
+ id: "event-login",
428
+ eventType: "login",
429
+ description: "Successful login from new device",
430
+ severity: "low",
431
+ ipAddress: "192.168.1.1",
432
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
433
+ location: "San Francisco, CA",
434
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
435
+ };
436
+ var mockSecurityEventPasswordChange = {
437
+ id: "event-password",
438
+ eventType: "password_change",
439
+ description: "Password changed successfully",
440
+ severity: "medium",
441
+ ipAddress: "192.168.1.1",
442
+ createdAt: new Date(Date.now() - 864e5).toISOString()
443
+ };
444
+ var mockSecurityEventSuspicious = {
445
+ id: "event-suspicious",
446
+ eventType: "suspicious_login",
447
+ description: "Login attempt from unusual location",
448
+ severity: "high",
449
+ ipAddress: "203.0.113.50",
450
+ location: "Unknown",
451
+ metadata: {
452
+ blocked: true,
453
+ reason: "Location mismatch"
454
+ },
455
+ createdAt: new Date(Date.now() - 36e5).toISOString()
456
+ };
457
+ var mockSecurityEvents = [
458
+ mockSecurityEventLogin,
459
+ mockSecurityEventPasswordChange,
460
+ mockSecurityEventSuspicious
461
+ ];
462
+ var mockUserPreferences = {
463
+ emailNotifications: true,
464
+ securityAlerts: true,
465
+ loginNotifications: true,
466
+ suspiciousActivityAlerts: true,
467
+ passwordChangeNotifications: true,
468
+ mfaChangeNotifications: true,
469
+ accountUpdates: true,
470
+ systemMaintenance: false,
471
+ featureAnnouncements: true,
472
+ newsAndUpdates: false,
473
+ apiKeyExpiration: true,
474
+ apiUsageAlerts: false,
475
+ rateLimit: true,
476
+ dataExportReady: true,
477
+ reportGeneration: true,
478
+ theme: "system",
479
+ language: "en",
480
+ timezone: "America/Los_Angeles",
481
+ dateFormat: "MM/DD/YYYY",
482
+ timeFormat: "12h"
483
+ };
484
+ function createMockLoginSuccess(options = {}) {
485
+ const tokenPair = createMockTokenPair(options.userId ?? mockUser.id);
486
+ return {
487
+ ...tokenPair,
488
+ user: options.user ? { ...mockUser, ...options.user } : mockUser,
489
+ expiresIn: options.expiresIn ?? 3600
490
+ };
491
+ }
492
+ function createMockMFARequired(options = {}) {
493
+ return {
494
+ accessToken: "",
495
+ refreshToken: "",
496
+ user: mockMFAUser,
497
+ expiresIn: 0,
498
+ requiresMFA: true,
499
+ mfaToken: options.mfaToken ?? mockMFAToken,
500
+ availableMethods: options.availableMethods ?? ["totp"]
501
+ };
502
+ }
503
+ function createMockSession(overrides = {}) {
504
+ return {
505
+ ...mockCurrentSession,
506
+ id: `session-${Date.now()}`,
507
+ ...overrides
508
+ };
509
+ }
510
+ function createMockDevice(overrides = {}) {
511
+ return {
512
+ ...mockTrustedDevice,
513
+ id: `device-${Date.now()}`,
514
+ ...overrides
515
+ };
516
+ }
517
+ function createMockSecurityEvent(overrides = {}) {
518
+ return {
519
+ ...mockSecurityEventLogin,
520
+ id: `event-${Date.now()}`,
521
+ ...overrides
522
+ };
523
+ }
524
+
525
+ // src/testing/mocks/storage.ts
526
+ var MockStorageAdapter = class {
527
+ constructor(options = {}) {
528
+ this.data = /* @__PURE__ */ new Map();
529
+ this.callHistory = [];
530
+ if (options.initialData) {
531
+ Object.entries(options.initialData).forEach(([key, value]) => {
532
+ this.data.set(key, value);
533
+ });
534
+ }
535
+ }
536
+ // ============================================================================
537
+ // StorageAdapter Interface
538
+ // ============================================================================
539
+ /**
540
+ * Get an item from storage.
541
+ */
542
+ getItem(key) {
543
+ this.callHistory.push({
544
+ method: "getItem",
545
+ key,
546
+ timestamp: Date.now()
547
+ });
548
+ return this.data.get(key) ?? null;
549
+ }
550
+ /**
551
+ * Set an item in storage.
552
+ */
553
+ setItem(key, value) {
554
+ this.callHistory.push({
555
+ method: "setItem",
556
+ key,
557
+ value,
558
+ timestamp: Date.now()
559
+ });
560
+ this.data.set(key, value);
561
+ }
562
+ /**
563
+ * Remove an item from storage.
564
+ */
565
+ removeItem(key) {
566
+ this.callHistory.push({
567
+ method: "removeItem",
568
+ key,
569
+ timestamp: Date.now()
570
+ });
571
+ this.data.delete(key);
572
+ }
573
+ // ============================================================================
574
+ // Test Utilities
575
+ // ============================================================================
576
+ /**
577
+ * Clear all data from storage.
578
+ */
579
+ clear() {
580
+ this.data.clear();
581
+ }
582
+ /**
583
+ * Reset the call history.
584
+ */
585
+ resetHistory() {
586
+ this.callHistory = [];
587
+ }
588
+ /**
589
+ * Reset both data and history.
590
+ */
591
+ reset() {
592
+ this.clear();
593
+ this.resetHistory();
594
+ }
595
+ /**
596
+ * Get all storage keys.
597
+ */
598
+ keys() {
599
+ return Array.from(this.data.keys());
600
+ }
601
+ /**
602
+ * Check if a key exists in storage.
603
+ */
604
+ has(key) {
605
+ return this.data.has(key);
606
+ }
607
+ /**
608
+ * Get the number of items in storage.
609
+ */
610
+ get size() {
611
+ return this.data.size;
612
+ }
613
+ /**
614
+ * Get all data as a plain object.
615
+ */
616
+ getData() {
617
+ return Object.fromEntries(this.data);
618
+ }
619
+ /**
620
+ * Set multiple items at once.
621
+ */
622
+ setData(data) {
623
+ Object.entries(data).forEach(([key, value]) => {
624
+ this.data.set(key, value);
625
+ });
626
+ }
627
+ // ============================================================================
628
+ // Call History Utilities
629
+ // ============================================================================
630
+ /**
631
+ * Get the full call history.
632
+ */
633
+ getHistory() {
634
+ return [...this.callHistory];
635
+ }
636
+ /**
637
+ * Get calls for a specific method.
638
+ */
639
+ getCallsFor(method) {
640
+ return this.callHistory.filter((call) => call.method === method);
641
+ }
642
+ /**
643
+ * Get calls for a specific key.
644
+ */
645
+ getCallsForKey(key) {
646
+ return this.callHistory.filter((call) => call.key === key);
647
+ }
648
+ /**
649
+ * Check if a method was called.
650
+ */
651
+ wasCalled(method) {
652
+ return this.callHistory.some((call) => call.method === method);
653
+ }
654
+ /**
655
+ * Check if a method was called with a specific key.
656
+ */
657
+ wasCalledWith(method, key) {
658
+ return this.callHistory.some((call) => call.method === method && call.key === key);
659
+ }
660
+ /**
661
+ * Get the last call made.
662
+ */
663
+ getLastCall() {
664
+ return this.callHistory[this.callHistory.length - 1];
665
+ }
666
+ /**
667
+ * Get the total number of calls made.
668
+ */
669
+ get callCount() {
670
+ return this.callHistory.length;
671
+ }
672
+ };
673
+ function createMockStorage(options = {}) {
674
+ return new MockStorageAdapter(options);
675
+ }
676
+ function createMockStorageWithAuth(accessToken, refreshToken, user, storageKey = "classic_auth") {
677
+ const storage = new MockStorageAdapter();
678
+ storage.setItem(
679
+ storageKey,
680
+ JSON.stringify({
681
+ accessToken,
682
+ refreshToken,
683
+ user
684
+ })
685
+ );
686
+ return storage;
687
+ }
688
+
689
+ // src/testing/mocks/fetch.ts
690
+ var MockFetchInstance = class {
691
+ constructor(options = {}) {
692
+ this.routes = [];
693
+ this.callHistory = [];
694
+ // ============================================================================
695
+ // Fetch Implementation
696
+ // ============================================================================
697
+ /**
698
+ * The mock fetch function.
699
+ */
700
+ this.fetch = async (input, init) => {
701
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
702
+ const method = (init?.method ?? "GET").toUpperCase();
703
+ const path = this.extractPath(url);
704
+ this.callHistory.push({
705
+ url,
706
+ options: init ?? {},
707
+ timestamp: Date.now()
708
+ });
709
+ let body = null;
710
+ if (init?.body) {
711
+ try {
712
+ body = JSON.parse(init.body);
713
+ } catch {
714
+ body = init.body;
715
+ }
716
+ }
717
+ const request = {
718
+ method,
719
+ url,
720
+ path,
721
+ body,
722
+ headers: new Headers(init?.headers)
723
+ };
724
+ const route = this.routes.find((r) => r.method === method && this.pathMatches(r.path, path));
725
+ if (!route) {
726
+ return this.createResponse(this.defaultResponse);
727
+ }
728
+ if (route.delay) {
729
+ await new Promise((resolve) => setTimeout(resolve, route.delay));
730
+ }
731
+ let responseData;
732
+ if (route.handler) {
733
+ responseData = await route.handler(request);
734
+ } else {
735
+ responseData = {
736
+ status: route.status ?? 200,
737
+ response: route.response,
738
+ headers: route.headers
739
+ };
740
+ }
741
+ return this.createResponse(responseData);
742
+ };
743
+ this.baseUrl = options.baseUrl ?? "http://localhost:3000";
744
+ this.defaultResponse = options.defaultResponse ?? {
745
+ status: 404,
746
+ response: { error: { message: "Not Found" } }
747
+ };
748
+ this.setupDefaultRoutes();
749
+ }
750
+ // ============================================================================
751
+ // Route Management
752
+ // ============================================================================
753
+ /**
754
+ * Add a route to the mock.
755
+ */
756
+ addRoute(route) {
757
+ this.routes.unshift(route);
758
+ return this;
759
+ }
760
+ /**
761
+ * Add multiple routes at once.
762
+ */
763
+ addRoutes(routes) {
764
+ routes.forEach((route) => this.addRoute(route));
765
+ return this;
766
+ }
767
+ /**
768
+ * Remove a route by path and method.
769
+ */
770
+ removeRoute(method, path) {
771
+ this.routes = this.routes.filter(
772
+ (route) => !(route.method === method && this.pathMatches(route.path, path.toString()))
773
+ );
774
+ return this;
775
+ }
776
+ /**
777
+ * Clear all routes.
778
+ */
779
+ clearRoutes() {
780
+ this.routes = [];
781
+ return this;
782
+ }
783
+ /**
784
+ * Reset to default routes.
785
+ */
786
+ reset() {
787
+ this.routes = [];
788
+ this.callHistory = [];
789
+ this.setupDefaultRoutes();
790
+ return this;
791
+ }
792
+ // ============================================================================
793
+ // Response Helpers
794
+ // ============================================================================
795
+ createResponse(data) {
796
+ const status = data.status ?? 200;
797
+ const body = data.response !== void 0 ? JSON.stringify(data.response) : null;
798
+ const headers = new Headers({
799
+ "Content-Type": "application/json",
800
+ ...data.headers
801
+ });
802
+ return new Response(body, { status, headers });
803
+ }
804
+ extractPath(url) {
805
+ try {
806
+ const urlObj = new URL(url, this.baseUrl);
807
+ return urlObj.pathname + urlObj.search;
808
+ } catch {
809
+ return url;
810
+ }
811
+ }
812
+ pathMatches(pattern, path) {
813
+ if (pattern instanceof RegExp) {
814
+ return pattern.test(path);
815
+ }
816
+ if (pattern.includes("*")) {
817
+ const regexPattern = pattern.replace(/\*/g, "[^/]+");
818
+ return new RegExp(`^${regexPattern}$`).test(path);
819
+ }
820
+ if (path.includes("?") && !pattern.includes("?")) {
821
+ return path.startsWith(pattern);
822
+ }
823
+ return pattern === path;
824
+ }
825
+ // ============================================================================
826
+ // Call History Utilities
827
+ // ============================================================================
828
+ /**
829
+ * Get all recorded calls.
830
+ */
831
+ getCalls() {
832
+ return [...this.callHistory];
833
+ }
834
+ /**
835
+ * Get calls to a specific path.
836
+ */
837
+ getCallsTo(path) {
838
+ return this.callHistory.filter((call) => {
839
+ const callPath = this.extractPath(call.url);
840
+ return callPath.startsWith(path);
841
+ });
842
+ }
843
+ /**
844
+ * Get calls with a specific method.
845
+ */
846
+ getCallsWithMethod(method) {
847
+ return this.callHistory.filter(
848
+ (call) => (call.options.method ?? "GET").toUpperCase() === method.toUpperCase()
849
+ );
850
+ }
851
+ /**
852
+ * Check if a path was called.
853
+ */
854
+ wasCalled(path) {
855
+ return this.getCallsTo(path).length > 0;
856
+ }
857
+ /**
858
+ * Check if a path was called with a specific method.
859
+ */
860
+ wasCalledWith(method, path) {
861
+ return this.callHistory.some((call) => {
862
+ const callPath = this.extractPath(call.url);
863
+ const callMethod = (call.options.method ?? "GET").toUpperCase();
864
+ return callMethod === method.toUpperCase() && callPath.startsWith(path);
865
+ });
866
+ }
867
+ /**
868
+ * Assert that a path was called.
869
+ */
870
+ assertCalled(path, message) {
871
+ if (!this.wasCalled(path)) {
872
+ throw new Error(message ?? `Expected ${path} to be called`);
873
+ }
874
+ }
875
+ /**
876
+ * Assert that a path was not called.
877
+ */
878
+ assertNotCalled(path, message) {
879
+ if (this.wasCalled(path)) {
880
+ throw new Error(message ?? `Expected ${path} not to be called`);
881
+ }
882
+ }
883
+ /**
884
+ * Get the last call made.
885
+ */
886
+ getLastCall() {
887
+ return this.callHistory[this.callHistory.length - 1];
888
+ }
889
+ /**
890
+ * Clear call history.
891
+ */
892
+ clearHistory() {
893
+ this.callHistory = [];
894
+ return this;
895
+ }
896
+ /**
897
+ * Get total call count.
898
+ */
899
+ get callCount() {
900
+ return this.callHistory.length;
901
+ }
902
+ // ============================================================================
903
+ // Scenario Configuration Helpers
904
+ // ============================================================================
905
+ /**
906
+ * Configure login to require MFA.
907
+ */
908
+ requireMFA() {
909
+ this.removeRoute("POST", "/auth/login");
910
+ this.addRoute({
911
+ method: "POST",
912
+ path: "/auth/login",
913
+ response: { data: mockMFARequired }
914
+ });
915
+ return this;
916
+ }
917
+ /**
918
+ * Configure login to fail.
919
+ */
920
+ failLogin(error = "Invalid credentials") {
921
+ this.removeRoute("POST", "/auth/login");
922
+ this.addRoute({
923
+ method: "POST",
924
+ path: "/auth/login",
925
+ status: 401,
926
+ response: { error: { message: error } }
927
+ });
928
+ return this;
929
+ }
930
+ /**
931
+ * Configure an endpoint to fail.
932
+ */
933
+ failEndpoint(method, path, status = 500, error = "Internal Server Error") {
934
+ this.removeRoute(method, path);
935
+ this.addRoute({
936
+ method,
937
+ path,
938
+ status,
939
+ response: { error: { message: error } }
940
+ });
941
+ return this;
942
+ }
943
+ /**
944
+ * Configure logout to return SSO redirect URL.
945
+ */
946
+ enableSSOLogout(logoutUrl = "https://sso.example.com/logout") {
947
+ this.removeRoute("POST", "/auth/logout");
948
+ this.addRoute({
949
+ method: "POST",
950
+ path: "/auth/logout",
951
+ response: { data: { ...mockSSOLogoutResponse, logoutUrl } }
952
+ });
953
+ return this;
954
+ }
955
+ /**
956
+ * Add response delay to all routes.
957
+ */
958
+ setDelay(delay) {
959
+ this.routes.forEach((route) => {
960
+ route.delay = delay;
961
+ });
962
+ return this;
963
+ }
964
+ // ============================================================================
965
+ // Default Routes Setup
966
+ // ============================================================================
967
+ setupDefaultRoutes() {
968
+ this.addRoute({
969
+ method: "POST",
970
+ path: "/auth/login",
971
+ response: { data: mockLoginSuccess }
972
+ });
973
+ this.addRoute({
974
+ method: "POST",
975
+ path: "/auth/logout",
976
+ response: { data: mockLogoutSuccess }
977
+ });
978
+ this.addRoute({
979
+ method: "POST",
980
+ path: "/auth/register",
981
+ response: { data: mockRegisterSuccess }
982
+ });
983
+ this.addRoute({
984
+ method: "POST",
985
+ path: "/auth/refresh",
986
+ response: { data: { accessToken: mockAccessToken, refreshToken: mockRefreshToken } }
987
+ });
988
+ this.addRoute({
989
+ method: "POST",
990
+ path: "/auth/forgot-password",
991
+ response: { message: "Password reset email sent" }
992
+ });
993
+ this.addRoute({
994
+ method: "POST",
995
+ path: "/auth/reset-password",
996
+ response: { message: "Password reset successful" }
997
+ });
998
+ this.addRoute({
999
+ method: "GET",
1000
+ path: "/auth/profile",
1001
+ response: mockUser
1002
+ });
1003
+ this.addRoute({
1004
+ method: "PUT",
1005
+ path: "/auth/profile",
1006
+ response: { data: mockUser }
1007
+ });
1008
+ this.addRoute({
1009
+ method: "PUT",
1010
+ path: "/auth/change-password",
1011
+ response: { message: "Password changed successfully" }
1012
+ });
1013
+ this.addRoute({
1014
+ method: "POST",
1015
+ path: "/auth/resend-verification",
1016
+ response: { message: "Verification email sent" }
1017
+ });
1018
+ this.addRoute({
1019
+ method: "POST",
1020
+ path: "/auth/verify-email",
1021
+ response: { message: "Email verified successfully", user: mockUser }
1022
+ });
1023
+ this.addRoute({
1024
+ method: "GET",
1025
+ path: "/auth/sessions",
1026
+ response: { sessions: mockSessions, total: mockSessions.length }
1027
+ });
1028
+ this.addRoute({
1029
+ method: "DELETE",
1030
+ path: /^\/auth\/sessions(\/.*)?$/,
1031
+ response: { message: "Session revoked" }
1032
+ });
1033
+ this.addRoute({
1034
+ method: "GET",
1035
+ path: "/auth/api-keys",
1036
+ response: { apiKeys: mockApiKeys }
1037
+ });
1038
+ this.addRoute({
1039
+ method: "POST",
1040
+ path: "/auth/api-keys",
1041
+ response: { data: { ...mockApiKeys[0], key: "sk_live_full_key_shown_once" } }
1042
+ });
1043
+ this.addRoute({
1044
+ method: "DELETE",
1045
+ path: /^\/auth\/api-keys\/.+$/,
1046
+ response: { message: "API key revoked" }
1047
+ });
1048
+ this.addRoute({
1049
+ method: "PUT",
1050
+ path: /^\/auth\/api-keys\/.+$/,
1051
+ response: { message: "API key updated" }
1052
+ });
1053
+ this.addRoute({
1054
+ method: "GET",
1055
+ path: "/auth/mfa/status",
1056
+ response: mockMFAStatusEnabled
1057
+ });
1058
+ this.addRoute({
1059
+ method: "POST",
1060
+ path: "/auth/mfa-setup",
1061
+ response: { data: mockMFASetup }
1062
+ });
1063
+ this.addRoute({
1064
+ method: "POST",
1065
+ path: "/auth/mfa/verify",
1066
+ response: { message: "MFA setup complete" }
1067
+ });
1068
+ this.addRoute({
1069
+ method: "POST",
1070
+ path: "/auth/mfa/challenge",
1071
+ response: { data: mockLoginSuccess }
1072
+ });
1073
+ this.addRoute({
1074
+ method: "DELETE",
1075
+ path: "/auth/mfa",
1076
+ response: { message: "MFA disabled" }
1077
+ });
1078
+ this.addRoute({
1079
+ method: "POST",
1080
+ path: "/auth/mfa/regenerate-backup-codes",
1081
+ response: { data: { backupCodes: mockMFASetup.backupCodes } }
1082
+ });
1083
+ this.addRoute({
1084
+ method: "GET",
1085
+ path: "/auth/devices",
1086
+ response: { devices: mockDevices, total: mockDevices.length }
1087
+ });
1088
+ this.addRoute({
1089
+ method: "PUT",
1090
+ path: /^\/auth\/devices\/.+\/trust$/,
1091
+ response: { message: "Device trusted" }
1092
+ });
1093
+ this.addRoute({
1094
+ method: "DELETE",
1095
+ path: /^\/auth\/devices\/.+\/(revoke)?$/,
1096
+ response: { message: "Device removed" }
1097
+ });
1098
+ this.addRoute({
1099
+ method: "POST",
1100
+ path: "/auth/device/approve",
1101
+ response: { data: { message: "Device approved", device: mockDevices[0] } }
1102
+ });
1103
+ this.addRoute({
1104
+ method: "POST",
1105
+ path: "/auth/device/block",
1106
+ response: { data: { message: "Device blocked", device: mockDevices[0] } }
1107
+ });
1108
+ this.addRoute({
1109
+ method: "GET",
1110
+ path: "/auth/preferences",
1111
+ response: { preferences: mockUserPreferences }
1112
+ });
1113
+ this.addRoute({
1114
+ method: "PUT",
1115
+ path: "/auth/preferences",
1116
+ response: { message: "Preferences updated" }
1117
+ });
1118
+ this.addRoute({
1119
+ method: "GET",
1120
+ path: "/auth/sso/accounts",
1121
+ response: { accounts: [mockLinkedAccount], count: 1 }
1122
+ });
1123
+ this.addRoute({
1124
+ method: "DELETE",
1125
+ path: "/auth/sso/unlink",
1126
+ response: { message: "Account unlinked" }
1127
+ });
1128
+ this.addRoute({
1129
+ method: "GET",
1130
+ path: "/auth/security/events",
1131
+ response: {
1132
+ data: {
1133
+ events: mockSecurityEvents,
1134
+ pagination: { page: 1, limit: 10, total: mockSecurityEvents.length, totalPages: 1 }
1135
+ }
1136
+ }
1137
+ });
1138
+ }
1139
+ };
1140
+ function createMockFetch(options = {}) {
1141
+ return new MockFetchInstance(options);
1142
+ }
1143
+ function createMockFetchWithRoutes(routes) {
1144
+ const mock = new MockFetchInstance();
1145
+ mock.clearRoutes();
1146
+ mock.addRoutes(routes);
1147
+ return mock;
1148
+ }
1149
+
1150
+ // src/testing/mocks/auth-store.ts
1151
+ var MockAuthStore = class {
1152
+ constructor(options = {}) {
1153
+ this.subscribers = /* @__PURE__ */ new Set();
1154
+ this.callHistory = [];
1155
+ this.state = {
1156
+ accessToken: options.initialState?.accessToken ?? null,
1157
+ refreshToken: options.initialState?.refreshToken ?? null,
1158
+ user: options.initialState?.user ?? null,
1159
+ isAuthenticated: options.initialState?.isAuthenticated ?? false
1160
+ };
1161
+ }
1162
+ // ============================================================================
1163
+ // Store Contract
1164
+ // ============================================================================
1165
+ /**
1166
+ * Subscribe to state changes (Svelte store contract).
1167
+ */
1168
+ subscribe(subscriber) {
1169
+ this.subscribers.add(subscriber);
1170
+ subscriber(this.state);
1171
+ return () => this.subscribers.delete(subscriber);
1172
+ }
1173
+ notify() {
1174
+ this.subscribers.forEach((sub) => sub(this.state));
1175
+ }
1176
+ recordCall(method, args) {
1177
+ this.callHistory.push({
1178
+ method,
1179
+ args,
1180
+ timestamp: Date.now()
1181
+ });
1182
+ }
1183
+ // ============================================================================
1184
+ // Getters
1185
+ // ============================================================================
1186
+ get accessToken() {
1187
+ return this.state.accessToken;
1188
+ }
1189
+ get refreshToken() {
1190
+ return this.state.refreshToken;
1191
+ }
1192
+ get user() {
1193
+ return this.state.user;
1194
+ }
1195
+ get isAuthenticated() {
1196
+ return this.state.isAuthenticated;
1197
+ }
1198
+ /**
1199
+ * Get the current auth state snapshot.
1200
+ */
1201
+ getState() {
1202
+ return { ...this.state };
1203
+ }
1204
+ // ============================================================================
1205
+ // Auth Actions
1206
+ // ============================================================================
1207
+ /**
1208
+ * Set auth data after successful login.
1209
+ */
1210
+ setAuth(accessToken, refreshToken, user, sessionToken) {
1211
+ this.recordCall("setAuth", [accessToken, refreshToken, user, sessionToken]);
1212
+ this.state = {
1213
+ accessToken,
1214
+ refreshToken,
1215
+ user,
1216
+ isAuthenticated: true
1217
+ };
1218
+ this.notify();
1219
+ }
1220
+ /**
1221
+ * Update tokens (e.g., after refresh).
1222
+ */
1223
+ updateTokens(accessToken, refreshToken) {
1224
+ this.recordCall("updateTokens", [accessToken, refreshToken]);
1225
+ this.state = {
1226
+ ...this.state,
1227
+ accessToken,
1228
+ refreshToken
1229
+ };
1230
+ this.notify();
1231
+ }
1232
+ /**
1233
+ * Update user data.
1234
+ */
1235
+ updateUser(user) {
1236
+ this.recordCall("updateUser", [user]);
1237
+ this.state = {
1238
+ ...this.state,
1239
+ user
1240
+ };
1241
+ this.notify();
1242
+ }
1243
+ /**
1244
+ * Clear auth state (logout).
1245
+ */
1246
+ logout() {
1247
+ this.recordCall("logout", []);
1248
+ this.state = {
1249
+ accessToken: null,
1250
+ refreshToken: null,
1251
+ user: null,
1252
+ isAuthenticated: false
1253
+ };
1254
+ this.notify();
1255
+ }
1256
+ /**
1257
+ * Logout with SSO support.
1258
+ */
1259
+ async logoutWithSSO() {
1260
+ this.recordCall("logoutWithSSO", []);
1261
+ this.logout();
1262
+ return {};
1263
+ }
1264
+ /**
1265
+ * Re-hydrate state from storage.
1266
+ */
1267
+ rehydrate() {
1268
+ this.recordCall("rehydrate", []);
1269
+ }
1270
+ // ============================================================================
1271
+ // Permission Checks
1272
+ // ============================================================================
1273
+ /**
1274
+ * Check if user has a specific permission.
1275
+ */
1276
+ hasPermission(permission) {
1277
+ return this.state.user?.permissions?.includes(permission) ?? false;
1278
+ }
1279
+ /**
1280
+ * Check if user has a specific role.
1281
+ */
1282
+ hasRole(role) {
1283
+ return this.state.user?.roles?.includes(role) ?? false;
1284
+ }
1285
+ /**
1286
+ * Check if user has any of the specified roles.
1287
+ */
1288
+ hasAnyRole(roles) {
1289
+ return roles.some((role) => this.state.user?.roles?.includes(role)) ?? false;
1290
+ }
1291
+ /**
1292
+ * Check if user has all of the specified roles.
1293
+ */
1294
+ hasAllRoles(roles) {
1295
+ return roles.every((role) => this.state.user?.roles?.includes(role)) ?? false;
1296
+ }
1297
+ /**
1298
+ * Check if user has any of the specified permissions.
1299
+ */
1300
+ hasAnyPermission(permissions) {
1301
+ return permissions.some((perm) => this.state.user?.permissions?.includes(perm)) ?? false;
1302
+ }
1303
+ /**
1304
+ * Check if user has all of the specified permissions.
1305
+ */
1306
+ hasAllPermissions(permissions) {
1307
+ return permissions.every((perm) => this.state.user?.permissions?.includes(perm)) ?? false;
1308
+ }
1309
+ // ============================================================================
1310
+ // Test Utilities
1311
+ // ============================================================================
1312
+ /**
1313
+ * Reset the store to initial state.
1314
+ */
1315
+ reset() {
1316
+ this.state = {
1317
+ accessToken: null,
1318
+ refreshToken: null,
1319
+ user: null,
1320
+ isAuthenticated: false
1321
+ };
1322
+ this.callHistory = [];
1323
+ this.notify();
1324
+ }
1325
+ /**
1326
+ * Simulate an authenticated state.
1327
+ */
1328
+ simulateAuthenticated(user = mockUser) {
1329
+ this.state = {
1330
+ accessToken: mockAccessToken,
1331
+ refreshToken: mockRefreshToken,
1332
+ user,
1333
+ isAuthenticated: true
1334
+ };
1335
+ this.notify();
1336
+ }
1337
+ /**
1338
+ * Simulate an unauthenticated state.
1339
+ */
1340
+ simulateUnauthenticated() {
1341
+ this.state = {
1342
+ accessToken: null,
1343
+ refreshToken: null,
1344
+ user: null,
1345
+ isAuthenticated: false
1346
+ };
1347
+ this.notify();
1348
+ }
1349
+ /**
1350
+ * Set the state directly for testing.
1351
+ */
1352
+ setState(state) {
1353
+ this.state = {
1354
+ ...this.state,
1355
+ ...state
1356
+ };
1357
+ this.notify();
1358
+ }
1359
+ // ============================================================================
1360
+ // Call History
1361
+ // ============================================================================
1362
+ /**
1363
+ * Get all recorded method calls.
1364
+ */
1365
+ getCalls() {
1366
+ return [...this.callHistory];
1367
+ }
1368
+ /**
1369
+ * Get calls for a specific method.
1370
+ */
1371
+ getCallsFor(method) {
1372
+ return this.callHistory.filter((call) => call.method === method);
1373
+ }
1374
+ /**
1375
+ * Check if a method was called.
1376
+ */
1377
+ wasCalled(method) {
1378
+ return this.callHistory.some((call) => call.method === method);
1379
+ }
1380
+ /**
1381
+ * Assert that a method was called.
1382
+ */
1383
+ assertMethodCalled(method, message) {
1384
+ if (!this.wasCalled(method)) {
1385
+ throw new Error(message ?? `Expected ${method} to be called`);
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Assert that a method was not called.
1390
+ */
1391
+ assertMethodNotCalled(method, message) {
1392
+ if (this.wasCalled(method)) {
1393
+ throw new Error(message ?? `Expected ${method} not to be called`);
1394
+ }
1395
+ }
1396
+ /**
1397
+ * Get the last call made.
1398
+ */
1399
+ getLastCall() {
1400
+ return this.callHistory[this.callHistory.length - 1];
1401
+ }
1402
+ /**
1403
+ * Get the last call to a specific method.
1404
+ */
1405
+ getLastCallTo(method) {
1406
+ return [...this.callHistory].reverse().find((call) => call.method === method);
1407
+ }
1408
+ /**
1409
+ * Get total call count.
1410
+ */
1411
+ get callCount() {
1412
+ return this.callHistory.length;
1413
+ }
1414
+ /**
1415
+ * Clear call history.
1416
+ */
1417
+ clearHistory() {
1418
+ this.callHistory = [];
1419
+ }
1420
+ };
1421
+ function createMockAuthStore(options = {}) {
1422
+ return new MockAuthStore(options);
1423
+ }
1424
+ function createAuthenticatedMockStore(user = mockUser) {
1425
+ const store = new MockAuthStore();
1426
+ store.simulateAuthenticated(user);
1427
+ return store;
1428
+ }
1429
+ function createMockAuthActions(store) {
1430
+ return {
1431
+ setAuth: (accessToken, refreshToken, user, sessionToken) => store.setAuth(accessToken, refreshToken, user, sessionToken),
1432
+ updateTokens: (accessToken, refreshToken) => store.updateTokens(accessToken, refreshToken),
1433
+ updateUser: (user) => store.updateUser(user),
1434
+ logout: () => store.logout(),
1435
+ logoutWithSSO: () => store.logoutWithSSO(),
1436
+ hasPermission: (permission) => store.hasPermission(permission),
1437
+ hasRole: (role) => store.hasRole(role),
1438
+ hasAnyRole: (roles) => store.hasAnyRole(roles),
1439
+ hasAllRoles: (roles) => store.hasAllRoles(roles),
1440
+ hasAnyPermission: (permissions) => store.hasAnyPermission(permissions),
1441
+ hasAllPermissions: (permissions) => store.hasAllPermissions(permissions),
1442
+ rehydrate: () => store.rehydrate()
1443
+ };
1444
+ }
1445
+ function createMockIsAuthenticated(store) {
1446
+ return {
1447
+ subscribe: (subscriber) => {
1448
+ return store.subscribe((state) => subscriber(state.isAuthenticated));
1449
+ }
1450
+ };
1451
+ }
1452
+ function createMockCurrentUser(store) {
1453
+ return {
1454
+ subscribe: (subscriber) => {
1455
+ return store.subscribe((state) => subscriber(state.user));
1456
+ }
1457
+ };
1458
+ }
1459
+ function initAuth(options) {
1460
+ ({
1461
+ ...options,
1462
+ storageKey: options.storageKey ?? "classic_auth"
1463
+ });
1464
+ }
1465
+
1466
+ // src/testing/helpers/setup.ts
1467
+ function setupTestAuth(options = {}) {
1468
+ const {
1469
+ baseUrl = "http://localhost:3000",
1470
+ storageKey = "classic_auth",
1471
+ initialStorage,
1472
+ initialAuthState,
1473
+ fetchOptions,
1474
+ initConfig = true
1475
+ } = options;
1476
+ const mockStorage = createMockStorage({ initialData: initialStorage });
1477
+ const mockFetch = createMockFetch({ baseUrl, ...fetchOptions });
1478
+ const mockStore = createMockAuthStore({ initialState: initialAuthState });
1479
+ if (initConfig) {
1480
+ const config2 = {
1481
+ baseUrl,
1482
+ storageKey,
1483
+ storage: mockStorage,
1484
+ fetch: mockFetch.fetch
1485
+ };
1486
+ initAuth(config2);
1487
+ }
1488
+ const cleanup = () => {
1489
+ mockStorage.reset();
1490
+ mockFetch.reset();
1491
+ mockStore.reset();
1492
+ };
1493
+ return {
1494
+ mockFetch,
1495
+ mockStorage,
1496
+ mockStore,
1497
+ cleanup
1498
+ };
1499
+ }
1500
+ function createTestAuthHelpers(options = {}) {
1501
+ let context = null;
1502
+ return {
1503
+ /**
1504
+ * Setup function to call in beforeEach.
1505
+ */
1506
+ setup: () => {
1507
+ context = setupTestAuth(options);
1508
+ return context;
1509
+ },
1510
+ /**
1511
+ * Cleanup function to call in afterEach.
1512
+ */
1513
+ cleanup: () => {
1514
+ context?.cleanup();
1515
+ context = null;
1516
+ },
1517
+ /**
1518
+ * Get the current test context.
1519
+ */
1520
+ getContext: () => {
1521
+ if (!context) {
1522
+ throw new Error("Test context not initialized. Call setup() first.");
1523
+ }
1524
+ return context;
1525
+ },
1526
+ /**
1527
+ * Get mocks directly (convenience accessors).
1528
+ */
1529
+ getMockFetch: () => context?.mockFetch ?? null,
1530
+ getMockStorage: () => context?.mockStorage ?? null,
1531
+ getMockStore: () => context?.mockStore ?? null
1532
+ };
1533
+ }
1534
+ function quickSetupAuth(options = {}) {
1535
+ const { cleanup } = setupTestAuth(options);
1536
+ return cleanup;
1537
+ }
1538
+ function initAuthWithMocks(mockFetch, mockStorage, options = {}) {
1539
+ initAuth({
1540
+ baseUrl: options.baseUrl ?? "http://localhost:3000",
1541
+ storageKey: options.storageKey ?? "classic_auth",
1542
+ storage: mockStorage,
1543
+ fetch: mockFetch.fetch,
1544
+ ...options
1545
+ });
1546
+ }
1547
+ function resetTestAuth(mockFetch, mockStorage, mockStore) {
1548
+ mockFetch?.reset();
1549
+ mockStorage?.reset();
1550
+ mockStore?.reset();
1551
+ }
1552
+ async function withTestAuth(fn, options = {}) {
1553
+ const context = setupTestAuth(options);
1554
+ try {
1555
+ return await fn(context);
1556
+ } finally {
1557
+ context.cleanup();
1558
+ }
1559
+ }
1560
+ async function withTestAuthEach(tests, options = {}) {
1561
+ const results = [];
1562
+ for (const test of tests) {
1563
+ const result = await withTestAuth(test, options);
1564
+ results.push(result);
1565
+ }
1566
+ return results;
1567
+ }
1568
+
1569
+ // src/testing/helpers/states.ts
1570
+ var authScenarios = {
1571
+ unauthenticated: {
1572
+ state: {
1573
+ accessToken: null,
1574
+ refreshToken: null,
1575
+ user: null,
1576
+ isAuthenticated: false
1577
+ },
1578
+ user: null,
1579
+ description: "User is not logged in"
1580
+ },
1581
+ authenticated: {
1582
+ state: {
1583
+ accessToken: mockAccessToken,
1584
+ refreshToken: mockRefreshToken,
1585
+ user: mockUser,
1586
+ isAuthenticated: true
1587
+ },
1588
+ user: mockUser,
1589
+ description: "Standard authenticated user"
1590
+ },
1591
+ admin: {
1592
+ state: {
1593
+ accessToken: mockAdminToken,
1594
+ refreshToken: mockRefreshToken,
1595
+ user: mockAdminUser,
1596
+ isAuthenticated: true
1597
+ },
1598
+ user: mockAdminUser,
1599
+ description: "Admin user with elevated permissions"
1600
+ },
1601
+ ssoUser: {
1602
+ state: {
1603
+ accessToken: mockAccessToken,
1604
+ refreshToken: mockRefreshToken,
1605
+ user: mockSSOUser,
1606
+ isAuthenticated: true
1607
+ },
1608
+ user: mockSSOUser,
1609
+ description: "SSO-authenticated user"
1610
+ },
1611
+ mfaEnabled: {
1612
+ state: {
1613
+ accessToken: mockAccessToken,
1614
+ refreshToken: mockRefreshToken,
1615
+ user: mockMFAUser,
1616
+ isAuthenticated: true
1617
+ },
1618
+ user: mockMFAUser,
1619
+ description: "User with MFA enabled"
1620
+ },
1621
+ unverifiedEmail: {
1622
+ state: {
1623
+ accessToken: mockAccessToken,
1624
+ refreshToken: mockRefreshToken,
1625
+ user: mockUnverifiedUser,
1626
+ isAuthenticated: true
1627
+ },
1628
+ user: mockUnverifiedUser,
1629
+ description: "User with unverified email"
1630
+ },
1631
+ inactive: {
1632
+ state: {
1633
+ accessToken: null,
1634
+ refreshToken: null,
1635
+ user: mockInactiveUser,
1636
+ isAuthenticated: false
1637
+ },
1638
+ user: mockInactiveUser,
1639
+ description: "Inactive/deactivated user"
1640
+ },
1641
+ expiredToken: {
1642
+ state: {
1643
+ accessToken: "expired.token.here",
1644
+ refreshToken: mockRefreshToken,
1645
+ user: mockUser,
1646
+ isAuthenticated: true
1647
+ // Still "authenticated" until refresh fails
1648
+ },
1649
+ user: mockUser,
1650
+ description: "User with expired access token"
1651
+ }
1652
+ };
1653
+ function applyScenario(store, scenario) {
1654
+ const scenarioData = authScenarios[scenario];
1655
+ store.setState(scenarioData.state);
1656
+ }
1657
+ function applyScenarios(store, scenarios) {
1658
+ scenarios.forEach((scenario) => applyScenario(store, scenario));
1659
+ }
1660
+ function getScenarioState(scenario) {
1661
+ return authScenarios[scenario];
1662
+ }
1663
+ function configureMFAFlow(mockFetch) {
1664
+ mockFetch.removeRoute("POST", "/auth/login");
1665
+ mockFetch.addRoute({
1666
+ method: "POST",
1667
+ path: "/auth/login",
1668
+ response: { data: mockMFARequired }
1669
+ });
1670
+ mockFetch.removeRoute("POST", "/auth/mfa/challenge");
1671
+ mockFetch.addRoute({
1672
+ method: "POST",
1673
+ path: "/auth/mfa/challenge",
1674
+ response: { data: mockLoginSuccess }
1675
+ });
1676
+ }
1677
+ function configureTokenRefresh(mockFetch, options = {}) {
1678
+ const { success = true, newTokens, errorMessage = "Refresh token expired" } = options;
1679
+ mockFetch.removeRoute("POST", "/auth/refresh");
1680
+ if (success) {
1681
+ const tokens = newTokens ?? createMockTokenPair();
1682
+ mockFetch.addRoute({
1683
+ method: "POST",
1684
+ path: "/auth/refresh",
1685
+ response: { data: tokens }
1686
+ });
1687
+ } else {
1688
+ mockFetch.addRoute({
1689
+ method: "POST",
1690
+ path: "/auth/refresh",
1691
+ status: 401,
1692
+ response: { error: { message: errorMessage } }
1693
+ });
1694
+ }
1695
+ }
1696
+ function configureSSOLogout(mockFetch, logoutUrl = "https://sso.example.com/logout") {
1697
+ mockFetch.removeRoute("POST", "/auth/logout");
1698
+ mockFetch.addRoute({
1699
+ method: "POST",
1700
+ path: "/auth/logout",
1701
+ response: { data: { ...mockSSOLogoutResponse, logoutUrl } }
1702
+ });
1703
+ }
1704
+ function configureAuthFailure(mockFetch, options = {}) {
1705
+ const { endpoint, status = 401, message = "Unauthorized" } = options;
1706
+ if (endpoint) {
1707
+ mockFetch.failEndpoint("GET", endpoint, status, message);
1708
+ mockFetch.failEndpoint("POST", endpoint, status, message);
1709
+ } else {
1710
+ mockFetch.failLogin(message);
1711
+ mockFetch.failEndpoint("GET", "/auth/profile", status, message);
1712
+ mockFetch.failEndpoint("GET", "/auth/sessions", status, message);
1713
+ }
1714
+ }
1715
+ function configureRateLimiting(mockFetch, endpoint) {
1716
+ mockFetch.failEndpoint("GET", endpoint, 429, "Too Many Requests");
1717
+ mockFetch.failEndpoint("POST", endpoint, 429, "Too Many Requests");
1718
+ }
1719
+ function createTestUserWithPermissions(permissions, roles = ["user"], overrides = {}) {
1720
+ return createMockUserWithRoles(roles, permissions, overrides);
1721
+ }
1722
+ function createTestAdminUser(overrides = {}) {
1723
+ return {
1724
+ ...mockAdminUser,
1725
+ ...overrides
1726
+ };
1727
+ }
1728
+ function createTestSSOUser(provider = "authentik", overrides = {}) {
1729
+ return {
1730
+ ...mockSSOUser,
1731
+ authMethod: "oauth",
1732
+ ssoProfileUrl: `https://${provider}.example.com/profile/${mockSSOUser.id}`,
1733
+ ...overrides
1734
+ };
1735
+ }
1736
+ function simulateLogin(store, user = mockUser, tokens) {
1737
+ const { accessToken, refreshToken } = tokens ?? createMockTokenPair(user.id);
1738
+ store.setAuth(accessToken, refreshToken, user);
1739
+ }
1740
+ function simulateLogout(store) {
1741
+ store.logout();
1742
+ }
1743
+ function simulateTokenRefresh(store, newTokens) {
1744
+ const tokens = newTokens ?? createMockTokenPair();
1745
+ store.updateTokens(tokens.accessToken, tokens.refreshToken);
1746
+ }
1747
+ function simulateProfileUpdate(store, updates) {
1748
+ const currentUser = store.user;
1749
+ if (currentUser) {
1750
+ store.updateUser({ ...currentUser, ...updates });
1751
+ }
1752
+ }
1753
+ function configureMFAEnrollmentFlow(mockFetch) {
1754
+ mockFetch.addRoute({
1755
+ method: "GET",
1756
+ path: "/auth/mfa/status",
1757
+ response: { enabled: false, methods: [] }
1758
+ });
1759
+ }
1760
+ function configurePasswordResetFlow(mockFetch) {
1761
+ mockFetch.addRoute({
1762
+ method: "POST",
1763
+ path: "/auth/forgot-password",
1764
+ response: { message: "Password reset email sent" }
1765
+ });
1766
+ mockFetch.addRoute({
1767
+ method: "POST",
1768
+ path: "/auth/reset-password",
1769
+ response: { message: "Password reset successful" }
1770
+ });
1771
+ }
1772
+ function configureSessionManagementFlow(mockFetch, sessionCount = 3) {
1773
+ const sessions = Array.from({ length: sessionCount }, (_, i) => ({
1774
+ id: `session-${i}`,
1775
+ deviceName: `Device ${i}`,
1776
+ browser: "Chrome",
1777
+ location: "Test Location",
1778
+ ipAddress: `192.168.1.${i}`,
1779
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
1780
+ isCurrent: i === 0,
1781
+ isTrusted: i === 0,
1782
+ deviceFingerprint: `fp-${i}`,
1783
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1784
+ expiresAt: new Date(Date.now() + 6048e5).toISOString()
1785
+ }));
1786
+ mockFetch.removeRoute("GET", "/auth/sessions");
1787
+ mockFetch.addRoute({
1788
+ method: "GET",
1789
+ path: "/auth/sessions",
1790
+ response: { sessions, total: sessionCount }
1791
+ });
1792
+ }
1793
+
1794
+ // src/core/jwt.ts
1795
+ function decodeJWT(token) {
1796
+ try {
1797
+ const parts = token.split(".");
1798
+ if (parts.length !== 3) {
1799
+ return null;
1800
+ }
1801
+ const payload = parts[1];
1802
+ const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
1803
+ return JSON.parse(decoded);
1804
+ } catch {
1805
+ return null;
1806
+ }
1807
+ }
1808
+ function isTokenExpired(token) {
1809
+ const payload = decodeJWT(token);
1810
+ if (!payload || !payload.exp) {
1811
+ return true;
1812
+ }
1813
+ return payload.exp * 1e3 < Date.now();
1814
+ }
1815
+
1816
+ // src/testing/helpers/assertions.ts
1817
+ function assertAuthenticated(state, message = "Expected state to be authenticated") {
1818
+ if (!state.isAuthenticated) {
1819
+ throw new Error(message);
1820
+ }
1821
+ if (!state.accessToken) {
1822
+ throw new Error(`${message}: missing access token`);
1823
+ }
1824
+ if (!state.user) {
1825
+ throw new Error(`${message}: missing user`);
1826
+ }
1827
+ }
1828
+ function assertUnauthenticated(state, message = "Expected state to be unauthenticated") {
1829
+ if (state.isAuthenticated) {
1830
+ throw new Error(message);
1831
+ }
1832
+ if (state.accessToken) {
1833
+ throw new Error(`${message}: should not have access token`);
1834
+ }
1835
+ if (state.user) {
1836
+ throw new Error(`${message}: should not have user`);
1837
+ }
1838
+ }
1839
+ function assertHasUser(state, userId, message) {
1840
+ if (!state.user) {
1841
+ throw new Error(message ?? `Expected user with id ${userId} but no user found`);
1842
+ }
1843
+ if (state.user.id !== userId) {
1844
+ throw new Error(message ?? `Expected user id ${userId} but got ${state.user.id}`);
1845
+ }
1846
+ }
1847
+ function assertHasPermissions(user, permissions, message) {
1848
+ const missing = permissions.filter((p) => !user.permissions?.includes(p));
1849
+ if (missing.length > 0) {
1850
+ throw new Error(message ?? `User missing permissions: ${missing.join(", ")}`);
1851
+ }
1852
+ }
1853
+ function assertLacksPermissions(user, permissions, message) {
1854
+ const present = permissions.filter((p) => user.permissions?.includes(p));
1855
+ if (present.length > 0) {
1856
+ throw new Error(message ?? `User should not have permissions: ${present.join(", ")}`);
1857
+ }
1858
+ }
1859
+ function assertHasRoles(user, roles, message) {
1860
+ const missing = roles.filter((r) => !user.roles?.includes(r));
1861
+ if (missing.length > 0) {
1862
+ throw new Error(message ?? `User missing roles: ${missing.join(", ")}`);
1863
+ }
1864
+ }
1865
+ function assertLacksRoles(user, roles, message) {
1866
+ const present = roles.filter((r) => user.roles?.includes(r));
1867
+ if (present.length > 0) {
1868
+ throw new Error(message ?? `User should not have roles: ${present.join(", ")}`);
1869
+ }
1870
+ }
1871
+ function assertTokenValid(token, message = "Expected token to be valid") {
1872
+ if (isTokenExpired(token)) {
1873
+ throw new Error(message);
1874
+ }
1875
+ }
1876
+ function assertTokenExpired(token, message = "Expected token to be expired") {
1877
+ if (!isTokenExpired(token)) {
1878
+ throw new Error(message);
1879
+ }
1880
+ }
1881
+ function assertTokenHasClaims(token, claims, message) {
1882
+ const payload = decodeJWT(token);
1883
+ if (!payload) {
1884
+ throw new Error(message ?? "Could not decode token");
1885
+ }
1886
+ for (const [key, value] of Object.entries(claims)) {
1887
+ if (payload[key] !== value) {
1888
+ throw new Error(
1889
+ message ?? `Expected token claim ${key} to be ${value} but got ${payload[key]}`
1890
+ );
1891
+ }
1892
+ }
1893
+ }
1894
+ function assertTokenSubject(token, subject, message) {
1895
+ const payload = decodeJWT(token);
1896
+ if (!payload || payload.sub !== subject) {
1897
+ throw new Error(message ?? `Expected token subject ${subject} but got ${payload?.sub}`);
1898
+ }
1899
+ }
1900
+ function assertApiCalled(mockFetch, method, path, options = {}) {
1901
+ const calls = mockFetch.getCallsTo(path).filter((call) => (call.options.method ?? "GET").toUpperCase() === method.toUpperCase());
1902
+ if (calls.length === 0) {
1903
+ throw new Error(`Expected ${method} ${path} to be called`);
1904
+ }
1905
+ if (options.times !== void 0 && calls.length !== options.times) {
1906
+ throw new Error(
1907
+ `Expected ${method} ${path} to be called ${options.times} times but was called ${calls.length} times`
1908
+ );
1909
+ }
1910
+ if (options.body !== void 0) {
1911
+ const lastCall = calls[calls.length - 1];
1912
+ let body;
1913
+ try {
1914
+ body = JSON.parse(lastCall.options.body);
1915
+ } catch {
1916
+ body = lastCall.options.body;
1917
+ }
1918
+ const bodyStr = JSON.stringify(body);
1919
+ const expectedStr = JSON.stringify(options.body);
1920
+ if (bodyStr !== expectedStr) {
1921
+ throw new Error(
1922
+ `Expected ${method} ${path} to be called with body ${expectedStr} but got ${bodyStr}`
1923
+ );
1924
+ }
1925
+ }
1926
+ if (options.headers) {
1927
+ const lastCall = calls[calls.length - 1];
1928
+ const callHeaders = lastCall.options.headers;
1929
+ for (const [key, value] of Object.entries(options.headers)) {
1930
+ if (callHeaders?.[key] !== value) {
1931
+ throw new Error(`Expected ${method} ${path} to have header ${key}: ${value}`);
1932
+ }
1933
+ }
1934
+ }
1935
+ }
1936
+ function assertApiNotCalled(mockFetch, method, path, message) {
1937
+ const calls = mockFetch.getCallsTo(path).filter((call) => (call.options.method ?? "GET").toUpperCase() === method.toUpperCase());
1938
+ if (calls.length > 0) {
1939
+ throw new Error(
1940
+ message ?? `Expected ${method} ${path} not to be called but was called ${calls.length} times`
1941
+ );
1942
+ }
1943
+ }
1944
+ function assertApiCallOrder(mockFetch, expectedOrder) {
1945
+ const calls = mockFetch.getCalls();
1946
+ let lastIndex = -1;
1947
+ for (const expected of expectedOrder) {
1948
+ const index = calls.findIndex((call, i) => {
1949
+ if (i <= lastIndex) return false;
1950
+ const method = (call.options.method ?? "GET").toUpperCase();
1951
+ const path = new URL(call.url, "http://localhost").pathname;
1952
+ return method === expected.method.toUpperCase() && path.startsWith(expected.path);
1953
+ });
1954
+ if (index === -1) {
1955
+ throw new Error(
1956
+ `Expected ${expected.method} ${expected.path} to be called after previous calls`
1957
+ );
1958
+ }
1959
+ lastIndex = index;
1960
+ }
1961
+ }
1962
+ function assertStoreMethodCalled(store, method, options = {}) {
1963
+ const calls = store.getCallsFor(method);
1964
+ if (calls.length === 0) {
1965
+ throw new Error(`Expected store.${method}() to be called`);
1966
+ }
1967
+ if (options.times !== void 0 && calls.length !== options.times) {
1968
+ throw new Error(
1969
+ `Expected store.${method}() to be called ${options.times} times but was called ${calls.length} times`
1970
+ );
1971
+ }
1972
+ if (options.args !== void 0) {
1973
+ const lastCall = calls[calls.length - 1];
1974
+ const argsStr = JSON.stringify(lastCall.args);
1975
+ const expectedStr = JSON.stringify(options.args);
1976
+ if (argsStr !== expectedStr) {
1977
+ throw new Error(
1978
+ `Expected store.${method}() to be called with ${expectedStr} but got ${argsStr}`
1979
+ );
1980
+ }
1981
+ }
1982
+ }
1983
+ function assertStoreMethodNotCalled(store, method, message) {
1984
+ const calls = store.getCallsFor(method);
1985
+ if (calls.length > 0) {
1986
+ throw new Error(
1987
+ message ?? `Expected store.${method}() not to be called but was called ${calls.length} times`
1988
+ );
1989
+ }
1990
+ }
1991
+ function assertRequiresMFA(response, message = "Expected response to require MFA") {
1992
+ if (!response.requiresMFA && !response.mfaRequired) {
1993
+ throw new Error(message);
1994
+ }
1995
+ if (!response.mfaToken) {
1996
+ throw new Error(`${message}: missing MFA token`);
1997
+ }
1998
+ }
1999
+ function assertNoMFARequired(response, message = "Expected response not to require MFA") {
2000
+ if (response.requiresMFA || response.mfaRequired) {
2001
+ throw new Error(message);
2002
+ }
2003
+ if (!response.accessToken) {
2004
+ throw new Error(`${message}: missing access token`);
2005
+ }
2006
+ }
2007
+ function assertEmailVerified(user, message = "Expected user email to be verified") {
2008
+ if (!user.emailVerified) {
2009
+ throw new Error(message);
2010
+ }
2011
+ }
2012
+ function assertEmailNotVerified(user, message = "Expected user email not to be verified") {
2013
+ if (user.emailVerified) {
2014
+ throw new Error(message);
2015
+ }
2016
+ }
2017
+ function assertUserActive(user, message = "Expected user to be active") {
2018
+ if (!user.isActive) {
2019
+ throw new Error(message);
2020
+ }
2021
+ }
2022
+ function assertUserInactive(user, message = "Expected user to be inactive") {
2023
+ if (user.isActive) {
2024
+ throw new Error(message);
2025
+ }
2026
+ }
2027
+ function assertAuthMethod(user, method, message) {
2028
+ if (user.authMethod !== method) {
2029
+ throw new Error(message ?? `Expected user auth method ${method} but got ${user.authMethod}`);
2030
+ }
2031
+ }
2032
+
2033
+ export { MockAuthStore, MockFetchInstance, MockStorageAdapter, applyScenario, applyScenarios, assertApiCallOrder, assertApiCalled, assertApiNotCalled, assertAuthMethod, assertAuthenticated, assertEmailNotVerified, assertEmailVerified, assertHasPermissions, assertHasRoles, assertHasUser, assertLacksPermissions, assertLacksRoles, assertNoMFARequired, assertRequiresMFA, assertStoreMethodCalled, assertStoreMethodNotCalled, assertTokenExpired, assertTokenHasClaims, assertTokenSubject, assertTokenValid, assertUnauthenticated, assertUserActive, assertUserInactive, authScenarios, configureAuthFailure, configureMFAEnrollmentFlow, configureMFAFlow, configurePasswordResetFlow, configureRateLimiting, configureSSOLogout, configureSessionManagementFlow, configureTokenRefresh, createAuthenticatedMockStore, createExpiredMockJWT, createMockAuthActions, createMockAuthStore, createMockCurrentUser, createMockDevice, createMockFetch, createMockFetchWithRoutes, createMockIsAuthenticated, createMockJWT, createMockLoginSuccess, createMockMFARequired, createMockMFAToken, createMockSecurityEvent, createMockSession, createMockStorage, createMockStorageWithAuth, createMockTokenPair, createMockUser, createMockUserWithAuthMethod, createMockUserWithRoles, createTestAdminUser, createTestAuthHelpers, createTestSSOUser, createTestUserWithPermissions, getScenarioState, initAuthWithMocks, mockAccessToken, mockAdminLoginSuccess, mockAdminToken, mockAdminUser, mockApiKey, mockApiKeys, mockCurrentSession, mockDevices, mockExpiredToken, mockInactiveUser, mockLinkedAccount, mockLoginSuccess, mockLogoutSuccess, mockMFARequired, mockMFARequiredLegacy, mockMFASetup, mockMFAStatusDisabled, mockMFAStatusEnabled, mockMFAToken, mockMFAUser, mockOtherSession, mockRefreshToken, mockRegisterRequiresVerification, mockRegisterSuccess, mockSSOLoginSuccess, mockSSOLogoutResponse, mockSSOUser, mockSecurityEventLogin, mockSecurityEventPasswordChange, mockSecurityEventSuspicious, mockSecurityEvents, mockSessionToken, mockSessions, mockTrustedDevice, mockUntrustedDevice, mockUnverifiedUser, mockUser, mockUserPreferences, quickSetupAuth, resetTestAuth, setupTestAuth, simulateLogin, simulateLogout, simulateProfileUpdate, simulateTokenRefresh, withTestAuth, withTestAuthEach };