@openora/core 0.4.1-canary.37 → 0.4.1-canary.39

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 (259) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/admin-console/__tests__/backoffice.router.test.js +67 -51
  3. package/dist/admin-console/__tests__/backoffice.router.test.js.map +1 -1
  4. package/dist/admin-console/__tests__/backoffice.service.test.js +1 -13
  5. package/dist/admin-console/__tests__/backoffice.service.test.js.map +1 -1
  6. package/dist/analytics/__tests__/analytics.contract.test.js +12 -0
  7. package/dist/analytics/__tests__/analytics.contract.test.js.map +1 -1
  8. package/dist/analytics/__tests__/financial-analytics.service.test.js +3 -3
  9. package/dist/analytics/__tests__/financial-analytics.service.test.js.map +1 -1
  10. package/dist/analytics/service/financial-analytics.service.d.ts.map +1 -1
  11. package/dist/analytics/service/financial-analytics.service.js +14 -17
  12. package/dist/analytics/service/financial-analytics.service.js.map +1 -1
  13. package/dist/analytics/service/funnel-analytics.service.d.ts.map +1 -1
  14. package/dist/analytics/service/funnel-analytics.service.js +8 -11
  15. package/dist/analytics/service/funnel-analytics.service.js.map +1 -1
  16. package/dist/audit/__tests__/audit.service.test.js +128 -251
  17. package/dist/audit/__tests__/audit.service.test.js.map +1 -1
  18. package/dist/casino/gaming/__tests__/gaming.service.test.js +83 -89
  19. package/dist/casino/gaming/__tests__/gaming.service.test.js.map +1 -1
  20. package/dist/casino/lobby/__tests__/lobby.cache.test.js +42 -33
  21. package/dist/casino/lobby/__tests__/lobby.cache.test.js.map +1 -1
  22. package/dist/cms/__tests__/cms.reads.test.js +59 -80
  23. package/dist/cms/__tests__/cms.reads.test.js.map +1 -1
  24. package/dist/cms/__tests__/cms.router.test.js +62 -20
  25. package/dist/cms/__tests__/cms.router.test.js.map +1 -1
  26. package/dist/cms/__tests__/cms.service.test.js +116 -14
  27. package/dist/cms/__tests__/cms.service.test.js.map +1 -1
  28. package/dist/common/errors/__tests__/errors.mapper.test.d.ts +2 -0
  29. package/dist/common/errors/__tests__/errors.mapper.test.d.ts.map +1 -0
  30. package/dist/common/errors/__tests__/errors.mapper.test.js +59 -0
  31. package/dist/common/errors/__tests__/errors.mapper.test.js.map +1 -0
  32. package/dist/compliance/__tests__/assert.test.d.ts +2 -0
  33. package/dist/compliance/__tests__/assert.test.d.ts.map +1 -0
  34. package/dist/compliance/__tests__/assert.test.js +34 -0
  35. package/dist/compliance/__tests__/assert.test.js.map +1 -0
  36. package/dist/compliance/__tests__/compliance.service.test.js +149 -84
  37. package/dist/compliance/__tests__/compliance.service.test.js.map +1 -1
  38. package/dist/compliance/__tests__/kyc.service.test.js +247 -155
  39. package/dist/compliance/__tests__/kyc.service.test.js.map +1 -1
  40. package/dist/compliance/__tests__/limits.contract.test.d.ts +2 -0
  41. package/dist/compliance/__tests__/limits.contract.test.d.ts.map +1 -0
  42. package/dist/compliance/__tests__/limits.contract.test.js +43 -0
  43. package/dist/compliance/__tests__/limits.contract.test.js.map +1 -0
  44. package/dist/compliance/__tests__/rg-monitoring.service.test.js +256 -101
  45. package/dist/compliance/__tests__/rg-monitoring.service.test.js.map +1 -1
  46. package/dist/compliance/__tests__/rg.contract.test.js +1 -20
  47. package/dist/compliance/__tests__/rg.contract.test.js.map +1 -1
  48. package/dist/compliance/__tests__/rg.router.test.js +113 -65
  49. package/dist/compliance/__tests__/rg.router.test.js.map +1 -1
  50. package/dist/compliance/__tests__/rg.service.test.js +277 -235
  51. package/dist/compliance/__tests__/rg.service.test.js.map +1 -1
  52. package/dist/compliance/service/rg-monitoring.service.d.ts.map +1 -1
  53. package/dist/compliance/service/rg-monitoring.service.js +11 -8
  54. package/dist/compliance/service/rg-monitoring.service.js.map +1 -1
  55. package/dist/contracts/__tests__/compose-contract.test.d.ts +2 -0
  56. package/dist/contracts/__tests__/compose-contract.test.d.ts.map +1 -0
  57. package/dist/contracts/__tests__/compose-contract.test.js +27 -0
  58. package/dist/contracts/__tests__/compose-contract.test.js.map +1 -0
  59. package/dist/contracts/adapters/__tests__/token.test.d.ts +2 -0
  60. package/dist/contracts/adapters/__tests__/token.test.d.ts.map +1 -0
  61. package/dist/contracts/adapters/__tests__/token.test.js +14 -0
  62. package/dist/contracts/adapters/__tests__/token.test.js.map +1 -0
  63. package/dist/contracts/schemas/__tests__/events.test.d.ts +2 -0
  64. package/dist/contracts/schemas/__tests__/events.test.d.ts.map +1 -0
  65. package/dist/contracts/schemas/__tests__/events.test.js +51 -0
  66. package/dist/contracts/schemas/__tests__/events.test.js.map +1 -0
  67. package/dist/contracts/schemas/__tests__/igaming-config.test.d.ts +2 -0
  68. package/dist/contracts/schemas/__tests__/igaming-config.test.d.ts.map +1 -0
  69. package/dist/contracts/schemas/__tests__/igaming-config.test.js +38 -0
  70. package/dist/contracts/schemas/__tests__/igaming-config.test.js.map +1 -0
  71. package/dist/contracts/schemas/reporting.d.ts +7 -2
  72. package/dist/contracts/schemas/reporting.d.ts.map +1 -1
  73. package/dist/contracts/schemas/reporting.js +2 -1
  74. package/dist/contracts/schemas/reporting.js.map +1 -1
  75. package/dist/engagement/chat/__tests__/chat.service.test.js +524 -364
  76. package/dist/engagement/chat/__tests__/chat.service.test.js.map +1 -1
  77. package/dist/engagement/notifications/__tests__/notifications.service.test.js +125 -68
  78. package/dist/engagement/notifications/__tests__/notifications.service.test.js.map +1 -1
  79. package/dist/iam/__tests__/iam.service.test.js +367 -596
  80. package/dist/iam/__tests__/iam.service.test.js.map +1 -1
  81. package/dist/pam/identity/__tests__/admin-user-directory.test.js +144 -68
  82. package/dist/pam/identity/__tests__/admin-user-directory.test.js.map +1 -1
  83. package/dist/pam/identity/__tests__/identity.rate-limit.test.js +33 -22
  84. package/dist/pam/identity/__tests__/identity.rate-limit.test.js.map +1 -1
  85. package/dist/pam/identity/__tests__/identity.service.test.js +217 -298
  86. package/dist/pam/identity/__tests__/identity.service.test.js.map +1 -1
  87. package/dist/pam/identity/__tests__/login-enforcement.service.test.js +97 -32
  88. package/dist/pam/identity/__tests__/login-enforcement.service.test.js.map +1 -1
  89. package/dist/pam/identity/__tests__/phone-login.service.test.js +163 -134
  90. package/dist/pam/identity/__tests__/phone-login.service.test.js.map +1 -1
  91. package/dist/pam/identity/__tests__/rg-guard.test.d.ts +2 -0
  92. package/dist/pam/identity/__tests__/rg-guard.test.d.ts.map +1 -0
  93. package/dist/pam/identity/__tests__/rg-guard.test.js +21 -0
  94. package/dist/pam/identity/__tests__/rg-guard.test.js.map +1 -0
  95. package/dist/pam/player-management/__tests__/kyc-status-writer.test.js +73 -26
  96. package/dist/pam/player-management/__tests__/kyc-status-writer.test.js.map +1 -1
  97. package/dist/pam/player-management/__tests__/player.router.test.js +63 -46
  98. package/dist/pam/player-management/__tests__/player.router.test.js.map +1 -1
  99. package/dist/pam/player-management/__tests__/player.service.test.js +207 -111
  100. package/dist/pam/player-management/__tests__/player.service.test.js.map +1 -1
  101. package/dist/pam/profile/__tests__/profile.service.test.js +89 -41
  102. package/dist/pam/profile/__tests__/profile.service.test.js.map +1 -1
  103. package/dist/pam/shared/__tests__/shared-mappers.test.d.ts +2 -0
  104. package/dist/pam/shared/__tests__/shared-mappers.test.d.ts.map +1 -0
  105. package/dist/pam/shared/__tests__/shared-mappers.test.js +55 -0
  106. package/dist/pam/shared/__tests__/shared-mappers.test.js.map +1 -0
  107. package/dist/pam/tag/__tests__/get-active-tag-keys.test.js +72 -30
  108. package/dist/pam/tag/__tests__/get-active-tag-keys.test.js.map +1 -1
  109. package/dist/pam/tag/__tests__/tag-evaluation.service.test.js +308 -235
  110. package/dist/pam/tag/__tests__/tag-evaluation.service.test.js.map +1 -1
  111. package/dist/pam/tag/__tests__/tag-rule.service.test.js +136 -140
  112. package/dist/pam/tag/__tests__/tag-rule.service.test.js.map +1 -1
  113. package/dist/pam/tag/__tests__/tag.router.test.js +76 -25
  114. package/dist/pam/tag/__tests__/tag.router.test.js.map +1 -1
  115. package/dist/pam/tag/__tests__/tag.service.test.js +224 -247
  116. package/dist/pam/tag/__tests__/tag.service.test.js.map +1 -1
  117. package/dist/server/auth/__tests__/admin-guard.test.js +107 -117
  118. package/dist/server/auth/__tests__/admin-guard.test.js.map +1 -1
  119. package/dist/server/auth/__tests__/permission-levels.test.d.ts +2 -0
  120. package/dist/server/auth/__tests__/permission-levels.test.d.ts.map +1 -0
  121. package/dist/server/auth/__tests__/permission-levels.test.js +89 -0
  122. package/dist/server/auth/__tests__/permission-levels.test.js.map +1 -0
  123. package/dist/server/auth/__tests__/session-resolver.test.js +45 -13
  124. package/dist/server/auth/__tests__/session-resolver.test.js.map +1 -1
  125. package/dist/server/db/__tests__/query-helpers.test.js +72 -1
  126. package/dist/server/db/__tests__/query-helpers.test.js.map +1 -1
  127. package/dist/server/db/outbox/__tests__/relay.test.js +74 -81
  128. package/dist/server/db/outbox/__tests__/relay.test.js.map +1 -1
  129. package/dist/server/kernel/__tests__/bullmq-job-queue.test.js +204 -154
  130. package/dist/server/kernel/__tests__/bullmq-job-queue.test.js.map +1 -1
  131. package/dist/server/kernel/__tests__/cache.test.js +26 -13
  132. package/dist/server/kernel/__tests__/cache.test.js.map +1 -1
  133. package/dist/server/kernel/__tests__/domain-error.test.d.ts +2 -0
  134. package/dist/server/kernel/__tests__/domain-error.test.d.ts.map +1 -0
  135. package/dist/server/kernel/__tests__/domain-error.test.js +35 -0
  136. package/dist/server/kernel/__tests__/domain-error.test.js.map +1 -0
  137. package/dist/server/kernel/__tests__/error-reporter.test.d.ts +2 -0
  138. package/dist/server/kernel/__tests__/error-reporter.test.d.ts.map +1 -0
  139. package/dist/server/kernel/__tests__/error-reporter.test.js +52 -0
  140. package/dist/server/kernel/__tests__/error-reporter.test.js.map +1 -0
  141. package/dist/server/kernel/__tests__/event-bus.test.js +60 -20
  142. package/dist/server/kernel/__tests__/event-bus.test.js.map +1 -1
  143. package/dist/server/kernel/__tests__/orpc-error-map.test.d.ts +2 -0
  144. package/dist/server/kernel/__tests__/orpc-error-map.test.d.ts.map +1 -0
  145. package/dist/server/kernel/__tests__/orpc-error-map.test.js +54 -0
  146. package/dist/server/kernel/__tests__/orpc-error-map.test.js.map +1 -0
  147. package/dist/server/kernel/__tests__/platform-config-loader.test.d.ts +2 -0
  148. package/dist/server/kernel/__tests__/platform-config-loader.test.d.ts.map +1 -0
  149. package/dist/server/kernel/__tests__/platform-config-loader.test.js +112 -0
  150. package/dist/server/kernel/__tests__/platform-config-loader.test.js.map +1 -0
  151. package/dist/server/kernel/__tests__/rate-limiter.test.js +22 -13
  152. package/dist/server/kernel/__tests__/rate-limiter.test.js.map +1 -1
  153. package/dist/server/kernel/__tests__/realtime-transport.test.d.ts.map +1 -0
  154. package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.js.map +1 -1
  155. package/dist/server/kernel/__tests__/redis-cache.test.js +34 -34
  156. package/dist/server/kernel/__tests__/redis-cache.test.js.map +1 -1
  157. package/dist/server/kernel/__tests__/redis-rate-limiter.test.js +49 -44
  158. package/dist/server/kernel/__tests__/redis-rate-limiter.test.js.map +1 -1
  159. package/dist/server/kernel/__tests__/redis-streams-broker.test.js +141 -262
  160. package/dist/server/kernel/__tests__/redis-streams-broker.test.js.map +1 -1
  161. package/dist/server/kernel/__tests__/request-context.test.d.ts +2 -0
  162. package/dist/server/kernel/__tests__/request-context.test.d.ts.map +1 -0
  163. package/dist/server/kernel/__tests__/request-context.test.js +64 -0
  164. package/dist/server/kernel/__tests__/request-context.test.js.map +1 -0
  165. package/dist/server/kernel/__tests__/router-utils.test.d.ts +2 -0
  166. package/dist/server/kernel/__tests__/router-utils.test.d.ts.map +1 -0
  167. package/dist/server/kernel/__tests__/router-utils.test.js +80 -0
  168. package/dist/server/kernel/__tests__/router-utils.test.js.map +1 -0
  169. package/dist/server/kernel/bullmq-job-queue.d.ts.map +1 -1
  170. package/dist/server/kernel/bullmq-job-queue.js +8 -2
  171. package/dist/server/kernel/bullmq-job-queue.js.map +1 -1
  172. package/dist/server/runtime/__tests__/create-app.test.js +26 -18
  173. package/dist/server/runtime/__tests__/create-app.test.js.map +1 -1
  174. package/dist/testing/index.d.ts +2 -6
  175. package/dist/testing/index.d.ts.map +1 -1
  176. package/dist/testing/index.js +2 -10
  177. package/dist/testing/index.js.map +1 -1
  178. package/dist/testing/mock.d.ts +28 -16
  179. package/dist/testing/mock.d.ts.map +1 -1
  180. package/dist/testing/mock.js +58 -37
  181. package/dist/testing/mock.js.map +1 -1
  182. package/dist/testing/real-infra.d.ts +47 -0
  183. package/dist/testing/real-infra.d.ts.map +1 -0
  184. package/dist/testing/real-infra.js +122 -0
  185. package/dist/testing/real-infra.js.map +1 -0
  186. package/dist/wallet/__tests__/admin-reporting.test.js +201 -90
  187. package/dist/wallet/__tests__/admin-reporting.test.js.map +1 -1
  188. package/dist/wallet/__tests__/rail-for.test.d.ts +2 -0
  189. package/dist/wallet/__tests__/rail-for.test.d.ts.map +1 -0
  190. package/dist/wallet/__tests__/rail-for.test.js +22 -0
  191. package/dist/wallet/__tests__/rail-for.test.js.map +1 -0
  192. package/dist/wallet/__tests__/wallet-auto-rule.router.test.js +82 -63
  193. package/dist/wallet/__tests__/wallet-auto-rule.router.test.js.map +1 -1
  194. package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js +414 -437
  195. package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js.map +1 -1
  196. package/dist/wallet/__tests__/wallet-commands.service.test.js +116 -155
  197. package/dist/wallet/__tests__/wallet-commands.service.test.js.map +1 -1
  198. package/dist/wallet/__tests__/wallet-kyc-gate.test.js +63 -42
  199. package/dist/wallet/__tests__/wallet-kyc-gate.test.js.map +1 -1
  200. package/dist/wallet/__tests__/wallet-webhook.router.test.js +129 -51
  201. package/dist/wallet/__tests__/wallet-webhook.router.test.js.map +1 -1
  202. package/dist/wallet/__tests__/wallet.rate-limit.test.js +74 -21
  203. package/dist/wallet/__tests__/wallet.rate-limit.test.js.map +1 -1
  204. package/dist/wallet/__tests__/wallet.router.test.js +56 -36
  205. package/dist/wallet/__tests__/wallet.router.test.js.map +1 -1
  206. package/dist/wallet/__tests__/wallet.service.test.js +623 -887
  207. package/dist/wallet/__tests__/wallet.service.test.js.map +1 -1
  208. package/package.json +1 -2
  209. package/dist/casino/lobby/__tests__/lobby.service.test.d.ts +0 -2
  210. package/dist/casino/lobby/__tests__/lobby.service.test.d.ts.map +0 -1
  211. package/dist/casino/lobby/__tests__/lobby.service.test.js +0 -11
  212. package/dist/casino/lobby/__tests__/lobby.service.test.js.map +0 -1
  213. package/dist/contracts/schemas/__tests__/identity-schemas.test.d.ts +0 -2
  214. package/dist/contracts/schemas/__tests__/identity-schemas.test.d.ts.map +0 -1
  215. package/dist/contracts/schemas/__tests__/identity-schemas.test.js +0 -43
  216. package/dist/contracts/schemas/__tests__/identity-schemas.test.js.map +0 -1
  217. package/dist/testing/fakes/__tests__/cache.test.d.ts +0 -2
  218. package/dist/testing/fakes/__tests__/cache.test.d.ts.map +0 -1
  219. package/dist/testing/fakes/__tests__/cache.test.js +0 -89
  220. package/dist/testing/fakes/__tests__/cache.test.js.map +0 -1
  221. package/dist/testing/fakes/__tests__/job-queue.test.d.ts +0 -2
  222. package/dist/testing/fakes/__tests__/job-queue.test.d.ts.map +0 -1
  223. package/dist/testing/fakes/__tests__/job-queue.test.js +0 -135
  224. package/dist/testing/fakes/__tests__/job-queue.test.js.map +0 -1
  225. package/dist/testing/fakes/__tests__/rate-limiter.test.d.ts +0 -2
  226. package/dist/testing/fakes/__tests__/rate-limiter.test.d.ts.map +0 -1
  227. package/dist/testing/fakes/__tests__/rate-limiter.test.js +0 -83
  228. package/dist/testing/fakes/__tests__/rate-limiter.test.js.map +0 -1
  229. package/dist/testing/fakes/__tests__/realtime-authorizer.test.d.ts +0 -2
  230. package/dist/testing/fakes/__tests__/realtime-authorizer.test.d.ts.map +0 -1
  231. package/dist/testing/fakes/__tests__/realtime-authorizer.test.js +0 -19
  232. package/dist/testing/fakes/__tests__/realtime-authorizer.test.js.map +0 -1
  233. package/dist/testing/fakes/__tests__/realtime-transport.test.d.ts.map +0 -1
  234. package/dist/testing/fakes/cache.d.ts +0 -14
  235. package/dist/testing/fakes/cache.d.ts.map +0 -1
  236. package/dist/testing/fakes/cache.js +0 -56
  237. package/dist/testing/fakes/cache.js.map +0 -1
  238. package/dist/testing/fakes/event-bus.d.ts +0 -17
  239. package/dist/testing/fakes/event-bus.d.ts.map +0 -1
  240. package/dist/testing/fakes/event-bus.js +0 -30
  241. package/dist/testing/fakes/event-bus.js.map +0 -1
  242. package/dist/testing/fakes/job-queue.d.ts +0 -40
  243. package/dist/testing/fakes/job-queue.d.ts.map +0 -1
  244. package/dist/testing/fakes/job-queue.js +0 -200
  245. package/dist/testing/fakes/job-queue.js.map +0 -1
  246. package/dist/testing/fakes/rate-limiter.d.ts +0 -11
  247. package/dist/testing/fakes/rate-limiter.d.ts.map +0 -1
  248. package/dist/testing/fakes/rate-limiter.js +0 -39
  249. package/dist/testing/fakes/rate-limiter.js.map +0 -1
  250. package/dist/testing/fakes/realtime-authorizer.d.ts +0 -10
  251. package/dist/testing/fakes/realtime-authorizer.d.ts.map +0 -1
  252. package/dist/testing/fakes/realtime-authorizer.js +0 -10
  253. package/dist/testing/fakes/realtime-authorizer.js.map +0 -1
  254. package/dist/testing/fakes/realtime-transport.d.ts +0 -2
  255. package/dist/testing/fakes/realtime-transport.d.ts.map +0 -1
  256. package/dist/testing/fakes/realtime-transport.js +0 -2
  257. package/dist/testing/fakes/realtime-transport.js.map +0 -1
  258. /package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.d.ts +0 -0
  259. /package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.js +0 -0
