@onebun/core 0.1.2 → 0.1.4
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/package.json +6 -6
- package/src/{application.test.ts → application/application.test.ts} +6 -5
- package/src/{application.ts → application/application.ts} +131 -12
- package/src/application/index.ts +9 -0
- package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
- package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
- package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
- package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
- package/src/{decorators.ts → decorators/decorators.ts} +3 -2
- package/src/decorators/index.ts +15 -0
- package/src/index.ts +47 -134
- package/src/module/index.ts +12 -0
- package/src/{module.test.ts → module/module.test.ts} +3 -2
- package/src/{module.ts → module/module.ts} +6 -5
- package/src/queue/adapters/index.ts +8 -0
- package/src/queue/adapters/memory.adapter.test.ts +405 -0
- package/src/queue/adapters/memory.adapter.ts +509 -0
- package/src/queue/adapters/redis.adapter.ts +673 -0
- package/src/queue/cron-expression.test.ts +145 -0
- package/src/queue/cron-expression.ts +115 -0
- package/src/queue/cron-parser.test.ts +185 -0
- package/src/queue/cron-parser.ts +287 -0
- package/src/queue/decorators.test.ts +292 -0
- package/src/queue/decorators.ts +493 -0
- package/src/queue/docs-examples.test.ts +449 -0
- package/src/queue/guards.test.ts +309 -0
- package/src/queue/guards.ts +307 -0
- package/src/queue/index.ts +118 -0
- package/src/queue/pattern-matcher.test.ts +191 -0
- package/src/queue/pattern-matcher.ts +252 -0
- package/src/queue/queue.service.ts +421 -0
- package/src/queue/scheduler.test.ts +235 -0
- package/src/queue/scheduler.ts +379 -0
- package/src/queue/types.ts +502 -0
- package/src/redis/index.ts +8 -0
- package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
- package/src/service-client/index.ts +10 -0
- package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
- package/src/{service-client.ts → service-client/service-client.ts} +1 -1
- package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
- package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
- package/src/testing/index.ts +7 -0
- package/src/types.ts +34 -5
- package/src/websocket/index.ts +50 -0
- package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
- package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
- package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
- package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
- /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
- /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
- /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
- /package/src/{config.service.ts → module/config.service.ts} +0 -0
- /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
- /package/src/{controller.ts → module/controller.ts} +0 -0
- /package/src/{service.test.ts → module/service.test.ts} +0 -0
- /package/src/{service.ts → module/service.ts} +0 -0
- /package/src/{redis-client.ts → redis/redis-client.ts} +0 -0
- /package/src/{shared-redis.ts → redis/shared-redis.ts} +0 -0
- /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
- /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
- /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
- /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
- /package/src/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
- /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
- /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
- /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
- /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
- /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
- /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
- /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
- /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
- /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
- /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
- /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
- /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
- /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
- /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
- /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
- /package/src/{ws.types.ts → websocket/ws.types.ts} +0 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Examples Tests for Queue Module
|
|
3
|
+
*
|
|
4
|
+
* This file tests code examples from:
|
|
5
|
+
* - docs/api/queue.md
|
|
6
|
+
*
|
|
7
|
+
* Each test case corresponds to a code block in the documentation.
|
|
8
|
+
* Keep these tests in sync with the documentation!
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
describe,
|
|
13
|
+
it,
|
|
14
|
+
expect,
|
|
15
|
+
beforeEach,
|
|
16
|
+
afterEach,
|
|
17
|
+
} from 'bun:test';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
Subscribe,
|
|
21
|
+
Cron,
|
|
22
|
+
Interval,
|
|
23
|
+
Timeout,
|
|
24
|
+
UseMessageGuards,
|
|
25
|
+
OnQueueReady,
|
|
26
|
+
OnQueueError,
|
|
27
|
+
OnMessageReceived,
|
|
28
|
+
OnMessageProcessed,
|
|
29
|
+
OnMessageFailed,
|
|
30
|
+
MessageAuthGuard,
|
|
31
|
+
MessageServiceGuard,
|
|
32
|
+
MessageHeaderGuard,
|
|
33
|
+
MessageTraceGuard,
|
|
34
|
+
MessageAllGuards,
|
|
35
|
+
MessageAnyGuard,
|
|
36
|
+
createMessageGuard,
|
|
37
|
+
type Message,
|
|
38
|
+
CronExpression,
|
|
39
|
+
parseCronExpression,
|
|
40
|
+
getNextRun,
|
|
41
|
+
isValidCronExpression,
|
|
42
|
+
matchQueuePattern,
|
|
43
|
+
isQueuePatternMatch,
|
|
44
|
+
createQueuePatternMatcher,
|
|
45
|
+
InMemoryQueueAdapter,
|
|
46
|
+
getSubscribeMetadata,
|
|
47
|
+
getCronMetadata,
|
|
48
|
+
getIntervalMetadata,
|
|
49
|
+
getTimeoutMetadata,
|
|
50
|
+
hasQueueDecorators,
|
|
51
|
+
} from './index';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @source docs/api/queue.md#quick-start
|
|
55
|
+
*/
|
|
56
|
+
describe('Quick Start Example (docs/api/queue.md)', () => {
|
|
57
|
+
it('should define service with queue decorators', () => {
|
|
58
|
+
// From docs/api/queue.md: Quick Start
|
|
59
|
+
class OrderService {
|
|
60
|
+
@OnQueueReady()
|
|
61
|
+
onReady() {
|
|
62
|
+
// console.log('Queue connected');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Subscribe('orders.created')
|
|
66
|
+
async handleOrderCreated(message: Message<{ orderId: number }>) {
|
|
67
|
+
// console.log('New order:', message.data.orderId);
|
|
68
|
+
expect(message.data.orderId).toBeDefined();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Cron(CronExpression.EVERY_HOUR, { pattern: 'cleanup.expired' })
|
|
72
|
+
getCleanupData() {
|
|
73
|
+
return { timestamp: Date.now() };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Verify decorators are registered
|
|
78
|
+
expect(hasQueueDecorators(OrderService)).toBe(true);
|
|
79
|
+
|
|
80
|
+
const subscriptions = getSubscribeMetadata(OrderService);
|
|
81
|
+
expect(subscriptions.length).toBe(1);
|
|
82
|
+
expect(subscriptions[0].pattern).toBe('orders.created');
|
|
83
|
+
|
|
84
|
+
const cronJobs = getCronMetadata(OrderService);
|
|
85
|
+
expect(cronJobs.length).toBe(1);
|
|
86
|
+
expect(cronJobs[0].expression).toBe(CronExpression.EVERY_HOUR);
|
|
87
|
+
expect(cronJobs[0].options.pattern).toBe('cleanup.expired');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @source docs/api/queue.md#subscribe-decorator
|
|
93
|
+
*/
|
|
94
|
+
describe('Subscribe Decorator Examples (docs/api/queue.md)', () => {
|
|
95
|
+
it('should match wildcard patterns', () => {
|
|
96
|
+
// From docs/api/queue.md: Pattern Syntax table
|
|
97
|
+
// orders.created -> exact match
|
|
98
|
+
expect(matchQueuePattern('orders.created', 'orders.created').matched).toBe(true);
|
|
99
|
+
|
|
100
|
+
// orders.* -> single-level wildcard
|
|
101
|
+
expect(matchQueuePattern('orders.*', 'orders.created').matched).toBe(true);
|
|
102
|
+
expect(matchQueuePattern('orders.*', 'orders.updated').matched).toBe(true);
|
|
103
|
+
|
|
104
|
+
// events.# -> multi-level wildcard
|
|
105
|
+
expect(matchQueuePattern('events.#', 'events.user.created').matched).toBe(true);
|
|
106
|
+
expect(matchQueuePattern('events.#', 'events.order.paid').matched).toBe(true);
|
|
107
|
+
|
|
108
|
+
// orders.{id} -> named parameter
|
|
109
|
+
const result = matchQueuePattern('orders.{id}', 'orders.123');
|
|
110
|
+
expect(result.matched).toBe(true);
|
|
111
|
+
expect(result.params).toEqual({ id: '123' });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should define subscribe with options', () => {
|
|
115
|
+
// From docs/api/queue.md: Subscribe Options
|
|
116
|
+
class OrderProcessor {
|
|
117
|
+
@Subscribe('orders.*', {
|
|
118
|
+
ackMode: 'manual',
|
|
119
|
+
group: 'order-processors',
|
|
120
|
+
prefetch: 10,
|
|
121
|
+
retry: {
|
|
122
|
+
attempts: 3,
|
|
123
|
+
backoff: 'exponential',
|
|
124
|
+
delay: 1000,
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
async handleOrder(message: Message<unknown>) {
|
|
128
|
+
try {
|
|
129
|
+
// await this.processOrder(message.data);
|
|
130
|
+
await message.ack();
|
|
131
|
+
} catch {
|
|
132
|
+
await message.nack(true); // requeue
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const subscriptions = getSubscribeMetadata(OrderProcessor);
|
|
138
|
+
expect(subscriptions.length).toBe(1);
|
|
139
|
+
expect(subscriptions[0].options?.ackMode).toBe('manual');
|
|
140
|
+
expect(subscriptions[0].options?.group).toBe('order-processors');
|
|
141
|
+
expect(subscriptions[0].options?.prefetch).toBe(10);
|
|
142
|
+
expect(subscriptions[0].options?.retry?.attempts).toBe(3);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @source docs/api/queue.md#scheduling-decorators
|
|
148
|
+
*/
|
|
149
|
+
describe('Scheduling Decorators Examples (docs/api/queue.md)', () => {
|
|
150
|
+
it('should define cron job with expression', () => {
|
|
151
|
+
// From docs/api/queue.md: @Cron section
|
|
152
|
+
class ReportService {
|
|
153
|
+
// Daily at 9 AM
|
|
154
|
+
@Cron('0 0 9 * * *', { pattern: 'reports.daily' })
|
|
155
|
+
getDailyReportData() {
|
|
156
|
+
return { type: 'daily', date: new Date() };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Using CronExpression enum
|
|
160
|
+
@Cron(CronExpression.EVERY_HOUR, { pattern: 'health.check' })
|
|
161
|
+
getHealthData() {
|
|
162
|
+
return { status: 'ok' };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const cronJobs = getCronMetadata(ReportService);
|
|
167
|
+
expect(cronJobs.length).toBe(2);
|
|
168
|
+
|
|
169
|
+
expect(cronJobs[0].expression).toBe('0 0 9 * * *');
|
|
170
|
+
expect(cronJobs[0].options.pattern).toBe('reports.daily');
|
|
171
|
+
|
|
172
|
+
expect(cronJobs[1].expression).toBe(CronExpression.EVERY_HOUR);
|
|
173
|
+
expect(cronJobs[1].options.pattern).toBe('health.check');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should define interval job', () => {
|
|
177
|
+
// From docs/api/queue.md: @Interval section
|
|
178
|
+
class MetricsService {
|
|
179
|
+
// Every 60 seconds
|
|
180
|
+
@Interval(60000, { pattern: 'metrics.collect' })
|
|
181
|
+
getMetrics() {
|
|
182
|
+
return { cpu: process.cpuUsage() };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const intervals = getIntervalMetadata(MetricsService);
|
|
187
|
+
expect(intervals.length).toBe(1);
|
|
188
|
+
expect(intervals[0].milliseconds).toBe(60000);
|
|
189
|
+
expect(intervals[0].options.pattern).toBe('metrics.collect');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should define timeout job', () => {
|
|
193
|
+
// From docs/api/queue.md: @Timeout section
|
|
194
|
+
class InitService {
|
|
195
|
+
private startTime = Date.now();
|
|
196
|
+
|
|
197
|
+
// After 5 seconds
|
|
198
|
+
@Timeout(5000, { pattern: 'init.complete' })
|
|
199
|
+
getInitData() {
|
|
200
|
+
return { startedAt: this.startTime };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const timeouts = getTimeoutMetadata(InitService);
|
|
205
|
+
expect(timeouts.length).toBe(1);
|
|
206
|
+
expect(timeouts[0].milliseconds).toBe(5000);
|
|
207
|
+
expect(timeouts[0].options.pattern).toBe('init.complete');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @source docs/api/queue.md#cronexpression-constants
|
|
213
|
+
*/
|
|
214
|
+
describe('CronExpression Constants (docs/api/queue.md)', () => {
|
|
215
|
+
it('should have valid cron expressions', () => {
|
|
216
|
+
// From docs/api/queue.md: CronExpression Constants table
|
|
217
|
+
expect(CronExpression.EVERY_SECOND).toBe('* * * * * *');
|
|
218
|
+
expect(CronExpression.EVERY_5_SECONDS).toBe('*/5 * * * * *');
|
|
219
|
+
expect(CronExpression.EVERY_MINUTE).toBe('0 * * * * *');
|
|
220
|
+
expect(CronExpression.EVERY_5_MINUTES).toBe('0 */5 * * * *');
|
|
221
|
+
expect(CronExpression.EVERY_HOUR).toBe('0 0 * * * *');
|
|
222
|
+
expect(CronExpression.EVERY_DAY_AT_MIDNIGHT).toBe('0 0 0 * * *');
|
|
223
|
+
expect(CronExpression.EVERY_DAY_AT_NOON).toBe('0 0 12 * * *');
|
|
224
|
+
expect(CronExpression.EVERY_WEEKDAY).toBe('0 0 0 * * 1-5');
|
|
225
|
+
expect(CronExpression.EVERY_WEEK).toBe('0 0 0 * * 0');
|
|
226
|
+
expect(CronExpression.EVERY_MONTH).toBe('0 0 0 1 * *');
|
|
227
|
+
|
|
228
|
+
// All should be valid
|
|
229
|
+
expect(isValidCronExpression(CronExpression.EVERY_SECOND)).toBe(true);
|
|
230
|
+
expect(isValidCronExpression(CronExpression.EVERY_HOUR)).toBe(true);
|
|
231
|
+
expect(isValidCronExpression(CronExpression.EVERY_MONTH)).toBe(true);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @source docs/api/queue.md#message-guards
|
|
237
|
+
*/
|
|
238
|
+
describe('Message Guards Examples (docs/api/queue.md)', () => {
|
|
239
|
+
it('should use built-in guards', () => {
|
|
240
|
+
// From docs/api/queue.md: Built-in Guards section
|
|
241
|
+
class SecureService {
|
|
242
|
+
// Require authorization token
|
|
243
|
+
@UseMessageGuards(MessageAuthGuard)
|
|
244
|
+
@Subscribe('secure.events')
|
|
245
|
+
async handleSecure(_message: Message<unknown>) {}
|
|
246
|
+
|
|
247
|
+
// Require specific service
|
|
248
|
+
@UseMessageGuards(new MessageServiceGuard(['payment-service']))
|
|
249
|
+
@Subscribe('internal.events')
|
|
250
|
+
async handleInternal(_message: Message<unknown>) {}
|
|
251
|
+
|
|
252
|
+
// Require header
|
|
253
|
+
@UseMessageGuards(new MessageHeaderGuard('x-api-key'))
|
|
254
|
+
@Subscribe('api.events')
|
|
255
|
+
async handleApi(_message: Message<unknown>) {}
|
|
256
|
+
|
|
257
|
+
// Require trace context
|
|
258
|
+
@UseMessageGuards(MessageTraceGuard)
|
|
259
|
+
@Subscribe('traced.events')
|
|
260
|
+
async handleTraced(_message: Message<unknown>) {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
expect(hasQueueDecorators(SecureService)).toBe(true);
|
|
264
|
+
const subscriptions = getSubscribeMetadata(SecureService);
|
|
265
|
+
expect(subscriptions.length).toBe(4);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should use composite guards', () => {
|
|
269
|
+
// From docs/api/queue.md: Composite Guards section
|
|
270
|
+
class StrictService {
|
|
271
|
+
// All guards must pass
|
|
272
|
+
@UseMessageGuards(
|
|
273
|
+
new MessageAllGuards([MessageAuthGuard, new MessageServiceGuard(['allowed-service'])]),
|
|
274
|
+
)
|
|
275
|
+
@Subscribe('strict.events')
|
|
276
|
+
async handleStrict(_message: Message<unknown>) {}
|
|
277
|
+
|
|
278
|
+
// Any guard can pass
|
|
279
|
+
@UseMessageGuards(
|
|
280
|
+
new MessageAnyGuard([new MessageServiceGuard(['internal-service']), MessageAuthGuard]),
|
|
281
|
+
)
|
|
282
|
+
@Subscribe('flexible.events')
|
|
283
|
+
async handleFlexible(_message: Message<unknown>) {}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
expect(hasQueueDecorators(StrictService)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should create custom guard', () => {
|
|
290
|
+
// From docs/api/queue.md: Custom Guards section
|
|
291
|
+
const customGuard = createMessageGuard((context) => {
|
|
292
|
+
const metadata = context.getMetadata();
|
|
293
|
+
|
|
294
|
+
return metadata.headers?.['x-custom'] === 'expected';
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Verify guard was created
|
|
298
|
+
expect(typeof customGuard.canActivate).toBe('function');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* @source docs/api/queue.md#lifecycle-decorators
|
|
304
|
+
*/
|
|
305
|
+
describe('Lifecycle Decorators Examples (docs/api/queue.md)', () => {
|
|
306
|
+
it('should define lifecycle handlers', () => {
|
|
307
|
+
// From docs/api/queue.md: Lifecycle Decorators section
|
|
308
|
+
class EventService {
|
|
309
|
+
@OnQueueReady()
|
|
310
|
+
handleReady() {
|
|
311
|
+
// console.log('Queue connected');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@OnQueueError()
|
|
315
|
+
handleError(_error: Error) {
|
|
316
|
+
// console.error('Queue error:', error);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@OnMessageReceived()
|
|
320
|
+
handleReceived(_message: Message<unknown>) {
|
|
321
|
+
// console.log(`Received: ${message.id}`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@OnMessageProcessed()
|
|
325
|
+
handleProcessed(_message: Message<unknown>) {
|
|
326
|
+
// console.log(`Processed: ${message.id}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@OnMessageFailed()
|
|
330
|
+
handleFailed(_message: Message<unknown>, _error: Error) {
|
|
331
|
+
// console.error(`Failed: ${message.id}`, error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Class should be defined without errors
|
|
336
|
+
expect(EventService).toBeDefined();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @source docs/api/queue.md#inmemoryqueueadapter
|
|
342
|
+
*/
|
|
343
|
+
describe('InMemoryQueueAdapter Examples (docs/api/queue.md)', () => {
|
|
344
|
+
let adapter: InMemoryQueueAdapter;
|
|
345
|
+
|
|
346
|
+
beforeEach(async () => {
|
|
347
|
+
adapter = new InMemoryQueueAdapter();
|
|
348
|
+
await adapter.connect();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
afterEach(async () => {
|
|
352
|
+
await adapter.disconnect();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should publish and subscribe', async () => {
|
|
356
|
+
// From docs/api/queue.md: InMemoryQueueAdapter section
|
|
357
|
+
const received: Message<unknown>[] = [];
|
|
358
|
+
|
|
359
|
+
await adapter.subscribe('events.*', async (message) => {
|
|
360
|
+
received.push(message);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
await adapter.publish('events.created', { id: 1 });
|
|
364
|
+
|
|
365
|
+
expect(received.length).toBe(1);
|
|
366
|
+
expect((received[0].data as { id: number }).id).toBe(1);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* @source docs/api/queue.md#cron-parser
|
|
372
|
+
*/
|
|
373
|
+
describe('Cron Parser Examples (docs/api/queue.md)', () => {
|
|
374
|
+
it('should parse cron expression', () => {
|
|
375
|
+
// From docs/api/queue.md: Cron Parser section
|
|
376
|
+
const schedule = parseCronExpression('0 30 9 * * 1-5');
|
|
377
|
+
|
|
378
|
+
expect(schedule.seconds).toEqual([0]);
|
|
379
|
+
expect(schedule.minutes).toEqual([30]);
|
|
380
|
+
expect(schedule.hours).toEqual([9]);
|
|
381
|
+
expect(schedule.daysOfMonth.length).toBe(31);
|
|
382
|
+
expect(schedule.months.length).toBe(12);
|
|
383
|
+
expect(schedule.daysOfWeek).toEqual([1, 2, 3, 4, 5]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should get next run time', () => {
|
|
387
|
+
// From docs/api/queue.md: Cron Parser section
|
|
388
|
+
const schedule = parseCronExpression('0 0 * * * *'); // Every hour
|
|
389
|
+
const nextRun = getNextRun(schedule);
|
|
390
|
+
|
|
391
|
+
expect(nextRun).not.toBeNull();
|
|
392
|
+
expect(nextRun!.getMinutes()).toBe(0);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should validate expressions', () => {
|
|
396
|
+
// From docs/api/queue.md: Cron Parser section
|
|
397
|
+
expect(isValidCronExpression('0 0 * * *')).toBe(true);
|
|
398
|
+
expect(isValidCronExpression('invalid')).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @source docs/api/queue.md#pattern-matcher
|
|
404
|
+
*/
|
|
405
|
+
describe('Pattern Matcher Examples (docs/api/queue.md)', () => {
|
|
406
|
+
it('should match with parameter extraction', () => {
|
|
407
|
+
// From docs/api/queue.md: Pattern Matcher section
|
|
408
|
+
const result = matchQueuePattern('orders.{id}.status', 'orders.123.status');
|
|
409
|
+
|
|
410
|
+
expect(result.matched).toBe(true);
|
|
411
|
+
expect(result.params).toEqual({ id: '123' });
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should perform simple match check', () => {
|
|
415
|
+
// From docs/api/queue.md: Pattern Matcher section
|
|
416
|
+
expect(isQueuePatternMatch('events.*', 'events.user')).toBe(true);
|
|
417
|
+
expect(isQueuePatternMatch('events.*', 'orders.user')).toBe(false);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it('should create reusable matcher', () => {
|
|
421
|
+
// From docs/api/queue.md: Pattern Matcher section
|
|
422
|
+
const matcher = createQueuePatternMatcher('orders.*.status');
|
|
423
|
+
|
|
424
|
+
expect(matcher('orders.123.status').matched).toBe(true);
|
|
425
|
+
expect(matcher('orders.456.status').matched).toBe(true);
|
|
426
|
+
expect(matcher('orders.123.other').matched).toBe(false);
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @source docs/api/queue.md#feature-support-matrix
|
|
432
|
+
*/
|
|
433
|
+
describe('Feature Support Matrix (docs/api/queue.md)', () => {
|
|
434
|
+
it('should report correct feature support for InMemoryQueueAdapter', () => {
|
|
435
|
+
// From docs/api/queue.md: Feature Support Matrix table
|
|
436
|
+
const adapter = new InMemoryQueueAdapter();
|
|
437
|
+
|
|
438
|
+
// Supported
|
|
439
|
+
expect(adapter.supports('pattern-subscriptions')).toBe(true);
|
|
440
|
+
expect(adapter.supports('delayed-messages')).toBe(true);
|
|
441
|
+
expect(adapter.supports('priority')).toBe(true);
|
|
442
|
+
expect(adapter.supports('scheduled-jobs')).toBe(true);
|
|
443
|
+
|
|
444
|
+
// Not supported
|
|
445
|
+
expect(adapter.supports('consumer-groups')).toBe(false);
|
|
446
|
+
expect(adapter.supports('dead-letter-queue')).toBe(false);
|
|
447
|
+
expect(adapter.supports('retry')).toBe(false);
|
|
448
|
+
});
|
|
449
|
+
});
|