@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,38 +1,85 @@
1
- import { describe, it, expect, vi } from 'vitest';
1
+ import { describe, it, expect, 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 { player } from '@openora/core/pam/schema/profile';
6
+ import { migrate as migrateProfile } from '@openora/core/pam/migrate/profile';
7
+ import { makeEventBus } from '../../../testing/mock.js';
2
8
  import { PlayerKycStatusWriter } from '../service/kyc-status-writer.js';
3
- import { mock, mockDb } from '../../../testing/mock.js';
4
- function makeDb(selectResult) {
5
- const builder = new Proxy(function () { }, {
6
- get(_t, prop) {
7
- if (prop === 'then') {
8
- return (res) => res(selectResult);
9
- }
10
- return () => builder;
11
- },
12
- apply: () => builder,
13
- });
14
- return mockDb(builder);
9
+ let db;
10
+ function makeWriter() {
11
+ const events = makeEventBus();
12
+ return { writer: new PlayerKycStatusWriter(db.drizzle, events), events };
13
+ }
14
+ async function seedPlayer(overrides = {}) {
15
+ const [row] = await db.drizzle.db
16
+ .insert(player)
17
+ .values({ userId: randomUUID(), displayName: 'Player', ...overrides })
18
+ .returning();
19
+ return row;
15
20
  }
16
- function makeWriter(selectResult) {
17
- const events = { emit: vi.fn(), on: vi.fn() };
18
- const writer = new PlayerKycStatusWriter(makeDb(selectResult), mock(events));
19
- return { writer, events };
21
+ async function statusOf(userId) {
22
+ const [row] = await db.drizzle.db.select().from(player).where(eq(player.userId, userId));
23
+ return row?.kycStatus;
20
24
  }
21
- describe('PlayerKycStatusWriter.setStatus', () => {
22
- it('writes player.kycStatus and emits compliance.kyc.updated on a real change', async () => {
23
- const { writer, events } = makeWriter([{ kycStatus: 'pending' }]);
24
- await writer.setStatus('u-1', 'verified', { actorId: 'admin-1', source: 'manual' });
25
+ beforeAll(async () => {
26
+ db = await createTestDb([migrateProfile]);
27
+ });
28
+ afterAll(async () => {
29
+ await db.drop();
30
+ });
31
+ beforeEach(async () => {
32
+ await db.drizzle.db.execute(sql `TRUNCATE ${player} RESTART IDENTITY CASCADE`);
33
+ });
34
+ describe('PlayerKycStatusWriter.setStatus (real PG)', () => {
35
+ it('writes the new status and emits the change with its previous value', async () => {
36
+ const { writer, events } = makeWriter();
37
+ const { userId } = await seedPlayer({ kycStatus: 'pending' });
38
+ const actorId = randomUUID();
39
+ await writer.setStatus(userId, 'verified', { actorId, source: 'manual' });
40
+ expect(await statusOf(userId)).toBe('verified');
25
41
  expect(events.emit).toHaveBeenCalledWith('compliance.kyc.updated', {
26
- userId: 'u-1',
27
- actorId: 'admin-1',
42
+ userId,
43
+ actorId,
28
44
  status: 'verified',
29
45
  previousStatus: 'pending',
30
46
  });
31
47
  });
32
- it('is a no-op when the status is unchanged', async () => {
33
- const { writer, events } = makeWriter([{ kycStatus: 'verified' }]);
34
- await writer.setStatus('u-1', 'verified', { actorId: null, source: 'vendor' });
48
+ it('is a silent no-op when the status is unchanged', async () => {
49
+ const { writer, events } = makeWriter();
50
+ const { userId } = await seedPlayer({ kycStatus: 'verified' });
51
+ await writer.setStatus(userId, 'verified', { actorId: null, source: 'vendor' });
35
52
  expect(events.emit).not.toHaveBeenCalled();
36
53
  });
54
+ it('is a no-op for a user with no player profile', async () => {
55
+ const { writer, events } = makeWriter();
56
+ await writer.setStatus(randomUUID(), 'verified', { actorId: null, source: 'vendor' });
57
+ expect(events.emit).not.toHaveBeenCalled();
58
+ });
59
+ it('leaves other players untouched', async () => {
60
+ const { writer } = makeWriter();
61
+ const target = await seedPlayer({ kycStatus: 'pending' });
62
+ const other = await seedPlayer({ kycStatus: 'pending' });
63
+ await writer.setStatus(target.userId, 'rejected', { actorId: null, source: 'vendor' });
64
+ expect(await statusOf(other.userId)).toBe('pending');
65
+ });
66
+ it('writes on the callers transaction when one is supplied', async () => {
67
+ const { writer, events } = makeWriter();
68
+ const { userId } = await seedPlayer({ kycStatus: 'pending' });
69
+ await db.drizzle.db.transaction(async (tx) => {
70
+ await writer.setStatus(userId, 'verified', { actorId: null, source: 'webhook' }, tx);
71
+ });
72
+ expect(await statusOf(userId)).toBe('verified');
73
+ expect(events.emit).toHaveBeenCalledTimes(1);
74
+ });
75
+ it('rolls the status back with the callers transaction', async () => {
76
+ const { writer } = makeWriter();
77
+ const { userId } = await seedPlayer({ kycStatus: 'pending' });
78
+ await expect(db.drizzle.db.transaction(async (tx) => {
79
+ await writer.setStatus(userId, 'verified', { actorId: null, source: 'webhook' }, tx);
80
+ throw new Error('caller failed after the status write');
81
+ })).rejects.toThrow('caller failed');
82
+ expect(await statusOf(userId)).toBe('pending');
83
+ });
37
84
  });
38
85
  //# sourceMappingURL=kyc-status-writer.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"kyc-status-writer.test.js","sourceRoot":"","sources":["../../../../src/pam/player-management/__tests__/kyc-status-writer.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAExD,SAAS,MAAM,CAAC,YAAqB;IACnC,MAAM,OAAO,GAAY,IAAI,KAAK,CAAC,cAAa,CAAC,EAAE;QACjD,GAAG,CAAC,EAAE,EAAE,IAAI;YACV,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,OAAO,CAAC,GAA4B,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;QACvB,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO;KACrB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,UAAU,CAAC,YAAqB;IACvC,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,qBAAqB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,CAAW,MAAM,CAAC,CAAC,CAAC;IACvF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC5B,CAAC;AAED,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpF,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,EAAE;YACjE,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,SAAS;YAClB,MAAM,EAAE,UAAU;YAClB,cAAc,EAAE,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QACvD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"kyc-status-writer.test.js","sourceRoot":"","sources":["../../../../src/pam/player-management/__tests__/kyc-status-writer.test.ts"],"names":[],"mappings":"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"}
@@ -1,57 +1,74 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { mock, adminCaller, testContext } 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';
3
4
  import { call, ORPCError } from '@orpc/server';
5
+ import { createTestDb } from '@openora/core/testing';
6
+ import { user } from '@openora/core/pam/schema/identity';
7
+ import { migrate as migrateIdentity } from '@openora/core/pam/migrate/identity';
8
+ import { player } from '@openora/core/pam/schema/profile';
9
+ import { migrate as migrateProfile } from '@openora/core/pam/migrate/profile';
10
+ import { tag, playerTag } from '@openora/core/pam/schema/tag';
11
+ import { migrate as migrateTag } from '@openora/core/pam/migrate/tag';
12
+ import { mock, makeAdminGuard, testContext } from '../../../testing/mock.js';
4
13
  import { createPlayerRouter } from '../router/index.js';
14
+ import { PlayerService } from '../service/player.service.js';
5
15
  const CTX = testContext();
6
- const PLAYER_ID = '63d3c264-3bf4-4d08-9b92-ea3eaf40a440';
7
- function fakeGuard(allowed) {
8
- return mock({
9
- assert: vi.fn(async (_ctx, resource, action) => {
10
- if (resource && action && !allowed.includes(`${resource}:${action}`)) {
11
- throw new ORPCError('FORBIDDEN', { message: `Missing permission: ${resource}:${action}` });
12
- }
13
- return adminCaller();
14
- }),
15
- });
16
+ const CALLER = '44444444-4444-4444-8444-444444444444';
17
+ let db;
18
+ beforeAll(async () => {
19
+ db = await createTestDb([migrateIdentity, migrateProfile, migrateTag]);
20
+ });
21
+ afterAll(async () => {
22
+ await db.drop();
23
+ });
24
+ beforeEach(async () => {
25
+ await db.drizzle.db.execute(sql `TRUNCATE ${playerTag}, ${tag}, ${player}, ${user} RESTART IDENTITY CASCADE`);
26
+ });
27
+ const guardAllowing = (allow) => makeAdminGuard({ allow, caller: { userId: CALLER } });
28
+ function build(adminGuard) {
29
+ const kycStatusWriter = mock({ setStatus: vi.fn() });
30
+ const service = new PlayerService(db.drizzle, kycStatusWriter);
31
+ return { router: createPlayerRouter(service, adminGuard), kycStatusWriter };
32
+ }
33
+ async function seedPlayer() {
34
+ const [account] = await db.drizzle.db
35
+ .insert(user)
36
+ .values({ name: 'Player', email: `${randomUUID()}@example.com` })
37
+ .returning();
38
+ const [row] = await db.drizzle.db
39
+ .insert(player)
40
+ .values({ userId: account.id, displayName: 'Player' })
41
+ .returning();
42
+ return row;
16
43
  }
17
- function fakeService() {
18
- const playerDto = {
19
- id: PLAYER_ID,
20
- userId: PLAYER_ID,
21
- displayName: 'Player',
22
- email: 'p@example.com',
23
- country: 'MT',
24
- currency: 'EUR',
25
- status: 'active',
26
- kycStatus: 'verified',
27
- level: 1,
28
- totalWagered: '0',
29
- totalDeposits: '0',
30
- lastSeenAt: null,
31
- createdAt: '2026-01-01T00:00:00.000Z',
32
- updatedAt: '2026-01-01T00:00:00.000Z',
33
- tags: [],
34
- };
35
- return mock({ update: vi.fn().mockResolvedValue(playerDto) });
44
+ async function storedPlayer(id) {
45
+ const [row] = await db.drizzle.db.select().from(player).where(eq(player.id, id));
46
+ return row;
36
47
  }
37
48
  describe('player router update KYC authz', () => {
38
- it('rejects a kycStatus change without compliance:override-limit', async () => {
39
- const svc = fakeService();
40
- const router = createPlayerRouter(svc, fakeGuard(['player:update']));
41
- await expect(call(router.update, { playerId: PLAYER_ID, kycStatus: 'verified' }, { context: CTX })).rejects.toBeInstanceOf(ORPCError);
42
- expect(svc.update).not.toHaveBeenCalled();
49
+ it('rejects a kycStatus change without compliance:override-limit and writes nothing', async () => {
50
+ const seeded = await seedPlayer();
51
+ const { router, kycStatusWriter } = build(guardAllowing(['player:update']));
52
+ await expect(call(router.update, { playerId: seeded.id, kycStatus: 'verified' }, { context: CTX })).rejects.toBeInstanceOf(ORPCError);
53
+ expect(kycStatusWriter.setStatus).not.toHaveBeenCalled();
54
+ expect((await storedPlayer(seeded.id))?.kycStatus).toBe(seeded.kycStatus);
55
+ });
56
+ it('routes a gated kycStatus change through KYC_STATUS_WRITER', async () => {
57
+ const seeded = await seedPlayer();
58
+ const { router, kycStatusWriter } = build(guardAllowing(['player:update', 'compliance:override-limit']));
59
+ await call(router.update, { playerId: seeded.id, kycStatus: 'verified' }, { context: CTX });
60
+ expect(kycStatusWriter.setStatus).toHaveBeenCalledWith(seeded.userId, 'verified', { actorId: CALLER, source: 'manual' }, expect.anything());
43
61
  });
44
- it('allows a kycStatus change with the compliance gate', async () => {
45
- const svc = fakeService();
46
- const router = createPlayerRouter(svc, fakeGuard(['player:update', 'compliance:override-limit']));
47
- await call(router.update, { playerId: PLAYER_ID, kycStatus: 'verified' }, { context: CTX });
48
- expect(svc.update).toHaveBeenCalledWith(PLAYER_ID, expect.objectContaining({ kycStatus: 'verified' }), 'admin-1');
62
+ it('persists a non-KYC update with only player:update', async () => {
63
+ const seeded = await seedPlayer();
64
+ const { router } = build(guardAllowing(['player:update']));
65
+ const result = await call(router.update, { playerId: seeded.id, displayName: 'New' }, { context: CTX });
66
+ expect(result.displayName).toBe('New');
67
+ expect((await storedPlayer(seeded.id))?.displayName).toBe('New');
49
68
  });
50
- it('allows a non-KYC update with only player:update', async () => {
51
- const svc = fakeService();
52
- const router = createPlayerRouter(svc, fakeGuard(['player:update']));
53
- await call(router.update, { playerId: PLAYER_ID, displayName: 'New' }, { context: CTX });
54
- expect(svc.update).toHaveBeenCalledOnce();
69
+ it('maps an unknown player to NOT_FOUND', async () => {
70
+ const { router } = build(guardAllowing(['player:update']));
71
+ await expect(call(router.update, { playerId: randomUUID(), displayName: 'New' }, { context: CTX })).rejects.toMatchObject({ code: 'NOT_FOUND' });
55
72
  });
56
73
  });
57
74
  //# sourceMappingURL=player.router.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"player.router.test.js","sourceRoot":"","sources":["../../../../src/pam/player-management/__tests__/player.router.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"player.router.test.js","sourceRoot":"","sources":["../../../../src/pam/player-management/__tests__/player.router.test.ts"],"names":[],"mappings":"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"}
@@ -1,144 +1,240 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { PlayerService, PlayerNotFoundError } from '../service/player.service.js';
3
- function chain(result) {
4
- const proxy = new Proxy(function () { }, {
5
- get(_t, prop) {
6
- if (prop === 'then') {
7
- return (res) => res(result);
8
- }
9
- return () => proxy;
10
- },
11
- apply: () => proxy,
12
- });
13
- return proxy;
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 { user } from '@openora/core/pam/schema/identity';
6
+ import { migrate as migrateIdentity } from '@openora/core/pam/migrate/identity';
7
+ import { player } from '@openora/core/pam/schema/profile';
8
+ import { migrate as migrateProfile } from '@openora/core/pam/migrate/profile';
9
+ import { tag, playerTag } from '@openora/core/pam/schema/tag';
10
+ import { migrate as migrateTag } from '@openora/core/pam/migrate/tag';
11
+ import { mock } from '../../../testing/mock.js';
12
+ import { PlayerService, PlayerNotFoundError, DuplicateEmailError, } from '../service/player.service.js';
13
+ let db;
14
+ function makeService(writer = { setStatus: vi.fn() }) {
15
+ const kycStatusWriter = mock(writer);
16
+ return { svc: new PlayerService(db.drizzle, kycStatusWriter), kycStatusWriter };
14
17
  }
15
- // Chainable proxy that captures the first non-undefined argument passed to .where()
16
- function makeCapturingChain(results) {
17
- const captured = { where: [] };
18
- let callIdx = 0;
19
- const proxy = new Proxy(function () { }, {
20
- get(_t, prop) {
21
- if (prop === 'then') {
22
- const result = results[callIdx++ % results.length];
23
- return (res) => res(result);
24
- }
25
- if (prop === 'where') {
26
- return (clause) => {
27
- captured.where.push(clause);
28
- return proxy;
29
- };
30
- }
31
- return () => proxy;
32
- },
33
- apply: () => proxy,
34
- });
35
- return { proxy, captured };
18
+ async function seedUser(overrides = {}) {
19
+ const [row] = await db.drizzle.db
20
+ .insert(user)
21
+ .values({ name: 'Player', email: `${randomUUID()}@example.com`, ...overrides })
22
+ .returning();
23
+ return row;
36
24
  }
37
- function makeService(dayCounts = []) {
38
- const db = { select: vi.fn(() => chain(dayCounts)) };
39
- const writer = { setStatus: vi.fn() };
40
- return new PlayerService({ db }, writer);
25
+ async function seedPlayer(userId, overrides = {}) {
26
+ const [row] = await db.drizzle.db
27
+ .insert(player)
28
+ .values({ userId, displayName: 'Player', ...overrides })
29
+ .returning();
30
+ return row;
41
31
  }
42
- describe('PlayerService domain errors', () => {
43
- it('PlayerNotFoundError carries the playerId', () => {
44
- const err = new PlayerNotFoundError('p-123');
45
- expect(err).toBeInstanceOf(Error);
46
- expect(err.name).toBe('PlayerNotFoundError');
47
- expect(err.message).toContain('p-123');
32
+ async function seedPlayerWithUser(userOverrides = {}, playerOverrides = {}) {
33
+ const account = await seedUser(userOverrides);
34
+ const row = await seedPlayer(account.id, playerOverrides);
35
+ return { account, player: row };
36
+ }
37
+ async function seedTag(key) {
38
+ const [row] = await db.drizzle.db.insert(tag).values({ key }).returning();
39
+ return row;
40
+ }
41
+ async function assignTag(playerId, tagId) {
42
+ await db.drizzle.db.insert(playerTag).values({
43
+ playerId,
44
+ tagId,
45
+ assignReason: 'test',
46
+ assignActor: 'manual',
47
+ assignActorUserId: null,
48
48
  });
49
+ }
50
+ async function rowById(playerId) {
51
+ const [row] = await db.drizzle.db.select().from(player).where(eq(player.id, playerId));
52
+ return row;
53
+ }
54
+ beforeAll(async () => {
55
+ db = await createTestDb([migrateIdentity, migrateProfile, migrateTag]);
56
+ });
57
+ afterAll(async () => {
58
+ await db.drop();
59
+ });
60
+ beforeEach(async () => {
61
+ await db.drizzle.db.execute(sql `TRUNCATE ${playerTag}, ${tag}, ${player}, ${user} RESTART IDENTITY CASCADE`);
49
62
  });
50
- describe('PlayerService.remove', () => {
51
- function makeRemoveService(existing) {
52
- const setArgs = [];
53
- const update = vi.fn(() => ({
54
- set: vi.fn((patch) => {
55
- setArgs.push(patch);
56
- return { where: vi.fn(() => Promise.resolve()) };
57
- }),
58
- }));
59
- const db = { select: vi.fn(() => chain(existing)), update };
60
- const svc = new PlayerService({ db }, { setStatus: vi.fn() });
61
- return { svc, db, setArgs };
62
- }
63
+ describe('PlayerService.remove (real PG)', () => {
63
64
  it('soft-deletes by closing the player, never deletes the row', async () => {
64
- const { svc, db, setArgs } = makeRemoveService([{ id: 'p-1' }]);
65
- const result = await svc.remove('p-1');
65
+ const { svc } = makeService();
66
+ const { player: seeded } = await seedPlayerWithUser();
67
+ const result = await svc.remove(seeded.id);
66
68
  expect(result).toEqual({ success: true });
67
- expect(setArgs).toEqual([{ status: 'closed' }]);
68
- expect(db.delete).toBeUndefined();
69
+ expect(await rowById(seeded.id)).toMatchObject({ status: 'closed' });
69
70
  });
70
71
  it('throws PlayerNotFoundError when the player does not exist', async () => {
71
- const { svc } = makeRemoveService([]);
72
- await expect(svc.remove('missing')).rejects.toThrow(PlayerNotFoundError);
72
+ const { svc } = makeService();
73
+ await expect(svc.remove(randomUUID())).rejects.toBeInstanceOf(PlayerNotFoundError);
73
74
  });
74
75
  });
75
- describe('PlayerService.getByUserId', () => {
76
+ describe('PlayerService.getByUserId (real PG)', () => {
77
+ it('returns the player enriched with the identity email', async () => {
78
+ const { svc } = makeService();
79
+ const { account, player: seeded } = await seedPlayerWithUser({ email: 'jordan@example.com' });
80
+ const result = await svc.getByUserId(account.id);
81
+ expect(result).toMatchObject({
82
+ id: seeded.id,
83
+ userId: account.id,
84
+ email: 'jordan@example.com',
85
+ });
86
+ });
76
87
  it('throws PlayerNotFoundError when no player owns the userId', async () => {
77
- const db = { select: vi.fn(() => chain([])) };
78
- const svc = new PlayerService({ db }, { setStatus: vi.fn() });
79
- await expect(svc.getByUserId('u-missing')).rejects.toThrow(PlayerNotFoundError);
88
+ const { svc } = makeService();
89
+ await expect(svc.getByUserId(randomUUID())).rejects.toBeInstanceOf(PlayerNotFoundError);
80
90
  });
81
91
  });
82
- describe('PlayerService.registrationsOverTime', () => {
92
+ describe('PlayerService.registrationsOverTime (real PG)', () => {
83
93
  it('returns one zero-filled bucket per day in the window', async () => {
84
- const svc = makeService([]);
94
+ const { svc } = makeService();
85
95
  const points = await svc.registrationsOverTime(7);
86
96
  expect(points).toHaveLength(7);
87
97
  expect(points.every((p) => p.count === 0)).toBe(true);
88
98
  const dates = points.map((p) => p.date);
89
99
  expect(new Set(dates).size).toBe(7);
90
100
  expect(dates).toEqual([...dates].sort());
91
- expect(dates[0]).toMatch(/^\d{4}-\d{2}-\d{2}$/);
92
101
  });
93
- it('places the DB day-count into its matching day bucket', async () => {
102
+ it('places each days registrations into its matching bucket', async () => {
103
+ const { svc } = makeService();
94
104
  const todayKey = new Date().toISOString().slice(0, 10);
95
- const svc = makeService([{ date: todayKey, n: 3 }]);
105
+ await seedPlayer(randomUUID(), { createdAt: new Date() });
106
+ await seedPlayer(randomUUID(), { createdAt: new Date() });
107
+ await seedPlayer(randomUUID(), { createdAt: new Date() });
96
108
  const points = await svc.registrationsOverTime(7);
97
109
  const today = points.find((p) => p.date === todayKey);
98
110
  expect(today?.count).toBe(3);
99
111
  expect(points.filter((p) => p.date !== todayKey).every((p) => p.count === 0)).toBe(true);
100
112
  });
101
113
  });
102
- describe('PlayerService.list tag filter', () => {
103
- const emptyPlayerRow = {
104
- player: {
105
- id: 'p-1',
106
- userId: 'u-1',
107
- displayName: 'Player',
108
- status: 'active',
114
+ describe('PlayerService.list (real PG)', () => {
115
+ it('filters by status', async () => {
116
+ const { svc } = makeService();
117
+ await seedPlayerWithUser({}, { status: 'active' });
118
+ await seedPlayerWithUser({}, { status: 'suspended' });
119
+ const { items, total } = await svc.list({ page: 1, limit: 20, status: 'suspended' });
120
+ expect(total).toBe(1);
121
+ expect(items[0]?.status).toBe('suspended');
122
+ });
123
+ it('matches players by a display name substring', async () => {
124
+ const { svc } = makeService();
125
+ await seedPlayerWithUser({}, { displayName: 'Alice Anderson' });
126
+ await seedPlayerWithUser({}, { displayName: 'Bob Baker' });
127
+ const { items, total } = await svc.list({ page: 1, limit: 20, search: 'ander' });
128
+ expect(total).toBe(1);
129
+ expect(items[0]?.displayName).toBe('Alice Anderson');
130
+ });
131
+ it('matches players by an email substring', async () => {
132
+ const { svc } = makeService();
133
+ await seedPlayerWithUser({ email: 'searchable-target@example.com' });
134
+ await seedPlayerWithUser({ email: 'someone-else@example.com' });
135
+ const { items, total } = await svc.list({ page: 1, limit: 20, search: 'searchable-target' });
136
+ expect(total).toBe(1);
137
+ expect(items[0]?.email).toBe('searchable-target@example.com');
138
+ });
139
+ it('restricts results to players carrying any of the given tags, excluding removed assignments', async () => {
140
+ const { svc } = makeService();
141
+ const vip = await seedTag('vip');
142
+ const highRisk = await seedTag('high_risk');
143
+ const { player: tagged } = await seedPlayerWithUser();
144
+ const { player: removedTag } = await seedPlayerWithUser();
145
+ const { player: untagged } = await seedPlayerWithUser();
146
+ await assignTag(tagged.id, vip.id);
147
+ await assignTag(removedTag.id, highRisk.id);
148
+ await db.drizzle.db
149
+ .update(playerTag)
150
+ .set({ removedAt: new Date() })
151
+ .where(eq(playerTag.playerId, removedTag.id));
152
+ const { items, total } = await svc.list({ page: 1, limit: 20, tags: ['vip', 'high_risk'] });
153
+ expect(total).toBe(1);
154
+ expect(items.map((i) => i.id)).toEqual([tagged.id]);
155
+ expect(items.map((i) => i.id)).not.toContain(untagged.id);
156
+ });
157
+ it('keeps the total across the whole filtered set, not just the returned page', async () => {
158
+ const { svc } = makeService();
159
+ await seedPlayerWithUser({}, { status: 'active' });
160
+ await seedPlayerWithUser({}, { status: 'active' });
161
+ await seedPlayerWithUser({}, { status: 'active' });
162
+ await seedPlayerWithUser({}, { status: 'suspended' });
163
+ const { items, total } = await svc.list({ page: 2, limit: 2, status: 'active' });
164
+ expect(total).toBe(3);
165
+ expect(items).toHaveLength(1);
166
+ });
167
+ it('orders by the requested sort field and direction', async () => {
168
+ const { svc } = makeService();
169
+ await seedPlayerWithUser({}, { displayName: 'Charlie' });
170
+ await seedPlayerWithUser({}, { displayName: 'Alice' });
171
+ await seedPlayerWithUser({}, { displayName: 'Bob' });
172
+ const { items } = await svc.list({
173
+ page: 1,
174
+ limit: 20,
175
+ sortBy: 'displayName',
176
+ sortOrder: 'asc',
177
+ });
178
+ expect(items.map((i) => i.displayName)).toEqual(['Alice', 'Bob', 'Charlie']);
179
+ });
180
+ });
181
+ describe('PlayerService.get / getExtended (real PG)', () => {
182
+ it('returns the player with its assigned tags', async () => {
183
+ const { svc } = makeService();
184
+ const vip = await seedTag('vip');
185
+ const { player: seeded } = await seedPlayerWithUser();
186
+ await assignTag(seeded.id, vip.id);
187
+ const result = await svc.get(seeded.id);
188
+ expect(result.tags).toEqual(['vip']);
189
+ });
190
+ it('throws PlayerNotFoundError for an unknown id', async () => {
191
+ const { svc } = makeService();
192
+ await expect(svc.get(randomUUID())).rejects.toBeInstanceOf(PlayerNotFoundError);
193
+ });
194
+ });
195
+ describe('PlayerService.update (real PG)', () => {
196
+ it('persists simple field changes and returns the enriched row', async () => {
197
+ const { svc } = makeService();
198
+ const { player: seeded, account } = await seedPlayerWithUser();
199
+ const result = await svc.update(seeded.id, { displayName: 'Renamed', level: 5 }, account.id);
200
+ expect(result).toMatchObject({ displayName: 'Renamed', level: 5 });
201
+ expect(await rowById(seeded.id)).toMatchObject({ displayName: 'Renamed', level: 5 });
202
+ });
203
+ it('throws DuplicateEmailError when the new email is already used by a different user', async () => {
204
+ const { svc } = makeService();
205
+ const taken = await seedUser({ email: 'taken@example.com' });
206
+ const { player: seeded, account } = await seedPlayerWithUser();
207
+ await expect(svc.update(seeded.id, { email: taken.email }, account.id)).rejects.toBeInstanceOf(DuplicateEmailError);
208
+ });
209
+ it('delegates a kycStatus change to the KYC_STATUS_WRITER on the same transaction', async () => {
210
+ const setStatus = vi.fn(async () => undefined);
211
+ const { svc } = makeService({ setStatus });
212
+ const { player: seeded, account } = await seedPlayerWithUser({}, { kycStatus: 'pending' });
213
+ await svc.update(seeded.id, { kycStatus: 'verified' }, account.id);
214
+ expect(setStatus).toHaveBeenCalledWith(account.id, 'verified', { actorId: account.id, source: 'manual' }, expect.anything());
215
+ });
216
+ it('does not call the KYC_STATUS_WRITER when kycStatus is unchanged', async () => {
217
+ const setStatus = vi.fn(async () => undefined);
218
+ const { svc } = makeService({ setStatus });
219
+ const { player: seeded, account } = await seedPlayerWithUser({}, { kycStatus: 'verified' });
220
+ await svc.update(seeded.id, { kycStatus: 'verified' }, account.id);
221
+ expect(setStatus).not.toHaveBeenCalled();
222
+ });
223
+ it('rolls the whole update back when the KYC_STATUS_WRITER throws', async () => {
224
+ const setStatus = vi.fn(async () => {
225
+ throw new Error('writer down');
226
+ });
227
+ const { svc } = makeService({ setStatus });
228
+ const { player: seeded, account } = await seedPlayerWithUser({}, { displayName: 'Original', kycStatus: 'pending' });
229
+ await expect(svc.update(seeded.id, { displayName: 'Renamed', kycStatus: 'verified' }, account.id)).rejects.toThrow('writer down');
230
+ expect(await rowById(seeded.id)).toMatchObject({
231
+ displayName: 'Original',
109
232
  kycStatus: 'pending',
110
- level: 1,
111
- totalWagered: '0',
112
- totalDeposits: '0',
113
- lastSeenAt: null,
114
- createdAt: new Date(),
115
- updatedAt: new Date(),
116
- },
117
- email: 'p@test.com',
118
- tags: [],
119
- };
120
- it('applies a WHERE clause when tags are provided', async () => {
121
- const { proxy, captured } = makeCapturingChain([[emptyPlayerRow], [{ n: 0 }]]);
122
- const db = { select: vi.fn(() => proxy) };
123
- const svc = new PlayerService({ db }, { setStatus: vi.fn() });
124
- await svc.list({ page: 1, limit: 20, tags: ['self_excluded', 'kyc_rejected'] }).catch(() => { });
125
- // At least one .where() call must have received a non-undefined clause
126
- expect(captured.where.some((c) => c !== undefined)).toBe(true);
127
- });
128
- it('applies no WHERE clause when tags are absent', async () => {
129
- const { proxy, captured } = makeCapturingChain([[emptyPlayerRow], [{ n: 0 }]]);
130
- const db = { select: vi.fn(() => proxy) };
131
- const svc = new PlayerService({ db }, { setStatus: vi.fn() });
132
- await svc.list({ page: 1, limit: 20 }).catch(() => { });
133
- // All .where() calls should receive undefined when no filter is active
134
- expect(captured.where.every((c) => c === undefined)).toBe(true);
135
- });
136
- it('applies no WHERE clause when tags is an empty array', async () => {
137
- const { proxy, captured } = makeCapturingChain([[emptyPlayerRow], [{ n: 0 }]]);
138
- const db = { select: vi.fn(() => proxy) };
139
- const svc = new PlayerService({ db }, { setStatus: vi.fn() });
140
- await svc.list({ page: 1, limit: 20, tags: [] }).catch(() => { });
141
- expect(captured.where.every((c) => c === undefined)).toBe(true);
233
+ });
234
+ });
235
+ it('throws PlayerNotFoundError for an unknown id', async () => {
236
+ const { svc } = makeService();
237
+ await expect(svc.update(randomUUID(), { displayName: 'X' }, randomUUID())).rejects.toBeInstanceOf(PlayerNotFoundError);
142
238
  });
143
239
  });
144
240
  //# sourceMappingURL=player.service.test.js.map