@osimatic/helpers-js 1.4.24 → 1.4.26
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/.claude/settings.local.json +2 -1
- package/chartjs.js +1 -1
- package/date_time.js +25 -16
- package/draw.js +3 -2
- package/duration.js +176 -130
- package/event_bus.js +2 -2
- package/file.js +20 -5
- package/form_helper.js +1 -1
- package/google_charts.js +2 -1
- package/http_client.js +2 -0
- package/jwt.js +18 -6
- package/location.js +5 -1
- package/media.js +7 -7
- package/multi_files_input.js +3 -1
- package/number.js +2 -3
- package/package.json +4 -2
- package/paging.js +2 -2
- package/social_network.js +5 -0
- package/string.js +11 -2
- package/tests/__mocks__/socket.io-client.js +13 -0
- package/tests/chartjs.test.js +273 -0
- package/tests/count_down.test.js +580 -0
- package/tests/date_time/DatePeriod.test.js +179 -0
- package/tests/date_time/DateTime.test.js +492 -0
- package/tests/date_time/SqlDate.test.js +205 -0
- package/tests/date_time/SqlDateTime.test.js +326 -0
- package/tests/date_time/SqlTime.test.js +162 -0
- package/tests/date_time/TimestampUnix.test.js +262 -0
- package/tests/details_sub_array.test.js +367 -0
- package/tests/draw.test.js +271 -0
- package/tests/duration.test.js +365 -0
- package/tests/event_bus.test.js +268 -0
- package/tests/file.test.js +568 -0
- package/tests/flash_message.test.js +297 -0
- package/tests/form_date.test.js +1559 -0
- package/tests/form_helper.test.js +1065 -0
- package/tests/google_charts.test.js +768 -0
- package/tests/google_maps.test.js +655 -0
- package/tests/google_recaptcha.test.js +441 -0
- package/tests/http_client.test.js +570 -0
- package/tests/import_from_csv.test.js +797 -0
- package/tests/jwt.test.js +804 -0
- package/tests/list_box.test.js +255 -0
- package/tests/location.test.js +86 -0
- package/tests/media.test.js +473 -0
- package/tests/multi_files_input.test.js +1015 -0
- package/tests/multiple_action_in_table.test.js +477 -0
- package/tests/network.test.js +489 -0
- package/tests/number.test.js +448 -0
- package/tests/open_street_map.test.js +388 -0
- package/tests/paging.test.js +646 -0
- package/tests/select_all.test.js +360 -0
- package/tests/shopping_cart.test.js +355 -0
- package/tests/social_network.test.js +333 -0
- package/tests/sortable_list.test.js +602 -0
- package/tests/string.test.js +489 -0
- package/tests/user.test.js +204 -0
- package/tests/util.test.js +99 -0
- package/tests/visitor.test.js +508 -0
- package/tests/web_rtc.test.js +458 -0
- package/tests/web_socket.test.js +538 -0
- package/visitor.js +2 -2
- package/tmpclaude-0fa4-cwd +0 -1
- package/tmpclaude-104f-cwd +0 -1
- package/tmpclaude-1468-cwd +0 -1
- package/tmpclaude-324b-cwd +0 -1
- package/tmpclaude-35d3-cwd +0 -1
- package/tmpclaude-4aa8-cwd +0 -1
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
const { JwtToken, JwtSession, ApiTokenSession } = require('../jwt');
|
|
2
|
+
|
|
3
|
+
// Helper function to create a valid JWT token for testing
|
|
4
|
+
function createJwtToken(payload) {
|
|
5
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
6
|
+
const encodedHeader = btoa(JSON.stringify(header));
|
|
7
|
+
const encodedPayload = btoa(JSON.stringify(payload))
|
|
8
|
+
.replace(/\+/g, '-')
|
|
9
|
+
.replace(/\//g, '_')
|
|
10
|
+
.replace(/=+$/, '');
|
|
11
|
+
const signature = 'fake_signature';
|
|
12
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('JwtToken', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// Mock atob for Node.js environment
|
|
18
|
+
global.atob = (str) => Buffer.from(str, 'base64').toString('binary');
|
|
19
|
+
global.btoa = (str) => Buffer.from(str, 'binary').toString('base64');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('parseJwt', () => {
|
|
23
|
+
test('should parse valid JWT token', () => {
|
|
24
|
+
const payload = { sub: '1234567890', name: 'John Doe', iat: 1516239022 };
|
|
25
|
+
const token = createJwtToken(payload);
|
|
26
|
+
|
|
27
|
+
const result = JwtToken.parseJwt(token);
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual(payload);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should parse JWT with roles', () => {
|
|
33
|
+
const payload = { sub: '123', roles: ['ROLE_USER', 'ROLE_ADMIN'] };
|
|
34
|
+
const token = createJwtToken(payload);
|
|
35
|
+
|
|
36
|
+
const result = JwtToken.parseJwt(token);
|
|
37
|
+
|
|
38
|
+
expect(result.roles).toEqual(['ROLE_USER', 'ROLE_ADMIN']);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should handle JWT with special characters', () => {
|
|
42
|
+
const payload = { name: 'José García', email: 'jose@example.com' };
|
|
43
|
+
const token = createJwtToken(payload);
|
|
44
|
+
|
|
45
|
+
const result = JwtToken.parseJwt(token);
|
|
46
|
+
|
|
47
|
+
expect(result.name).toBe('José García');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('should parse JWT with nested objects', () => {
|
|
51
|
+
const payload = {
|
|
52
|
+
user: {
|
|
53
|
+
id: 1,
|
|
54
|
+
profile: { firstName: 'John', lastName: 'Doe' }
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const token = createJwtToken(payload);
|
|
58
|
+
|
|
59
|
+
const result = JwtToken.parseJwt(token);
|
|
60
|
+
|
|
61
|
+
expect(result.user.profile.firstName).toBe('John');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should parse JWT with array data', () => {
|
|
65
|
+
const payload = { permissions: ['read', 'write', 'delete'] };
|
|
66
|
+
const token = createJwtToken(payload);
|
|
67
|
+
|
|
68
|
+
const result = JwtToken.parseJwt(token);
|
|
69
|
+
|
|
70
|
+
expect(result.permissions).toEqual(['read', 'write', 'delete']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('should handle JWT with URL-safe base64', () => {
|
|
74
|
+
const payload = { data: 'test-data_with/special+chars' };
|
|
75
|
+
const token = createJwtToken(payload);
|
|
76
|
+
|
|
77
|
+
const result = JwtToken.parseJwt(token);
|
|
78
|
+
|
|
79
|
+
expect(result.data).toBe('test-data_with/special+chars');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('getData', () => {
|
|
84
|
+
test('should extract data from token by key', () => {
|
|
85
|
+
const payload = { userId: 123, username: 'john' };
|
|
86
|
+
const token = createJwtToken(payload);
|
|
87
|
+
|
|
88
|
+
const result = JwtToken.getData(token, 'userId');
|
|
89
|
+
|
|
90
|
+
expect(result).toBe(123);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should return null for missing key', () => {
|
|
94
|
+
const payload = { userId: 123 };
|
|
95
|
+
const token = createJwtToken(payload);
|
|
96
|
+
|
|
97
|
+
const result = JwtToken.getData(token, 'nonexistent');
|
|
98
|
+
|
|
99
|
+
expect(result).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('should return null for null token', () => {
|
|
103
|
+
const result = JwtToken.getData(null, 'userId');
|
|
104
|
+
|
|
105
|
+
expect(result).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should extract nested data', () => {
|
|
109
|
+
const payload = { user: { id: 123, name: 'John' } };
|
|
110
|
+
const token = createJwtToken(payload);
|
|
111
|
+
|
|
112
|
+
const result = JwtToken.getData(token, 'user');
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual({ id: 123, name: 'John' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should extract array data', () => {
|
|
118
|
+
const payload = { roles: ['ROLE_USER', 'ROLE_ADMIN'] };
|
|
119
|
+
const token = createJwtToken(payload);
|
|
120
|
+
|
|
121
|
+
const result = JwtToken.getData(token, 'roles');
|
|
122
|
+
|
|
123
|
+
expect(result).toEqual(['ROLE_USER', 'ROLE_ADMIN']);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should handle undefined token', () => {
|
|
127
|
+
const result = JwtToken.getData(undefined, 'key');
|
|
128
|
+
|
|
129
|
+
expect(result).toBeNull();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('hasRole', () => {
|
|
134
|
+
test('should return true when user has role', () => {
|
|
135
|
+
const payload = { roles: ['ROLE_USER', 'ROLE_ADMIN'] };
|
|
136
|
+
const token = createJwtToken(payload);
|
|
137
|
+
|
|
138
|
+
const result = JwtToken.hasRole(token, 'ROLE_ADMIN');
|
|
139
|
+
|
|
140
|
+
expect(result).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should return false when user does not have role', () => {
|
|
144
|
+
const payload = { roles: ['ROLE_USER'] };
|
|
145
|
+
const token = createJwtToken(payload);
|
|
146
|
+
|
|
147
|
+
const result = JwtToken.hasRole(token, 'ROLE_ADMIN');
|
|
148
|
+
|
|
149
|
+
expect(result).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('should return false when token has no roles', () => {
|
|
153
|
+
const payload = { userId: 123 };
|
|
154
|
+
const token = createJwtToken(payload);
|
|
155
|
+
|
|
156
|
+
const result = JwtToken.hasRole(token, 'ROLE_USER');
|
|
157
|
+
|
|
158
|
+
expect(result).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should return false for null token', () => {
|
|
162
|
+
const result = JwtToken.hasRole(null, 'ROLE_USER');
|
|
163
|
+
|
|
164
|
+
expect(result).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('should handle empty roles array', () => {
|
|
168
|
+
const payload = { roles: [] };
|
|
169
|
+
const token = createJwtToken(payload);
|
|
170
|
+
|
|
171
|
+
const result = JwtToken.hasRole(token, 'ROLE_USER');
|
|
172
|
+
|
|
173
|
+
expect(result).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('should be case sensitive', () => {
|
|
177
|
+
const payload = { roles: ['ROLE_USER'] };
|
|
178
|
+
const token = createJwtToken(payload);
|
|
179
|
+
|
|
180
|
+
const result = JwtToken.hasRole(token, 'role_user');
|
|
181
|
+
|
|
182
|
+
expect(result).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('JwtSession', () => {
|
|
188
|
+
let localStorageMock;
|
|
189
|
+
let consoleLogSpy;
|
|
190
|
+
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
// Mock localStorage
|
|
193
|
+
localStorageMock = (() => {
|
|
194
|
+
let store = {};
|
|
195
|
+
return {
|
|
196
|
+
getItem: jest.fn((key) => store[key] || null),
|
|
197
|
+
setItem: jest.fn((key, value) => {
|
|
198
|
+
store[key] = value.toString();
|
|
199
|
+
}),
|
|
200
|
+
removeItem: jest.fn((key) => {
|
|
201
|
+
delete store[key];
|
|
202
|
+
}),
|
|
203
|
+
clear: jest.fn(() => {
|
|
204
|
+
store = {};
|
|
205
|
+
})
|
|
206
|
+
};
|
|
207
|
+
})();
|
|
208
|
+
|
|
209
|
+
global.localStorage = localStorageMock;
|
|
210
|
+
global.atob = (str) => Buffer.from(str, 'base64').toString('binary');
|
|
211
|
+
global.btoa = (str) => Buffer.from(str, 'binary').toString('base64');
|
|
212
|
+
global.window = { location: { href: '' } };
|
|
213
|
+
|
|
214
|
+
// Mock console.log
|
|
215
|
+
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation();
|
|
216
|
+
|
|
217
|
+
// Reset callbacks
|
|
218
|
+
JwtSession.onLoginCallback = undefined;
|
|
219
|
+
JwtSession.onLogoutCallback = undefined;
|
|
220
|
+
JwtSession.onNewTokenCallback = undefined;
|
|
221
|
+
JwtSession.onSessionExpireCallback = undefined;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
afterEach(() => {
|
|
225
|
+
jest.clearAllMocks();
|
|
226
|
+
consoleLogSpy.mockRestore();
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('Callback setters', () => {
|
|
230
|
+
test('should set onLoginCallback', () => {
|
|
231
|
+
const callback = jest.fn();
|
|
232
|
+
JwtSession.setOnLoginCallback(callback);
|
|
233
|
+
|
|
234
|
+
expect(JwtSession.onLoginCallback).toBe(callback);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('should set onLogoutCallback', () => {
|
|
238
|
+
const callback = jest.fn();
|
|
239
|
+
JwtSession.setOnLogoutCallback(callback);
|
|
240
|
+
|
|
241
|
+
expect(JwtSession.onLogoutCallback).toBe(callback);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('should set onNewTokenCallback', () => {
|
|
245
|
+
const callback = jest.fn();
|
|
246
|
+
JwtSession.setOnNewTokenCallback(callback);
|
|
247
|
+
|
|
248
|
+
expect(JwtSession.onNewTokenCallback).toBe(callback);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('should set onSessionExpireCallback', () => {
|
|
252
|
+
const callback = jest.fn();
|
|
253
|
+
JwtSession.setOnSessionExpireCallback(callback);
|
|
254
|
+
|
|
255
|
+
expect(JwtSession.onSessionExpireCallback).toBe(callback);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('Token management', () => {
|
|
260
|
+
test('should get and set access token', () => {
|
|
261
|
+
JwtSession.setToken('test_token');
|
|
262
|
+
|
|
263
|
+
expect(JwtSession.getToken()).toBe('test_token');
|
|
264
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('access_token', 'test_token');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('should get and set refresh token', () => {
|
|
268
|
+
JwtSession.setRefreshToken('refresh_token');
|
|
269
|
+
|
|
270
|
+
expect(JwtSession.getRefreshToken()).toBe('refresh_token');
|
|
271
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('refresh_token', 'refresh_token');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('should return null when no token exists', () => {
|
|
275
|
+
expect(JwtSession.getToken()).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe('login', () => {
|
|
280
|
+
test('should store tokens and call callbacks', () => {
|
|
281
|
+
const loginCallback = jest.fn();
|
|
282
|
+
const onComplete = jest.fn();
|
|
283
|
+
JwtSession.setOnLoginCallback(loginCallback);
|
|
284
|
+
|
|
285
|
+
JwtSession.login({ access_token: 'token123', refresh_token: 'refresh123' }, null, onComplete);
|
|
286
|
+
|
|
287
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('access_token', 'token123');
|
|
288
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('refresh_token', 'refresh123');
|
|
289
|
+
expect(loginCallback).toHaveBeenCalled();
|
|
290
|
+
expect(onComplete).toHaveBeenCalled();
|
|
291
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('JwtSession.login()');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('should handle token key instead of access_token', () => {
|
|
295
|
+
JwtSession.login({ token: 'token123', refresh_token: 'refresh123' });
|
|
296
|
+
|
|
297
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('access_token', 'token123');
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test('should redirect when redirectUrl is provided', () => {
|
|
301
|
+
JwtSession.login({ access_token: 'token123' }, 'https://example.com/dashboard');
|
|
302
|
+
|
|
303
|
+
expect(window.location.href).toBe('https://example.com/dashboard');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('should not redirect when redirectUrl is null', () => {
|
|
307
|
+
window.location.href = 'https://example.com';
|
|
308
|
+
JwtSession.login({ access_token: 'token123' }, null);
|
|
309
|
+
|
|
310
|
+
expect(window.location.href).toBe('https://example.com');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test('should remove real_users from storage', () => {
|
|
314
|
+
JwtSession.login({ access_token: 'token123' });
|
|
315
|
+
|
|
316
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('real_users');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('should work without callbacks', () => {
|
|
320
|
+
expect(() => {
|
|
321
|
+
JwtSession.login({ access_token: 'token123' });
|
|
322
|
+
}).not.toThrow();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('updateToken', () => {
|
|
327
|
+
test('should update access token', () => {
|
|
328
|
+
const onComplete = jest.fn();
|
|
329
|
+
|
|
330
|
+
JwtSession.updateToken('new_token', 'new_refresh', onComplete);
|
|
331
|
+
|
|
332
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('access_token', 'new_token');
|
|
333
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('refresh_token', 'new_refresh');
|
|
334
|
+
expect(onComplete).toHaveBeenCalledWith('new_token', 'new_refresh');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test('should call onNewTokenCallback', () => {
|
|
338
|
+
const callback = jest.fn();
|
|
339
|
+
JwtSession.setOnNewTokenCallback(callback);
|
|
340
|
+
|
|
341
|
+
JwtSession.updateToken('new_token');
|
|
342
|
+
|
|
343
|
+
expect(callback).toHaveBeenCalled();
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('should not update refresh token if undefined', () => {
|
|
347
|
+
JwtSession.updateToken('new_token', undefined);
|
|
348
|
+
|
|
349
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('access_token', 'new_token');
|
|
350
|
+
expect(localStorageMock.setItem).not.toHaveBeenCalledWith('refresh_token', undefined);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('should work without callbacks', () => {
|
|
354
|
+
expect(() => {
|
|
355
|
+
JwtSession.updateToken('new_token');
|
|
356
|
+
}).not.toThrow();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('logout', () => {
|
|
361
|
+
test('should remove tokens and call callbacks', () => {
|
|
362
|
+
const logoutCallback = jest.fn();
|
|
363
|
+
const onComplete = jest.fn();
|
|
364
|
+
JwtSession.setOnLogoutCallback(logoutCallback);
|
|
365
|
+
|
|
366
|
+
JwtSession.logout(null, onComplete);
|
|
367
|
+
|
|
368
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('access_token');
|
|
369
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('refresh_token');
|
|
370
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('real_users');
|
|
371
|
+
expect(logoutCallback).toHaveBeenCalled();
|
|
372
|
+
expect(onComplete).toHaveBeenCalled();
|
|
373
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('JwtSession.logout()');
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('should redirect when redirectUrl is provided', () => {
|
|
377
|
+
JwtSession.logout('https://example.com/login');
|
|
378
|
+
|
|
379
|
+
expect(window.location.href).toBe('https://example.com/login');
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('should work without callbacks', () => {
|
|
383
|
+
expect(() => {
|
|
384
|
+
JwtSession.logout();
|
|
385
|
+
}).not.toThrow();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('expireSession', () => {
|
|
390
|
+
test('should remove tokens and call expire callback', () => {
|
|
391
|
+
const expireCallback = jest.fn();
|
|
392
|
+
const onComplete = jest.fn();
|
|
393
|
+
JwtSession.setOnSessionExpireCallback(expireCallback);
|
|
394
|
+
|
|
395
|
+
JwtSession.expireSession(null, onComplete);
|
|
396
|
+
|
|
397
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('access_token');
|
|
398
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('refresh_token');
|
|
399
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('real_users');
|
|
400
|
+
expect(expireCallback).toHaveBeenCalled();
|
|
401
|
+
expect(onComplete).toHaveBeenCalled();
|
|
402
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('JwtSession.expireSession()');
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('should redirect when redirectUrl is provided', () => {
|
|
406
|
+
JwtSession.expireSession('https://example.com/expired');
|
|
407
|
+
|
|
408
|
+
expect(window.location.href).toBe('https://example.com/expired');
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe('getData', () => {
|
|
413
|
+
test('should get data from current token', () => {
|
|
414
|
+
const payload = { userId: 123, username: 'john' };
|
|
415
|
+
const token = createJwtToken(payload);
|
|
416
|
+
JwtSession.setToken(token);
|
|
417
|
+
|
|
418
|
+
const result = JwtSession.getData('userId');
|
|
419
|
+
|
|
420
|
+
expect(result).toBe(123);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('should return null when no token', () => {
|
|
424
|
+
const result = JwtSession.getData('userId');
|
|
425
|
+
|
|
426
|
+
expect(result).toBeNull();
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
describe('isAnonymous', () => {
|
|
431
|
+
test('should return true when no token', () => {
|
|
432
|
+
expect(JwtSession.isAnonymous()).toBe(true);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('should return false when token exists', () => {
|
|
436
|
+
JwtSession.setToken('token123');
|
|
437
|
+
|
|
438
|
+
expect(JwtSession.isAnonymous()).toBe(false);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe('isGranted', () => {
|
|
443
|
+
test('should return true when user has role', () => {
|
|
444
|
+
const payload = { roles: ['ROLE_USER', 'ROLE_ADMIN'] };
|
|
445
|
+
const token = createJwtToken(payload);
|
|
446
|
+
JwtSession.setToken(token);
|
|
447
|
+
|
|
448
|
+
expect(JwtSession.isGranted('ROLE_ADMIN')).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('should return false when user does not have role', () => {
|
|
452
|
+
const payload = { roles: ['ROLE_USER'] };
|
|
453
|
+
const token = createJwtToken(payload);
|
|
454
|
+
JwtSession.setToken(token);
|
|
455
|
+
|
|
456
|
+
expect(JwtSession.isGranted('ROLE_ADMIN')).toBe(false);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('denyAccessUnlessGranted', () => {
|
|
461
|
+
test('should return true when user has at least one role', () => {
|
|
462
|
+
const payload = { roles: ['ROLE_USER'] };
|
|
463
|
+
const token = createJwtToken(payload);
|
|
464
|
+
JwtSession.setToken(token);
|
|
465
|
+
|
|
466
|
+
const result = JwtSession.denyAccessUnlessGranted(['ROLE_ADMIN', 'ROLE_USER']);
|
|
467
|
+
|
|
468
|
+
expect(result).toBe(true);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test('should return false when user has none of the roles', () => {
|
|
472
|
+
const payload = { roles: ['ROLE_USER'] };
|
|
473
|
+
const token = createJwtToken(payload);
|
|
474
|
+
JwtSession.setToken(token);
|
|
475
|
+
|
|
476
|
+
const result = JwtSession.denyAccessUnlessGranted(['ROLE_ADMIN', 'ROLE_SUPER_ADMIN']);
|
|
477
|
+
|
|
478
|
+
expect(result).toBe(false);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test('should return false when no token', () => {
|
|
482
|
+
const result = JwtSession.denyAccessUnlessGranted(['ROLE_USER']);
|
|
483
|
+
|
|
484
|
+
expect(result).toBe(false);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
describe('Simulate login', () => {
|
|
489
|
+
test('should save real user and switch to simulated user', () => {
|
|
490
|
+
const realToken = createJwtToken({ userId: 1 });
|
|
491
|
+
const simulatedToken = createJwtToken({ userId: 2 });
|
|
492
|
+
|
|
493
|
+
JwtSession.setToken(realToken);
|
|
494
|
+
JwtSession.setRefreshToken('refresh1');
|
|
495
|
+
|
|
496
|
+
JwtSession.simulateLogin({ access_token: simulatedToken, refresh_token: 'refresh2' });
|
|
497
|
+
|
|
498
|
+
expect(JwtSession.getToken()).toBe(simulatedToken);
|
|
499
|
+
const realUsers = JwtSession.getRealLoggedUsers();
|
|
500
|
+
expect(realUsers).toHaveLength(1);
|
|
501
|
+
expect(realUsers[0].access_token).toBe(realToken);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test('should handle multiple simulated logins', () => {
|
|
505
|
+
JwtSession.setToken('token1');
|
|
506
|
+
JwtSession.simulateLogin({ access_token: 'token2' });
|
|
507
|
+
JwtSession.simulateLogin({ access_token: 'token3' });
|
|
508
|
+
|
|
509
|
+
const realUsers = JwtSession.getRealLoggedUsers();
|
|
510
|
+
expect(realUsers).toHaveLength(2);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test('should handle token key instead of access_token', () => {
|
|
514
|
+
JwtSession.simulateLogin({ token: 'simulated_token' });
|
|
515
|
+
|
|
516
|
+
expect(JwtSession.getToken()).toBe('simulated_token');
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
test('should call onComplete callback', () => {
|
|
520
|
+
const onComplete = jest.fn();
|
|
521
|
+
JwtSession.simulateLogin({ access_token: 'token' }, null, onComplete);
|
|
522
|
+
|
|
523
|
+
expect(onComplete).toHaveBeenCalled();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test('should redirect when redirectUrl is provided', () => {
|
|
527
|
+
JwtSession.simulateLogin({ access_token: 'token' }, 'https://example.com/simulated');
|
|
528
|
+
|
|
529
|
+
expect(window.location.href).toBe('https://example.com/simulated');
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe('cancelSimulatedLogin', () => {
|
|
534
|
+
test('should restore real user token', () => {
|
|
535
|
+
const realToken = 'real_token';
|
|
536
|
+
JwtSession.setToken(realToken);
|
|
537
|
+
JwtSession.simulateLogin({ access_token: 'simulated_token' });
|
|
538
|
+
|
|
539
|
+
JwtSession.cancelSimulatedLogin();
|
|
540
|
+
|
|
541
|
+
expect(JwtSession.getToken()).toBe(realToken);
|
|
542
|
+
expect(JwtSession.getRealLoggedUsers()).toHaveLength(0);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('should handle multiple cancel operations', () => {
|
|
546
|
+
JwtSession.setToken('token1');
|
|
547
|
+
JwtSession.simulateLogin({ access_token: 'token2' });
|
|
548
|
+
JwtSession.simulateLogin({ access_token: 'token3' });
|
|
549
|
+
|
|
550
|
+
JwtSession.cancelSimulatedLogin();
|
|
551
|
+
expect(JwtSession.getToken()).toBe('token2');
|
|
552
|
+
|
|
553
|
+
JwtSession.cancelSimulatedLogin();
|
|
554
|
+
expect(JwtSession.getToken()).toBe('token1');
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('should do nothing when no simulated login exists', () => {
|
|
558
|
+
JwtSession.setToken('token1');
|
|
559
|
+
|
|
560
|
+
expect(() => {
|
|
561
|
+
JwtSession.cancelSimulatedLogin();
|
|
562
|
+
}).not.toThrow();
|
|
563
|
+
|
|
564
|
+
expect(JwtSession.getToken()).toBe('token1');
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('should handle token key instead of access_token', () => {
|
|
568
|
+
JwtSession.setToken('real_token');
|
|
569
|
+
JwtSession.simulateLogin({ token: 'simulated' });
|
|
570
|
+
JwtSession.cancelSimulatedLogin();
|
|
571
|
+
|
|
572
|
+
expect(JwtSession.getToken()).toBe('real_token');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
test('should call onComplete callback', () => {
|
|
576
|
+
const onComplete = jest.fn();
|
|
577
|
+
JwtSession.setToken('real');
|
|
578
|
+
JwtSession.simulateLogin({ access_token: 'simulated' });
|
|
579
|
+
JwtSession.cancelSimulatedLogin(null, onComplete);
|
|
580
|
+
|
|
581
|
+
expect(onComplete).toHaveBeenCalled();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
test('should redirect when redirectUrl is provided', () => {
|
|
585
|
+
JwtSession.setToken('real');
|
|
586
|
+
JwtSession.simulateLogin({ access_token: 'simulated' });
|
|
587
|
+
JwtSession.cancelSimulatedLogin('https://example.com/back');
|
|
588
|
+
|
|
589
|
+
expect(window.location.href).toBe('https://example.com/back');
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe('getRealLoggedUsers', () => {
|
|
594
|
+
test('should return empty array when no real users', () => {
|
|
595
|
+
expect(JwtSession.getRealLoggedUsers()).toEqual([]);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test('should return array of real users', () => {
|
|
599
|
+
const users = [{ access_token: 'token1' }, { access_token: 'token2' }];
|
|
600
|
+
localStorageMock.setItem('real_users', JSON.stringify(users));
|
|
601
|
+
|
|
602
|
+
expect(JwtSession.getRealLoggedUsers()).toEqual(users);
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
describe('ApiTokenSession', () => {
|
|
608
|
+
let localStorageMock;
|
|
609
|
+
|
|
610
|
+
beforeEach(() => {
|
|
611
|
+
localStorageMock = (() => {
|
|
612
|
+
let store = {};
|
|
613
|
+
return {
|
|
614
|
+
getItem: jest.fn((key) => store[key] || null),
|
|
615
|
+
setItem: jest.fn((key, value) => {
|
|
616
|
+
store[key] = value.toString();
|
|
617
|
+
}),
|
|
618
|
+
removeItem: jest.fn((key) => {
|
|
619
|
+
delete store[key];
|
|
620
|
+
})
|
|
621
|
+
};
|
|
622
|
+
})();
|
|
623
|
+
|
|
624
|
+
global.localStorage = localStorageMock;
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
afterEach(() => {
|
|
628
|
+
jest.clearAllMocks();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe('Token management', () => {
|
|
632
|
+
test('should get and set token', () => {
|
|
633
|
+
ApiTokenSession.setToken('api_token_123');
|
|
634
|
+
|
|
635
|
+
expect(ApiTokenSession.getToken()).toBe('api_token_123');
|
|
636
|
+
expect(localStorageMock.setItem).toHaveBeenCalledWith('api_token', 'api_token_123');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test('should return null when no token', () => {
|
|
640
|
+
expect(ApiTokenSession.getToken()).toBeNull();
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
describe('Token data management', () => {
|
|
645
|
+
test('should get and set token data', () => {
|
|
646
|
+
const data = { userId: 123, username: 'john' };
|
|
647
|
+
ApiTokenSession.setTokenData(data);
|
|
648
|
+
|
|
649
|
+
expect(ApiTokenSession.getTokenData()).toEqual(data);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test('should return null when no token data', () => {
|
|
653
|
+
expect(ApiTokenSession.getTokenData()).toBeNull();
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test('should parse JSON correctly', () => {
|
|
657
|
+
const data = { roles: ['ROLE_USER'], permissions: ['read', 'write'] };
|
|
658
|
+
ApiTokenSession.setTokenData(data);
|
|
659
|
+
|
|
660
|
+
const result = ApiTokenSession.getTokenData();
|
|
661
|
+
expect(result.roles).toEqual(['ROLE_USER']);
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
describe('logout', () => {
|
|
666
|
+
test('should remove token and token data', () => {
|
|
667
|
+
ApiTokenSession.setToken('token123');
|
|
668
|
+
ApiTokenSession.setTokenData({ userId: 123 });
|
|
669
|
+
|
|
670
|
+
ApiTokenSession.logout();
|
|
671
|
+
|
|
672
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('api_token');
|
|
673
|
+
expect(localStorageMock.removeItem).toHaveBeenCalledWith('token_data');
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe('getData', () => {
|
|
678
|
+
test('should get data by key', () => {
|
|
679
|
+
ApiTokenSession.setTokenData({ userId: 123, username: 'john' });
|
|
680
|
+
|
|
681
|
+
expect(ApiTokenSession.getData('userId')).toBe(123);
|
|
682
|
+
expect(ApiTokenSession.getData('username')).toBe('john');
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test('should return null for missing key', () => {
|
|
686
|
+
ApiTokenSession.setTokenData({ userId: 123 });
|
|
687
|
+
|
|
688
|
+
expect(ApiTokenSession.getData('nonexistent')).toBeNull();
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test('should return null when no token data', () => {
|
|
692
|
+
expect(ApiTokenSession.getData('userId')).toBeNull();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test('should handle nested objects', () => {
|
|
696
|
+
ApiTokenSession.setTokenData({ user: { id: 123, name: 'John' } });
|
|
697
|
+
|
|
698
|
+
expect(ApiTokenSession.getData('user')).toEqual({ id: 123, name: 'John' });
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
describe('isAnonymous', () => {
|
|
703
|
+
test('should return true when no token', () => {
|
|
704
|
+
expect(ApiTokenSession.isAnonymous()).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test('should return false when token exists', () => {
|
|
708
|
+
ApiTokenSession.setToken('token123');
|
|
709
|
+
|
|
710
|
+
expect(ApiTokenSession.isAnonymous()).toBe(false);
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
describe('isGranted', () => {
|
|
715
|
+
test('should return true when user has role in roles array', () => {
|
|
716
|
+
ApiTokenSession.setToken('token123');
|
|
717
|
+
ApiTokenSession.setTokenData({ roles: ['ROLE_USER', 'ROLE_ADMIN'] });
|
|
718
|
+
|
|
719
|
+
expect(ApiTokenSession.isGranted('ROLE_ADMIN')).toBe(true);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test('should return true when user has role in role string', () => {
|
|
723
|
+
ApiTokenSession.setToken('token123');
|
|
724
|
+
ApiTokenSession.setTokenData({ role: 'ROLE_USER' });
|
|
725
|
+
|
|
726
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(true);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test('should handle single role as string', () => {
|
|
730
|
+
ApiTokenSession.setToken('token123');
|
|
731
|
+
ApiTokenSession.setTokenData({ roles: 'ROLE_USER' });
|
|
732
|
+
|
|
733
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(true);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
test('should return false when user does not have role', () => {
|
|
737
|
+
ApiTokenSession.setToken('token123');
|
|
738
|
+
ApiTokenSession.setTokenData({ roles: ['ROLE_USER'] });
|
|
739
|
+
|
|
740
|
+
expect(ApiTokenSession.isGranted('ROLE_ADMIN')).toBe(false);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
test('should return false when no token', () => {
|
|
744
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(false);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test('should prefer roles over role when both exist', () => {
|
|
748
|
+
ApiTokenSession.setToken('token123');
|
|
749
|
+
ApiTokenSession.setTokenData({ role: 'ROLE_USER', roles: ['ROLE_ADMIN'] });
|
|
750
|
+
|
|
751
|
+
expect(ApiTokenSession.isGranted('ROLE_ADMIN')).toBe(true);
|
|
752
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(false);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
test('should handle empty roles array', () => {
|
|
756
|
+
ApiTokenSession.setToken('token123');
|
|
757
|
+
ApiTokenSession.setTokenData({ roles: [] });
|
|
758
|
+
|
|
759
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(false);
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
test('should handle null role and roles', () => {
|
|
763
|
+
ApiTokenSession.setToken('token123');
|
|
764
|
+
ApiTokenSession.setTokenData({ userId: 123 });
|
|
765
|
+
|
|
766
|
+
expect(ApiTokenSession.isGranted('ROLE_USER')).toBe(false);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
describe('denyAccessUnlessGranted', () => {
|
|
771
|
+
test('should return true when user has at least one role', () => {
|
|
772
|
+
ApiTokenSession.setToken('token123');
|
|
773
|
+
ApiTokenSession.setTokenData({ roles: ['ROLE_USER'] });
|
|
774
|
+
|
|
775
|
+
const result = ApiTokenSession.denyAccessUnlessGranted(['ROLE_ADMIN', 'ROLE_USER']);
|
|
776
|
+
|
|
777
|
+
expect(result).toBe(true);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
test('should return false when user has none of the roles', () => {
|
|
781
|
+
ApiTokenSession.setToken('token123');
|
|
782
|
+
ApiTokenSession.setTokenData({ roles: ['ROLE_USER'] });
|
|
783
|
+
|
|
784
|
+
const result = ApiTokenSession.denyAccessUnlessGranted(['ROLE_ADMIN', 'ROLE_SUPER_ADMIN']);
|
|
785
|
+
|
|
786
|
+
expect(result).toBe(false);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test('should return false when no token', () => {
|
|
790
|
+
const result = ApiTokenSession.denyAccessUnlessGranted(['ROLE_USER']);
|
|
791
|
+
|
|
792
|
+
expect(result).toBe(false);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test('should return true when user has multiple required roles', () => {
|
|
796
|
+
ApiTokenSession.setToken('token123');
|
|
797
|
+
ApiTokenSession.setTokenData({ roles: ['ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] });
|
|
798
|
+
|
|
799
|
+
const result = ApiTokenSession.denyAccessUnlessGranted(['ROLE_ADMIN', 'ROLE_SUPER_ADMIN']);
|
|
800
|
+
|
|
801
|
+
expect(result).toBe(true);
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
});
|