@@ -1,124 +1,117 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { mock } from '../../../../testing/mock.js';
1
+ import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { eq, sql } from 'drizzle-orm';
4
+ import { createTestDb } from '@openora/core/testing';
5
+ import { migrate } from '@openora/core/server/migrate';
3
6
  import { OutboxRelay } from '../relay.js';
4
- function row(id, topic) {
5
- return {
6
- eventId: id,
7
- topic,
8
- payload: { id },
9
- schemaVersion: 1,
10
- traceId: null,
11
- orderingKey: null,
12
- occurredAt: new Date('2026-01-01T00:00:00.000Z'),
13
- publishedAt: null,
14
- };
7
+ import { eventOutbox } from '../schema.js';
8
+ let db;
9
+ async function seedRow(overrides = {}) {
10
+ const [row] = await db.drizzle.db
11
+ .insert(eventOutbox)
12
+ .values({
13
+ eventId: randomUUID(),
14
+ topic: 'wallet.deposit.completed',
15
+ payload: { id: randomUUID() },
16
+ occurredAt: new Date(),
17
+ ...overrides,
18
+ })
19
+ .returning();
20
+ return row;
15
21
  }
16
- // Fake drizzle: transaction() answers the SKIP LOCKED claim select; each row is then
17
- // marked via a top-level update(). trackPublish links that update() to the row the
18
- // broker just published, so a mid-batch throw leaves already-marked rows published.
19
- function harness(rows) {
20
- let lastPublishedId = null;
21
- const db = mock({
22
- transaction: async (callback) => callback({
23
- select: () => ({
24
- from: () => ({
25
- where: () => ({
26
- orderBy: () => ({
27
- limit: () => ({
28
- for: async () => rows.filter((r) => r.publishedAt === null).map((r) => ({ ...r })),
29
- }),
30
- }),
31
- }),
32
- }),
33
- }),
34
- }),
35
- update: () => ({
36
- set: (values) => ({
37
- where: async () => {
38
- const r = rows.find((x) => x.eventId === lastPublishedId);
39
- if (r) {
40
- r.publishedAt = values.publishedAt;
41
- }
42
- },
43
- }),
44
- }),
45
- });
46
- return { db, trackPublish: (eventId) => (lastPublishedId = eventId) };
47
- }
48
- function brokerThat(trackPublish, onPublish) {
22
+ function brokerThat(onPublish) {
49
23
  return {
50
- publish: vi.fn(async (env) => {
51
- trackPublish(env.eventId);
52
- onPublish?.(env.eventId);
24
+ publish: vi.fn(async (envelope) => {
25
+ onPublish?.(envelope);
53
26
  }),
54
27
  subscribe: () => () => { },
55
28
  close: async () => { },
56
29
  };
57
30
  }
58
- describe('OutboxRelay.drainOnce', () => {
31
+ async function rowsById() {
32
+ const rows = await db.drizzle.db.select().from(eventOutbox);
33
+ return new Map(rows.map((r) => [r.eventId, r]));
34
+ }
35
+ beforeAll(async () => {
36
+ db = await createTestDb([migrate]);
37
+ });
38
+ afterAll(async () => {
39
+ await db.drop();
40
+ });
41
+ beforeEach(async () => {
42
+ await db.drizzle.db.execute(sql `TRUNCATE ${eventOutbox} RESTART IDENTITY CASCADE`);
43
+ });
44
+ describe('OutboxRelay.drainOnce (real PG)', () => {
59
45
  it('publishes pending rows in order and marks them published', async () => {
60
- const rows = [row('a', 'wallet.deposit.completed'), row('b', 'wallet.withdrawal.completed')];
61
- const { db, trackPublish } = harness(rows);
62
- const broker = brokerThat(trackPublish);
63
- const relay = new OutboxRelay(db, broker);
46
+ const a = await seedRow({
47
+ topic: 'wallet.deposit.completed',
48
+ occurredAt: new Date('2026-01-01T00:00:00.000Z'),
49
+ });
50
+ await seedRow({
51
+ topic: 'wallet.withdrawal.completed',
52
+ occurredAt: new Date('2026-01-01T00:00:01.000Z'),
53
+ });
54
+ const broker = brokerThat();
55
+ const relay = new OutboxRelay(db.drizzle.db, broker);
64
56
  const n = await relay.drainOnce();
65
57
  expect(n).toBe(2);
66
58
  expect(broker.publish).toHaveBeenCalledTimes(2);
67
59
  expect(broker.publish.mock.calls[0][0]).toMatchObject({
68
- eventId: 'a',
69
- topic: 'wallet.deposit.completed',
60
+ eventId: a.eventId,
61
+ topic: a.topic,
70
62
  });
63
+ const rows = await db.drizzle.db.select().from(eventOutbox);
71
64
  expect(rows.every((r) => r.publishedAt !== null)).toBe(true);
72
65
  });
73
66
  it('is a no-op when nothing is pending', async () => {
74
- const rows = [{ ...row('a', 'x'), publishedAt: new Date() }];
75
- const { db, trackPublish } = harness(rows);
76
- const broker = brokerThat(trackPublish);
77
- const relay = new OutboxRelay(db, broker);
67
+ await seedRow({ publishedAt: new Date() });
68
+ const broker = brokerThat();
69
+ const relay = new OutboxRelay(db.drizzle.db, broker);
78
70
  const n = await relay.drainOnce();
79
71
  expect(n).toBe(0);
80
72
  expect(broker.publish).not.toHaveBeenCalled();
81
73
  });
82
74
  it('leaves the row pending when broker.publish throws', async () => {
83
- const rows = [row('a', 'wallet.deposit.completed')];
84
- const { db, trackPublish } = harness(rows);
75
+ const row = await seedRow();
85
76
  const broker = {
86
- publish: vi.fn(async (env) => {
87
- trackPublish(env.eventId);
77
+ publish: vi.fn(async () => {
88
78
  throw new Error('broker unreachable');
89
79
  }),
90
80
  subscribe: () => () => { },
91
81
  close: async () => { },
92
82
  };
93
- const relay = new OutboxRelay(db, broker);
83
+ const relay = new OutboxRelay(db.drizzle.db, broker);
94
84
  await expect(relay.drainOnce()).rejects.toThrow('broker unreachable');
95
- expect(rows[0]?.publishedAt).toBeNull();
85
+ const [after] = await db.drizzle.db
86
+ .select()
87
+ .from(eventOutbox)
88
+ .where(eq(eventOutbox.eventId, row.eventId));
89
+ expect(after?.publishedAt).toBeNull();
96
90
  });
97
91
  it('keeps rows published before a mid-batch publish failure, retrying only the failing row', async () => {
98
- const rows = [
99
- row('a', 'wallet.deposit.completed'),
100
- row('b', 'wallet.withdrawal.completed'),
101
- row('c', 'wallet.deposit.completed'),
102
- ];
103
- const { db, trackPublish } = harness(rows);
104
- const broker = brokerThat(trackPublish, (eventId) => {
105
- if (eventId === 'b') {
92
+ const a = await seedRow({ occurredAt: new Date('2026-01-01T00:00:00.000Z') });
93
+ const b = await seedRow({ occurredAt: new Date('2026-01-01T00:00:01.000Z') });
94
+ const c = await seedRow({ occurredAt: new Date('2026-01-01T00:00:02.000Z') });
95
+ const broker = brokerThat((envelope) => {
96
+ if (envelope.eventId === b.eventId) {
106
97
  throw new Error('broker unreachable');
107
98
  }
108
99
  });
109
- const relay = new OutboxRelay(db, broker);
100
+ const relay = new OutboxRelay(db.drizzle.db, broker);
110
101
  await expect(relay.drainOnce()).rejects.toThrow('broker unreachable');
111
- expect(rows[0]?.publishedAt).not.toBeNull(); // a - published before the failure
112
- expect(rows[1]?.publishedAt).toBeNull(); // b - failed
113
- expect(rows[2]?.publishedAt).toBeNull(); // c - never reached
102
+ const afterFailure = await rowsById();
103
+ expect(afterFailure.get(a.eventId)?.publishedAt).not.toBeNull();
104
+ expect(afterFailure.get(b.eventId)?.publishedAt).toBeNull();
105
+ expect(afterFailure.get(c.eventId)?.publishedAt).toBeNull();
114
106
  const publishSpy = broker.publish;
115
107
  publishSpy.mockClear();
116
- publishSpy.mockImplementation(async (env) => trackPublish(env.eventId));
108
+ publishSpy.mockImplementation(async () => undefined);
117
109
  const n = await relay.drainOnce();
118
110
  expect(n).toBe(2);
119
111
  expect(publishSpy).toHaveBeenCalledTimes(2);
120
- expect(publishSpy.mock.calls.map((c) => c[0].eventId)).toEqual(['b', 'c']);
121
- expect(rows.every((r) => r.publishedAt !== null)).toBe(true);
112
+ expect(publishSpy.mock.calls.map((call) => call[0].eventId)).toEqual([b.eventId, c.eventId]);
113
+ const afterRetry = await rowsById();
114
+ expect([...afterRetry.values()].every((r) => r.publishedAt !== null)).toBe(true);
122
115
  });
123
116
  });
124
117
  //# sourceMappingURL=relay.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"relay.test.js","sourceRoot":"","sources":["../../../../../src/server/db/outbox/__tests__/relay.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay.test.js","sourceRoot":"","sources":["../../../../../src/server/db/outbox/__tests__/relay.test.ts"],"names":[],"mappings":"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"}
@@ -1,190 +1,240 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
1
+ import { describe, it, expect, vi, beforeAll, afterEach, afterAll } from 'vitest';
2
+ import { Queue } from 'bullmq';
2
3
  import { z } from 'zod';
3
4
  import { queue } from '@openora/core/contracts';
4
- const { queues, workers, closeOrder, FakeQueue, FakeWorker } = vi.hoisted(() => {
5
- const closeOrder = [];
6
- class FakeQueue {
7
- name;
8
- opts;
9
- add = vi.fn(async () => ({ id: 'job-1' }));
10
- upsertJobScheduler = vi.fn(async () => ({}));
11
- removeJobScheduler = vi.fn(async () => true);
12
- close = vi.fn(async () => {
13
- closeOrder.push('queue');
14
- });
15
- constructor(name, opts) {
16
- this.name = name;
17
- this.opts = opts;
18
- queues.push(this);
19
- }
20
- }
21
- class FakeWorker {
22
- name;
23
- processor;
24
- opts;
25
- handlers = new Map();
26
- close = vi.fn(async () => {
27
- closeOrder.push('worker');
28
- });
29
- constructor(name, processor, opts) {
30
- this.name = name;
31
- this.processor = processor;
32
- this.opts = opts;
33
- workers.push(this);
34
- }
35
- on(event, cb) {
36
- this.handlers.set(event, cb);
37
- return this;
38
- }
39
- }
40
- const queues = [];
41
- const workers = [];
42
- return { queues, workers, closeOrder, FakeQueue, FakeWorker };
43
- });
44
- vi.mock('bullmq', () => ({ Queue: FakeQueue, Worker: FakeWorker }));
45
- const { BullMqJobQueue } = await import('../bullmq-job-queue.js');
5
+ import { createTestRedis, redisUrlForWorker } from '@openora/core/testing';
6
+ import { BullMqJobQueue } from '../bullmq-job-queue.js';
46
7
  const Payload = z.object({ value: z.string() });
47
- const flush = () => new Promise((r) => setTimeout(r, 0));
48
- const REDIS_URL = 'redis://localhost:6379';
49
- beforeEach(() => {
50
- queues.length = 0;
51
- workers.length = 0;
52
- closeOrder.length = 0;
8
+ const POLL = { timeout: 5000, interval: 20 };
9
+ let redis;
10
+ const instances = [];
11
+ const rawQueues = [];
12
+ function makeQueue() {
13
+ const q = new BullMqJobQueue(redisUrlForWorker());
14
+ instances.push(q);
15
+ return q;
16
+ }
17
+ function rawQueue(name) {
18
+ const q = new Queue(name, {
19
+ connection: { url: redisUrlForWorker(), maxRetriesPerRequest: null },
20
+ });
21
+ rawQueues.push(q);
22
+ return q;
23
+ }
24
+ beforeAll(async () => {
25
+ redis = await createTestRedis();
26
+ });
27
+ afterEach(async () => {
28
+ await Promise.allSettled(instances.map((q) => q.close()));
29
+ await Promise.allSettled(rawQueues.map((q) => q.close()));
30
+ instances.length = 0;
31
+ rawQueues.length = 0;
32
+ await redis.flush();
33
+ });
34
+ afterAll(async () => {
35
+ await redis.quit();
53
36
  });
54
37
  describe('BullMqJobQueue', () => {
55
- it('maps enqueue options to BullMQ job options and wraps the payload in an envelope', async () => {
56
- const q = new BullMqJobQueue(REDIS_URL);
57
- const result = await q.enqueue(queue('demo'), { value: 'hi' }, {
58
- idempotencyKey: 'k1',
59
- delayMs: 5000,
60
- attempts: 3,
61
- backoff: { type: 'exponential', delayMs: 500 },
62
- priority: 2,
63
- meta: { correlationId: 'c1' },
38
+ it('enqueues, validates the payload, and hands the worker a JobContext', async () => {
39
+ const q = makeQueue();
40
+ const seen = [];
41
+ q.registerWorker({
42
+ queue: queue('demo'),
43
+ schema: Payload,
44
+ handler: (ctx) => {
45
+ seen.push({ ...ctx });
46
+ },
64
47
  });
65
- expect(result).toEqual({ id: 'job-1' });
66
- expect(queues[0]?.add).toHaveBeenCalledTimes(1);
67
- expect(queues[0]?.add).toHaveBeenCalledWith('demo', { payload: { value: 'hi' }, meta: { correlationId: 'c1', idempotencyKey: 'k1' } }, {
68
- jobId: 'k1',
69
- delay: 5000,
70
- attempts: 3,
71
- backoff: { type: 'exponential', delay: 500 },
72
- priority: 2,
48
+ const result = await q.enqueue(queue('demo'), { value: 'hi' }, { idempotencyKey: 'k1', meta: { correlationId: 'c1' } });
49
+ expect(result.id).toBe('k1');
50
+ await vi.waitFor(() => expect(seen).toHaveLength(1), POLL);
51
+ const ctx = seen[0];
52
+ expect(ctx['id']).toBe('k1');
53
+ expect(ctx['name']).toBe('demo');
54
+ expect(ctx['payload']).toEqual({ value: 'hi' });
55
+ expect(ctx['attempt']).toBe(1);
56
+ expect(ctx['meta']).toEqual({ correlationId: 'c1', idempotencyKey: 'k1' });
57
+ expect(ctx['enqueuedAt']).toBeInstanceOf(Date);
58
+ });
59
+ it('dedupes concurrent enqueues that share an idempotencyKey', async () => {
60
+ const q = makeQueue();
61
+ let count = 0;
62
+ q.registerWorker({
63
+ queue: queue('dedupe'),
64
+ schema: Payload,
65
+ handler: () => {
66
+ count += 1;
67
+ },
73
68
  });
69
+ await q.enqueue(queue('dedupe'), { value: 'a' }, { idempotencyKey: 'k1' });
70
+ await q.enqueue(queue('dedupe'), { value: 'b' }, { idempotencyKey: 'k1' });
71
+ await vi.waitFor(() => expect(count).toBe(1), POLL);
72
+ await new Promise((r) => setTimeout(r, 150));
73
+ expect(count).toBe(1);
74
74
  });
75
- it('returns the BullMQ-assigned id when no idempotencyKey is given', async () => {
76
- const q = new BullMqJobQueue(REDIS_URL);
77
- const result = await q.enqueue(queue('demo'), { value: 'hi' });
78
- expect(result).toEqual({ id: 'job-1' });
79
- expect(queues[0]?.add).toHaveBeenCalledWith('demo', { payload: { value: 'hi' }, meta: { idempotencyKey: undefined } }, {
80
- jobId: undefined,
81
- delay: undefined,
82
- attempts: undefined,
83
- backoff: undefined,
84
- priority: undefined,
75
+ it('runs a job whose idempotencyKey contains the reserved colon separator', async () => {
76
+ const q = makeQueue();
77
+ const seen = [];
78
+ q.registerWorker({
79
+ queue: queue('colon-key'),
80
+ schema: Payload,
81
+ handler: ({ payload }) => {
82
+ seen.push(payload.value);
83
+ },
85
84
  });
85
+ await q.enqueue(queue('colon-key'), { value: 'a' }, { idempotencyKey: 'rg-eval:user-1' });
86
+ await vi.waitFor(() => expect(seen).toEqual(['a']), POLL);
86
87
  });
87
- it('validates the payload and builds a JobContext with attempt = attemptsMade + 1 and carried meta', async () => {
88
- const q = new BullMqJobQueue(REDIS_URL);
88
+ it('keeps the original idempotencyKey in meta after encoding it into a job id', async () => {
89
+ const q = makeQueue();
89
90
  const seen = [];
90
91
  q.registerWorker({
91
- queue: queue('demo'),
92
+ queue: queue('colon-meta'),
92
93
  schema: Payload,
93
- handler: (ctx) => {
94
- seen.push({ ...ctx });
94
+ handler: ({ meta }) => {
95
+ seen.push(meta);
95
96
  },
96
97
  });
97
- await workers[0]?.processor({
98
- id: 'j1',
99
- name: 'demo',
100
- data: { payload: { value: 'hi' }, meta: { idempotencyKey: 'k1' } },
101
- attemptsMade: 1,
102
- timestamp: 1000,
103
- opts: { attempts: 3 },
98
+ await q.enqueue(queue('colon-meta'), { value: 'a' }, { idempotencyKey: 'rg-eval:user-1' });
99
+ await vi.waitFor(() => expect(seen).toHaveLength(1), POLL);
100
+ expect(seen[0]?.['idempotencyKey']).toBe('rg-eval:user-1');
101
+ });
102
+ it('does not collapse two colon keys that differ only where the separator sits', async () => {
103
+ const q = makeQueue();
104
+ let count = 0;
105
+ q.registerWorker({
106
+ queue: queue('colon-distinct'),
107
+ schema: Payload,
108
+ handler: () => {
109
+ count += 1;
110
+ },
104
111
  });
105
- expect(seen[0]).toEqual({
106
- id: 'j1',
107
- name: 'demo',
108
- payload: { value: 'hi' },
109
- attempt: 2,
110
- enqueuedAt: new Date(1000),
111
- meta: { idempotencyKey: 'k1' },
112
+ await q.enqueue(queue('colon-distinct'), { value: 'a' }, { idempotencyKey: 'a:b' });
113
+ await q.enqueue(queue('colon-distinct'), { value: 'b' }, { idempotencyKey: 'a%3Ab' });
114
+ await vi.waitFor(() => expect(count).toBe(2), POLL);
115
+ });
116
+ it('runs a job whose idempotencyKey is all digits, which BullMQ rejects as a raw id', async () => {
117
+ const q = makeQueue();
118
+ const seen = [];
119
+ q.registerWorker({
120
+ queue: queue('digit-key'),
121
+ schema: Payload,
122
+ handler: ({ payload }) => {
123
+ seen.push(payload.value);
124
+ },
112
125
  });
126
+ await q.enqueue(queue('digit-key'), { value: 'a' }, { idempotencyKey: '12345' });
127
+ await vi.waitFor(() => expect(seen).toEqual(['a']), POLL);
113
128
  });
114
- it('fails the job when the payload does not match the schema', async () => {
115
- const q = new BullMqJobQueue(REDIS_URL);
116
- q.registerWorker({ queue: queue('demo'), schema: Payload, handler: () => undefined });
117
- await expect(workers[0]?.processor({
118
- id: 'j1',
119
- name: 'demo',
120
- data: { payload: { value: 42 }, meta: {} },
121
- attemptsMade: 0,
122
- timestamp: 1000,
123
- opts: { attempts: 1 },
124
- })).rejects.toThrow();
129
+ it('delays processing by delayMs', async () => {
130
+ const q = makeQueue();
131
+ const enqueuedAt = Date.now();
132
+ let processedAt = 0;
133
+ q.registerWorker({
134
+ queue: queue('delayed'),
135
+ schema: Payload,
136
+ handler: () => {
137
+ processedAt = Date.now();
138
+ },
139
+ });
140
+ await q.enqueue(queue('delayed'), { value: 'hi' }, { delayMs: 300 });
141
+ await vi.waitFor(() => expect(processedAt).toBeGreaterThan(0), POLL);
142
+ expect(processedAt - enqueuedAt).toBeGreaterThanOrEqual(250);
125
143
  });
126
- it('passes worker concurrency through to BullMQ', () => {
127
- const q = new BullMqJobQueue(REDIS_URL);
144
+ it('retries a failing handler up to attempts with backoff, then dead-letters', async () => {
145
+ const q = makeQueue();
146
+ const attempts = [];
147
+ const deadLettered = [];
128
148
  q.registerWorker({
129
- queue: queue('demo'),
149
+ queue: queue('retry'),
130
150
  schema: Payload,
131
- handler: () => undefined,
132
- options: { concurrency: 5 },
151
+ handler: (ctx) => {
152
+ attempts.push(ctx.attempt);
153
+ throw new Error('always fails');
154
+ },
155
+ onDeadLetter: (ctx, error) => {
156
+ deadLettered.push({ attempt: ctx.attempt, error: error.message });
157
+ },
133
158
  });
134
- expect(workers[0]?.opts.concurrency).toBe(5);
159
+ await q.enqueue(queue('retry'), { value: 'hi' }, { attempts: 2, backoff: { type: 'fixed', delayMs: 30 } });
160
+ await vi.waitFor(() => expect(attempts).toEqual([1, 2]), POLL);
161
+ await vi.waitFor(() => expect(deadLettered).toHaveLength(1), POLL);
162
+ expect(deadLettered[0]).toEqual({ attempt: 2, error: 'always fails' });
135
163
  });
136
- it('dead-letters only on terminal failure and swallows a throwing hook', async () => {
137
- const q = new BullMqJobQueue(REDIS_URL);
138
- const onDeadLetter = vi.fn(async () => {
139
- throw new Error('hook boom');
164
+ it('dead-letters only once retries are exhausted and swallows a throwing hook', async () => {
165
+ const q = makeQueue();
166
+ let deadLetterCalls = 0;
167
+ q.registerWorker({
168
+ queue: queue('poison'),
169
+ schema: Payload,
170
+ handler: () => {
171
+ throw new Error('boom');
172
+ },
173
+ onDeadLetter: async () => {
174
+ deadLetterCalls += 1;
175
+ throw new Error('hook boom');
176
+ },
140
177
  });
178
+ await q.enqueue(queue('poison'), { value: 'hi' }, { attempts: 2, backoff: { type: 'fixed', delayMs: 30 } });
179
+ await vi.waitFor(() => expect(deadLetterCalls).toBe(1), POLL);
180
+ // The hook threw, but the queue kept running (no unhandled rejection) - still one call.
181
+ await new Promise((r) => setTimeout(r, 120));
182
+ expect(deadLetterCalls).toBe(1);
183
+ });
184
+ it('fails the job when the payload does not match the schema', async () => {
185
+ const q = makeQueue();
186
+ let handlerCalls = 0;
187
+ const deadLettered = [];
141
188
  q.registerWorker({
142
- queue: queue('demo'),
189
+ queue: queue('validate'),
143
190
  schema: Payload,
144
- handler: () => undefined,
145
- onDeadLetter,
191
+ handler: () => {
192
+ handlerCalls += 1;
193
+ },
194
+ onDeadLetter: (ctx) => {
195
+ deadLettered.push(ctx.payload);
196
+ },
146
197
  });
147
- const failed = workers[0]?.handlers.get('failed');
148
- const err = new Error('boom');
149
- const job = {
150
- id: 'j1',
151
- name: 'demo',
152
- data: { payload: { value: 'hi' }, meta: { idempotencyKey: 'k1' } },
153
- attemptsMade: 1,
154
- timestamp: 1000,
155
- opts: { attempts: 3 },
156
- };
157
- failed?.(job, err);
158
- await flush();
159
- expect(onDeadLetter).not.toHaveBeenCalled();
160
- failed?.({ ...job, attemptsMade: 3 }, err);
161
- await flush();
162
- expect(onDeadLetter).toHaveBeenCalledWith(expect.objectContaining({
163
- id: 'j1',
164
- name: 'demo',
165
- attempt: 3,
166
- payload: { value: 'hi' },
167
- meta: { idempotencyKey: 'k1' },
168
- }), err);
198
+ await q.enqueue(queue('validate'), { value: 42 });
199
+ await vi.waitFor(() => expect(deadLettered).toHaveLength(1), POLL);
200
+ expect(handlerCalls).toBe(0);
201
+ expect(deadLettered[0]).toEqual({ value: 42 });
169
202
  });
170
- it('maps a cron schedule to a job scheduler and unschedules by id', async () => {
171
- const q = new BullMqJobQueue(REDIS_URL);
203
+ it('registers a cron job scheduler and removes it on unschedule', async () => {
204
+ const q = makeQueue();
172
205
  await q.schedule(queue('sched'), 'daily', { value: 'x' }, { cron: '0 0 * * *', timezone: 'UTC' });
173
- expect(queues[0]?.upsertJobScheduler).toHaveBeenCalledWith('daily', { pattern: '0 0 * * *', every: undefined, tz: 'UTC' }, { name: 'sched', data: { payload: { value: 'x' }, meta: {} } });
206
+ const raw = rawQueue('sched');
207
+ const scheduled = await raw.getJobSchedulers();
208
+ const daily = scheduled.find((s) => s.key === 'daily');
209
+ expect(daily?.pattern).toBe('0 0 * * *');
210
+ expect(daily?.tz).toBe('UTC');
174
211
  await q.unschedule(queue('sched'), 'daily');
175
- expect(queues[0]?.removeJobScheduler).toHaveBeenCalledWith('daily');
212
+ expect(await raw.getJobSchedulers()).toHaveLength(0);
176
213
  });
177
- it('maps an everyMs schedule to the every option', async () => {
178
- const q = new BullMqJobQueue(REDIS_URL);
179
- await q.schedule(queue('sched'), 'tick', { value: 'x' }, { everyMs: 60000 });
180
- expect(queues[0]?.upsertJobScheduler).toHaveBeenCalledWith('tick', { pattern: undefined, every: 60000, tz: undefined }, { name: 'sched', data: { payload: { value: 'x' }, meta: {} } });
214
+ it('registers an everyMs job scheduler', async () => {
215
+ const q = makeQueue();
216
+ await q.schedule(queue('tick'), 'interval', { value: 'x' }, { everyMs: 60000 });
217
+ const raw = rawQueue('tick');
218
+ const interval = (await raw.getJobSchedulers()).find((s) => s.key === 'interval');
219
+ expect(interval?.every).toBe(60000);
181
220
  });
182
- it('closes workers before queues', async () => {
183
- const q = new BullMqJobQueue(REDIS_URL);
184
- q.registerWorker({ queue: queue('demo'), schema: Payload, handler: () => undefined });
185
- await q.enqueue(queue('demo'), { value: 'hi' });
221
+ it('close drains an in-flight job before resolving', async () => {
222
+ const q = makeQueue();
223
+ let started = false;
224
+ let finished = false;
225
+ q.registerWorker({
226
+ queue: queue('drain'),
227
+ schema: Payload,
228
+ handler: async () => {
229
+ started = true;
230
+ await new Promise((r) => setTimeout(r, 100));
231
+ finished = true;
232
+ },
233
+ });
234
+ await q.enqueue(queue('drain'), { value: 'hi' });
235
+ await vi.waitFor(() => expect(started).toBe(true), POLL);
186
236
  await q.close();
187
- expect(closeOrder).toEqual(['worker', 'queue']);
237
+ expect(finished).toBe(true);
188
238
  });
189
239
  });
190
240
  //# sourceMappingURL=bullmq-job-queue.test.js.map