@tstdl/base 0.93.182 → 0.93.183

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 (353) hide show
  1. package/api/server/api-request-token.provider.js +1 -1
  2. package/api/server/gateway.js +6 -1
  3. package/authentication/authentication.api.d.ts +13 -40
  4. package/authentication/authentication.api.js +5 -14
  5. package/authentication/client/authentication.service.d.ts +6 -14
  6. package/authentication/client/authentication.service.js +22 -4
  7. package/authentication/client/module.d.ts +1 -1
  8. package/authentication/client/module.js +4 -4
  9. package/authentication/models/index.d.ts +1 -0
  10. package/authentication/models/index.js +1 -0
  11. package/authentication/models/totp-results.model.d.ts +11 -0
  12. package/authentication/models/totp-results.model.js +37 -0
  13. package/authentication/server/authentication.api-controller.d.ts +3 -3
  14. package/authentication/server/authentication.api-controller.js +31 -4
  15. package/authentication/server/authentication.service.d.ts +5 -14
  16. package/authentication/server/authentication.service.js +6 -4
  17. package/core.d.ts +0 -5
  18. package/core.js +0 -8
  19. package/document-management/api/document-management.api.d.ts +2 -2
  20. package/document-management/service-models/document.service-model.d.ts +1 -1
  21. package/examples/config.d.ts +25 -0
  22. package/examples/config.js +26 -0
  23. package/notification/server/module.d.ts +1 -1
  24. package/notification/server/module.js +1 -1
  25. package/package.json +5 -5
  26. package/signals/api.d.ts +5 -1
  27. package/signals/api.js +3 -1
  28. package/signals/implementation/api.d.ts +13 -5
  29. package/signals/implementation/api.js +7 -1
  30. package/signals/implementation/asserts.d.ts +2 -2
  31. package/signals/implementation/asserts.js +3 -3
  32. package/signals/implementation/computed.d.ts +7 -34
  33. package/signals/implementation/computed.js +14 -83
  34. package/signals/implementation/configure.js +6 -2
  35. package/signals/implementation/effect.d.ts +65 -46
  36. package/signals/implementation/effect.js +97 -62
  37. package/signals/implementation/index.d.ts +2 -4
  38. package/signals/implementation/index.js +2 -4
  39. package/signals/implementation/linked_signal.d.ts +36 -0
  40. package/signals/implementation/linked_signal.js +34 -0
  41. package/signals/implementation/primitive/computed.d.ts +55 -0
  42. package/signals/implementation/primitive/computed.js +107 -0
  43. package/signals/implementation/primitive/effect.d.ts +26 -0
  44. package/signals/implementation/primitive/effect.js +31 -0
  45. package/signals/implementation/{equality.d.ts → primitive/equality.d.ts} +1 -1
  46. package/signals/implementation/{equality.js → primitive/equality.js} +1 -1
  47. package/signals/implementation/primitive/errors.d.ts +10 -0
  48. package/signals/implementation/{errors.js → primitive/errors.js} +3 -4
  49. package/signals/implementation/primitive/formatter.d.ts +19 -0
  50. package/signals/implementation/primitive/formatter.js +136 -0
  51. package/signals/implementation/{graph.d.ts → primitive/graph.d.ts} +68 -36
  52. package/signals/implementation/primitive/graph.js +386 -0
  53. package/signals/implementation/primitive/linked_signal.d.ts +46 -0
  54. package/signals/implementation/primitive/linked_signal.js +110 -0
  55. package/signals/implementation/primitive/signal.d.ts +31 -0
  56. package/signals/implementation/primitive/signal.js +80 -0
  57. package/signals/implementation/primitive/untracked.d.ts +12 -0
  58. package/signals/implementation/primitive/untracked.js +23 -0
  59. package/signals/implementation/{watch.d.ts → primitive/watch.d.ts} +1 -2
  60. package/signals/implementation/{watch.js → primitive/watch.js} +22 -16
  61. package/signals/implementation/resource/api.d.ts +275 -0
  62. package/signals/implementation/resource/api.js +26 -0
  63. package/signals/implementation/resource/debounce.d.ts +13 -0
  64. package/signals/implementation/resource/debounce.js +113 -0
  65. package/signals/implementation/resource/from_snapshots.d.ts +16 -0
  66. package/signals/implementation/resource/from_snapshots.js +44 -0
  67. package/signals/implementation/resource/index.d.ts +11 -0
  68. package/signals/implementation/resource/index.js +11 -0
  69. package/signals/implementation/resource/resource.d.ts +110 -0
  70. package/signals/implementation/resource/resource.js +402 -0
  71. package/signals/implementation/root_effect_scheduler.d.ts +50 -0
  72. package/signals/implementation/root_effect_scheduler.js +66 -0
  73. package/signals/implementation/signal.d.ts +42 -18
  74. package/signals/implementation/signal.js +29 -49
  75. package/signals/implementation/to-observable.d.ts +12 -5
  76. package/signals/implementation/to-observable.js +12 -2
  77. package/signals/implementation/to-signal.d.ts +9 -18
  78. package/signals/implementation/to-signal.js +46 -13
  79. package/signals/implementation/untracked.d.ts +1 -1
  80. package/signals/implementation/untracked.js +3 -11
  81. package/signals/operators/debounce.d.ts +8 -0
  82. package/signals/operators/debounce.js +19 -0
  83. package/signals/operators/derive-async.js +43 -15
  84. package/signals/operators/index.d.ts +2 -0
  85. package/signals/operators/index.js +2 -0
  86. package/signals/operators/throttle.d.ts +8 -0
  87. package/signals/operators/throttle.js +31 -0
  88. package/ai/genkit/tests/multi-region.test.d.ts +0 -2
  89. package/ai/genkit/tests/multi-region.test.js +0 -179
  90. package/ai/genkit/tests/token-limit-fallback.test.d.ts +0 -2
  91. package/ai/genkit/tests/token-limit-fallback.test.js +0 -209
  92. package/ai/prompts/tests/prompt-builder.test.d.ts +0 -1
  93. package/ai/prompts/tests/prompt-builder.test.js +0 -22
  94. package/ai/tests/instructions-formatter.test.d.ts +0 -1
  95. package/ai/tests/instructions-formatter.test.js +0 -116
  96. package/ai/tests/steering.test.d.ts +0 -1
  97. package/ai/tests/steering.test.js +0 -37
  98. package/api/client/tests/api-client.test.d.ts +0 -1
  99. package/api/client/tests/api-client.test.js +0 -194
  100. package/api/server/tests/csrf.middleware.test.d.ts +0 -1
  101. package/api/server/tests/csrf.middleware.test.js +0 -91
  102. package/authentication/tests/authentication-password-requirements.validator.test.d.ts +0 -1
  103. package/authentication/tests/authentication-password-requirements.validator.test.js +0 -29
  104. package/authentication/tests/authentication.api-controller.test.d.ts +0 -1
  105. package/authentication/tests/authentication.api-controller.test.js +0 -156
  106. package/authentication/tests/authentication.api-request-token.provider.test.d.ts +0 -1
  107. package/authentication/tests/authentication.api-request-token.provider.test.js +0 -48
  108. package/authentication/tests/authentication.client-error-handling.test.d.ts +0 -1
  109. package/authentication/tests/authentication.client-error-handling.test.js +0 -123
  110. package/authentication/tests/authentication.client-middleware.test.d.ts +0 -1
  111. package/authentication/tests/authentication.client-middleware.test.js +0 -118
  112. package/authentication/tests/authentication.client-service-methods.test.d.ts +0 -1
  113. package/authentication/tests/authentication.client-service-methods.test.js +0 -177
  114. package/authentication/tests/authentication.client-service-refresh.test.d.ts +0 -1
  115. package/authentication/tests/authentication.client-service-refresh.test.js +0 -153
  116. package/authentication/tests/authentication.client-service.test.d.ts +0 -1
  117. package/authentication/tests/authentication.client-service.test.js +0 -76
  118. package/authentication/tests/authentication.refresh-busy-loop.test.d.ts +0 -1
  119. package/authentication/tests/authentication.refresh-busy-loop.test.js +0 -84
  120. package/authentication/tests/authentication.service.test.d.ts +0 -1
  121. package/authentication/tests/authentication.service.test.js +0 -167
  122. package/authentication/tests/authentication.test-ancillary-service.d.ts +0 -9
  123. package/authentication/tests/authentication.test-ancillary-service.js +0 -27
  124. package/authentication/tests/brute-force-protection.test.d.ts +0 -1
  125. package/authentication/tests/brute-force-protection.test.js +0 -211
  126. package/authentication/tests/helper.test.d.ts +0 -1
  127. package/authentication/tests/helper.test.js +0 -122
  128. package/authentication/tests/password-requirements.error.test.d.ts +0 -1
  129. package/authentication/tests/password-requirements.error.test.js +0 -14
  130. package/authentication/tests/remember.api.test.d.ts +0 -1
  131. package/authentication/tests/remember.api.test.js +0 -117
  132. package/authentication/tests/remember.service.test.d.ts +0 -1
  133. package/authentication/tests/remember.service.test.js +0 -83
  134. package/authentication/tests/subject.service.test.d.ts +0 -1
  135. package/authentication/tests/subject.service.test.js +0 -140
  136. package/authentication/tests/suspended-subject.test.d.ts +0 -1
  137. package/authentication/tests/suspended-subject.test.js +0 -120
  138. package/authentication/tests/totp.enrollment.test.d.ts +0 -1
  139. package/authentication/tests/totp.enrollment.test.js +0 -123
  140. package/authentication/tests/totp.login.test.d.ts +0 -1
  141. package/authentication/tests/totp.login.test.js +0 -213
  142. package/authentication/tests/totp.recovery-codes.test.d.ts +0 -1
  143. package/authentication/tests/totp.recovery-codes.test.js +0 -97
  144. package/authentication/tests/totp.status.test.d.ts +0 -1
  145. package/authentication/tests/totp.status.test.js +0 -72
  146. package/cancellation/tests/coverage.test.d.ts +0 -1
  147. package/cancellation/tests/coverage.test.js +0 -49
  148. package/cancellation/tests/leak.test.d.ts +0 -1
  149. package/cancellation/tests/leak.test.js +0 -35
  150. package/cancellation/tests/token.test.d.ts +0 -1
  151. package/cancellation/tests/token.test.js +0 -136
  152. package/circuit-breaker/tests/circuit-breaker.test.d.ts +0 -1
  153. package/circuit-breaker/tests/circuit-breaker.test.js +0 -116
  154. package/cryptography/tests/cryptography.test.d.ts +0 -1
  155. package/cryptography/tests/cryptography.test.js +0 -175
  156. package/cryptography/tests/jwt.test.d.ts +0 -1
  157. package/cryptography/tests/jwt.test.js +0 -54
  158. package/cryptography/tests/modern.test.d.ts +0 -1
  159. package/cryptography/tests/modern.test.js +0 -105
  160. package/cryptography/tests/module.test.d.ts +0 -1
  161. package/cryptography/tests/module.test.js +0 -100
  162. package/cryptography/tests/totp.test.d.ts +0 -1
  163. package/cryptography/tests/totp.test.js +0 -108
  164. package/document-management/tests/ai-config-hierarchy.test.d.ts +0 -1
  165. package/document-management/tests/ai-config-hierarchy.test.js +0 -59
  166. package/document-management/tests/ai-config-integration.test.d.ts +0 -1
  167. package/document-management/tests/ai-config-integration.test.js +0 -125
  168. package/document-management/tests/ai-config-merge.test.d.ts +0 -1
  169. package/document-management/tests/ai-config-merge.test.js +0 -46
  170. package/document-management/tests/document-management-ai-overrides.test.d.ts +0 -1
  171. package/document-management/tests/document-management-ai-overrides.test.js +0 -63
  172. package/document-management/tests/document-management-core.test.d.ts +0 -1
  173. package/document-management/tests/document-management-core.test.js +0 -157
  174. package/document-management/tests/document-management.api.test.d.ts +0 -1
  175. package/document-management/tests/document-management.api.test.js +0 -101
  176. package/document-management/tests/document-statistics.service.test.d.ts +0 -1
  177. package/document-management/tests/document-statistics.service.test.js +0 -498
  178. package/document-management/tests/document-validation-ai-overrides.test.d.ts +0 -1
  179. package/document-management/tests/document-validation-ai-overrides.test.js +0 -87
  180. package/document-management/tests/document.service.test.d.ts +0 -1
  181. package/document-management/tests/document.service.test.js +0 -143
  182. package/document-management/tests/enum-helpers.test.d.ts +0 -1
  183. package/document-management/tests/enum-helpers.test.js +0 -452
  184. package/document-management/tests/helper.d.ts +0 -24
  185. package/document-management/tests/helper.js +0 -39
  186. package/errors/tests/format.test.d.ts +0 -1
  187. package/errors/tests/format.test.js +0 -84
  188. package/http/tests/server-timing.test.d.ts +0 -1
  189. package/http/tests/server-timing.test.js +0 -42
  190. package/injector/tests/advanced.test.d.ts +0 -1
  191. package/injector/tests/advanced.test.js +0 -116
  192. package/injector/tests/async-init.test.d.ts +0 -1
  193. package/injector/tests/async-init.test.js +0 -77
  194. package/injector/tests/basic.test.d.ts +0 -1
  195. package/injector/tests/basic.test.js +0 -114
  196. package/injector/tests/hierarchical.test.d.ts +0 -1
  197. package/injector/tests/hierarchical.test.js +0 -59
  198. package/injector/tests/leak.test.d.ts +0 -1
  199. package/injector/tests/leak.test.js +0 -45
  200. package/injector/tests/lifecycles.test.d.ts +0 -1
  201. package/injector/tests/lifecycles.test.js +0 -109
  202. package/logger/tests/pretty-print.test.d.ts +0 -1
  203. package/logger/tests/pretty-print.test.js +0 -60
  204. package/notification/tests/notification-api.test.d.ts +0 -1
  205. package/notification/tests/notification-api.test.js +0 -124
  206. package/notification/tests/notification-client.test.d.ts +0 -1
  207. package/notification/tests/notification-client.test.js +0 -101
  208. package/notification/tests/notification-flow.test.d.ts +0 -1
  209. package/notification/tests/notification-flow.test.js +0 -296
  210. package/notification/tests/notification-sse.service.test.d.ts +0 -1
  211. package/notification/tests/notification-sse.service.test.js +0 -43
  212. package/notification/tests/notification-type.service.test.d.ts +0 -1
  213. package/notification/tests/notification-type.service.test.js +0 -41
  214. package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +0 -1
  215. package/object-storage/s3/tests/s3.object-storage.integration.test.js +0 -303
  216. package/orm/tests/build-jsonb.test.d.ts +0 -1
  217. package/orm/tests/build-jsonb.test.js +0 -39
  218. package/orm/tests/data-types.test.d.ts +0 -1
  219. package/orm/tests/data-types.test.js +0 -39
  220. package/orm/tests/database-extension.test.d.ts +0 -1
  221. package/orm/tests/database-extension.test.js +0 -63
  222. package/orm/tests/database-migration.test.d.ts +0 -1
  223. package/orm/tests/database-migration.test.js +0 -83
  224. package/orm/tests/decorators.test.d.ts +0 -1
  225. package/orm/tests/decorators.test.js +0 -77
  226. package/orm/tests/encryption.test.d.ts +0 -1
  227. package/orm/tests/encryption.test.js +0 -31
  228. package/orm/tests/query-complex.test.d.ts +0 -1
  229. package/orm/tests/query-complex.test.js +0 -172
  230. package/orm/tests/query-converter-complex.test.d.ts +0 -1
  231. package/orm/tests/query-converter-complex.test.js +0 -131
  232. package/orm/tests/query-converter.test.d.ts +0 -1
  233. package/orm/tests/query-converter.test.js +0 -123
  234. package/orm/tests/repository-advanced.test.d.ts +0 -1
  235. package/orm/tests/repository-advanced.test.js +0 -189
  236. package/orm/tests/repository-attributes.test.d.ts +0 -1
  237. package/orm/tests/repository-attributes.test.js +0 -83
  238. package/orm/tests/repository-compound-primary-key.test.d.ts +0 -2
  239. package/orm/tests/repository-compound-primary-key.test.js +0 -226
  240. package/orm/tests/repository-comprehensive.test.d.ts +0 -1
  241. package/orm/tests/repository-comprehensive.test.js +0 -162
  242. package/orm/tests/repository-coverage.test.d.ts +0 -2
  243. package/orm/tests/repository-coverage.test.js +0 -242
  244. package/orm/tests/repository-cti-complex.test.d.ts +0 -1
  245. package/orm/tests/repository-cti-complex.test.js +0 -151
  246. package/orm/tests/repository-cti-embedded.test.d.ts +0 -1
  247. package/orm/tests/repository-cti-embedded.test.js +0 -178
  248. package/orm/tests/repository-cti-extensive.test.d.ts +0 -2
  249. package/orm/tests/repository-cti-extensive.test.js +0 -279
  250. package/orm/tests/repository-cti-mapping.test.d.ts +0 -2
  251. package/orm/tests/repository-cti-mapping.test.js +0 -108
  252. package/orm/tests/repository-cti-search.test.d.ts +0 -1
  253. package/orm/tests/repository-cti-search.test.js +0 -141
  254. package/orm/tests/repository-cti-soft-delete.test.d.ts +0 -2
  255. package/orm/tests/repository-cti-soft-delete.test.js +0 -103
  256. package/orm/tests/repository-cti-transactions.test.d.ts +0 -1
  257. package/orm/tests/repository-cti-transactions.test.js +0 -112
  258. package/orm/tests/repository-cti-upsert-many.test.d.ts +0 -2
  259. package/orm/tests/repository-cti-upsert-many.test.js +0 -115
  260. package/orm/tests/repository-cti.test.d.ts +0 -2
  261. package/orm/tests/repository-cti.test.js +0 -390
  262. package/orm/tests/repository-edge-cases.test.d.ts +0 -1
  263. package/orm/tests/repository-edge-cases.test.js +0 -178
  264. package/orm/tests/repository-expiration.test.d.ts +0 -2
  265. package/orm/tests/repository-expiration.test.js +0 -140
  266. package/orm/tests/repository-extra-coverage.test.d.ts +0 -2
  267. package/orm/tests/repository-extra-coverage.test.js +0 -402
  268. package/orm/tests/repository-mapping.test.d.ts +0 -2
  269. package/orm/tests/repository-mapping.test.js +0 -65
  270. package/orm/tests/repository-regression.test.d.ts +0 -1
  271. package/orm/tests/repository-regression.test.js +0 -288
  272. package/orm/tests/repository-search-coverage.test.d.ts +0 -1
  273. package/orm/tests/repository-search-coverage.test.js +0 -107
  274. package/orm/tests/repository-search.test.d.ts +0 -1
  275. package/orm/tests/repository-search.test.js +0 -105
  276. package/orm/tests/repository-soft-delete.test.d.ts +0 -1
  277. package/orm/tests/repository-soft-delete.test.js +0 -118
  278. package/orm/tests/repository-transactions-nested.test.d.ts +0 -1
  279. package/orm/tests/repository-transactions-nested.test.js +0 -178
  280. package/orm/tests/repository-types.test.d.ts +0 -1
  281. package/orm/tests/repository-types.test.js +0 -184
  282. package/orm/tests/repository-undelete.test.d.ts +0 -2
  283. package/orm/tests/repository-undelete.test.js +0 -201
  284. package/orm/tests/schema-converter.test.d.ts +0 -1
  285. package/orm/tests/schema-converter.test.js +0 -82
  286. package/orm/tests/schema-generation.test.d.ts +0 -2
  287. package/orm/tests/schema-generation.test.js +0 -174
  288. package/orm/tests/sql-helpers.test.d.ts +0 -1
  289. package/orm/tests/sql-helpers.test.js +0 -67
  290. package/orm/tests/transaction-safety.test.d.ts +0 -1
  291. package/orm/tests/transaction-safety.test.js +0 -81
  292. package/orm/tests/transactional.test.d.ts +0 -1
  293. package/orm/tests/transactional.test.js +0 -215
  294. package/orm/tests/utils.test.d.ts +0 -1
  295. package/orm/tests/utils.test.js +0 -70
  296. package/pdf/tests/utils.test.d.ts +0 -1
  297. package/pdf/tests/utils.test.js +0 -187
  298. package/process/tests/spawn.test.d.ts +0 -1
  299. package/process/tests/spawn.test.js +0 -182
  300. package/rate-limit/tests/postgres-rate-limiter.test.d.ts +0 -1
  301. package/rate-limit/tests/postgres-rate-limiter.test.js +0 -84
  302. package/renderer/tests/renderer.test.d.ts +0 -1
  303. package/renderer/tests/renderer.test.js +0 -88
  304. package/rpc/tests/rpc.integration.test.d.ts +0 -1
  305. package/rpc/tests/rpc.integration.test.js +0 -615
  306. package/signals/implementation/errors.d.ts +0 -2
  307. package/signals/implementation/graph.js +0 -312
  308. package/signals/implementation/writable-signal.d.ts +0 -48
  309. package/signals/implementation/writable-signal.js +0 -32
  310. package/task-queue/tests/coverage-branch.test.d.ts +0 -1
  311. package/task-queue/tests/coverage-branch.test.js +0 -395
  312. package/task-queue/tests/coverage-enhancement.test.d.ts +0 -1
  313. package/task-queue/tests/coverage-enhancement.test.js +0 -150
  314. package/task-queue/tests/dag.test.d.ts +0 -1
  315. package/task-queue/tests/dag.test.js +0 -188
  316. package/task-queue/tests/dependencies.test.d.ts +0 -1
  317. package/task-queue/tests/dependencies.test.js +0 -296
  318. package/task-queue/tests/enqueue-batch.test.d.ts +0 -1
  319. package/task-queue/tests/enqueue-batch.test.js +0 -125
  320. package/task-queue/tests/enqueue-item.test.d.ts +0 -1
  321. package/task-queue/tests/enqueue-item.test.js +0 -12
  322. package/task-queue/tests/fan-out-spawning.test.d.ts +0 -1
  323. package/task-queue/tests/fan-out-spawning.test.js +0 -94
  324. package/task-queue/tests/idempotent-replacement.test.d.ts +0 -1
  325. package/task-queue/tests/idempotent-replacement.test.js +0 -114
  326. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +0 -1
  327. package/task-queue/tests/missing-idempotent-tasks.test.js +0 -39
  328. package/task-queue/tests/optimization-edge-cases.test.d.ts +0 -1
  329. package/task-queue/tests/optimization-edge-cases.test.js +0 -124
  330. package/task-queue/tests/queue-generic.test.d.ts +0 -1
  331. package/task-queue/tests/queue-generic.test.js +0 -8
  332. package/task-queue/tests/queue.test.d.ts +0 -1
  333. package/task-queue/tests/queue.test.js +0 -756
  334. package/task-queue/tests/shutdown.test.d.ts +0 -1
  335. package/task-queue/tests/shutdown.test.js +0 -41
  336. package/task-queue/tests/task-context.test.d.ts +0 -1
  337. package/task-queue/tests/task-context.test.js +0 -7
  338. package/task-queue/tests/task-union.test.d.ts +0 -1
  339. package/task-queue/tests/task-union.test.js +0 -18
  340. package/task-queue/tests/transactions.test.d.ts +0 -1
  341. package/task-queue/tests/transactions.test.js +0 -47
  342. package/task-queue/tests/typing.test.d.ts +0 -1
  343. package/task-queue/tests/typing.test.js +0 -9
  344. package/task-queue/tests/worker.test.d.ts +0 -1
  345. package/task-queue/tests/worker.test.js +0 -258
  346. package/task-queue/tests/zombie-parent.test.d.ts +0 -1
  347. package/task-queue/tests/zombie-parent.test.js +0 -45
  348. package/task-queue/tests/zombie-recovery.test.d.ts +0 -1
  349. package/task-queue/tests/zombie-recovery.test.js +0 -51
  350. package/utils/tests/backoff.test.d.ts +0 -1
  351. package/utils/tests/backoff.test.js +0 -41
  352. package/utils/tests/retry-with-backoff.test.d.ts +0 -1
  353. package/utils/tests/retry-with-backoff.test.js +0 -49
