@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,222 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import {
5
+ UseMiddleware,
6
+ RateLimit,
7
+ getClassMiddlewares,
8
+ getMethodMiddlewares,
9
+ } from '../../src/middleware/decorators';
10
+ import type { Middleware } from '../../src/middleware';
11
+ import type { Context } from '../../src/core/context';
12
+
13
+ // 模拟中间件
14
+ const mockMiddleware1: Middleware = async (ctx, next) => {
15
+ return await next();
16
+ };
17
+
18
+ const mockMiddleware2: Middleware = async (ctx, next) => {
19
+ return await next();
20
+ };
21
+
22
+ const mockMiddleware3: Middleware = async (ctx, next) => {
23
+ return await next();
24
+ };
25
+
26
+ describe('UseMiddleware Decorator', () => {
27
+ describe('Class-level middleware', () => {
28
+ test('should register single middleware on class', () => {
29
+ @UseMiddleware(mockMiddleware1)
30
+ class TestController {}
31
+
32
+ const middlewares = getClassMiddlewares(TestController);
33
+ expect(middlewares.length).toBe(1);
34
+ expect(middlewares[0]).toBe(mockMiddleware1);
35
+ });
36
+
37
+ test('should register multiple middlewares on class', () => {
38
+ @UseMiddleware(mockMiddleware1, mockMiddleware2, mockMiddleware3)
39
+ class TestController {}
40
+
41
+ const middlewares = getClassMiddlewares(TestController);
42
+ expect(middlewares.length).toBe(3);
43
+ expect(middlewares).toContain(mockMiddleware1);
44
+ expect(middlewares).toContain(mockMiddleware2);
45
+ expect(middlewares).toContain(mockMiddleware3);
46
+ });
47
+
48
+ test('should accumulate middlewares from multiple decorators', () => {
49
+ @UseMiddleware(mockMiddleware1)
50
+ @UseMiddleware(mockMiddleware2)
51
+ class TestController {}
52
+
53
+ const middlewares = getClassMiddlewares(TestController);
54
+ expect(middlewares.length).toBe(2);
55
+ });
56
+
57
+ test('should return empty array for class without middleware', () => {
58
+ class TestController {}
59
+
60
+ const middlewares = getClassMiddlewares(TestController);
61
+ expect(middlewares).toEqual([]);
62
+ });
63
+
64
+ test('should not register when empty array passed', () => {
65
+ @UseMiddleware()
66
+ class TestController {}
67
+
68
+ const middlewares = getClassMiddlewares(TestController);
69
+ expect(middlewares.length).toBe(0);
70
+ });
71
+ });
72
+
73
+ describe('Method-level middleware', () => {
74
+ test('should register single middleware on method', () => {
75
+ class TestController {
76
+ @UseMiddleware(mockMiddleware1)
77
+ public testMethod(): void {}
78
+ }
79
+
80
+ const middlewares = getMethodMiddlewares(
81
+ TestController.prototype,
82
+ 'testMethod',
83
+ );
84
+ expect(middlewares.length).toBe(1);
85
+ expect(middlewares[0]).toBe(mockMiddleware1);
86
+ });
87
+
88
+ test('should register multiple middlewares on method', () => {
89
+ class TestController {
90
+ @UseMiddleware(mockMiddleware1, mockMiddleware2)
91
+ public testMethod(): void {}
92
+ }
93
+
94
+ const middlewares = getMethodMiddlewares(
95
+ TestController.prototype,
96
+ 'testMethod',
97
+ );
98
+ expect(middlewares.length).toBe(2);
99
+ });
100
+
101
+ test('should accumulate middlewares from multiple decorators on method', () => {
102
+ class TestController {
103
+ @UseMiddleware(mockMiddleware1)
104
+ @UseMiddleware(mockMiddleware2)
105
+ public testMethod(): void {}
106
+ }
107
+
108
+ const middlewares = getMethodMiddlewares(
109
+ TestController.prototype,
110
+ 'testMethod',
111
+ );
112
+ expect(middlewares.length).toBe(2);
113
+ });
114
+
115
+ test('should return empty array for method without middleware', () => {
116
+ class TestController {
117
+ public testMethod(): void {}
118
+ }
119
+
120
+ const middlewares = getMethodMiddlewares(
121
+ TestController.prototype,
122
+ 'testMethod',
123
+ );
124
+ expect(middlewares).toEqual([]);
125
+ });
126
+
127
+ test('should keep separate middlewares for different methods', () => {
128
+ class TestController {
129
+ @UseMiddleware(mockMiddleware1)
130
+ public method1(): void {}
131
+
132
+ @UseMiddleware(mockMiddleware2)
133
+ public method2(): void {}
134
+ }
135
+
136
+ const middlewares1 = getMethodMiddlewares(
137
+ TestController.prototype,
138
+ 'method1',
139
+ );
140
+ const middlewares2 = getMethodMiddlewares(
141
+ TestController.prototype,
142
+ 'method2',
143
+ );
144
+
145
+ expect(middlewares1.length).toBe(1);
146
+ expect(middlewares1[0]).toBe(mockMiddleware1);
147
+ expect(middlewares2.length).toBe(1);
148
+ expect(middlewares2[0]).toBe(mockMiddleware2);
149
+ });
150
+ });
151
+
152
+ describe('Mixed class and method middleware', () => {
153
+ test('should keep class and method middlewares separate', () => {
154
+ @UseMiddleware(mockMiddleware1)
155
+ class TestController {
156
+ @UseMiddleware(mockMiddleware2)
157
+ public testMethod(): void {}
158
+ }
159
+
160
+ const classMiddlewares = getClassMiddlewares(TestController);
161
+ const methodMiddlewares = getMethodMiddlewares(
162
+ TestController.prototype,
163
+ 'testMethod',
164
+ );
165
+
166
+ expect(classMiddlewares.length).toBe(1);
167
+ expect(classMiddlewares[0]).toBe(mockMiddleware1);
168
+ expect(methodMiddlewares.length).toBe(1);
169
+ expect(methodMiddlewares[0]).toBe(mockMiddleware2);
170
+ });
171
+ });
172
+ });
173
+
174
+ describe('RateLimit Decorator', () => {
175
+ test('should create rate limit middleware on method', () => {
176
+ class TestController {
177
+ @RateLimit({ limit: 100, windowMs: 60000 })
178
+ public testMethod(): void {}
179
+ }
180
+
181
+ const middlewares = getMethodMiddlewares(
182
+ TestController.prototype,
183
+ 'testMethod',
184
+ );
185
+
186
+ expect(middlewares.length).toBe(1);
187
+ expect(typeof middlewares[0]).toBe('function');
188
+ });
189
+
190
+ test('should create rate limit middleware with custom options', () => {
191
+ class TestController {
192
+ @RateLimit({
193
+ limit: 10,
194
+ windowMs: 1000,
195
+ keyGenerator: (ctx: Context) => ctx.request.url,
196
+ })
197
+ public limitedMethod(): void {}
198
+ }
199
+
200
+ const middlewares = getMethodMiddlewares(
201
+ TestController.prototype,
202
+ 'limitedMethod',
203
+ );
204
+
205
+ expect(middlewares.length).toBe(1);
206
+ });
207
+
208
+ test('should work alongside UseMiddleware', () => {
209
+ class TestController {
210
+ @UseMiddleware(mockMiddleware1)
211
+ @RateLimit({ limit: 50, windowMs: 30000 })
212
+ public testMethod(): void {}
213
+ }
214
+
215
+ const middlewares = getMethodMiddlewares(
216
+ TestController.prototype,
217
+ 'testMethod',
218
+ );
219
+
220
+ expect(middlewares.length).toBe(2);
221
+ });
222
+ });
@@ -0,0 +1,160 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { MiddlewarePipeline, runMiddlewares } from '../../src/middleware/pipeline';
5
+ import { Context } from '../../src/core/context';
6
+ import { Container } from '../../src/di/container';
7
+ import type { Middleware } from '../../src/middleware';
8
+
9
+ describe('MiddlewarePipeline', () => {
10
+ let pipeline: MiddlewarePipeline;
11
+ let context: Context;
12
+
13
+ beforeEach(() => {
14
+ pipeline = new MiddlewarePipeline();
15
+ const request = new Request('http://localhost/test');
16
+ context = new Context(request, new Container());
17
+ });
18
+
19
+ describe('constructor', () => {
20
+ test('should create empty pipeline', () => {
21
+ const p = new MiddlewarePipeline();
22
+ expect(p.hasMiddlewares()).toBe(false);
23
+ });
24
+
25
+ test('should create pipeline with initial middlewares', () => {
26
+ const middleware: Middleware = async (ctx, next) => next();
27
+ const p = new MiddlewarePipeline([middleware]);
28
+ expect(p.hasMiddlewares()).toBe(true);
29
+ });
30
+ });
31
+
32
+ describe('use', () => {
33
+ test('should add middleware', () => {
34
+ expect(pipeline.hasMiddlewares()).toBe(false);
35
+
36
+ pipeline.use(async (ctx, next) => next());
37
+
38
+ expect(pipeline.hasMiddlewares()).toBe(true);
39
+ });
40
+ });
41
+
42
+ describe('clear', () => {
43
+ test('should remove all middlewares', () => {
44
+ pipeline.use(async (ctx, next) => next());
45
+ pipeline.use(async (ctx, next) => next());
46
+
47
+ pipeline.clear();
48
+
49
+ expect(pipeline.hasMiddlewares()).toBe(false);
50
+ });
51
+ });
52
+
53
+ describe('run', () => {
54
+ test('should run final handler when no middlewares', async () => {
55
+ let finalCalled = false;
56
+ const response = await pipeline.run(context, async () => {
57
+ finalCalled = true;
58
+ return new Response('final');
59
+ });
60
+
61
+ expect(finalCalled).toBe(true);
62
+ expect(await response.text()).toBe('final');
63
+ });
64
+
65
+ test('should execute middlewares in order', async () => {
66
+ const order: number[] = [];
67
+
68
+ pipeline.use(async (ctx, next) => {
69
+ order.push(1);
70
+ const res = await next();
71
+ order.push(4);
72
+ return res;
73
+ });
74
+
75
+ pipeline.use(async (ctx, next) => {
76
+ order.push(2);
77
+ const res = await next();
78
+ order.push(3);
79
+ return res;
80
+ });
81
+
82
+ await pipeline.run(context, async () => new Response('ok'));
83
+
84
+ expect(order).toEqual([1, 2, 3, 4]);
85
+ });
86
+
87
+ test('should allow middleware to modify response', async () => {
88
+ pipeline.use(async (ctx, next) => {
89
+ const res = await next();
90
+ return new Response('modified', { headers: res.headers });
91
+ });
92
+
93
+ const response = await pipeline.run(context, async () => new Response('original'));
94
+
95
+ expect(await response.text()).toBe('modified');
96
+ });
97
+
98
+ test('should allow middleware to short-circuit', async () => {
99
+ let finalCalled = false;
100
+
101
+ pipeline.use(async (ctx, next) => {
102
+ return new Response('short-circuit');
103
+ });
104
+
105
+ const response = await pipeline.run(context, async () => {
106
+ finalCalled = true;
107
+ return new Response('final');
108
+ });
109
+
110
+ expect(finalCalled).toBe(false);
111
+ expect(await response.text()).toBe('short-circuit');
112
+ });
113
+
114
+ test('should track middleware calls', async () => {
115
+ let nextCallCount = 0;
116
+
117
+ pipeline.use(async (ctx, next) => {
118
+ nextCallCount++;
119
+ return await next();
120
+ });
121
+
122
+ await pipeline.run(context, async () => new Response('ok'));
123
+
124
+ expect(nextCallCount).toBe(1);
125
+ });
126
+ });
127
+ });
128
+
129
+ describe('runMiddlewares', () => {
130
+ let context: Context;
131
+
132
+ beforeEach(() => {
133
+ const request = new Request('http://localhost/test');
134
+ context = new Context(request, new Container());
135
+ });
136
+
137
+ test('should run final handler when no middlewares', async () => {
138
+ const response = await runMiddlewares([], context, async () => new Response('final'));
139
+ expect(await response.text()).toBe('final');
140
+ });
141
+
142
+ test('should execute middlewares', async () => {
143
+ const order: number[] = [];
144
+
145
+ const middlewares: Middleware[] = [
146
+ async (ctx, next) => {
147
+ order.push(1);
148
+ return await next();
149
+ },
150
+ async (ctx, next) => {
151
+ order.push(2);
152
+ return await next();
153
+ },
154
+ ];
155
+
156
+ await runMiddlewares(middlewares, context, async () => new Response('ok'));
157
+
158
+ expect(order).toEqual([1, 2]);
159
+ });
160
+ });
@@ -0,0 +1,139 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import {
5
+ Queue,
6
+ Cron,
7
+ getQueueMetadata,
8
+ getCronMetadata,
9
+ type QueueOptions,
10
+ type CronDecoratorOptions,
11
+ } from '../../src/queue/decorators';
12
+
13
+ describe('Queue Decorator', () => {
14
+ test('should set queue metadata with default options', () => {
15
+ class TestService {
16
+ @Queue()
17
+ public processTask(): void {}
18
+ }
19
+
20
+ const metadata = getQueueMetadata(TestService.prototype.processTask);
21
+ expect(metadata).toBeDefined();
22
+ expect(metadata?.name).toBeUndefined();
23
+ });
24
+
25
+ test('should set queue metadata with custom name', () => {
26
+ class TestService {
27
+ @Queue({ name: 'email-queue' })
28
+ public sendEmail(): void {}
29
+ }
30
+
31
+ const metadata = getQueueMetadata(TestService.prototype.sendEmail);
32
+ expect(metadata?.name).toBe('email-queue');
33
+ });
34
+ });
35
+
36
+ describe('Cron Decorator', () => {
37
+ test('should set cron metadata with required pattern', () => {
38
+ class TestService {
39
+ @Cron({ pattern: '0 * * * *' })
40
+ public hourlyTask(): void {}
41
+ }
42
+
43
+ const metadata = getCronMetadata(TestService.prototype.hourlyTask);
44
+ expect(metadata).toBeDefined();
45
+ expect(metadata?.pattern).toBe('0 * * * *');
46
+ expect(metadata?.runOnInit).toBe(false);
47
+ });
48
+
49
+ test('should set cron metadata with timezone', () => {
50
+ class TestService {
51
+ @Cron({ pattern: '0 9 * * *', timezone: 'Asia/Shanghai' })
52
+ public dailyTask(): void {}
53
+ }
54
+
55
+ const metadata = getCronMetadata(TestService.prototype.dailyTask);
56
+ expect(metadata?.timezone).toBe('Asia/Shanghai');
57
+ });
58
+
59
+ test('should set cron metadata with runOnInit', () => {
60
+ class TestService {
61
+ @Cron({ pattern: '*/5 * * * *', runOnInit: true })
62
+ public frequentTask(): void {}
63
+ }
64
+
65
+ const metadata = getCronMetadata(TestService.prototype.frequentTask);
66
+ expect(metadata?.runOnInit).toBe(true);
67
+ });
68
+
69
+ test('should set cron metadata with queueName', () => {
70
+ class TestService {
71
+ @Cron({ pattern: '0 0 * * *', queueName: 'nightly-queue' })
72
+ public nightlyTask(): void {}
73
+ }
74
+
75
+ const metadata = getCronMetadata(TestService.prototype.nightlyTask);
76
+ expect(metadata?.queueName).toBe('nightly-queue');
77
+ });
78
+
79
+ test('should set all cron options together', () => {
80
+ const options: CronDecoratorOptions = {
81
+ pattern: '0 0 * * 0',
82
+ timezone: 'UTC',
83
+ runOnInit: true,
84
+ queueName: 'weekly-queue',
85
+ };
86
+
87
+ class TestService {
88
+ @Cron(options)
89
+ public weeklyTask(): void {}
90
+ }
91
+
92
+ const metadata = getCronMetadata(TestService.prototype.weeklyTask);
93
+ expect(metadata?.pattern).toBe('0 0 * * 0');
94
+ expect(metadata?.timezone).toBe('UTC');
95
+ expect(metadata?.runOnInit).toBe(true);
96
+ expect(metadata?.queueName).toBe('weekly-queue');
97
+ });
98
+ });
99
+
100
+ describe('Metadata getters', () => {
101
+ test('getQueueMetadata should return undefined for non-decorated method', () => {
102
+ class TestService {
103
+ public normalMethod(): void {}
104
+ }
105
+
106
+ const metadata = getQueueMetadata(TestService.prototype.normalMethod);
107
+ expect(metadata).toBeUndefined();
108
+ });
109
+
110
+ test('getCronMetadata should return undefined for non-decorated method', () => {
111
+ class TestService {
112
+ public normalMethod(): void {}
113
+ }
114
+
115
+ const metadata = getCronMetadata(TestService.prototype.normalMethod);
116
+ expect(metadata).toBeUndefined();
117
+ });
118
+
119
+ test('should handle multiple decorated methods independently', () => {
120
+ class TestService {
121
+ @Queue({ name: 'queue1' })
122
+ public task1(): void {}
123
+
124
+ @Queue({ name: 'queue2' })
125
+ public task2(): void {}
126
+
127
+ @Cron({ pattern: '* * * * *' })
128
+ public cronTask(): void {}
129
+ }
130
+
131
+ const queue1Meta = getQueueMetadata(TestService.prototype.task1);
132
+ const queue2Meta = getQueueMetadata(TestService.prototype.task2);
133
+ const cronMeta = getCronMetadata(TestService.prototype.cronTask);
134
+
135
+ expect(queue1Meta?.name).toBe('queue1');
136
+ expect(queue2Meta?.name).toBe('queue2');
137
+ expect(cronMeta?.pattern).toBe('* * * * *');
138
+ });
139
+ });
@@ -0,0 +1,191 @@
1
+ import { describe, expect, test, beforeEach } from 'bun:test';
2
+ import 'reflect-metadata';
3
+
4
+ import { QueueService } from '../../src/queue/service';
5
+ import type { QueueStore, Job, JobData, QueueModuleOptions } from '../../src/queue/types';
6
+ import { QUEUE_OPTIONS_TOKEN } from '../../src/queue/types';
7
+
8
+ // Mock QueueStore
9
+ function createMockStore(): QueueStore {
10
+ const queues = new Map<string, Map<string, Job<JobData>>>();
11
+ let idCounter = 0;
12
+
13
+ return {
14
+ async add<T = JobData>(queueName: string, job: { name: string; data: T; options?: any }): Promise<string> {
15
+ if (!queues.has(queueName)) {
16
+ queues.set(queueName, new Map());
17
+ }
18
+ const jobId = `job-${++idCounter}`;
19
+ const newJob: Job<T> = {
20
+ id: jobId,
21
+ name: job.name,
22
+ data: job.data,
23
+ status: 'waiting',
24
+ createdAt: Date.now(),
25
+ options: job.options,
26
+ };
27
+ queues.get(queueName)!.set(jobId, newJob as Job<JobData>);
28
+ return jobId;
29
+ },
30
+
31
+ async get<T = JobData>(queueName: string, jobId: string): Promise<Job<T> | undefined> {
32
+ return queues.get(queueName)?.get(jobId) as Job<T> | undefined;
33
+ },
34
+
35
+ async delete(queueName: string, jobId: string): Promise<boolean> {
36
+ return queues.get(queueName)?.delete(jobId) ?? false;
37
+ },
38
+
39
+ async clear(queueName: string): Promise<boolean> {
40
+ queues.get(queueName)?.clear();
41
+ return true;
42
+ },
43
+
44
+ async getNext<T = JobData>(queueName: string): Promise<Job<T> | undefined> {
45
+ const queue = queues.get(queueName);
46
+ if (!queue || queue.size === 0) return undefined;
47
+ const first = queue.values().next().value;
48
+ return first as Job<T> | undefined;
49
+ },
50
+
51
+ async complete(queueName: string, jobId: string, result?: any): Promise<boolean> {
52
+ const job = queues.get(queueName)?.get(jobId);
53
+ if (job) {
54
+ job.status = 'completed';
55
+ job.result = result;
56
+ job.completedAt = Date.now();
57
+ return true;
58
+ }
59
+ return false;
60
+ },
61
+
62
+ async fail(queueName: string, jobId: string, error: Error): Promise<boolean> {
63
+ const job = queues.get(queueName)?.get(jobId);
64
+ if (job) {
65
+ job.status = 'failed';
66
+ job.error = error.message;
67
+ return true;
68
+ }
69
+ return false;
70
+ },
71
+
72
+ async getStats(queueName: string) {
73
+ const queue = queues.get(queueName);
74
+ if (!queue) {
75
+ return { waiting: 0, active: 0, completed: 0, failed: 0 };
76
+ }
77
+ let waiting = 0, active = 0, completed = 0, failed = 0;
78
+ for (const job of queue.values()) {
79
+ switch (job.status) {
80
+ case 'waiting': waiting++; break;
81
+ case 'active': active++; break;
82
+ case 'completed': completed++; break;
83
+ case 'failed': failed++; break;
84
+ }
85
+ }
86
+ return { waiting, active, completed, failed };
87
+ },
88
+ };
89
+ }
90
+
91
+ // Helper to create QueueService with mock store
92
+ function createQueueService(options?: Partial<QueueModuleOptions>): QueueService {
93
+ const mockStore = createMockStore();
94
+ const moduleOptions: QueueModuleOptions = {
95
+ store: mockStore,
96
+ enableWorker: false, // Disable worker for unit tests
97
+ ...options,
98
+ };
99
+
100
+ // Create service by setting metadata on a mock class
101
+ const service = new (QueueService as any)({
102
+ ...moduleOptions,
103
+ store: mockStore,
104
+ });
105
+
106
+ return service;
107
+ }
108
+
109
+ describe('QueueService', () => {
110
+ let service: QueueService;
111
+
112
+ beforeEach(() => {
113
+ service = createQueueService();
114
+ });
115
+
116
+ describe('add', () => {
117
+ test('should add job to queue', async () => {
118
+ const jobId = await service.add('test-job', { value: 42 });
119
+ expect(jobId).toBeDefined();
120
+ expect(typeof jobId).toBe('string');
121
+ });
122
+
123
+ test('should add job with options', async () => {
124
+ const jobId = await service.add(
125
+ 'test-job',
126
+ { data: 'test' },
127
+ { priority: 10, retries: 3 },
128
+ );
129
+ expect(jobId).toBeDefined();
130
+ });
131
+
132
+ test('should add job to custom queue', async () => {
133
+ const jobId = await service.add('test-job', {}, undefined, 'custom-queue');
134
+ expect(jobId).toBeDefined();
135
+ });
136
+ });
137
+
138
+ describe('get', () => {
139
+ test('should get job by id', async () => {
140
+ const jobId = await service.add('my-job', { key: 'value' });
141
+ const job = await service.get(jobId);
142
+
143
+ expect(job).toBeDefined();
144
+ expect(job?.name).toBe('my-job');
145
+ expect(job?.data).toEqual({ key: 'value' });
146
+ });
147
+
148
+ test('should return undefined for non-existent job', async () => {
149
+ const job = await service.get('non-existent');
150
+ expect(job).toBeUndefined();
151
+ });
152
+
153
+ test('should get job from custom queue', async () => {
154
+ const jobId = await service.add('job', {}, undefined, 'other-queue');
155
+ const job = await service.get(jobId, 'other-queue');
156
+ expect(job).toBeDefined();
157
+ });
158
+ });
159
+
160
+ describe('delete', () => {
161
+ test('should delete job', async () => {
162
+ const jobId = await service.add('deletable', {});
163
+ const deleted = await service.delete(jobId);
164
+ expect(deleted).toBe(true);
165
+
166
+ const job = await service.get(jobId);
167
+ expect(job).toBeUndefined();
168
+ });
169
+
170
+ test('should return false for non-existent job', async () => {
171
+ const deleted = await service.delete('non-existent');
172
+ expect(deleted).toBe(false);
173
+ });
174
+ });
175
+
176
+ describe('clear', () => {
177
+ test('should clear queue', async () => {
178
+ await service.add('job1', {});
179
+ await service.add('job2', {});
180
+
181
+ const cleared = await service.clear();
182
+ expect(cleared).toBe(true);
183
+ });
184
+
185
+ test('should clear custom queue', async () => {
186
+ await service.add('job', {}, undefined, 'test-queue');
187
+ const cleared = await service.clear('test-queue');
188
+ expect(cleared).toBe(true);
189
+ });
190
+ });
191
+ });