@jasperoosthoek/zustand-auth-registry 0.0.1 → 0.0.2
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 +107 -293
- package/dist/authConfig.d.ts +25 -20
- package/dist/authStore.d.ts +3 -3
- package/dist/errors.d.ts +39 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/useAuth.d.ts +3 -2
- package/package.json +2 -1
- package/src/authConfig.ts +119 -112
- package/src/authStore.ts +46 -57
- package/src/createAuthRegistry.ts +1 -1
- package/src/errors.ts +104 -0
- package/src/index.ts +2 -1
- package/src/useAuth.ts +181 -101
- package/dist/setupTests.d.ts +0 -1
- package/src/__tests__/authConfig.test.ts +0 -463
- package/src/__tests__/authStore.test.ts +0 -608
- package/src/__tests__/createAuthRegistry.test.ts +0 -202
- package/src/__tests__/testHelpers.ts +0 -92
- package/src/__tests__/testUtils.ts +0 -142
- package/src/__tests__/useAuth.test.ts +0 -975
- package/src/setupTests.ts +0 -46
|
@@ -1,608 +0,0 @@
|
|
|
1
|
-
import { createAuthStore } from '../authStore';
|
|
2
|
-
import { validateAuthConfig } from '../authConfig';
|
|
3
|
-
import { TestUser, createMockAxios, createMockStorage, resetAllMocks } from './testHelpers';
|
|
4
|
-
import { testConfigs, createStorageQuotaError } from './testUtils';
|
|
5
|
-
|
|
6
|
-
describe('createAuthStore', () => {
|
|
7
|
-
let mockAxios: any;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
resetAllMocks();
|
|
11
|
-
mockAxios = createMockAxios();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('initial state', () => {
|
|
15
|
-
it('should create store with empty initial state when no persistence', () => {
|
|
16
|
-
const config = validateAuthConfig({
|
|
17
|
-
...testConfigs.withoutPersistence,
|
|
18
|
-
axios: mockAxios
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const store = createAuthStore(config);
|
|
22
|
-
const state = store.getState();
|
|
23
|
-
|
|
24
|
-
expect(state.user).toBeNull();
|
|
25
|
-
expect(state.token).toBe('');
|
|
26
|
-
expect(state.isAuthenticated).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should restore state from localStorage when available', () => {
|
|
30
|
-
const mockStorage = window.localStorage as jest.Mocked<Storage>;
|
|
31
|
-
mockStorage.getItem.mockImplementation((key: string) => {
|
|
32
|
-
if (key === 'token') return 'stored-token';
|
|
33
|
-
if (key === 'user') return JSON.stringify({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
34
|
-
return null;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const config = validateAuthConfig({
|
|
38
|
-
axios: mockAxios,
|
|
39
|
-
loginUrl: '/login',
|
|
40
|
-
logoutUrl: '/logout',
|
|
41
|
-
extractToken: (data: any) => data.token
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const store = createAuthStore(config);
|
|
45
|
-
const state = store.getState();
|
|
46
|
-
|
|
47
|
-
expect(state.token).toBe('stored-token');
|
|
48
|
-
expect(state.user).toEqual({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
49
|
-
expect(state.isAuthenticated).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should handle corrupted user data in storage', () => {
|
|
53
|
-
const mockStorage = window.localStorage as jest.Mocked<Storage>;
|
|
54
|
-
mockStorage.getItem.mockImplementation((key: string) => {
|
|
55
|
-
if (key === 'token') return 'stored-token';
|
|
56
|
-
if (key === 'user') return 'invalid-json';
|
|
57
|
-
return null;
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const config = validateAuthConfig({
|
|
61
|
-
axios: mockAxios,
|
|
62
|
-
loginUrl: '/login',
|
|
63
|
-
logoutUrl: '/logout',
|
|
64
|
-
extractToken: (data: any) => data.token
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
const store = createAuthStore(config);
|
|
68
|
-
const state = store.getState();
|
|
69
|
-
|
|
70
|
-
expect(state.token).toBe('stored-token');
|
|
71
|
-
expect(state.user).toBeNull();
|
|
72
|
-
expect(state.isAuthenticated).toBe(true); // Token exists, so authenticated
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should work with custom storage keys', () => {
|
|
76
|
-
const mockStorage = window.sessionStorage as jest.Mocked<Storage>;
|
|
77
|
-
mockStorage.getItem.mockImplementation((key: string) => {
|
|
78
|
-
if (key === 'custom_token') return 'stored-token';
|
|
79
|
-
if (key === 'custom_user') return JSON.stringify({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
80
|
-
return null;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const config = validateAuthConfig({
|
|
84
|
-
...testConfigs.withCustomStorage,
|
|
85
|
-
axios: mockAxios
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const store = createAuthStore(config);
|
|
89
|
-
const state = store.getState();
|
|
90
|
-
|
|
91
|
-
expect(state.token).toBe('stored-token');
|
|
92
|
-
expect(state.user).toEqual({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('setToken', () => {
|
|
97
|
-
it('should update token and storage', () => {
|
|
98
|
-
const config = validateAuthConfig({
|
|
99
|
-
axios: mockAxios,
|
|
100
|
-
loginUrl: '/login',
|
|
101
|
-
logoutUrl: '/logout',
|
|
102
|
-
extractToken: (data: any) => data.token
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const store = createAuthStore(config);
|
|
106
|
-
const { setToken } = store.getState();
|
|
107
|
-
|
|
108
|
-
setToken('new-token');
|
|
109
|
-
|
|
110
|
-
const state = store.getState();
|
|
111
|
-
expect(state.token).toBe('new-token');
|
|
112
|
-
expect(state.isAuthenticated).toBe(true);
|
|
113
|
-
expect(window.localStorage.setItem).toHaveBeenCalledWith('token', 'new-token');
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should handle storage errors gracefully', () => {
|
|
117
|
-
const onError = jest.fn();
|
|
118
|
-
const config = validateAuthConfig({
|
|
119
|
-
axios: mockAxios,
|
|
120
|
-
loginUrl: '/login',
|
|
121
|
-
logoutUrl: '/logout',
|
|
122
|
-
extractToken: (data: any) => data.token,
|
|
123
|
-
onError
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const mockStorage = window.localStorage as jest.Mocked<Storage>;
|
|
127
|
-
mockStorage.setItem.mockImplementation(() => {
|
|
128
|
-
throw createStorageQuotaError();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const store = createAuthStore(config);
|
|
132
|
-
const { setToken } = store.getState();
|
|
133
|
-
|
|
134
|
-
setToken('new-token');
|
|
135
|
-
|
|
136
|
-
const state = store.getState();
|
|
137
|
-
expect(state.token).toBe('new-token');
|
|
138
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should not write to storage when persistence disabled', () => {
|
|
142
|
-
const config = validateAuthConfig({
|
|
143
|
-
...testConfigs.withoutPersistence,
|
|
144
|
-
axios: mockAxios
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const store = createAuthStore(config);
|
|
148
|
-
const { setToken } = store.getState();
|
|
149
|
-
|
|
150
|
-
setToken('new-token');
|
|
151
|
-
|
|
152
|
-
expect(window.localStorage.setItem).not.toHaveBeenCalled();
|
|
153
|
-
expect(store.getState().token).toBe('new-token');
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe('setUser', () => {
|
|
158
|
-
it('should update user and storage', () => {
|
|
159
|
-
const config = validateAuthConfig({
|
|
160
|
-
axios: mockAxios,
|
|
161
|
-
loginUrl: '/login',
|
|
162
|
-
logoutUrl: '/logout',
|
|
163
|
-
extractToken: (data: any) => data.token
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const store = createAuthStore(config);
|
|
167
|
-
const { setUser } = store.getState();
|
|
168
|
-
|
|
169
|
-
const user: TestUser = { id: 1, email: 'test@example.com', name: 'Test User' };
|
|
170
|
-
setUser(user);
|
|
171
|
-
|
|
172
|
-
const state = store.getState();
|
|
173
|
-
expect(state.user).toEqual(user);
|
|
174
|
-
expect(state.isAuthenticated).toBe(true);
|
|
175
|
-
expect(window.localStorage.setItem).toHaveBeenCalledWith('user', JSON.stringify(user));
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should handle storage errors for user data', () => {
|
|
179
|
-
const onError = jest.fn();
|
|
180
|
-
const config = validateAuthConfig({
|
|
181
|
-
axios: mockAxios,
|
|
182
|
-
loginUrl: '/login',
|
|
183
|
-
logoutUrl: '/logout',
|
|
184
|
-
extractToken: (data: any) => data.token,
|
|
185
|
-
onError
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const mockStorage = window.localStorage as jest.Mocked<Storage>;
|
|
189
|
-
mockStorage.setItem.mockImplementation((key: string) => {
|
|
190
|
-
if (key === 'user') throw createStorageQuotaError();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const store = createAuthStore(config);
|
|
194
|
-
const { setUser } = store.getState();
|
|
195
|
-
|
|
196
|
-
const user: TestUser = { id: 1, email: 'test@example.com', name: 'Test User' };
|
|
197
|
-
setUser(user);
|
|
198
|
-
|
|
199
|
-
expect(store.getState().user).toEqual(user);
|
|
200
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe('unsetUser', () => {
|
|
205
|
-
it('should clear user and token from state and storage', () => {
|
|
206
|
-
const config = validateAuthConfig({
|
|
207
|
-
axios: mockAxios,
|
|
208
|
-
loginUrl: '/login',
|
|
209
|
-
logoutUrl: '/logout',
|
|
210
|
-
extractToken: (data: any) => data.token
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const store = createAuthStore(config);
|
|
214
|
-
const { setToken, setUser, unsetUser } = store.getState();
|
|
215
|
-
|
|
216
|
-
// Set initial data
|
|
217
|
-
setToken('token');
|
|
218
|
-
setUser({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
219
|
-
|
|
220
|
-
// Clear data
|
|
221
|
-
unsetUser();
|
|
222
|
-
|
|
223
|
-
const state = store.getState();
|
|
224
|
-
expect(state.user).toBeNull();
|
|
225
|
-
expect(state.token).toBe('');
|
|
226
|
-
expect(state.isAuthenticated).toBe(false);
|
|
227
|
-
expect(window.localStorage.removeItem).toHaveBeenCalledWith('token');
|
|
228
|
-
expect(window.localStorage.removeItem).toHaveBeenCalledWith('user');
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should handle storage removal errors', () => {
|
|
232
|
-
const onError = jest.fn();
|
|
233
|
-
const config = validateAuthConfig({
|
|
234
|
-
axios: mockAxios,
|
|
235
|
-
loginUrl: '/login',
|
|
236
|
-
logoutUrl: '/logout',
|
|
237
|
-
extractToken: (data: any) => data.token,
|
|
238
|
-
onError
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
const mockStorage = window.localStorage as jest.Mocked<Storage>;
|
|
242
|
-
mockStorage.removeItem.mockImplementation(() => {
|
|
243
|
-
throw new Error('Storage error');
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const store = createAuthStore(config);
|
|
247
|
-
const { unsetUser } = store.getState();
|
|
248
|
-
|
|
249
|
-
unsetUser();
|
|
250
|
-
|
|
251
|
-
const state = store.getState();
|
|
252
|
-
expect(state.user).toBeNull();
|
|
253
|
-
expect(state.isAuthenticated).toBe(false);
|
|
254
|
-
expect(onError).toHaveBeenCalledWith(expect.any(Error));
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
describe('persistence configurations', () => {
|
|
259
|
-
it('should work with sessionStorage', () => {
|
|
260
|
-
const mockSessionStorage = window.sessionStorage as jest.Mocked<Storage>;
|
|
261
|
-
mockSessionStorage.getItem.mockImplementation((key: string) => {
|
|
262
|
-
if (key === 'token') return 'session-token';
|
|
263
|
-
return null;
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const config = validateAuthConfig({
|
|
267
|
-
axios: mockAxios,
|
|
268
|
-
loginUrl: '/login',
|
|
269
|
-
logoutUrl: '/logout',
|
|
270
|
-
extractToken: (data: any) => data.token,
|
|
271
|
-
persistence: {
|
|
272
|
-
storage: window.sessionStorage
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
const store = createAuthStore(config);
|
|
277
|
-
expect(store.getState().token).toBe('session-token');
|
|
278
|
-
|
|
279
|
-
const { setToken } = store.getState();
|
|
280
|
-
setToken('new-session-token');
|
|
281
|
-
|
|
282
|
-
expect(mockSessionStorage.setItem).toHaveBeenCalledWith('token', 'new-session-token');
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('should work with custom storage implementation', () => {
|
|
286
|
-
const customStorage = createMockStorage();
|
|
287
|
-
customStorage.getItem.mockReturnValue('custom-token');
|
|
288
|
-
|
|
289
|
-
const config = validateAuthConfig({
|
|
290
|
-
axios: mockAxios,
|
|
291
|
-
loginUrl: '/login',
|
|
292
|
-
logoutUrl: '/logout',
|
|
293
|
-
extractToken: (data: any) => data.token,
|
|
294
|
-
persistence: {
|
|
295
|
-
storage: customStorage as Storage
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const store = createAuthStore(config);
|
|
300
|
-
expect(store.getState().token).toBe('custom-token');
|
|
301
|
-
|
|
302
|
-
const { setToken } = store.getState();
|
|
303
|
-
setToken('new-custom-token');
|
|
304
|
-
|
|
305
|
-
expect(customStorage.setItem).toHaveBeenCalledWith('token', 'new-custom-token');
|
|
306
|
-
});
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
describe('SSR compatibility', () => {
|
|
310
|
-
it('should work when localStorage is not available', () => {
|
|
311
|
-
const config = validateAuthConfig({
|
|
312
|
-
axios: mockAxios,
|
|
313
|
-
loginUrl: '/login',
|
|
314
|
-
logoutUrl: '/logout',
|
|
315
|
-
extractToken: (data: any) => data.token,
|
|
316
|
-
persistence: {
|
|
317
|
-
storage: {} as Storage
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const store = createAuthStore(config);
|
|
322
|
-
const { setToken, setUser, unsetUser } = store.getState();
|
|
323
|
-
|
|
324
|
-
// Should not throw errors
|
|
325
|
-
expect(() => {
|
|
326
|
-
setToken('token');
|
|
327
|
-
setUser({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
328
|
-
unsetUser();
|
|
329
|
-
}).not.toThrow();
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
describe('authentication state logic', () => {
|
|
334
|
-
it('should set isAuthenticated to true when both token and user exist', () => {
|
|
335
|
-
const config = validateAuthConfig({
|
|
336
|
-
axios: mockAxios,
|
|
337
|
-
loginUrl: '/login',
|
|
338
|
-
logoutUrl: '/logout',
|
|
339
|
-
extractToken: (data: any) => data.token
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
const store = createAuthStore(config);
|
|
343
|
-
const { setToken, setUser } = store.getState();
|
|
344
|
-
|
|
345
|
-
setToken('token');
|
|
346
|
-
expect(store.getState().isAuthenticated).toBe(true);
|
|
347
|
-
|
|
348
|
-
setUser({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
349
|
-
expect(store.getState().isAuthenticated).toBe(true);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('should set isAuthenticated to false when token is empty', () => {
|
|
353
|
-
const config = validateAuthConfig({
|
|
354
|
-
axios: mockAxios,
|
|
355
|
-
loginUrl: '/login',
|
|
356
|
-
logoutUrl: '/logout',
|
|
357
|
-
extractToken: (data: any) => data.token
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const store = createAuthStore(config);
|
|
361
|
-
const { setToken, setUser } = store.getState();
|
|
362
|
-
|
|
363
|
-
setUser({ id: 1, email: 'test@example.com', name: 'Test User' });
|
|
364
|
-
setToken('');
|
|
365
|
-
|
|
366
|
-
expect(store.getState().isAuthenticated).toBe(false);
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
it('should set isAuthenticated to false when user is null', () => {
|
|
370
|
-
const config = validateAuthConfig({
|
|
371
|
-
axios: mockAxios,
|
|
372
|
-
loginUrl: '/login',
|
|
373
|
-
logoutUrl: '/logout',
|
|
374
|
-
extractToken: (data: any) => data.token
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const store = createAuthStore(config);
|
|
378
|
-
const { setToken, setUser } = store.getState();
|
|
379
|
-
|
|
380
|
-
setToken('token');
|
|
381
|
-
setUser(null as any);
|
|
382
|
-
|
|
383
|
-
expect(store.getState().isAuthenticated).toBe(true); // Still authenticated because token exists
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
describe('OAuth storage operations', () => {
|
|
388
|
-
it('should handle optional refresh token removal', () => {
|
|
389
|
-
const config = validateAuthConfig({
|
|
390
|
-
axios: mockAxios,
|
|
391
|
-
tokenUrl: '/oauth/token',
|
|
392
|
-
persistence: {
|
|
393
|
-
enabled: true,
|
|
394
|
-
storage: window.localStorage
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const store = createAuthStore(config);
|
|
399
|
-
|
|
400
|
-
// Clear previous test state
|
|
401
|
-
(window.localStorage.setItem as jest.Mock).mockClear();
|
|
402
|
-
(window.localStorage.removeItem as jest.Mock).mockClear();
|
|
403
|
-
|
|
404
|
-
// Set tokens with refresh token
|
|
405
|
-
store.getState().setTokens({
|
|
406
|
-
accessToken: 'access-token',
|
|
407
|
-
refreshToken: 'refresh-token',
|
|
408
|
-
tokenType: 'Bearer'
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
expect(window.localStorage.setItem).toHaveBeenCalledWith('refresh_token', 'refresh-token');
|
|
412
|
-
|
|
413
|
-
// Update tokens without refresh token
|
|
414
|
-
store.getState().setTokens({
|
|
415
|
-
accessToken: 'new-access-token',
|
|
416
|
-
tokenType: 'Bearer'
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
expect(window.localStorage.removeItem).toHaveBeenCalledWith('refresh_token');
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it('should handle optional expiry removal', () => {
|
|
423
|
-
const config = validateAuthConfig({
|
|
424
|
-
axios: mockAxios,
|
|
425
|
-
tokenUrl: '/oauth/token',
|
|
426
|
-
persistence: {
|
|
427
|
-
enabled: true,
|
|
428
|
-
storage: window.localStorage
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
const store = createAuthStore(config);
|
|
433
|
-
|
|
434
|
-
// First, set tokens with both refresh token and expiry
|
|
435
|
-
const expiresAt = Date.now() + 3600000;
|
|
436
|
-
store.getState().setTokens({
|
|
437
|
-
accessToken: 'access-token',
|
|
438
|
-
refreshToken: 'refresh-token',
|
|
439
|
-
tokenType: 'Bearer',
|
|
440
|
-
expiresAt
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Clear mocks after initial setup
|
|
444
|
-
(window.localStorage.setItem as jest.Mock).mockClear();
|
|
445
|
-
(window.localStorage.removeItem as jest.Mock).mockClear();
|
|
446
|
-
|
|
447
|
-
// Now update tokens with refresh token but without expiry
|
|
448
|
-
store.getState().setTokens({
|
|
449
|
-
accessToken: 'new-access-token',
|
|
450
|
-
refreshToken: 'refresh-token', // Keep refresh token
|
|
451
|
-
tokenType: 'Bearer'
|
|
452
|
-
// No expiresAt - this should trigger removal
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Verify that expires_at was removed when not provided
|
|
456
|
-
expect(window.localStorage.removeItem).toHaveBeenCalledWith('expires_at');
|
|
457
|
-
expect(window.localStorage.setItem).toHaveBeenCalledWith('token', 'new-access-token');
|
|
458
|
-
expect(window.localStorage.setItem).toHaveBeenCalledWith('refresh_token', 'refresh-token');
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it('should handle storage errors during refresh token operations', () => {
|
|
462
|
-
const onError = jest.fn();
|
|
463
|
-
const config = validateAuthConfig({
|
|
464
|
-
axios: mockAxios,
|
|
465
|
-
tokenUrl: '/oauth/token',
|
|
466
|
-
onError,
|
|
467
|
-
persistence: {
|
|
468
|
-
enabled: true,
|
|
469
|
-
storage: window.localStorage
|
|
470
|
-
}
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const store = createAuthStore(config);
|
|
474
|
-
const error = new Error('Storage quota exceeded');
|
|
475
|
-
|
|
476
|
-
// Mock storage error for removeItem
|
|
477
|
-
(window.localStorage.removeItem as jest.Mock).mockImplementation(() => {
|
|
478
|
-
throw error;
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
// Set tokens without refresh token (should trigger removeItem)
|
|
482
|
-
store.getState().setTokens({
|
|
483
|
-
accessToken: 'access-token',
|
|
484
|
-
tokenType: 'Bearer'
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
expect(onError).toHaveBeenCalledWith(error);
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
it('should handle storage errors during expiry operations', () => {
|
|
491
|
-
const onError = jest.fn();
|
|
492
|
-
const config = validateAuthConfig({
|
|
493
|
-
axios: mockAxios,
|
|
494
|
-
tokenUrl: '/oauth/token',
|
|
495
|
-
onError,
|
|
496
|
-
persistence: {
|
|
497
|
-
enabled: true,
|
|
498
|
-
storage: window.localStorage
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
const store = createAuthStore(config);
|
|
503
|
-
const error = new Error('Storage quota exceeded');
|
|
504
|
-
|
|
505
|
-
// Mock storage error for removeItem
|
|
506
|
-
(window.localStorage.removeItem as jest.Mock).mockImplementation(() => {
|
|
507
|
-
throw error;
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// Set tokens without expiry (should trigger removeItem)
|
|
511
|
-
store.getState().setTokens({
|
|
512
|
-
accessToken: 'access-token',
|
|
513
|
-
tokenType: 'Bearer'
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
expect(onError).toHaveBeenCalledWith(error);
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
it('should handle tokens without expiry information', () => {
|
|
520
|
-
const config = validateAuthConfig({
|
|
521
|
-
axios: mockAxios,
|
|
522
|
-
tokenUrl: '/oauth/token'
|
|
523
|
-
});
|
|
524
|
-
|
|
525
|
-
const store = createAuthStore(config);
|
|
526
|
-
|
|
527
|
-
// Set tokens without expiry
|
|
528
|
-
store.getState().setTokens({
|
|
529
|
-
accessToken: 'access-token',
|
|
530
|
-
tokenType: 'Bearer'
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const isExpired = store.getState().isTokenExpired();
|
|
534
|
-
expect(isExpired).toBe(false); // No expiry info means no expiration
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it('should return false when no expiration timestamp available', () => {
|
|
538
|
-
const config = validateAuthConfig({
|
|
539
|
-
axios: mockAxios,
|
|
540
|
-
tokenUrl: '/oauth/token'
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
const store = createAuthStore(config);
|
|
544
|
-
|
|
545
|
-
// No tokens set at all
|
|
546
|
-
const isExpired = store.getState().isTokenExpired();
|
|
547
|
-
expect(isExpired).toBe(false);
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
it('should handle tokens with expiry set to undefined', () => {
|
|
551
|
-
const config = validateAuthConfig({
|
|
552
|
-
axios: mockAxios,
|
|
553
|
-
tokenUrl: '/oauth/token'
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
const store = createAuthStore(config);
|
|
557
|
-
|
|
558
|
-
store.getState().setTokens({
|
|
559
|
-
accessToken: 'access-token',
|
|
560
|
-
tokenType: 'Bearer',
|
|
561
|
-
expiresAt: undefined
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const isExpired = store.getState().isTokenExpired();
|
|
565
|
-
expect(isExpired).toBe(false);
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
it('should correctly identify expired tokens', () => {
|
|
569
|
-
const config = validateAuthConfig({
|
|
570
|
-
axios: mockAxios,
|
|
571
|
-
tokenUrl: '/oauth/token'
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
const store = createAuthStore(config);
|
|
575
|
-
|
|
576
|
-
// Set expired tokens
|
|
577
|
-
const expiredTime = Date.now() - 1000; // 1 second ago
|
|
578
|
-
store.getState().setTokens({
|
|
579
|
-
accessToken: 'expired-token',
|
|
580
|
-
tokenType: 'Bearer',
|
|
581
|
-
expiresAt: expiredTime
|
|
582
|
-
});
|
|
583
|
-
|
|
584
|
-
const isExpired = store.getState().isTokenExpired();
|
|
585
|
-
expect(isExpired).toBe(true);
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
it('should correctly identify valid tokens', () => {
|
|
589
|
-
const config = validateAuthConfig({
|
|
590
|
-
axios: mockAxios,
|
|
591
|
-
tokenUrl: '/oauth/token'
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
const store = createAuthStore(config);
|
|
595
|
-
|
|
596
|
-
// Set future expiry
|
|
597
|
-
const futureTime = Date.now() + 3600000; // 1 hour from now
|
|
598
|
-
store.getState().setTokens({
|
|
599
|
-
accessToken: 'valid-token',
|
|
600
|
-
tokenType: 'Bearer',
|
|
601
|
-
expiresAt: futureTime
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
const isExpired = store.getState().isTokenExpired();
|
|
605
|
-
expect(isExpired).toBe(false);
|
|
606
|
-
});
|
|
607
|
-
});
|
|
608
|
-
});
|