@@ -1,124 +0,0 @@
1
- import { Subject } from 'rxjs';
2
- import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
3
- import { SubjectService } from '../../authentication/server/subject.service.js';
4
- import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
5
- import { NotificationChannel } from '../models/index.js';
6
- import { NotificationApiController } from '../server/api/notification.api-controller.js';
7
- import { NotificationSseService } from '../server/services/notification-sse.service.js';
8
- import { NotificationTypeService } from '../server/services/notification-type.service.js';
9
- import { NotificationService } from '../server/services/notification.service.js';
10
- describe('Notification API (Integration)', () => {
11
- let injector;
12
- let database;
13
- let controller;
14
- let notificationService;
15
- let sseService;
16
- let subjectService;
17
- const schema = 'notification';
18
- const tenantId = crypto.randomUUID();
19
- let userId;
20
- beforeAll(async () => {
21
- ({ injector, database } = await setupIntegrationTest({
22
- orm: { schema },
23
- modules: {
24
- notification: true,
25
- authentication: true,
26
- taskQueue: true,
27
- rateLimiter: true,
28
- },
29
- }));
30
- controller = injector.resolve(NotificationApiController);
31
- notificationService = injector.resolve(NotificationService);
32
- sseService = injector.resolve(NotificationSseService);
33
- subjectService = injector.resolve(SubjectService);
34
- });
35
- beforeEach(async () => {
36
- vi.clearAllMocks();
37
- await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
38
- // Create a dummy user
39
- const user = await subjectService.createUser({
40
- tenantId,
41
- email: 'api-test@example.com',
42
- firstName: 'Api',
43
- lastName: 'Test',
44
- });
45
- userId = user.id;
46
- });
47
- afterEach(async () => {
48
- await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
49
- await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
50
- });
51
- const createMockContext = (params = {}) => ({
52
- parameters: params,
53
- abortSignal: new AbortController().signal,
54
- serverSentEvents: {},
55
- getToken: async () => ({
56
- payload: {
57
- tenant: tenantId,
58
- subject: userId,
59
- },
60
- }),
61
- }); // Cast to any for controller compatibility if definition is strict
62
- test('stream should register sse client', async () => {
63
- const registerSpy = vi.spyOn(sseService, 'register');
64
- const subject = new Subject();
65
- // Mock register to return a dummy source or similar if needed,
66
- // but the real one might work if it just returns an object.
67
- // Looking at sseService, register returns a ServerSentEventsSource.
68
- // We might need to mock implementation if it tries to do something complex
69
- registerSpy.mockImplementation(() => subject);
70
- const generator = controller.stream(createMockContext());
71
- const nextPromise = generator.next();
72
- subject.complete();
73
- await nextPromise;
74
- expect(registerSpy).toHaveBeenCalledWith(tenantId, userId);
75
- });
76
- test('types should call service', async () => {
77
- const notificationTypeService = injector.resolve(NotificationTypeService);
78
- const getTypesSpy = vi.spyOn(notificationTypeService, 'getTypes').mockResolvedValue({ test: 'Test' });
79
- const result = await controller.types();
80
- expect(result).toEqual({ test: 'Test' });
81
- expect(getTypesSpy).toHaveBeenCalled();
82
- });
83
- test('listInApp should call service', async () => {
84
- const listInAppSpy = vi.spyOn(notificationService, 'listInApp').mockResolvedValue([]);
85
- const params = { limit: 10, offset: 0 };
86
- await controller.listInApp(createMockContext(params));
87
- expect(listInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
88
- });
89
- test('listArchivedInApp should call service', async () => {
90
- const listArchivedInAppSpy = vi.spyOn(notificationService, 'listArchivedInApp').mockResolvedValue([]);
91
- const params = { limit: 10, offset: 0 };
92
- await controller.listArchivedInApp(createMockContext(params));
93
- expect(listArchivedInAppSpy).toHaveBeenCalledWith(tenantId, userId, params);
94
- });
95
- test('markRead should call service', async () => {
96
- const markReadSpy = vi.spyOn(notificationService, 'markRead').mockResolvedValue();
97
- const params = { id: 'notif-id' };
98
- await controller.markRead(createMockContext(params));
99
- expect(markReadSpy).toHaveBeenCalledWith(tenantId, userId, 'notif-id');
100
- });
101
- test('archive should call service', async () => {
102
- const archiveSpy = vi.spyOn(notificationService, 'archive').mockResolvedValue();
103
- const params = { id: 'notif-id' };
104
- await controller.archive(createMockContext(params));
105
- expect(archiveSpy).toHaveBeenCalledWith(tenantId, userId, 'notif-id');
106
- });
107
- test('getPreferences should call service', async () => {
108
- const getPreferencesSpy = vi.spyOn(notificationService, 'getPreferences').mockResolvedValue([]);
109
- await controller.getPreferences(createMockContext());
110
- expect(getPreferencesSpy).toHaveBeenCalledWith(tenantId, userId);
111
- });
112
- test('updatePreferences should call service', async () => {
113
- const updatePreferencesSpy = vi.spyOn(notificationService, 'updatePreferences').mockResolvedValue();
114
- const params = [{ type: 'test', channel: NotificationChannel.Email, enabled: true }];
115
- await controller.updatePreferences(createMockContext(params));
116
- expect(updatePreferencesSpy).toHaveBeenCalledWith(tenantId, userId, params);
117
- });
118
- test('registerWebPush should call service', async () => {
119
- const registerWebPushSpy = vi.spyOn(notificationService, 'registerWebPush').mockResolvedValue();
120
- const params = { endpoint: 'url', keys: { p256dhBase64: 'key', authBase64: 'auth' } };
121
- await controller.registerWebPush(createMockContext(params));
122
- expect(registerWebPushSpy).toHaveBeenCalledWith(tenantId, userId, 'url', expect.any(Uint8Array), expect.any(Uint8Array));
123
- });
124
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,101 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
2
- import { AuthenticationClientService } from '../../authentication/client/authentication.service.js';
3
- import { Injector } from '../../injector/index.js';
4
- import { NotificationApiClient } from '../../notification/api/index.js';
5
- import { NotificationClient } from '../../notification/client/notification-client.js';
6
- import { configureDefaultSignalsImplementation } from '../../signals/implementation/configure.js';
7
- import { timeout } from '../../utils/timing.js';
8
- import { BehaviorSubject, of, Subject } from 'rxjs';
9
- describe('NotificationClient', () => {
10
- let injector;
11
- let authenticationServiceMock;
12
- let notificationApiClientMock;
13
- let notificationClient;
14
- const sessionId$ = new BehaviorSubject(undefined);
15
- const stream$ = new Subject();
16
- beforeEach(() => {
17
- configureDefaultSignalsImplementation();
18
- injector = new Injector('TestInjector');
19
- authenticationServiceMock = {
20
- sessionId$,
21
- };
22
- notificationApiClientMock = {
23
- listInApp: vi.fn().mockResolvedValue([]),
24
- unreadCount: vi.fn().mockResolvedValue(0),
25
- types: vi.fn().mockResolvedValue({}),
26
- stream: vi.fn().mockReturnValue(of(stream$)),
27
- };
28
- injector.register(AuthenticationClientService, { useValue: authenticationServiceMock });
29
- injector.register(NotificationApiClient, { useValue: notificationApiClientMock });
30
- notificationClient = injector.resolve(NotificationClient);
31
- });
32
- afterEach(() => {
33
- vi.clearAllMocks();
34
- sessionId$.next(undefined);
35
- });
36
- test('should initialize with empty state', () => {
37
- expect(notificationClient.notifications()).toEqual([]);
38
- expect(notificationClient.unreadCount()).toBe(0);
39
- expect(notificationClient.types()).toEqual({});
40
- });
41
- test('should load notifications on session start', async () => {
42
- const notifications = [{ id: '1', type: 'test' }];
43
- const unreadCount = 5;
44
- const types = { test: 'Test' };
45
- notificationApiClientMock.listInApp.mockResolvedValue(notifications);
46
- notificationApiClientMock.unreadCount.mockResolvedValue(unreadCount);
47
- notificationApiClientMock.types.mockResolvedValue(types);
48
- sessionId$.next('session-1');
49
- // Wait for async operations (microtasks)
50
- await timeout(0);
51
- expect(notificationClient.notifications()).toEqual(notifications);
52
- expect(notificationClient.unreadCount()).toBe(unreadCount);
53
- expect(notificationClient.types()).toEqual(types);
54
- expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 20 });
55
- expect(notificationApiClientMock.unreadCount).toHaveBeenCalled();
56
- expect(notificationApiClientMock.types).toHaveBeenCalled();
57
- expect(notificationApiClientMock.stream).toHaveBeenCalled();
58
- });
59
- test('should clear notifications on session end', async () => {
60
- const notifications = [{ id: '1', type: 'test' }];
61
- notificationApiClientMock.listInApp.mockResolvedValue(notifications);
62
- sessionId$.next('session-1');
63
- await timeout(0);
64
- expect(notificationClient.notifications()).toHaveLength(1);
65
- sessionId$.next(undefined);
66
- await timeout(0);
67
- expect(notificationClient.notifications()).toEqual([]);
68
- expect(notificationClient.unreadCount()).toBe(0);
69
- });
70
- test('should handle new notification from stream', async () => {
71
- const initialNotifications = [{ id: '1', type: 'test' }];
72
- notificationApiClientMock.listInApp.mockResolvedValue(initialNotifications);
73
- sessionId$.next('session-1');
74
- await timeout(0);
75
- const newNotification = { id: '2', type: 'test' };
76
- stream$.next({ notification: newNotification, unreadCount: 1 });
77
- expect(notificationClient.notifications()).toEqual([newNotification, ...initialNotifications]);
78
- expect(notificationClient.unreadCount()).toBe(1);
79
- });
80
- test('should handle unread count update from stream', async () => {
81
- notificationApiClientMock.listInApp.mockResolvedValue([]);
82
- sessionId$.next('session-1');
83
- await timeout(0);
84
- stream$.next({ unreadCount: 10 });
85
- expect(notificationClient.unreadCount()).toBe(10);
86
- });
87
- test('should load next page of notifications', async () => {
88
- const page1 = [{ id: '2', type: 'test' }];
89
- const page2 = [{ id: '1', type: 'test' }];
90
- notificationApiClientMock.listInApp
91
- .mockResolvedValueOnce(page1) // Initial load
92
- .mockResolvedValueOnce(page2); // Pagination
93
- sessionId$.next('session-1');
94
- await timeout(0);
95
- expect(notificationClient.notifications()).toEqual(page1);
96
- notificationClient.loadNext(10);
97
- await timeout(0);
98
- expect(notificationClient.notifications()).toEqual([...page1, ...page2]);
99
- expect(notificationApiClientMock.listInApp).toHaveBeenCalledWith({ limit: 10, after: '2' });
100
- });
101
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,296 +0,0 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
8
- import { SubjectService } from '../../authentication/server/subject.service.js';
9
- import { Singleton } from '../../injector/index.js';
10
- import { MailService } from '../../mail/mail.service.js';
11
- import { getRepository } from '../../orm/server/index.js';
12
- import { clearTenantData, setupIntegrationTest } from '../../testing/index.js';
13
- import { InAppNotification, NotificationChannel, NotificationLogEntity, NotificationStatus, WebPushSubscription } from '../models/index.js';
14
- import { configureNotification, EmailChannelProvider, InAppChannelProvider, NotificationAncillaryService, NotificationDeliveryWorker, NotificationService, NotificationSseService, NotificationTypeService } from '../server/index.js';
15
- let MockNotificationAncillaryService = class MockNotificationAncillaryService extends NotificationAncillaryService {
16
- async getViewData(_tenantId, notifications) {
17
- return notifications.map(() => ({}));
18
- }
19
- };
20
- MockNotificationAncillaryService = __decorate([
21
- Singleton()
22
- ], MockNotificationAncillaryService);
23
- describe('Notification Flow (Integration)', () => {
24
- let injector;
25
- let database;
26
- let notificationService;
27
- let worker;
28
- let typeService;
29
- let subjectService;
30
- let mailServiceMock;
31
- let logRepo;
32
- let inAppRepo;
33
- let webPushRepo;
34
- const schema = 'notification';
35
- let tenantId;
36
- beforeAll(async () => {
37
- ({ injector, database } = await setupIntegrationTest({
38
- orm: { schema },
39
- modules: {
40
- taskQueue: true,
41
- rateLimiter: true,
42
- authentication: true,
43
- messageBus: true,
44
- signals: true,
45
- notification: true,
46
- },
47
- }));
48
- // Mock MailService
49
- mailServiceMock = { send: vi.fn() };
50
- injector.register(MailService, { useValue: mailServiceMock });
51
- // Mock NotificationSseService
52
- injector.register(NotificationSseService, { useValue: { dispatch: vi.fn() } });
53
- // Mock NotificationAncillaryService
54
- configureNotification({ injector, ancillaryService: MockNotificationAncillaryService });
55
- // Resolve Services
56
- notificationService = injector.resolve(NotificationService);
57
- worker = injector.resolve(NotificationDeliveryWorker);
58
- typeService = injector.resolve(NotificationTypeService);
59
- subjectService = injector.resolve(SubjectService);
60
- logRepo = injector.resolve(getRepository(NotificationLogEntity));
61
- inAppRepo = injector.resolve(getRepository(InAppNotification));
62
- webPushRepo = injector.resolve(getRepository(WebPushSubscription));
63
- // Register providers
64
- worker.registerProvider(NotificationChannel.Email, injector.resolve(EmailChannelProvider));
65
- worker.registerProvider(NotificationChannel.InApp, injector.resolve(InAppChannelProvider));
66
- });
67
- beforeEach(async () => {
68
- tenantId = crypto.randomUUID();
69
- vi.clearAllMocks();
70
- await typeService.initializeTypes({
71
- test: {
72
- label: 'Test Category',
73
- escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
74
- },
75
- throttled: {
76
- label: 'Throttled Category',
77
- throttling: { limit: 1, interval: 60000 },
78
- },
79
- readTest: {
80
- label: 'Read Test',
81
- escalations: [{ delay: 1000, channel: NotificationChannel.Email }],
82
- },
83
- prefTest: { label: 'Preference Test' },
84
- unknownTest: { label: 'Unknown Test' },
85
- manageTest: { label: 'Manage Test' },
86
- testType: { label: 'Test Type' },
87
- auto: { label: 'Auto Test' },
88
- archive: { label: 'Archive All Test' },
89
- });
90
- });
91
- afterEach(async () => {
92
- await clearTenantData(database, schema, ['in_app', 'in_app_archive', 'log', 'preference', 'web_push_subscription'], tenantId);
93
- await clearTenantData(database, 'authentication', ['user', 'subject'], tenantId);
94
- });
95
- test('should execute full notification flow with escalation', async () => {
96
- const user = await subjectService.createUser({
97
- tenantId,
98
- email: 'test@example.com',
99
- firstName: 'Test',
100
- lastName: 'User',
101
- });
102
- await notificationService.send(tenantId, user.id, {
103
- type: 'test',
104
- priority: 'high',
105
- triggerSubjectId: user.id,
106
- payload: { message: 'Hello', testField: 'Test Value' },
107
- });
108
- const logs = await logRepo.loadManyByQuery({ tenantId });
109
- expect(logs).toHaveLength(1);
110
- const log = logs[0];
111
- expect(log.status).toBe(NotificationStatus.Pending);
112
- expect(log.currentStep).toBe(0);
113
- // Step 0 (In-App)
114
- const result0 = await worker.deliver(log.id);
115
- expect(result0.payload.action).toBe('reschedule');
116
- const inApps = await inAppRepo.loadManyByQuery({ tenantId });
117
- expect(inApps).toHaveLength(1);
118
- expect(inApps[0].logId).toBe(log.id);
119
- const logAfterStep0 = await logRepo.load(log.id);
120
- expect(logAfterStep0.status).toBe(NotificationStatus.Sent);
121
- expect(logAfterStep0.currentStep).toBe(1);
122
- // Step 1 (Email Escalation)
123
- const result1 = await worker.deliver(log.id);
124
- expect(result1.payload.action).toBe('complete');
125
- expect(mailServiceMock.send).toHaveBeenCalled();
126
- const mailArgs = mailServiceMock.send.mock.calls[0][0];
127
- expect(mailArgs.to).toBe('test@example.com');
128
- const logAfterStep1 = await logRepo.load(log.id);
129
- expect(logAfterStep1.currentStep).toBe(2);
130
- });
131
- test('should handle throttling correctly', async () => {
132
- const user = await subjectService.createUser({ tenantId, email: 'throttled@example.com', firstName: 'Throttled', lastName: 'User' });
133
- await notificationService.send(tenantId, user.id, {
134
- type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
135
- });
136
- await notificationService.send(tenantId, user.id, {
137
- type: 'throttled', priority: 'medium', triggerSubjectId: user.id, payload: {},
138
- });
139
- const logs = await logRepo.loadManyByQuery({ tenantId });
140
- logs.sort((a, b) => Number(a.timestamp) - Number(b.timestamp));
141
- // First one should pass
142
- const result1 = await worker.deliver(logs[0].id);
143
- expect(result1.payload.action).toBe('complete'); // No escalations
144
- // Second one should be throttled
145
- const result2 = await worker.deliver(logs[1].id);
146
- expect(result2.payload.action).toBe('reschedule');
147
- });
148
- test('should skip escalation if notification is read', async () => {
149
- const user = await subjectService.createUser({ tenantId, email: 'read@example.com', firstName: 'Read', lastName: 'User' });
150
- await notificationService.send(tenantId, user.id, {
151
- type: 'readTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
152
- });
153
- const logs = await logRepo.loadManyByQuery({ tenantId });
154
- const log = logs[0];
155
- // Step 0: Deliver In-App
156
- await worker.deliver(log.id);
157
- // Mark as Read
158
- const inAppNotifications = await notificationService.listInApp(tenantId, user.id);
159
- expect(inAppNotifications).toHaveLength(1);
160
- await notificationService.markRead(tenantId, user.id, inAppNotifications[0].id);
161
- // Step 1: Attempt Escalation
162
- const result = await worker.deliver(log.id);
163
- expect(result.payload.action).toBe('complete');
164
- expect(mailServiceMock.send).not.toHaveBeenCalled();
165
- });
166
- test('should respect user preferences', async () => {
167
- const user = await subjectService.createUser({ tenantId, email: 'pref@example.com', firstName: 'Pref', lastName: 'User' });
168
- // Disable InApp, Enable Email (even though not default)
169
- await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.InApp, false);
170
- await notificationService.updatePreference(tenantId, user.id, 'prefTest', NotificationChannel.Email, true);
171
- await notificationService.send(tenantId, user.id, {
172
- type: 'prefTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
173
- });
174
- const logs = await logRepo.loadManyByQuery({ tenantId });
175
- const log = logs[0];
176
- // Deliver
177
- await worker.deliver(log.id);
178
- // Should have sent Email but NOT InApp
179
- expect(mailServiceMock.send).toHaveBeenCalled();
180
- const inAppNotifications = await notificationService.listInApp(tenantId, user.id);
181
- expect(inAppNotifications).toHaveLength(0);
182
- });
183
- test('should handle unknown channels gracefully', async () => {
184
- const user = await subjectService.createUser({ tenantId, email: 'unknown@example.com', firstName: 'Unknown', lastName: 'User' });
185
- // Force a preference for an unknown channel/unregistered provider (e.g., WebPush if not registered or some random string if type allowed)
186
- // Since NotificationChannel is an enum, we use WebPush which we haven't registered in the test setup (wait, we didn't register WebPush)
187
- await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.WebPush, true);
188
- // Disable InApp so only WebPush is attempted
189
- await notificationService.updatePreference(tenantId, user.id, 'unknownTest', NotificationChannel.InApp, false);
190
- await notificationService.send(tenantId, user.id, {
191
- type: 'unknownTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
192
- });
193
- const logs = await logRepo.loadManyByQuery({ tenantId });
194
- const log = logs[0];
195
- // Deliver
196
- const result = await worker.deliver(log.id);
197
- // Should complete without error, just logging a warning (which we can't easily assert on unless we spy logger)
198
- expect(result.payload.action).toBe('complete');
199
- });
200
- test('should manage notification lists and status (markRead, archive)', async () => {
201
- const user = await subjectService.createUser({ tenantId, email: 'manage@example.com', firstName: 'Manage', lastName: 'User' });
202
- await notificationService.send(tenantId, user.id, {
203
- type: 'manageTest', priority: 'medium', triggerSubjectId: user.id, payload: {},
204
- });
205
- const logs = await logRepo.loadManyByQuery({ tenantId });
206
- await worker.deliver(logs[0].id);
207
- // List
208
- let list = await notificationService.listInApp(tenantId, user.id);
209
- expect(list).toHaveLength(1);
210
- expect(list[0].readTimestamp).toBeNull();
211
- expect(list[0].notification).toBeDefined();
212
- // Mark Read
213
- await notificationService.markRead(tenantId, user.id, list[0].id);
214
- list = await notificationService.listInApp(tenantId, user.id);
215
- expect(list[0].readTimestamp).not.toBeNull();
216
- // Archive
217
- await notificationService.archive(tenantId, user.id, list[0].id);
218
- // List (excludes archived)
219
- list = await notificationService.listInApp(tenantId, user.id);
220
- expect(list).toHaveLength(0);
221
- // List archived
222
- list = await notificationService.listArchivedInApp(tenantId, user.id);
223
- expect(list).toHaveLength(1);
224
- expect(list[0].archiveTimestamp).not.toBeNull();
225
- });
226
- test('should register web push subscription', async () => {
227
- const user = await subjectService.createUser({ tenantId, email: 'push@example.com', firstName: 'Push', lastName: 'User' });
228
- await notificationService.registerWebPush(tenantId, user.id, 'https://endpoint.com', new Uint8Array(32), new Uint8Array(32));
229
- const subs = await webPushRepo.loadManyByQuery({ tenantId, userId: user.id });
230
- expect(subs).toHaveLength(1);
231
- expect(subs[0].endpoint).toBe('https://endpoint.com');
232
- });
233
- test('should retrieve user preferences', async () => {
234
- const user = await subjectService.createUser({ tenantId, email: 'getpref@example.com', firstName: 'GetPref', lastName: 'User' });
235
- await notificationService.updatePreference(tenantId, user.id, 'testType', NotificationChannel.Email, true);
236
- const prefs = await notificationService.getPreferences(tenantId, user.id);
237
- expect(prefs).toHaveLength(1);
238
- prefs.sort((a, b) => a.type.localeCompare(b.type));
239
- expect(prefs[0].type).toBe('testType');
240
- expect(prefs[0].channel).toBe(NotificationChannel.Email);
241
- expect(prefs[0].enabled).toBe(true);
242
- });
243
- test('should support keyset pagination with after and orderBy', async () => {
244
- const user = await subjectService.createUser({ tenantId, email: 'pagination@example.com', firstName: 'Pagination', lastName: 'User' });
245
- // Create 3 notifications
246
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 1 } });
247
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 2 } });
248
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: { index: 3 } });
249
- const logs = await logRepo.loadManyByQuery({ tenantId });
250
- for (const log of logs) {
251
- await worker.deliver(log.id);
252
- }
253
- // Default list (desc timestamp, then desc id)
254
- const list = await notificationService.listInApp(tenantId, user.id);
255
- expect(list).toHaveLength(3);
256
- const firstId = list[0].id;
257
- const secondId = list[1].id;
258
- // After first
259
- const afterFirst = await notificationService.listInApp(tenantId, user.id, { after: firstId });
260
- expect(afterFirst).toHaveLength(2);
261
- expect(afterFirst[0].id).toBe(secondId);
262
- // After second
263
- const afterSecond = await notificationService.listInApp(tenantId, user.id, { after: secondId });
264
- expect(afterSecond).toHaveLength(1);
265
- expect(afterSecond[0].id).toBe(list[2].id);
266
- });
267
- test('should auto-archive old notifications', async () => {
268
- const user = await subjectService.createUser({ tenantId, email: 'auto@example.com', firstName: 'Auto', lastName: 'User' });
269
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
270
- const logs = await logRepo.loadManyByQuery({ tenantId });
271
- await worker.deliver(logs[0].id);
272
- // Verify active
273
- expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(1);
274
- // Manually update timestamp to be old (31 days ago)
275
- const oldTimestamp = Date.now() - 31 * 24 * 60 * 60 * 1000;
276
- await logRepo.updateByQuery({ id: logs[0].id }, { timestamp: oldTimestamp });
277
- await inAppRepo.updateByQuery({ tenantId, logId: logs[0].id }, { timestamp: oldTimestamp });
278
- await notificationService.runAutoArchive();
279
- // Verify archived
280
- expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
281
- expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(1);
282
- });
283
- test('should archive all notifications for a user', async () => {
284
- const user = await subjectService.createUser({ tenantId, email: 'archiveall@example.com', firstName: 'Archive', lastName: 'All' });
285
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
286
- await notificationService.send(tenantId, user.id, { type: 'test', triggerSubjectId: user.id, payload: {} });
287
- const logs = await logRepo.loadManyByQuery({ tenantId });
288
- for (const log of logs) {
289
- await worker.deliver(log.id);
290
- }
291
- expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(2);
292
- await notificationService.archiveAll(tenantId, user.id);
293
- expect(await notificationService.listInApp(tenantId, user.id)).toHaveLength(0);
294
- expect(await notificationService.listArchivedInApp(tenantId, user.id)).toHaveLength(2);
295
- });
296
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { setupIntegrationTest } from '../../testing/index.js';
3
- import { NotificationSseService } from '../server/services/notification-sse.service.js';
4
- describe('NotificationSseService', () => {
5
- test('should register and publish to bus', async () => {
6
- const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
7
- const service = injector.resolve(NotificationSseService);
8
- const tenantId = 't1';
9
- const userId = 'u1';
10
- const source = service.register(tenantId, userId);
11
- expect(source).toBeDefined();
12
- // We can't easily spy on the LocalMessageBus internals without more complex setup,
13
- // but we can verify that sending doesn't throw.
14
- const msg = { id: 'l1', tenantId, userId, logId: 'l1' };
15
- await expect(service.dispatch(tenantId, userId, { notification: msg, unreadCount: 1 })).resolves.not.toThrow();
16
- });
17
- test('should dispatch unread count update', async () => {
18
- const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
19
- const service = injector.resolve(NotificationSseService);
20
- const tenantId = 't1';
21
- const userId = 'u1';
22
- const source = service.register(tenantId, userId);
23
- const messages = [];
24
- source.subscribe((msg) => messages.push(msg));
25
- await service.dispatch(tenantId, userId, { unreadCount: 5 });
26
- expect(messages).toHaveLength(1);
27
- expect(messages[0]).toEqual({ unreadCount: 5 });
28
- });
29
- test('should dispatch mark read and mark all read', async () => {
30
- const { injector } = await setupIntegrationTest({ modules: { messageBus: true, signals: true } });
31
- const service = injector.resolve(NotificationSseService);
32
- const tenantId = 't1';
33
- const userId = 'u1';
34
- const source = service.register(tenantId, userId);
35
- const messages = [];
36
- source.subscribe((msg) => messages.push(msg));
37
- await service.dispatch(tenantId, userId, { readId: 'n1', unreadCount: 2 });
38
- await service.dispatch(tenantId, userId, { readAll: true, unreadCount: 0 });
39
- expect(messages).toHaveLength(2);
40
- expect(messages[0]).toEqual({ readId: 'n1', unreadCount: 2 });
41
- expect(messages[1]).toEqual({ readAll: true, unreadCount: 0 });
42
- });
43
- });
@@ -1,41 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from 'vitest';
2
- import { setupIntegrationTest, truncateTables } from '../../testing/index.js';
3
- import { NotificationTypeService } from '../server/services/notification-type.service.js';
4
- describe('NotificationTypeService', () => {
5
- let injector;
6
- let database;
7
- beforeEach(async () => {
8
- ({ injector, database } = await setupIntegrationTest({ modules: { notification: true, authentication: true } }));
9
- await truncateTables(database, 'notification', ['type']);
10
- });
11
- afterEach(async () => {
12
- await injector?.dispose();
13
- });
14
- test('should initialize types correctly', async () => {
15
- const service = injector.resolve(NotificationTypeService);
16
- const prefix = crypto.randomUUID();
17
- const type1 = `${prefix}_TYPE1`;
18
- const type2 = `${prefix}_TYPE2`;
19
- const typeData = {
20
- [type1]: { label: 'Type 1' },
21
- [type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } },
22
- };
23
- const result = await service.initializeTypes(typeData);
24
- expect(result[type1]?.label).toBe('Type 1');
25
- expect(result[type2]?.key).toBe(type2);
26
- expect(result[type2]?.throttling?.limit).toBe(1);
27
- // Verify persistence
28
- const dbTypes = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
29
- expect(dbTypes).toHaveLength(2);
30
- // Update
31
- const updatedData = {
32
- [type1]: { label: 'Type 1 Updated' },
33
- [type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } },
34
- };
35
- const resultUpdated = await service.initializeTypes(updatedData);
36
- expect(resultUpdated[type1]?.label).toBe('Type 1 Updated');
37
- const dbTypesUpdated = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
38
- expect(dbTypesUpdated).toHaveLength(2);
39
- expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
40
- });
41
- });