@dangao/bun-server 1.8.0 → 1.8.1

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/tests/auth/auth-decorators.test.ts +241 -0
  3. package/tests/auth/oauth2-service.test.ts +318 -0
  4. package/tests/cache/cache-decorators-extended.test.ts +272 -0
  5. package/tests/cache/cache-interceptors.test.ts +534 -0
  6. package/tests/cache/cache-service-proxy.test.ts +246 -0
  7. package/tests/cache/memory-cache-store.test.ts +155 -0
  8. package/tests/cache/redis-cache-store.test.ts +199 -0
  9. package/tests/config/config-center-integration.test.ts +334 -0
  10. package/tests/config/config-module-extended.test.ts +165 -0
  11. package/tests/controller/param-binder.test.ts +333 -0
  12. package/tests/error/error-handler.test.ts +166 -57
  13. package/tests/error/i18n-extended.test.ts +105 -0
  14. package/tests/events/event-listener-scanner.test.ts +114 -0
  15. package/tests/events/event-module.test.ts +133 -302
  16. package/tests/extensions/logger-module.test.ts +158 -0
  17. package/tests/files/file-storage.test.ts +136 -0
  18. package/tests/interceptor/base-interceptor.test.ts +605 -0
  19. package/tests/interceptor/builtin/cache-interceptor.test.ts +233 -86
  20. package/tests/interceptor/builtin/log-interceptor.test.ts +469 -0
  21. package/tests/interceptor/builtin/permission-interceptor.test.ts +219 -120
  22. package/tests/interceptor/interceptor-chain.test.ts +241 -189
  23. package/tests/interceptor/interceptor-metadata.test.ts +221 -0
  24. package/tests/microservice/circuit-breaker.test.ts +221 -0
  25. package/tests/microservice/service-client-decorators.test.ts +86 -0
  26. package/tests/microservice/service-client-interceptors.test.ts +274 -0
  27. package/tests/microservice/service-registry-decorators.test.ts +147 -0
  28. package/tests/microservice/tracer.test.ts +213 -0
  29. package/tests/microservice/tracing-collectors.test.ts +168 -0
  30. package/tests/middleware/builtin/middleware-builtin-extended.test.ts +237 -0
  31. package/tests/middleware/builtin/rate-limit.test.ts +257 -0
  32. package/tests/middleware/middleware-decorators.test.ts +222 -0
  33. package/tests/middleware/middleware-pipeline.test.ts +160 -0
  34. package/tests/queue/queue-decorators.test.ts +139 -0
  35. package/tests/queue/queue-service.test.ts +191 -0
  36. package/tests/request/body-parser-extended.test.ts +291 -0
  37. package/tests/request/request-wrapper.test.ts +319 -0
  38. package/tests/router/router-decorators.test.ts +260 -0
  39. package/tests/router/router-extended.test.ts +298 -0
  40. package/tests/security/guards/reflector.test.ts +188 -0
  41. package/tests/security/security-filter.test.ts +182 -0
  42. package/tests/security/security-module-extended.test.ts +133 -0
  43. package/tests/session/memory-session-store.test.ts +172 -0
  44. package/tests/session/session-decorators.test.ts +163 -0
  45. package/tests/swagger/ui.test.ts +212 -0
