@workos-inc/authkit-nextjs 2.12.2 → 2.14.0
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.
- package/README.md +138 -73
- package/dist/esm/errors.js +33 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/get-authorization-url.js +7 -2
- package/dist/esm/get-authorization-url.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/middleware-helpers.js +99 -0
- package/dist/esm/middleware-helpers.js.map +1 -0
- package/dist/esm/session.js +11 -35
- package/dist/esm/session.js.map +1 -1
- package/dist/esm/types/errors.d.ts +15 -0
- package/dist/esm/types/index.d.ts +3 -1
- package/dist/esm/types/middleware-helpers.d.ts +25 -0
- package/dist/esm/types/session.d.ts +1 -1
- package/dist/esm/types/validate-api-key.d.ts +1 -1
- package/dist/esm/types/workos.d.ts +1 -1
- package/dist/esm/utils.js +0 -2
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/workos.js +1 -1
- package/package.json +20 -21
- package/src/actions.spec.ts +14 -12
- package/src/auth.spec.ts +27 -29
- package/src/authkit-callback-route.spec.ts +31 -29
- package/src/components/authkit-provider.spec.tsx +67 -71
- package/src/components/button.spec.tsx +4 -6
- package/src/components/impersonation.spec.tsx +25 -25
- package/src/components/min-max-button.spec.tsx +2 -1
- package/src/components/tokenStore.spec.ts +21 -21
- package/src/components/useAccessToken.spec.tsx +73 -77
- package/src/components/useTokenClaims.spec.tsx +22 -22
- package/src/cookie.spec.ts +14 -9
- package/src/errors.spec.ts +108 -0
- package/src/errors.ts +46 -0
- package/src/get-authorization-url.spec.ts +12 -13
- package/src/get-authorization-url.ts +6 -10
- package/src/index.ts +16 -2
- package/src/middleware-helpers.spec.ts +231 -0
- package/src/middleware-helpers.ts +130 -0
- package/src/session.spec.ts +81 -73
- package/src/session.ts +16 -38
- package/src/utils.spec.ts +14 -31
- package/src/utils.ts +0 -2
- package/src/validate-api-key.spec.ts +4 -6
- package/src/workos.spec.ts +2 -2
- package/src/workos.ts +1 -1
|
@@ -10,17 +10,17 @@ import {
|
|
|
10
10
|
switchToOrganizationAction,
|
|
11
11
|
} from '../actions.js';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
checkSessionAction:
|
|
15
|
-
getAuthAction:
|
|
16
|
-
refreshAuthAction:
|
|
17
|
-
handleSignOutAction:
|
|
18
|
-
switchToOrganizationAction:
|
|
13
|
+
vi.mock('../actions', () => ({
|
|
14
|
+
checkSessionAction: vi.fn(),
|
|
15
|
+
getAuthAction: vi.fn(),
|
|
16
|
+
refreshAuthAction: vi.fn(),
|
|
17
|
+
handleSignOutAction: vi.fn(),
|
|
18
|
+
switchToOrganizationAction: vi.fn(),
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
describe('AuthKitProvider', () => {
|
|
22
22
|
beforeEach(() => {
|
|
23
|
-
|
|
23
|
+
vi.clearAllMocks();
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it('should render children', async () => {
|
|
@@ -134,7 +134,7 @@ describe('AuthKitProvider', () => {
|
|
|
134
134
|
});
|
|
135
135
|
|
|
136
136
|
it('should call getAuthAction when initialAuth is not provided', async () => {
|
|
137
|
-
(getAuthAction as
|
|
137
|
+
(getAuthAction as Mock).mockResolvedValueOnce({
|
|
138
138
|
user: { email: 'test@example.com' },
|
|
139
139
|
sessionId: 'test-session',
|
|
140
140
|
});
|
|
@@ -151,7 +151,7 @@ describe('AuthKitProvider', () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
it('should do nothing if onSessionExpired is false', async () => {
|
|
154
|
-
|
|
154
|
+
vi.spyOn(window, 'addEventListener');
|
|
155
155
|
|
|
156
156
|
await act(async () => {
|
|
157
157
|
render(
|
|
@@ -166,8 +166,8 @@ describe('AuthKitProvider', () => {
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
it('should call onSessionExpired when session is expired', async () => {
|
|
169
|
-
(checkSessionAction as
|
|
170
|
-
const onSessionExpired =
|
|
169
|
+
(checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
|
|
170
|
+
const onSessionExpired = vi.fn();
|
|
171
171
|
|
|
172
172
|
render(
|
|
173
173
|
<AuthKitProvider onSessionExpired={onSessionExpired}>
|
|
@@ -186,8 +186,8 @@ describe('AuthKitProvider', () => {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
it('should only call onSessionExpired once if multiple visibility changes occur', async () => {
|
|
189
|
-
(checkSessionAction as
|
|
190
|
-
const onSessionExpired =
|
|
189
|
+
(checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
|
|
190
|
+
const onSessionExpired = vi.fn();
|
|
191
191
|
|
|
192
192
|
render(
|
|
193
193
|
<AuthKitProvider onSessionExpired={onSessionExpired}>
|
|
@@ -207,9 +207,9 @@ describe('AuthKitProvider', () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
it('should pass through if checkSessionAction does not throw "Failed to fetch"', async () => {
|
|
210
|
-
(checkSessionAction as
|
|
210
|
+
(checkSessionAction as Mock).mockResolvedValueOnce(false);
|
|
211
211
|
|
|
212
|
-
const onSessionExpired =
|
|
212
|
+
const onSessionExpired = vi.fn();
|
|
213
213
|
|
|
214
214
|
render(
|
|
215
215
|
<AuthKitProvider onSessionExpired={onSessionExpired}>
|
|
@@ -227,74 +227,70 @@ describe('AuthKitProvider', () => {
|
|
|
227
227
|
});
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
describe('window.location.reload behavior', () => {
|
|
231
|
+
let originalLocation: Location;
|
|
232
232
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
window.location = { ...window.location, reload: jest.fn() };
|
|
239
|
-
|
|
240
|
-
render(
|
|
241
|
-
<AuthKitProvider>
|
|
242
|
-
<div>Test Child</div>
|
|
243
|
-
</AuthKitProvider>,
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
act(() => {
|
|
247
|
-
// Simulate visibility change
|
|
248
|
-
window.dispatchEvent(new Event('visibilitychange'));
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
originalLocation = window.location;
|
|
235
|
+
// @ts-expect-error - deleting window.location to mock it
|
|
236
|
+
delete window.location;
|
|
237
|
+
window.location = { reload: vi.fn() } as unknown as Location;
|
|
249
238
|
});
|
|
250
239
|
|
|
251
|
-
|
|
252
|
-
|
|
240
|
+
afterEach(() => {
|
|
241
|
+
window.location = originalLocation;
|
|
253
242
|
});
|
|
254
243
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
244
|
+
it('should reload the page when session is expired and no onSessionExpired handler is provided', async () => {
|
|
245
|
+
(checkSessionAction as Mock).mockRejectedValueOnce(new Error('Failed to fetch'));
|
|
258
246
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
247
|
+
render(
|
|
248
|
+
<AuthKitProvider>
|
|
249
|
+
<div>Test Child</div>
|
|
250
|
+
</AuthKitProvider>,
|
|
251
|
+
);
|
|
262
252
|
|
|
263
|
-
|
|
253
|
+
act(() => {
|
|
254
|
+
// Simulate visibility change
|
|
255
|
+
window.dispatchEvent(new Event('visibilitychange'));
|
|
256
|
+
});
|
|
264
257
|
|
|
265
|
-
|
|
266
|
-
|
|
258
|
+
await waitFor(() => {
|
|
259
|
+
expect(window.location.reload).toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
267
262
|
|
|
268
|
-
|
|
263
|
+
it('should not call onSessionExpired or reload the page if session is valid', async () => {
|
|
264
|
+
(checkSessionAction as Mock).mockResolvedValueOnce(true);
|
|
265
|
+
const onSessionExpired = vi.fn();
|
|
269
266
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
267
|
+
render(
|
|
268
|
+
<AuthKitProvider onSessionExpired={onSessionExpired}>
|
|
269
|
+
<div>Test Child</div>
|
|
270
|
+
</AuthKitProvider>,
|
|
271
|
+
);
|
|
275
272
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
act(() => {
|
|
274
|
+
// Simulate visibility change
|
|
275
|
+
window.dispatchEvent(new Event('visibilitychange'));
|
|
276
|
+
});
|
|
280
277
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
278
|
+
await waitFor(() => {
|
|
279
|
+
expect(onSessionExpired).not.toHaveBeenCalled();
|
|
280
|
+
expect(window.location.reload).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
284
282
|
});
|
|
285
|
-
|
|
286
|
-
window.location = originalLocation;
|
|
287
283
|
});
|
|
288
284
|
});
|
|
289
285
|
|
|
290
286
|
describe('useAuth', () => {
|
|
291
287
|
beforeEach(() => {
|
|
292
|
-
|
|
288
|
+
vi.clearAllMocks();
|
|
293
289
|
});
|
|
294
290
|
|
|
295
291
|
it('should call getAuth when a user is not returned when ensureSignedIn is true', async () => {
|
|
296
292
|
// First and second calls return no user, second call returns a user
|
|
297
|
-
(getAuthAction as
|
|
293
|
+
(getAuthAction as Mock)
|
|
298
294
|
.mockResolvedValueOnce({ user: null, loading: true })
|
|
299
295
|
.mockResolvedValueOnce({ user: { email: 'test@example.com' }, loading: false });
|
|
300
296
|
|
|
@@ -323,7 +319,7 @@ describe('useAuth', () => {
|
|
|
323
319
|
};
|
|
324
320
|
|
|
325
321
|
// Suppress console.error for this test since we expect an error
|
|
326
|
-
const consoleSpy =
|
|
322
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
327
323
|
|
|
328
324
|
expect(() => {
|
|
329
325
|
render(<TestComponent />);
|
|
@@ -333,7 +329,7 @@ describe('useAuth', () => {
|
|
|
333
329
|
});
|
|
334
330
|
|
|
335
331
|
it('should provide auth context values when used within AuthKitProvider', async () => {
|
|
336
|
-
(getAuthAction as
|
|
332
|
+
(getAuthAction as Mock).mockResolvedValueOnce({
|
|
337
333
|
user: { email: 'test@example.com' },
|
|
338
334
|
sessionId: 'test-session',
|
|
339
335
|
organizationId: 'test-org',
|
|
@@ -381,8 +377,8 @@ describe('useAuth', () => {
|
|
|
381
377
|
sessionId: 'test-session',
|
|
382
378
|
};
|
|
383
379
|
|
|
384
|
-
(getAuthAction as
|
|
385
|
-
(refreshAuthAction as
|
|
380
|
+
(getAuthAction as Mock).mockResolvedValueOnce(mockAuth);
|
|
381
|
+
(refreshAuthAction as Mock).mockResolvedValueOnce({
|
|
386
382
|
...mockAuth,
|
|
387
383
|
sessionId: 'new-session',
|
|
388
384
|
});
|
|
@@ -424,10 +420,10 @@ describe('useAuth', () => {
|
|
|
424
420
|
organizationId: 'new-org',
|
|
425
421
|
};
|
|
426
422
|
|
|
427
|
-
(getAuthAction as
|
|
423
|
+
(getAuthAction as Mock)
|
|
428
424
|
.mockResolvedValue(mockAuth)
|
|
429
425
|
.mockResolvedValueOnce({ ...mockAuth, organizationId: 'old-org' });
|
|
430
|
-
(switchToOrganizationAction as
|
|
426
|
+
(switchToOrganizationAction as Mock).mockResolvedValueOnce(mockAuth);
|
|
431
427
|
|
|
432
428
|
const TestComponent = () => {
|
|
433
429
|
const auth = useAuth();
|
|
@@ -460,7 +456,7 @@ describe('useAuth', () => {
|
|
|
460
456
|
});
|
|
461
457
|
|
|
462
458
|
it('should receive an error when refreshAuth fails with an error', async () => {
|
|
463
|
-
(refreshAuthAction as
|
|
459
|
+
(refreshAuthAction as Mock).mockRejectedValueOnce(new Error('Refresh failed'));
|
|
464
460
|
|
|
465
461
|
let error: string | undefined;
|
|
466
462
|
|
|
@@ -497,7 +493,7 @@ describe('useAuth', () => {
|
|
|
497
493
|
});
|
|
498
494
|
|
|
499
495
|
it('should receive an error when refreshAuth fails with a string error', async () => {
|
|
500
|
-
(refreshAuthAction as
|
|
496
|
+
(refreshAuthAction as Mock).mockRejectedValueOnce('Refresh failed');
|
|
501
497
|
|
|
502
498
|
let error: string | undefined;
|
|
503
499
|
|
|
@@ -534,7 +530,7 @@ describe('useAuth', () => {
|
|
|
534
530
|
});
|
|
535
531
|
|
|
536
532
|
it('should call handleSignOutAction when signOut is called', async () => {
|
|
537
|
-
(handleSignOutAction as
|
|
533
|
+
(handleSignOutAction as Mock).mockResolvedValueOnce({});
|
|
538
534
|
|
|
539
535
|
const TestComponent = () => {
|
|
540
536
|
const auth = useAuth();
|
|
@@ -560,7 +556,7 @@ describe('useAuth', () => {
|
|
|
560
556
|
});
|
|
561
557
|
|
|
562
558
|
it('should pass returnTo parameter to handleSignOutAction', async () => {
|
|
563
|
-
(handleSignOutAction as
|
|
559
|
+
(handleSignOutAction as Mock).mockResolvedValueOnce({});
|
|
564
560
|
|
|
565
561
|
const TestComponent = () => {
|
|
566
562
|
const auth = useAuth();
|
|
@@ -24,12 +24,10 @@ describe('Button', () => {
|
|
|
24
24
|
const { getByRole } = render(<Button style={{ backgroundColor: 'red' }}>Click me</Button>);
|
|
25
25
|
const button = getByRole('button');
|
|
26
26
|
|
|
27
|
-
expect(button).
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
justifyContent: 'center',
|
|
32
|
-
});
|
|
27
|
+
expect(button.style.backgroundColor).toBe('red');
|
|
28
|
+
expect(button.style.display).toBe('inline-flex');
|
|
29
|
+
expect(button.style.alignItems).toBe('center');
|
|
30
|
+
expect(button.style.justifyContent).toBe('center');
|
|
33
31
|
});
|
|
34
32
|
|
|
35
33
|
it('should pass through additional props', () => {
|
|
@@ -7,23 +7,23 @@ import * as React from 'react';
|
|
|
7
7
|
import { handleSignOutAction } from '../actions.js';
|
|
8
8
|
|
|
9
9
|
// Mock the useAuth hook
|
|
10
|
-
|
|
11
|
-
useAuth:
|
|
10
|
+
vi.mock('./authkit-provider', () => ({
|
|
11
|
+
useAuth: vi.fn(),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
// Mock the getOrganizationAction
|
|
15
|
-
|
|
16
|
-
getOrganizationAction:
|
|
17
|
-
handleSignOutAction:
|
|
15
|
+
vi.mock('../actions', () => ({
|
|
16
|
+
getOrganizationAction: vi.fn(),
|
|
17
|
+
handleSignOutAction: vi.fn(),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
20
|
describe('Impersonation', () => {
|
|
21
21
|
beforeEach(() => {
|
|
22
|
-
|
|
22
|
+
vi.clearAllMocks();
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('should return null if not impersonating', () => {
|
|
26
|
-
(useAuth as
|
|
26
|
+
(useAuth as Mock).mockReturnValue({
|
|
27
27
|
impersonator: null,
|
|
28
28
|
user: { id: '123', email: 'user@example.com' },
|
|
29
29
|
organizationId: null,
|
|
@@ -34,7 +34,7 @@ describe('Impersonation', () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it('should render impersonation banner when impersonating', () => {
|
|
37
|
-
(useAuth as
|
|
37
|
+
(useAuth as Mock).mockReturnValue({
|
|
38
38
|
impersonator: { email: 'admin@example.com' },
|
|
39
39
|
user: { id: '123', email: 'user@example.com' },
|
|
40
40
|
organizationId: null,
|
|
@@ -45,13 +45,13 @@ describe('Impersonation', () => {
|
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
it('should render with organization info when organizationId is provided', async () => {
|
|
48
|
-
(useAuth as
|
|
48
|
+
(useAuth as Mock).mockReturnValue({
|
|
49
49
|
impersonator: { email: 'admin@example.com' },
|
|
50
50
|
user: { id: '123', email: 'user@example.com' },
|
|
51
51
|
organizationId: 'org_123',
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
(getOrganizationAction as
|
|
54
|
+
(getOrganizationAction as Mock).mockResolvedValue({
|
|
55
55
|
id: 'org_123',
|
|
56
56
|
name: 'Test Org',
|
|
57
57
|
});
|
|
@@ -64,7 +64,7 @@ describe('Impersonation', () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('should render at the bottom by default', () => {
|
|
67
|
-
(useAuth as
|
|
67
|
+
(useAuth as Mock).mockReturnValue({
|
|
68
68
|
impersonator: { email: 'admin@example.com' },
|
|
69
69
|
user: { id: '123', email: 'user@example.com' },
|
|
70
70
|
organizationId: null,
|
|
@@ -76,7 +76,7 @@ describe('Impersonation', () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it('should render at the top when side prop is "top"', () => {
|
|
79
|
-
(useAuth as
|
|
79
|
+
(useAuth as Mock).mockReturnValue({
|
|
80
80
|
impersonator: { email: 'admin@example.com' },
|
|
81
81
|
user: { id: '123', email: 'user@example.com' },
|
|
82
82
|
organizationId: null,
|
|
@@ -88,7 +88,7 @@ describe('Impersonation', () => {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
it('should merge custom styles with default styles', () => {
|
|
91
|
-
(useAuth as
|
|
91
|
+
(useAuth as Mock).mockReturnValue({
|
|
92
92
|
impersonator: { email: 'admin@example.com' },
|
|
93
93
|
user: { id: '123', email: 'user@example.com' },
|
|
94
94
|
organizationId: null,
|
|
@@ -97,11 +97,11 @@ describe('Impersonation', () => {
|
|
|
97
97
|
const customStyle = { backgroundColor: 'red' };
|
|
98
98
|
const { container } = render(<Impersonation style={customStyle} />);
|
|
99
99
|
const root = container.querySelector('[data-workos-impersonation-root]');
|
|
100
|
-
expect(root).
|
|
100
|
+
expect((root as HTMLElement).style.backgroundColor).toBe('red');
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('should should sign out when the Stop button is called', async () => {
|
|
104
|
-
(useAuth as
|
|
104
|
+
(useAuth as Mock).mockReturnValue({
|
|
105
105
|
impersonator: { email: 'admin@example.com' },
|
|
106
106
|
user: { id: '123', email: 'user@example.com' },
|
|
107
107
|
organizationId: null,
|
|
@@ -114,7 +114,7 @@ describe('Impersonation', () => {
|
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
it('should pass returnTo prop to handleSignOutAction when provided', async () => {
|
|
117
|
-
(useAuth as
|
|
117
|
+
(useAuth as Mock).mockReturnValue({
|
|
118
118
|
impersonator: { email: 'admin@example.com' },
|
|
119
119
|
user: { id: '123', email: 'user@example.com' },
|
|
120
120
|
organizationId: null,
|
|
@@ -129,7 +129,7 @@ describe('Impersonation', () => {
|
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
it('should not call getOrganizationAction when organizationId is not provided', () => {
|
|
132
|
-
(useAuth as
|
|
132
|
+
(useAuth as Mock).mockReturnValue({
|
|
133
133
|
impersonator: { email: 'admin@example.com' },
|
|
134
134
|
user: { id: '123', email: 'user@example.com' },
|
|
135
135
|
organizationId: null,
|
|
@@ -140,7 +140,7 @@ describe('Impersonation', () => {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
it('should not call getOrganizationAction when impersonator is not present', () => {
|
|
143
|
-
(useAuth as
|
|
143
|
+
(useAuth as Mock).mockReturnValue({
|
|
144
144
|
impersonator: null,
|
|
145
145
|
user: { id: '123', email: 'user@example.com' },
|
|
146
146
|
organizationId: 'org_123',
|
|
@@ -151,7 +151,7 @@ describe('Impersonation', () => {
|
|
|
151
151
|
});
|
|
152
152
|
|
|
153
153
|
it('should not call getOrganizationAction when user is not present', () => {
|
|
154
|
-
(useAuth as
|
|
154
|
+
(useAuth as Mock).mockReturnValue({
|
|
155
155
|
impersonator: { email: 'admin@example.com' },
|
|
156
156
|
user: null,
|
|
157
157
|
organizationId: 'org_123',
|
|
@@ -167,10 +167,10 @@ describe('Impersonation', () => {
|
|
|
167
167
|
name: 'Test Org',
|
|
168
168
|
};
|
|
169
169
|
|
|
170
|
-
(getOrganizationAction as
|
|
170
|
+
(getOrganizationAction as Mock).mockResolvedValue(mockOrg);
|
|
171
171
|
|
|
172
172
|
const { rerender } = await act(async () => {
|
|
173
|
-
(useAuth as
|
|
173
|
+
(useAuth as Mock).mockReturnValue({
|
|
174
174
|
impersonator: { email: 'admin@example.com' },
|
|
175
175
|
user: { id: '123', email: 'user@example.com' },
|
|
176
176
|
organizationId: 'org_123',
|
|
@@ -188,7 +188,7 @@ describe('Impersonation', () => {
|
|
|
188
188
|
|
|
189
189
|
// Rerender with the same organizationId
|
|
190
190
|
await act(async () => {
|
|
191
|
-
(useAuth as
|
|
191
|
+
(useAuth as Mock).mockReturnValue({
|
|
192
192
|
impersonator: { email: 'admin@example.com' },
|
|
193
193
|
user: { id: '123', email: 'user@example.com' },
|
|
194
194
|
organizationId: 'org_123',
|
|
@@ -212,10 +212,10 @@ describe('Impersonation', () => {
|
|
|
212
212
|
name: 'Test Org 2',
|
|
213
213
|
};
|
|
214
214
|
|
|
215
|
-
(getOrganizationAction as
|
|
215
|
+
(getOrganizationAction as Mock).mockResolvedValueOnce(mockOrg1).mockResolvedValueOnce(mockOrg2);
|
|
216
216
|
|
|
217
217
|
const { rerender } = await act(async () => {
|
|
218
|
-
(useAuth as
|
|
218
|
+
(useAuth as Mock).mockReturnValue({
|
|
219
219
|
impersonator: { email: 'admin@example.com' },
|
|
220
220
|
user: { id: '123', email: 'user@example.com' },
|
|
221
221
|
organizationId: 'org_123',
|
|
@@ -234,7 +234,7 @@ describe('Impersonation', () => {
|
|
|
234
234
|
|
|
235
235
|
// Rerender with a different organizationId
|
|
236
236
|
await act(async () => {
|
|
237
|
-
(useAuth as
|
|
237
|
+
(useAuth as Mock).mockReturnValue({
|
|
238
238
|
impersonator: { email: 'admin@example.com' },
|
|
239
239
|
user: { id: '123', email: 'user@example.com' },
|
|
240
240
|
organizationId: 'org_456',
|
|
@@ -14,6 +14,7 @@ describe('MinMaxButton', () => {
|
|
|
14
14
|
afterEach(() => {
|
|
15
15
|
// Clean up after each test
|
|
16
16
|
document.body.innerHTML = '';
|
|
17
|
+
vi.restoreAllMocks();
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
it('sets minimized value when clicked', () => {
|
|
@@ -33,7 +34,7 @@ describe('MinMaxButton', () => {
|
|
|
33
34
|
const root = document.querySelector('[data-workos-impersonation-root]');
|
|
34
35
|
|
|
35
36
|
// Mock querySelector to return null for this test
|
|
36
|
-
|
|
37
|
+
vi.spyOn(document, 'querySelector').mockReturnValue(null);
|
|
37
38
|
|
|
38
39
|
act(() => {
|
|
39
40
|
getByRole('button').click();
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { tokenStore, TokenStore } from './tokenStore.js';
|
|
2
2
|
import { getAccessTokenAction, refreshAccessTokenAction } from '../actions.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
getAccessTokenAction:
|
|
6
|
-
refreshAccessTokenAction:
|
|
4
|
+
vi.mock('../actions.js', () => ({
|
|
5
|
+
getAccessTokenAction: vi.fn(),
|
|
6
|
+
refreshAccessTokenAction: vi.fn(),
|
|
7
7
|
}));
|
|
8
8
|
|
|
9
|
-
const mockGetAccessTokenAction = getAccessTokenAction as
|
|
10
|
-
const mockRefreshAccessTokenAction = refreshAccessTokenAction as
|
|
9
|
+
const mockGetAccessTokenAction = getAccessTokenAction as Mock;
|
|
10
|
+
const mockRefreshAccessTokenAction = refreshAccessTokenAction as Mock;
|
|
11
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
12
|
const _global = global as any;
|
|
13
13
|
|
|
14
14
|
describe('tokenStore', () => {
|
|
15
15
|
beforeEach(() => {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
vi.resetAllMocks();
|
|
18
18
|
tokenStore.reset();
|
|
19
19
|
|
|
20
20
|
// Clean up DOM globals
|
|
@@ -23,10 +23,10 @@ describe('tokenStore', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
afterEach(() => {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
vi.clearAllTimers();
|
|
27
|
+
vi.useRealTimers();
|
|
28
28
|
tokenStore.reset();
|
|
29
|
-
|
|
29
|
+
vi.restoreAllMocks();
|
|
30
30
|
|
|
31
31
|
// Clean up DOM globals
|
|
32
32
|
delete _global.document;
|
|
@@ -213,7 +213,7 @@ describe('tokenStore', () => {
|
|
|
213
213
|
};
|
|
214
214
|
const validToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(validPayload))}.mock-signature`;
|
|
215
215
|
|
|
216
|
-
const setTimeoutSpy =
|
|
216
|
+
const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
|
|
217
217
|
mockGetAccessTokenAction.mockResolvedValue(validToken);
|
|
218
218
|
|
|
219
219
|
await tokenStore.getAccessTokenSilently();
|
|
@@ -227,7 +227,7 @@ describe('tokenStore', () => {
|
|
|
227
227
|
|
|
228
228
|
describe('subscriber management', () => {
|
|
229
229
|
it('should notify subscribers when state changes', () => {
|
|
230
|
-
const listener =
|
|
230
|
+
const listener = vi.fn();
|
|
231
231
|
const unsubscribe = tokenStore.subscribe(listener);
|
|
232
232
|
|
|
233
233
|
// Trigger a state change
|
|
@@ -256,14 +256,14 @@ describe('tokenStore', () => {
|
|
|
256
256
|
mockGetAccessTokenAction.mockResolvedValue(validToken);
|
|
257
257
|
|
|
258
258
|
// Subscribe to create a listener
|
|
259
|
-
const listener =
|
|
259
|
+
const listener = vi.fn();
|
|
260
260
|
const unsubscribe = tokenStore.subscribe(listener);
|
|
261
261
|
|
|
262
262
|
// Get token to schedule a refresh
|
|
263
263
|
await tokenStore.getAccessTokenSilently();
|
|
264
264
|
|
|
265
265
|
// Spy on clearTimeout
|
|
266
|
-
const clearTimeoutSpy =
|
|
266
|
+
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
|
267
267
|
|
|
268
268
|
// Unsubscribe the last (only) subscriber - should clear timeout
|
|
269
269
|
unsubscribe();
|
|
@@ -354,7 +354,7 @@ describe('tokenStore', () => {
|
|
|
354
354
|
|
|
355
355
|
it('should consume eager auth cookie on first getAccessToken call', async () => {
|
|
356
356
|
const eagerToken = 'eager-auth-token';
|
|
357
|
-
const mockCookieSetter =
|
|
357
|
+
const mockCookieSetter = vi.fn();
|
|
358
358
|
|
|
359
359
|
// Mock document.cookie with both getter and setter
|
|
360
360
|
let cookieValue = `workos-access-token=${eagerToken};`;
|
|
@@ -404,7 +404,7 @@ describe('tokenStore', () => {
|
|
|
404
404
|
iat: now - 40,
|
|
405
405
|
};
|
|
406
406
|
const fastToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${btoa(JSON.stringify(fastPayload))}.mock-signature`;
|
|
407
|
-
const mockCookieSetter =
|
|
407
|
+
const mockCookieSetter = vi.fn();
|
|
408
408
|
|
|
409
409
|
let cookieValue = `workos-access-token=${fastToken};`;
|
|
410
410
|
|
|
@@ -433,7 +433,7 @@ describe('tokenStore', () => {
|
|
|
433
433
|
configurable: true,
|
|
434
434
|
});
|
|
435
435
|
|
|
436
|
-
const setTimeoutSpy =
|
|
436
|
+
const setTimeoutSpy = vi.spyOn(global, 'setTimeout');
|
|
437
437
|
|
|
438
438
|
// Call getAccessTokenSilently to trigger fast cookie consumption and refresh scheduling
|
|
439
439
|
const token = await tokenStore.getAccessTokenSilently();
|
|
@@ -480,7 +480,7 @@ describe('tokenStore', () => {
|
|
|
480
480
|
|
|
481
481
|
it('should handle HTTP protocol for cookie deletion', async () => {
|
|
482
482
|
const eagerToken = 'http-token';
|
|
483
|
-
const mockCookieSetter =
|
|
483
|
+
const mockCookieSetter = vi.fn();
|
|
484
484
|
|
|
485
485
|
let cookieValue = `workos-access-token=${eagerToken};`;
|
|
486
486
|
|
|
@@ -624,7 +624,7 @@ describe('tokenStore', () => {
|
|
|
624
624
|
await tokenStore.getAccessTokenSilently();
|
|
625
625
|
|
|
626
626
|
// Spy on clearTimeout
|
|
627
|
-
const clearTimeoutSpy =
|
|
627
|
+
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout');
|
|
628
628
|
|
|
629
629
|
// Clear token should clear the refresh timeout
|
|
630
630
|
tokenStore.clearToken();
|
|
@@ -738,7 +738,7 @@ describe('tokenStore', () => {
|
|
|
738
738
|
mockGetAccessTokenAction.mockClear();
|
|
739
739
|
mockRefreshAccessTokenAction.mockResolvedValue(existingToken); // Same token
|
|
740
740
|
|
|
741
|
-
const listener =
|
|
741
|
+
const listener = vi.fn();
|
|
742
742
|
tokenStore.subscribe(listener);
|
|
743
743
|
|
|
744
744
|
// Force a silent refresh that returns the same token
|
|
@@ -752,7 +752,7 @@ describe('tokenStore', () => {
|
|
|
752
752
|
|
|
753
753
|
describe('TokenStore constructor', () => {
|
|
754
754
|
const setupMockEnv = (cookieValue = '', protocol = 'https:') => {
|
|
755
|
-
const mockCookieSetter =
|
|
755
|
+
const mockCookieSetter = vi.fn();
|
|
756
756
|
|
|
757
757
|
Object.defineProperty(_global, 'document', {
|
|
758
758
|
value: { cookie: cookieValue },
|