ryauth 1.0.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.
@@ -0,0 +1,355 @@
1
+ import { BaseAdapter } from '../src/adapters/base.js';
2
+ import { MemoryAdapter } from '../src/adapters/memory.js';
3
+
4
+ describe('Data Adapters - BaseAdapter Contract', () => {
5
+ let baseAdapter;
6
+
7
+ beforeEach(() => {
8
+ baseAdapter = new BaseAdapter();
9
+ });
10
+
11
+ describe('BaseAdapter - Interface Methods', () => {
12
+ it('should throw "Not Implemented" error for findUserByEmail', async () => {
13
+ await expect(baseAdapter.findUserByEmail('test@example.com'))
14
+ .rejects
15
+ .toThrow('Method findUserByEmail() must be implemented');
16
+ });
17
+
18
+ it('should throw "Not Implemented" error for createUser', async () => {
19
+ await expect(baseAdapter.createUser({ email: 'test@example.com', hashedPassword: 'hash' }))
20
+ .rejects
21
+ .toThrow('Method createUser() must be implemented');
22
+ });
23
+
24
+ it('should throw "Not Implemented" error for saveRefreshToken', async () => {
25
+ await expect(baseAdapter.saveRefreshToken('user123', 'token123', new Date()))
26
+ .rejects
27
+ .toThrow('Method saveRefreshToken() must be implemented');
28
+ });
29
+
30
+ it('should throw "Not Implemented" error for isRefreshTokenValid', async () => {
31
+ await expect(baseAdapter.isRefreshTokenValid('token123'))
32
+ .rejects
33
+ .toThrow('Method isRefreshTokenValid() must be implemented');
34
+ });
35
+
36
+ it('should throw "Not Implemented" error for revokeRefreshToken', async () => {
37
+ await expect(baseAdapter.revokeRefreshToken('token123'))
38
+ .rejects
39
+ .toThrow('Method revokeRefreshToken() must be implemented');
40
+ });
41
+
42
+ it('should throw "Not Implemented" error for revokeAllUserSessions', async () => {
43
+ await expect(baseAdapter.revokeAllUserSessions('user123'))
44
+ .rejects
45
+ .toThrow('Method revokeAllUserSessions() must be implemented');
46
+ });
47
+ });
48
+ });
49
+
50
+ describe('Data Adapters - MemoryAdapter Implementation', () => {
51
+ let memoryAdapter;
52
+
53
+ beforeEach(async () => {
54
+ memoryAdapter = new MemoryAdapter();
55
+ await memoryAdapter.clear();
56
+ });
57
+
58
+ describe('findUserByEmail', () => {
59
+ it('should correctly retrieve a user by email', async () => {
60
+ // Create a user first
61
+ const userData = {
62
+ email: 'test@example.com',
63
+ hashedPassword: 'hashed_password_123',
64
+ role: 'user'
65
+ };
66
+ const createdUser = await memoryAdapter.createUser(userData);
67
+
68
+ // Find the user
69
+ const foundUser = await memoryAdapter.findUserByEmail('test@example.com');
70
+
71
+ expect(foundUser).not.toBeNull();
72
+ expect(foundUser.id).toBe(createdUser.id);
73
+ expect(foundUser.email).toBe('test@example.com');
74
+ expect(foundUser.hashedPassword).toBe('hashed_password_123');
75
+ });
76
+
77
+ it('should return null for non-existent user', async () => {
78
+ const user = await memoryAdapter.findUserByEmail('nonexistent@example.com');
79
+ expect(user).toBeNull();
80
+ });
81
+
82
+ it('should throw error if email is not a string', async () => {
83
+ await expect(memoryAdapter.findUserByEmail(123))
84
+ .rejects
85
+ .toThrow('Email must be a string');
86
+
87
+ await expect(memoryAdapter.findUserByEmail(null))
88
+ .rejects
89
+ .toThrow('Email must be a string');
90
+
91
+ await expect(memoryAdapter.findUserByEmail({}))
92
+ .rejects
93
+ .toThrow('Email must be a string');
94
+ });
95
+ });
96
+
97
+ describe('createUser', () => {
98
+ it('should create a user with valid data', async () => {
99
+ const userData = {
100
+ email: 'newuser@example.com',
101
+ hashedPassword: 'hashed_password_456',
102
+ role: 'admin'
103
+ };
104
+
105
+ const createdUser = await memoryAdapter.createUser(userData);
106
+
107
+ expect(createdUser).toHaveProperty('id');
108
+ expect(createdUser.email).toBe('newuser@example.com');
109
+ expect(createdUser.hashedPassword).toBe('hashed_password_456');
110
+ expect(createdUser.role).toBe('admin');
111
+ expect(createdUser).toHaveProperty('createdAt');
112
+ expect(createdUser.createdAt instanceof Date).toBeTruthy();
113
+ });
114
+
115
+ it('should generate unique IDs for different users', async () => {
116
+ const user1 = await memoryAdapter.createUser({
117
+ email: 'user1@example.com',
118
+ hashedPassword: 'hash1'
119
+ });
120
+
121
+ const user2 = await memoryAdapter.createUser({
122
+ email: 'user2@example.com',
123
+ hashedPassword: 'hash2'
124
+ });
125
+
126
+ expect(user1.id).not.toBe(user2.id);
127
+ });
128
+
129
+ it('should throw error for duplicate email', async () => {
130
+ await memoryAdapter.createUser({
131
+ email: 'duplicate@example.com',
132
+ hashedPassword: 'hash1'
133
+ });
134
+
135
+ await expect(memoryAdapter.createUser({
136
+ email: 'duplicate@example.com',
137
+ hashedPassword: 'hash2'
138
+ }))
139
+ .rejects
140
+ .toThrow('User with this email already exists');
141
+ });
142
+
143
+ it('should set default role if not provided', async () => {
144
+ const user = await memoryAdapter.createUser({
145
+ email: 'norole@example.com',
146
+ hashedPassword: 'hash1'
147
+ });
148
+
149
+ expect(user.role).toBe('user');
150
+ });
151
+
152
+ it('should validate email format', async () => {
153
+ await expect(memoryAdapter.createUser({
154
+ email: 'invalid-email',
155
+ hashedPassword: 'hash1'
156
+ }))
157
+ .rejects
158
+ .toThrow();
159
+ });
160
+
161
+ it('should require hashedPassword to be a string', async () => {
162
+ await expect(memoryAdapter.createUser({
163
+ email: 'test@example.com',
164
+ hashedPassword: 123
165
+ }))
166
+ .rejects
167
+ .toThrow();
168
+ });
169
+ });
170
+
171
+ describe('saveRefreshToken', () => {
172
+ it('should persist token with correct expiry', async () => {
173
+ const expiresAt = new Date();
174
+ expiresAt.setDate(expiresAt.getDate() + 7); // 7 days from now
175
+
176
+ await memoryAdapter.saveRefreshToken('user123', 'refresh_token_abc', expiresAt);
177
+
178
+ const isValid = await memoryAdapter.isRefreshTokenValid('refresh_token_abc');
179
+ expect(isValid).toBe(true);
180
+ });
181
+
182
+ it('should throw error if userId is not a string', async () => {
183
+ await expect(memoryAdapter.saveRefreshToken(123, 'token', new Date()))
184
+ .rejects
185
+ .toThrow('Invalid parameters');
186
+ });
187
+
188
+ it('should throw error if token is not a string', async () => {
189
+ await expect(memoryAdapter.saveRefreshToken('user123', 123, new Date()))
190
+ .rejects
191
+ .toThrow('Invalid parameters');
192
+ });
193
+
194
+ it('should throw error if expiresAt is not a Date', async () => {
195
+ await expect(memoryAdapter.saveRefreshToken('user123', 'token', 'not-a-date'))
196
+ .rejects
197
+ .toThrow('Invalid parameters');
198
+ });
199
+
200
+ it('should throw error if token is too short', async () => {
201
+ await expect(memoryAdapter.saveRefreshToken('user123', 'short', new Date()))
202
+ .rejects
203
+ .toThrow('Token too short');
204
+ });
205
+
206
+ it('should allow token to be reused after revocation', async () => {
207
+ // First save
208
+ await memoryAdapter.saveRefreshToken('user123', 'longtoken123', new Date(Date.now() + 86400000));
209
+
210
+ // Revoke it
211
+ await memoryAdapter.revokeRefreshToken('longtoken123');
212
+
213
+ // Verify it's revoked
214
+ expect(await memoryAdapter.isRefreshTokenValid('longtoken123')).toBe(false);
215
+
216
+ // Save again (should work)
217
+ await memoryAdapter.saveRefreshToken('user123', 'longtoken123', new Date(Date.now() + 86400000));
218
+
219
+ // Verify it's valid again
220
+ expect(await memoryAdapter.isRefreshTokenValid('longtoken123')).toBe(true);
221
+ });
222
+ });
223
+
224
+ describe('isRefreshTokenValid', () => {
225
+ it('should return true for valid token', async () => {
226
+ const expiresAt = new Date();
227
+ expiresAt.setDate(expiresAt.getDate() + 7);
228
+
229
+ await memoryAdapter.saveRefreshToken('user123', 'valid_token', expiresAt);
230
+
231
+ const isValid = await memoryAdapter.isRefreshTokenValid('valid_token');
232
+ expect(isValid).toBe(true);
233
+ });
234
+
235
+ it('should return false for non-existent token', async () => {
236
+ const isValid = await memoryAdapter.isRefreshTokenValid('nonexistent_token');
237
+ expect(isValid).toBe(false);
238
+ });
239
+
240
+ it('should return false for revoked token', async () => {
241
+ const expiresAt = new Date();
242
+ expiresAt.setDate(expiresAt.getDate() + 7);
243
+
244
+ await memoryAdapter.saveRefreshToken('user123', 'revoked_token', expiresAt);
245
+ await memoryAdapter.revokeRefreshToken('revoked_token');
246
+
247
+ const isValid = await memoryAdapter.isRefreshTokenValid('revoked_token');
248
+ expect(isValid).toBe(false);
249
+ });
250
+
251
+ it('should return false for expired token', async () => {
252
+ const expiresAt = new Date();
253
+ expiresAt.setDate(expiresAt.getDate() - 1); // Expired yesterday
254
+
255
+ await memoryAdapter.saveRefreshToken('user123', 'expired_token', expiresAt);
256
+
257
+ const isValid = await memoryAdapter.isRefreshTokenValid('expired_token');
258
+ expect(isValid).toBe(false);
259
+ });
260
+
261
+ it('should throw error if token is not a string', async () => {
262
+ await expect(memoryAdapter.isRefreshTokenValid(123))
263
+ .rejects
264
+ .toThrow('Token must be a string');
265
+
266
+ await expect(memoryAdapter.isRefreshTokenValid(null))
267
+ .rejects
268
+ .toThrow('Token must be a string');
269
+ });
270
+ });
271
+
272
+ describe('revokeRefreshToken', () => {
273
+ it('should revoke a single token', async () => {
274
+ const expiresAt = new Date();
275
+ expiresAt.setDate(expiresAt.getDate() + 7);
276
+
277
+ await memoryAdapter.saveRefreshToken('user123', 'token_to_revoke', expiresAt);
278
+
279
+ // Verify it's valid before revocation
280
+ expect(await memoryAdapter.isRefreshTokenValid('token_to_revoke')).toBe(true);
281
+
282
+ // Revoke it
283
+ await memoryAdapter.revokeRefreshToken('token_to_revoke');
284
+
285
+ // Verify it's revoked
286
+ expect(await memoryAdapter.isRefreshTokenValid('token_to_revoke')).toBe(false);
287
+ });
288
+
289
+ it('should throw error if token is not a string', async () => {
290
+ await expect(memoryAdapter.revokeRefreshToken(123))
291
+ .rejects
292
+ .toThrow('Token must be a string');
293
+
294
+ await expect(memoryAdapter.revokeRefreshToken(null))
295
+ .rejects
296
+ .toThrow('Token must be a string');
297
+ });
298
+
299
+ it('should not throw error when revoking non-existent token', async () => {
300
+ // Should not throw even if token doesn't exist
301
+ await expect(memoryAdapter.revokeRefreshToken('nonexistent_token'))
302
+ .resolves
303
+ .not.toThrow();
304
+ });
305
+ });
306
+
307
+ describe('revokeAllUserSessions', () => {
308
+ it('should invalidate every token for a specific userId', async () => {
309
+ const expiresAt = new Date();
310
+ expiresAt.setDate(expiresAt.getDate() + 7);
311
+
312
+ // Create tokens for user123
313
+ await memoryAdapter.saveRefreshToken('user123', 'token1_user123', expiresAt);
314
+ await memoryAdapter.saveRefreshToken('user123', 'token2_user123', expiresAt);
315
+ await memoryAdapter.saveRefreshToken('user123', 'token3_user123', expiresAt);
316
+
317
+ // Create tokens for user456 (should not be affected)
318
+ await memoryAdapter.saveRefreshToken('user456', 'token1_user456', expiresAt);
319
+
320
+ // Verify all tokens are valid before revocation
321
+ expect(await memoryAdapter.isRefreshTokenValid('token1_user123')).toBe(true);
322
+ expect(await memoryAdapter.isRefreshTokenValid('token2_user123')).toBe(true);
323
+ expect(await memoryAdapter.isRefreshTokenValid('token3_user123')).toBe(true);
324
+ expect(await memoryAdapter.isRefreshTokenValid('token1_user456')).toBe(true);
325
+
326
+ // Revoke all sessions for user123
327
+ await memoryAdapter.revokeAllUserSessions('user123');
328
+
329
+ // Verify user123's tokens are revoked
330
+ expect(await memoryAdapter.isRefreshTokenValid('token1_user123')).toBe(false);
331
+ expect(await memoryAdapter.isRefreshTokenValid('token2_user123')).toBe(false);
332
+ expect(await memoryAdapter.isRefreshTokenValid('token3_user123')).toBe(false);
333
+
334
+ // Verify user456's token is still valid
335
+ expect(await memoryAdapter.isRefreshTokenValid('token1_user456')).toBe(true);
336
+ });
337
+
338
+ it('should throw error if userId is not a string', async () => {
339
+ await expect(memoryAdapter.revokeAllUserSessions(123))
340
+ .rejects
341
+ .toThrow('User ID must be a string');
342
+
343
+ await expect(memoryAdapter.revokeAllUserSessions(null))
344
+ .rejects
345
+ .toThrow('User ID must be a string');
346
+ });
347
+
348
+ it('should not throw error when revoking sessions for non-existent user', async () => {
349
+ // Should not throw even if user doesn't exist
350
+ await expect(memoryAdapter.revokeAllUserSessions('nonexistent_user'))
351
+ .resolves
352
+ .not.toThrow();
353
+ });
354
+ });
355
+ });
@@ -0,0 +1,287 @@
1
+ import { describe, it, expect, beforeEach, jest } from '@jest/globals';
2
+ import { AuthService } from '../src/services/auth-service.js';
3
+ import { MemoryAdapter } from '../src/adapters/memory.js';
4
+ import { hashPassword } from '../src/core/crypto.js';
5
+
6
+ // Mock environment variables
7
+ const originalEnv = process.env;
8
+
9
+ beforeEach(async () => {
10
+ jest.resetModules();
11
+ process.env = { ...originalEnv };
12
+ process.env.ACCESS_TOKEN_SECRET = 'test_access_secret_32_characters_long';
13
+ process.env.REFRESH_TOKEN_SECRET = 'test_refresh_secret_32_characters_long';
14
+
15
+ // Clear adapter state
16
+ const adapter = new MemoryAdapter();
17
+ adapter.clear();
18
+
19
+ // Create test user
20
+ const testUser = {
21
+ id: 'test-user-id',
22
+ email: 'test@example.com',
23
+ hashedPassword: await hashPassword('password123'),
24
+ role: 'user'
25
+ };
26
+ await adapter.createUser(testUser);
27
+
28
+ // Save a valid refresh token for the test user
29
+ await adapter.saveRefreshToken(
30
+ testUser.id,
31
+ 'valid-refresh-token-abc123',
32
+ new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
33
+ );
34
+
35
+ // Store adapter in a global variable so tests can use it
36
+ global.testAdapter = adapter;
37
+ });
38
+
39
+ afterAll(() => {
40
+ process.env = originalEnv;
41
+ });
42
+
43
+ describe('AuthService - Registration', () => {
44
+ it('should successfully register a new user', async () => {
45
+ const adapter = new MemoryAdapter();
46
+ adapter.clear();
47
+ const service = new AuthService(adapter);
48
+
49
+ const result = await service.register('newuser@example.com', 'securePassword123');
50
+
51
+ expect(result.success).toBe(true);
52
+ expect(result.userId).toBeDefined();
53
+ expect(typeof result.userId).toBe('string');
54
+
55
+ // Verify user was created in adapter
56
+ const user = await adapter.findUserByEmail('newuser@example.com');
57
+ expect(user).toBeDefined();
58
+ expect(user.email).toBe('newuser@example.com');
59
+ });
60
+
61
+ it('should throw error for duplicate email', async () => {
62
+ const adapter = new MemoryAdapter();
63
+ const service = new AuthService(adapter);
64
+
65
+ // First registration should succeed
66
+ await service.register('duplicate@example.com', 'password123');
67
+
68
+ // Second registration should fail
69
+ await expect(service.register('duplicate@example.com', 'password456'))
70
+ .rejects
71
+ .toThrow('User already exists');
72
+ });
73
+
74
+ it('should throw error for invalid email format', async () => {
75
+ const adapter = new MemoryAdapter();
76
+ const service = new AuthService(adapter);
77
+
78
+ await expect(service.register('invalid-email', 'password123'))
79
+ .rejects
80
+ .toThrow();
81
+ });
82
+
83
+ it('should throw error for short password', async () => {
84
+ const adapter = new MemoryAdapter();
85
+ const service = new AuthService(adapter);
86
+
87
+ await expect(service.register('user@example.com', 'short'))
88
+ .rejects
89
+ .toThrow('Password must be at least 8 characters');
90
+ });
91
+ });
92
+
93
+ describe('AuthService - Login', () => {
94
+ it('should successfully login with correct credentials', async () => {
95
+ const service = new AuthService(global.testAdapter);
96
+
97
+ const result = await service.login('test@example.com', 'password123');
98
+
99
+ expect(result.success).toBe(true);
100
+ expect(result.accessToken).toBeDefined();
101
+ expect(result.refreshToken).toBeDefined();
102
+ expect(typeof result.accessToken).toBe('string');
103
+ expect(typeof result.refreshToken).toBe('string');
104
+
105
+ // Verify refresh token was saved
106
+ const isValid = await global.testAdapter.isRefreshTokenValid(result.refreshToken);
107
+ expect(isValid).toBe(true);
108
+ });
109
+
110
+ it('should throw error for invalid credentials (wrong password)', async () => {
111
+ const service = new AuthService(global.testAdapter);
112
+
113
+ await expect(service.login('test@example.com', 'wrongpassword'))
114
+ .rejects
115
+ .toThrow('Invalid credentials');
116
+ });
117
+
118
+ it('should throw error for invalid credentials (non-existent user)', async () => {
119
+ const service = new AuthService(global.testAdapter);
120
+
121
+ await expect(service.login('nonexistent@example.com', 'password123'))
122
+ .rejects
123
+ .toThrow('Invalid credentials');
124
+ });
125
+
126
+ it('should maintain timing safety for invalid credentials', async () => {
127
+ const service = new AuthService(global.testAdapter);
128
+
129
+ let startTime = Date.now();
130
+
131
+ // Test with non-existent user
132
+ try {
133
+ await service.login('nonexistent@example.com', 'password123');
134
+ } catch (error) {
135
+ // Expected to fail
136
+ }
137
+ const time1 = Date.now() - startTime;
138
+
139
+ // Test with existing user but wrong password
140
+ startTime = Date.now();
141
+ try {
142
+ await service.login('test@example.com', 'wrongpassword');
143
+ } catch (error) {
144
+ // Expected to fail
145
+ }
146
+ const time2 = Date.now() - startTime;
147
+
148
+ // Times should be similar (within 50ms tolerance)
149
+ expect(Math.abs(time1 - time2)).toBeLessThan(50);
150
+ });
151
+ });
152
+
153
+ describe('AuthService - Token Rotation', () => {
154
+ it('should successfully rotate tokens', async () => {
155
+ const service = new AuthService(global.testAdapter);
156
+
157
+ // First login to get initial tokens
158
+ const loginResult = await service.login('test@example.com', 'password123');
159
+ const oldRefreshToken = loginResult.refreshToken;
160
+
161
+ // Rotate tokens
162
+ const refreshResult = await service.refresh(oldRefreshToken);
163
+
164
+ expect(refreshResult.success).toBe(true);
165
+ expect(refreshResult.accessToken).toBeDefined();
166
+ expect(refreshResult.refreshToken).toBeDefined();
167
+ expect(refreshResult.accessToken).not.toBe(oldRefreshToken);
168
+ expect(refreshResult.refreshToken).not.toBe(oldRefreshToken);
169
+
170
+ // Verify old refresh token is revoked
171
+ const isOldTokenValid = await global.testAdapter.isRefreshTokenValid(oldRefreshToken);
172
+ expect(isOldTokenValid).toBe(false);
173
+
174
+ // Verify new refresh token is valid
175
+ const isNewTokenValid = await global.testAdapter.isRefreshTokenValid(refreshResult.refreshToken);
176
+ expect(isNewTokenValid).toBe(true);
177
+ });
178
+
179
+ it('should throw error for invalid refresh token', async () => {
180
+ const service = new AuthService(global.testAdapter);
181
+
182
+ await expect(service.refresh('invalid-token'))
183
+ .rejects
184
+ .toThrow('Invalid refresh token');
185
+ });
186
+
187
+ it('should throw error for revoked refresh token', async () => {
188
+ const service = new AuthService(global.testAdapter);
189
+
190
+ // Login to get a token
191
+ const loginResult = await service.login('test@example.com', 'password123');
192
+ const refreshToken = loginResult.refreshToken;
193
+
194
+ // Revoke the token
195
+ await global.testAdapter.revokeRefreshToken(refreshToken);
196
+
197
+ // Try to use revoked token
198
+ await expect(service.refresh(refreshToken))
199
+ .rejects
200
+ .toThrow('Session revoked - please login again');
201
+ });
202
+
203
+ it('should automatically revoke all sessions when refresh token is reused', async () => {
204
+ const service = new AuthService(global.testAdapter);
205
+
206
+ // Login to get initial tokens
207
+ const loginResult = await service.login('test@example.com', 'password123');
208
+ const firstRefreshToken = loginResult.refreshToken;
209
+
210
+ // Rotate once
211
+ const refreshResult1 = await service.refresh(firstRefreshToken);
212
+ const secondRefreshToken = refreshResult1.refreshToken;
213
+
214
+ // Rotate again
215
+ const refreshResult2 = await service.refresh(secondRefreshToken);
216
+ const thirdRefreshToken = refreshResult2.refreshToken;
217
+
218
+ // Now try to use the first refresh token (which was revoked)
219
+ await expect(service.refresh(firstRefreshToken))
220
+ .rejects
221
+ .toThrow('Session revoked - please login again');
222
+
223
+ // Verify all tokens for this user are now revoked
224
+ const isFirstValid = await global.testAdapter.isRefreshTokenValid(firstRefreshToken);
225
+ const isSecondValid = await global.testAdapter.isRefreshTokenValid(secondRefreshToken);
226
+ const isThirdValid = await global.testAdapter.isRefreshTokenValid(thirdRefreshToken);
227
+
228
+ expect(isFirstValid).toBe(false);
229
+ expect(isSecondValid).toBe(false);
230
+ expect(isThirdValid).toBe(false);
231
+ });
232
+
233
+ it('should throw error for expired refresh token', async () => {
234
+ const service = new AuthService(global.testAdapter);
235
+
236
+ // Create a user
237
+ await service.register('expired@example.com', 'password123');
238
+
239
+ // Login to get tokens
240
+ const loginResult = await service.login('expired@example.com', 'password123');
241
+ const refreshToken = loginResult.refreshToken;
242
+
243
+ // Manually revoke the token to simulate expiration
244
+ await global.testAdapter.revokeRefreshToken(refreshToken);
245
+
246
+ // Try to use expired token
247
+ await expect(service.refresh(refreshToken))
248
+ .rejects
249
+ .toThrow('Session revoked - please login again');
250
+ });
251
+
252
+ it('should throw error for short refresh token', async () => {
253
+ const service = new AuthService(global.testAdapter);
254
+
255
+ await expect(service.refresh('short'))
256
+ .rejects
257
+ .toThrow('Refresh token is required');
258
+ });
259
+ });
260
+
261
+ describe('AuthService - Integration with MemoryAdapter', () => {
262
+ it('should maintain proper state across multiple operations', async () => {
263
+ const adapter = new MemoryAdapter();
264
+ adapter.clear();
265
+ const service = new AuthService(adapter);
266
+
267
+ // Register multiple users
268
+ const user1 = await service.register('user1@example.com', 'password123');
269
+ const user2 = await service.register('user2@example.com', 'password456');
270
+
271
+ // Login both users
272
+ const login1 = await service.login('user1@example.com', 'password123');
273
+ const login2 = await service.login('user2@example.com', 'password456');
274
+
275
+ // Verify tokens are saved
276
+ expect(await adapter.isRefreshTokenValid(login1.refreshToken)).toBe(true);
277
+ expect(await adapter.isRefreshTokenValid(login2.refreshToken)).toBe(true);
278
+
279
+ // Rotate user1's token
280
+ const refresh1 = await service.refresh(login1.refreshToken);
281
+
282
+ // Verify user1's old token is revoked but user2's token is still valid
283
+ expect(await adapter.isRefreshTokenValid(login1.refreshToken)).toBe(false);
284
+ expect(await adapter.isRefreshTokenValid(login2.refreshToken)).toBe(true);
285
+ expect(await adapter.isRefreshTokenValid(refresh1.refreshToken)).toBe(true);
286
+ });
287
+ });