@@ -0,0 +1,188 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { Reflector, REFLECTOR_TOKEN } from '../../../src/security/guards/reflector';
5
+
6
+ const TEST_KEY = Symbol('test:metadata');
7
+ const ROLES_KEY = Symbol('roles');
8
+
9
+ describe('Reflector', () => {
10
+ let reflector: Reflector;
11
+
12
+ beforeEach(() => {
13
+ reflector = new Reflector();
14
+ });
15
+
16
+ describe('get', () => {
17
+ test('should get metadata from class', () => {
18
+ @Reflect.metadata(TEST_KEY, 'class-value')
19
+ class TestClass {}
20
+
21
+ const value = reflector.get<string>(TEST_KEY, TestClass);
22
+ expect(value).toBe('class-value');
23
+ });
24
+
25
+ test('should return undefined if no metadata', () => {
26
+ class TestClass {}
27
+
28
+ const value = reflector.get<string>(TEST_KEY, TestClass);
29
+ expect(value).toBeUndefined();
30
+ });
31
+ });
32
+
33
+ describe('getFromClass', () => {
34
+ test('should get metadata from class', () => {
35
+ @Reflect.metadata(ROLES_KEY, ['admin', 'user'])
36
+ class TestClass {}
37
+
38
+ const roles = reflector.getFromClass<string[]>(ROLES_KEY, TestClass);
39
+ expect(roles).toEqual(['admin', 'user']);
40
+ });
41
+
42
+ test('should return undefined for non-decorated class', () => {
43
+ class PlainClass {}
44
+
45
+ const value = reflector.getFromClass<string[]>(ROLES_KEY, PlainClass);
46
+ expect(value).toBeUndefined();
47
+ });
48
+ });
49
+
50
+ describe('getFromMethod', () => {
51
+ test('should get metadata from method', () => {
52
+ class TestClass {
53
+ @Reflect.metadata(ROLES_KEY, ['moderator'])
54
+ public testMethod(): void {}
55
+ }
56
+
57
+ const roles = reflector.getFromMethod<string[]>(
58
+ ROLES_KEY,
59
+ TestClass.prototype,
60
+ 'testMethod',
61
+ );
62
+ expect(roles).toEqual(['moderator']);
63
+ });
64
+
65
+ test('should return undefined for non-decorated method', () => {
66
+ class TestClass {
67
+ public plainMethod(): void {}
68
+ }
69
+
70
+ const value = reflector.getFromMethod<string[]>(
71
+ ROLES_KEY,
72
+ TestClass.prototype,
73
+ 'plainMethod',
74
+ );
75
+ expect(value).toBeUndefined();
76
+ });
77
+ });
78
+
79
+ describe('getAllAndMerge', () => {
80
+ test('should merge class and method metadata', () => {
81
+ @Reflect.metadata(ROLES_KEY, ['admin'])
82
+ class TestClass {
83
+ @Reflect.metadata(ROLES_KEY, ['user'])
84
+ public testMethod(): void {}
85
+ }
86
+
87
+ const roles = reflector.getAllAndMerge<string[]>(
88
+ ROLES_KEY,
89
+ TestClass,
90
+ 'testMethod',
91
+ );
92
+ expect(roles).toContain('admin');
93
+ expect(roles).toContain('user');
94
+ expect(roles.length).toBe(2);
95
+ });
96
+
97
+ test('should return class metadata if method has none', () => {
98
+ @Reflect.metadata(ROLES_KEY, ['admin'])
99
+ class TestClass {
100
+ public plainMethod(): void {}
101
+ }
102
+
103
+ const roles = reflector.getAllAndMerge<string[]>(
104
+ ROLES_KEY,
105
+ TestClass,
106
+ 'plainMethod',
107
+ );
108
+ expect(roles).toEqual(['admin']);
109
+ });
110
+
111
+ test('should return method metadata if class has none', () => {
112
+ class TestClass {
113
+ @Reflect.metadata(ROLES_KEY, ['user'])
114
+ public testMethod(): void {}
115
+ }
116
+
117
+ const roles = reflector.getAllAndMerge<string[]>(
118
+ ROLES_KEY,
119
+ TestClass,
120
+ 'testMethod',
121
+ );
122
+ expect(roles).toEqual(['user']);
123
+ });
124
+
125
+ test('should return empty array if neither has metadata', () => {
126
+ class TestClass {
127
+ public plainMethod(): void {}
128
+ }
129
+
130
+ const roles = reflector.getAllAndMerge<string[]>(
131
+ ROLES_KEY,
132
+ TestClass,
133
+ 'plainMethod',
134
+ );
135
+ expect(roles).toEqual([]);
136
+ });
137
+ });
138
+
139
+ describe('getAllAndOverride', () => {
140
+ test('should return method metadata when both exist', () => {
141
+ @Reflect.metadata(ROLES_KEY, 'class-value')
142
+ class TestClass {
143
+ @Reflect.metadata(ROLES_KEY, 'method-value')
144
+ public testMethod(): void {}
145
+ }
146
+
147
+ const value = reflector.getAllAndOverride<string>(
148
+ ROLES_KEY,
149
+ TestClass,
150
+ 'testMethod',
151
+ );
152
+ expect(value).toBe('method-value');
153
+ });
154
+
155
+ test('should return class metadata if method has none', () => {
156
+ @Reflect.metadata(ROLES_KEY, 'class-value')
157
+ class TestClass {
158
+ public plainMethod(): void {}
159
+ }
160
+
161
+ const value = reflector.getAllAndOverride<string>(
162
+ ROLES_KEY,
163
+ TestClass,
164
+ 'plainMethod',
165
+ );
166
+ expect(value).toBe('class-value');
167
+ });
168
+
169
+ test('should return undefined if neither has metadata', () => {
170
+ class TestClass {
171
+ public plainMethod(): void {}
172
+ }
173
+
174
+ const value = reflector.getAllAndOverride<string>(
175
+ ROLES_KEY,
176
+ TestClass,
177
+ 'plainMethod',
178
+ );
179
+ expect(value).toBeUndefined();
180
+ });
181
+ });
182
+
183
+ describe('REFLECTOR_TOKEN', () => {
184
+ test('should be a symbol', () => {
185
+ expect(typeof REFLECTOR_TOKEN).toBe('symbol');
186
+ });
187
+ });
188
+ });
@@ -0,0 +1,182 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import {
5
+ createSecurityFilter,
6
+ getGuardRegistry,
7
+ registerReflector,
8
+ } from '../../src/security/filter';
9
+ import { AuthenticationManager } from '../../src/security/authentication-manager';
10
+ import { JwtAuthenticationProvider } from '../../src/security/providers/jwt-provider';
11
+ import { JWTUtil } from '../../src/auth/jwt';
12
+ import { Context } from '../../src/core/context';
13
+ import { Container } from '../../src/di/container';
14
+ import { GuardRegistry } from '../../src/security/guards/guard-registry';
15
+ import { Reflector, REFLECTOR_TOKEN } from '../../src/security/guards/reflector';
16
+ import { GUARD_REGISTRY_TOKEN } from '../../src/security/guards/types';
17
+ import { Auth } from '../../src/auth/decorators';
18
+
19
+ describe('createSecurityFilter', () => {
20
+ let container: Container;
21
+ let jwtUtil: JWTUtil;
22
+ let authManager: AuthenticationManager;
23
+
24
+ beforeEach(() => {
25
+ container = new Container();
26
+ jwtUtil = new JWTUtil({
27
+ secret: 'test-secret-key-for-security-filter',
28
+ accessTokenExpiresIn: 3600,
29
+ });
30
+ authManager = new AuthenticationManager();
31
+ authManager.registerProvider(new JwtAuthenticationProvider(jwtUtil));
32
+ });
33
+
34
+ test('should allow request to excluded path', async () => {
35
+ const filter = createSecurityFilter({
36
+ authenticationManager: authManager,
37
+ excludePaths: ['/public', '/health'],
38
+ });
39
+
40
+ const request = new Request('http://localhost/public/data');
41
+ const context = new Context(request, container);
42
+
43
+ let passed = false;
44
+ await filter(context, async () => {
45
+ passed = true;
46
+ return new Response('OK');
47
+ });
48
+
49
+ expect(passed).toBe(true);
50
+ });
51
+
52
+ test('should authenticate valid token', async () => {
53
+ const filter = createSecurityFilter({
54
+ authenticationManager: authManager,
55
+ defaultAuthRequired: false,
56
+ });
57
+
58
+ const token = jwtUtil.generateAccessToken({
59
+ sub: 'user-123',
60
+ username: 'alice',
61
+ });
62
+
63
+ const request = new Request('http://localhost/api/data', {
64
+ headers: { Authorization: `Bearer ${token}` },
65
+ });
66
+ const context = new Context(request, container);
67
+
68
+ let passed = false;
69
+ await filter(context, async () => {
70
+ passed = true;
71
+ // Check auth was set on context
72
+ expect((context as any).auth).toBeDefined();
73
+ expect((context as any).auth.isAuthenticated).toBe(true);
74
+ return new Response('OK');
75
+ });
76
+
77
+ expect(passed).toBe(true);
78
+ });
79
+
80
+ test('should reject when auth required but no token', async () => {
81
+ const filter = createSecurityFilter({
82
+ authenticationManager: authManager,
83
+ defaultAuthRequired: true,
84
+ excludePaths: [],
85
+ });
86
+
87
+ const request = new Request('http://localhost/api/data');
88
+ const context = new Context(request, container);
89
+
90
+ await expect(
91
+ filter(context, async () => new Response('OK')),
92
+ ).rejects.toThrow();
93
+ });
94
+
95
+ test('should use custom token extractor', async () => {
96
+ let extractorCalled = false;
97
+ const filter = createSecurityFilter({
98
+ authenticationManager: authManager,
99
+ defaultAuthRequired: false,
100
+ extractToken: (ctx) => {
101
+ extractorCalled = true;
102
+ return ctx.getHeader('X-Custom-Token');
103
+ },
104
+ });
105
+
106
+ const token = jwtUtil.generateAccessToken({ sub: 'user-1' });
107
+ const request = new Request('http://localhost/api/data', {
108
+ headers: { 'X-Custom-Token': token },
109
+ });
110
+ const context = new Context(request, container);
111
+
112
+ await filter(context, async () => new Response('OK'));
113
+
114
+ expect(extractorCalled).toBe(true);
115
+ expect((context as any).auth.isAuthenticated).toBe(true);
116
+ });
117
+
118
+ test('should handle invalid bearer token format', async () => {
119
+ const filter = createSecurityFilter({
120
+ authenticationManager: authManager,
121
+ defaultAuthRequired: false,
122
+ });
123
+
124
+ const request = new Request('http://localhost/api/data', {
125
+ headers: { Authorization: 'InvalidFormat' },
126
+ });
127
+ const context = new Context(request, container);
128
+
129
+ let passed = false;
130
+ await filter(context, async () => {
131
+ passed = true;
132
+ return new Response('OK');
133
+ });
134
+
135
+ expect(passed).toBe(true);
136
+ expect((context as any).auth.isAuthenticated).toBe(false);
137
+ });
138
+ });
139
+
140
+ describe('getGuardRegistry', () => {
141
+ test('should return existing registry if registered', () => {
142
+ const container = new Container();
143
+ const existingRegistry = new GuardRegistry();
144
+ container.registerInstance(GUARD_REGISTRY_TOKEN, existingRegistry);
145
+
146
+ const registry = getGuardRegistry(container);
147
+ expect(registry).toBe(existingRegistry);
148
+ });
149
+
150
+ test('should create and register new registry if not exists', () => {
151
+ const container = new Container();
152
+
153
+ const registry = getGuardRegistry(container);
154
+ expect(registry).toBeInstanceOf(GuardRegistry);
155
+
156
+ // Should be registered now
157
+ const secondCall = getGuardRegistry(container);
158
+ expect(secondCall).toBe(registry);
159
+ });
160
+ });
161
+
162
+ describe('registerReflector', () => {
163
+ test('should return existing reflector if registered', () => {
164
+ const container = new Container();
165
+ const existingReflector = new Reflector();
166
+ container.registerInstance(REFLECTOR_TOKEN, existingReflector);
167
+
168
+ const reflector = registerReflector(container);
169
+ expect(reflector).toBe(existingReflector);
170
+ });
171
+
172
+ test('should create and register new reflector if not exists', () => {
173
+ const container = new Container();
174
+
175
+ const reflector = registerReflector(container);
176
+ expect(reflector).toBeInstanceOf(Reflector);
177
+
178
+ // Should be registered now
179
+ const secondCall = registerReflector(container);
180
+ expect(secondCall).toBe(reflector);
181
+ });
182
+ });
@@ -0,0 +1,133 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { SecurityModule } from '../../src/security/security-module';
5
+ import { MODULE_METADATA_KEY } from '../../src/di/module';
6
+ import { GuardRegistry } from '../../src/security/guards/guard-registry';
7
+ import { JWT_UTIL_TOKEN, OAUTH2_SERVICE_TOKEN } from '../../src/auth/controller';
8
+ import { GUARD_REGISTRY_TOKEN, type Guard, type ExecutionContext } from '../../src/security/guards/types';
9
+ import { REFLECTOR_TOKEN } from '../../src/security/guards/reflector';
10
+
11
+ describe('SecurityModule', () => {
12
+ beforeEach(() => {
13
+ SecurityModule.reset();
14
+ });
15
+
16
+ afterEach(() => {
17
+ SecurityModule.reset();
18
+ });
19
+
20
+ describe('forRoot', () => {
21
+ test('should create module with JWT config', () => {
22
+ SecurityModule.forRoot({
23
+ jwt: {
24
+ secret: 'test-secret',
25
+ accessTokenExpiresIn: 3600,
26
+ },
27
+ });
28
+
29
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule);
30
+ expect(metadata.providers).toBeDefined();
31
+ expect(metadata.exports).toContain(JWT_UTIL_TOKEN);
32
+ });
33
+
34
+ test('should create module with OAuth2 config', () => {
35
+ SecurityModule.forRoot({
36
+ jwt: { secret: 'secret' },
37
+ oauth2Clients: [
38
+ {
39
+ clientId: 'test-client',
40
+ clientSecret: 'test-secret',
41
+ redirectUris: ['http://localhost/callback'],
42
+ grantTypes: ['authorization_code'],
43
+ },
44
+ ],
45
+ });
46
+
47
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule);
48
+ expect(metadata.exports).toContain(OAUTH2_SERVICE_TOKEN);
49
+ });
50
+
51
+ test('should register guard registry', () => {
52
+ SecurityModule.forRoot({
53
+ jwt: { secret: 'secret' },
54
+ });
55
+
56
+ const guardRegistry = SecurityModule.getGuardRegistry();
57
+ expect(guardRegistry).toBeInstanceOf(GuardRegistry);
58
+ });
59
+
60
+ test('should export guard registry and reflector', () => {
61
+ SecurityModule.forRoot({
62
+ jwt: { secret: 'secret' },
63
+ });
64
+
65
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule);
66
+ expect(metadata.exports).toContain(GUARD_REGISTRY_TOKEN);
67
+ expect(metadata.exports).toContain(REFLECTOR_TOKEN);
68
+ });
69
+
70
+ test('should support exclude paths', () => {
71
+ SecurityModule.forRoot({
72
+ jwt: { secret: 'secret' },
73
+ excludePaths: ['/public', '/health'],
74
+ });
75
+
76
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule);
77
+ expect(metadata.middlewares.length).toBeGreaterThan(0);
78
+ });
79
+ });
80
+
81
+ describe('getGuardRegistry', () => {
82
+ test('should return null before forRoot', () => {
83
+ const registry = SecurityModule.getGuardRegistry();
84
+ expect(registry).toBeNull();
85
+ });
86
+
87
+ test('should return registry after forRoot', () => {
88
+ SecurityModule.forRoot({ jwt: { secret: 'secret' } });
89
+ const registry = SecurityModule.getGuardRegistry();
90
+ expect(registry).not.toBeNull();
91
+ });
92
+ });
93
+
94
+ describe('addGlobalGuards', () => {
95
+ test('should add guards to registry', () => {
96
+ SecurityModule.forRoot({ jwt: { secret: 'secret' } });
97
+
98
+ class TestGuard implements Guard {
99
+ public async canActivate(context: ExecutionContext): Promise<boolean> {
100
+ return true;
101
+ }
102
+ }
103
+
104
+ SecurityModule.addGlobalGuards(TestGuard);
105
+
106
+ const registry = SecurityModule.getGuardRegistry();
107
+ expect(registry).not.toBeNull();
108
+ });
109
+
110
+ test('should not throw when registry not initialized', () => {
111
+ class TestGuard implements Guard {
112
+ public async canActivate(context: ExecutionContext): Promise<boolean> {
113
+ return true;
114
+ }
115
+ }
116
+
117
+ expect(() => SecurityModule.addGlobalGuards(TestGuard)).not.toThrow();
118
+ });
119
+ });
120
+
121
+ describe('reset', () => {
122
+ test('should clear registry and metadata', () => {
123
+ SecurityModule.forRoot({ jwt: { secret: 'secret' } });
124
+ expect(SecurityModule.getGuardRegistry()).not.toBeNull();
125
+
126
+ SecurityModule.reset();
127
+
128
+ expect(SecurityModule.getGuardRegistry()).toBeNull();
129
+ const metadata = Reflect.getMetadata(MODULE_METADATA_KEY, SecurityModule);
130
+ expect(metadata).toBeUndefined();
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,172 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+
3
+ import { MemorySessionStore, type Session } from '../../src/session/types';
4
+
5
+ describe('MemorySessionStore', () => {
6
+ let store: MemorySessionStore;
7
+
8
+ beforeEach(() => {
9
+ store = new MemorySessionStore();
10
+ });
11
+
12
+ function createSession(id: string, data: Record<string, unknown> = {}): Session {
13
+ const now = Date.now();
14
+ return {
15
+ id,
16
+ data,
17
+ createdAt: now,
18
+ lastAccessedAt: now,
19
+ expiresAt: now + 3600000, // 1 hour
20
+ };
21
+ }
22
+
23
+ describe('get', () => {
24
+ test('should return undefined for non-existent session', async () => {
25
+ const session = await store.get('non-existent');
26
+ expect(session).toBeUndefined();
27
+ });
28
+
29
+ test('should return session if exists and not expired', async () => {
30
+ const session = createSession('test-1', { user: 'alice' });
31
+ await store.set(session, 3600000);
32
+
33
+ const retrieved = await store.get('test-1');
34
+ expect(retrieved).toBeDefined();
35
+ expect(retrieved?.data.user).toBe('alice');
36
+ });
37
+
38
+ test('should return undefined for expired session', async () => {
39
+ const session = createSession('test-2');
40
+ await store.set(session, 50); // 50ms TTL
41
+
42
+ // Wait for expiration
43
+ await new Promise((resolve) => setTimeout(resolve, 100));
44
+
45
+ const retrieved = await store.get('test-2');
46
+ expect(retrieved).toBeUndefined();
47
+ });
48
+ });
49
+
50
+ describe('set', () => {
51
+ test('should set session successfully', async () => {
52
+ const session = createSession('test-3', { count: 42 });
53
+ const result = await store.set(session, 3600000);
54
+ expect(result).toBe(true);
55
+
56
+ const retrieved = await store.get('test-3');
57
+ expect(retrieved?.data.count).toBe(42);
58
+ });
59
+
60
+ test('should update expiresAt and lastAccessedAt', async () => {
61
+ const session = createSession('test-4');
62
+ const before = Date.now();
63
+ await store.set(session, 60000);
64
+
65
+ const retrieved = await store.get('test-4');
66
+ expect(retrieved?.lastAccessedAt).toBeGreaterThanOrEqual(before);
67
+ expect(retrieved?.expiresAt).toBeGreaterThan(Date.now());
68
+ });
69
+
70
+ test('should overwrite existing session', async () => {
71
+ const session1 = createSession('test-5', { version: 1 });
72
+ await store.set(session1, 3600000);
73
+
74
+ const session2 = createSession('test-5', { version: 2 });
75
+ await store.set(session2, 3600000);
76
+
77
+ const retrieved = await store.get('test-5');
78
+ expect(retrieved?.data.version).toBe(2);
79
+ });
80
+ });
81
+
82
+ describe('delete', () => {
83
+ test('should delete existing session', async () => {
84
+ const session = createSession('test-6');
85
+ await store.set(session, 3600000);
86
+
87
+ const deleted = await store.delete('test-6');
88
+ expect(deleted).toBe(true);
89
+
90
+ const retrieved = await store.get('test-6');
91
+ expect(retrieved).toBeUndefined();
92
+ });
93
+
94
+ test('should return false for non-existent session', async () => {
95
+ const deleted = await store.delete('non-existent');
96
+ expect(deleted).toBe(false);
97
+ });
98
+ });
99
+
100
+ describe('has', () => {
101
+ test('should return true for existing session', async () => {
102
+ const session = createSession('test-7');
103
+ await store.set(session, 3600000);
104
+
105
+ const exists = await store.has('test-7');
106
+ expect(exists).toBe(true);
107
+ });
108
+
109
+ test('should return false for non-existent session', async () => {
110
+ const exists = await store.has('non-existent');
111
+ expect(exists).toBe(false);
112
+ });
113
+
114
+ test('should return false for expired session', async () => {
115
+ const session = createSession('test-8');
116
+ await store.set(session, 50); // 50ms TTL
117
+
118
+ // Wait for expiration
119
+ await new Promise((resolve) => setTimeout(resolve, 100));
120
+
121
+ const exists = await store.has('test-8');
122
+ expect(exists).toBe(false);
123
+ });
124
+ });
125
+
126
+ describe('touch', () => {
127
+ test('should update lastAccessedAt', async () => {
128
+ const session = createSession('test-9');
129
+ await store.set(session, 3600000);
130
+
131
+ const beforeTouch = await store.get('test-9');
132
+ const lastAccessedBefore = beforeTouch?.lastAccessedAt;
133
+
134
+ await new Promise((resolve) => setTimeout(resolve, 10));
135
+ await store.touch('test-9');
136
+
137
+ const afterTouch = await store.get('test-9');
138
+ expect(afterTouch?.lastAccessedAt).toBeGreaterThanOrEqual(lastAccessedBefore!);
139
+ });
140
+
141
+ test('should return false for non-existent session', async () => {
142
+ const result = await store.touch('non-existent');
143
+ expect(result).toBe(false);
144
+ });
145
+
146
+ test('should return false for expired session', async () => {
147
+ const session = createSession('test-10');
148
+ await store.set(session, 50); // 50ms TTL
149
+
150
+ // Wait for expiration
151
+ await new Promise((resolve) => setTimeout(resolve, 100));
152
+
153
+ const result = await store.touch('test-10');
154
+ expect(result).toBe(false);
155
+ });
156
+ });
157
+
158
+ describe('clear', () => {
159
+ test('should clear all sessions', async () => {
160
+ await store.set(createSession('s1'), 3600000);
161
+ await store.set(createSession('s2'), 3600000);
162
+ await store.set(createSession('s3'), 3600000);
163
+
164
+ const result = await store.clear();
165
+ expect(result).toBe(true);
166
+
167
+ expect(await store.has('s1')).toBe(false);
168
+ expect(await store.has('s2')).toBe(false);
169
+ expect(await store.has('s3')).toBe(false);
170
+ });
171
+ });
172
+ });