@wopr-network/platform-core 1.13.2 → 1.14.0

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 (238) hide show
  1. package/dist/api/routes/admin-credits.d.ts +2 -2
  2. package/dist/api/routes/admin-credits.js +9 -4
  3. package/dist/api/routes/quota.d.ts +2 -2
  4. package/dist/api/routes/verify-email.d.ts +3 -3
  5. package/dist/backup/on-demand-snapshot-service.d.ts +2 -2
  6. package/dist/billing/payram/webhook.d.ts +3 -3
  7. package/dist/billing/payram/webhook.js +5 -1
  8. package/dist/billing/payram/webhook.test.js +5 -4
  9. package/dist/billing/stripe/stripe-payment-processor.d.ts +2 -2
  10. package/dist/billing/stripe/stripe-payment-processor.test.js +7 -0
  11. package/dist/billing/stripe/tenant-store.d.ts +1 -1
  12. package/dist/billing/stripe/tenant-store.js +1 -1
  13. package/dist/credits/auto-topup-charge.d.ts +2 -2
  14. package/dist/credits/auto-topup-charge.js +5 -1
  15. package/dist/credits/auto-topup-charge.test.js +5 -4
  16. package/dist/credits/auto-topup-usage.d.ts +2 -2
  17. package/dist/credits/auto-topup-usage.test.js +53 -12
  18. package/dist/credits/credit-expiry-cron.d.ts +2 -2
  19. package/dist/credits/credit-expiry-cron.js +7 -4
  20. package/dist/credits/credit-expiry-cron.test.js +25 -8
  21. package/dist/credits/credit-ledger.d.ts +2 -2
  22. package/dist/credits/credit-ledger.js +1 -1
  23. package/dist/credits/dividend-cron.d.ts +4 -6
  24. package/dist/credits/dividend-cron.js +10 -16
  25. package/dist/credits/dividend-cron.test.js +31 -44
  26. package/dist/credits/dividend-repository.js +19 -22
  27. package/dist/credits/dividend-repository.test.js +4 -3
  28. package/dist/credits/index.d.ts +4 -2
  29. package/dist/credits/index.js +2 -1
  30. package/dist/credits/ledger.d.ts +195 -0
  31. package/dist/credits/ledger.js +561 -0
  32. package/dist/credits/ledger.test.js +418 -0
  33. package/dist/credits/signup-grant.d.ts +2 -2
  34. package/dist/credits/signup-grant.js +4 -4
  35. package/dist/credits/signup-grant.test.js +5 -3
  36. package/dist/credits/trial-balance-cron.d.ts +19 -0
  37. package/dist/credits/trial-balance-cron.js +30 -0
  38. package/dist/credits/trial-balance-cron.test.js +55 -0
  39. package/dist/db/schema/gateway-service-keys.d.ts +109 -0
  40. package/dist/db/schema/gateway-service-keys.js +18 -0
  41. package/dist/db/schema/index.d.ts +2 -0
  42. package/dist/db/schema/index.js +2 -0
  43. package/dist/db/schema/ledger.d.ts +442 -0
  44. package/dist/db/schema/ledger.js +76 -0
  45. package/dist/gateway/credit-gate.d.ts +2 -2
  46. package/dist/gateway/credit-gate.js +5 -1
  47. package/dist/gateway/credit-gate.test.js +35 -33
  48. package/dist/gateway/gateway-routes.test.js +1 -1
  49. package/dist/gateway/index.d.ts +2 -0
  50. package/dist/gateway/index.js +1 -0
  51. package/dist/gateway/protocol/anthropic.js +1 -1
  52. package/dist/gateway/protocol/deps.d.ts +5 -5
  53. package/dist/gateway/protocol/openai.js +1 -1
  54. package/dist/gateway/proxy.d.ts +4 -4
  55. package/dist/gateway/route-mounting.test.js +1 -1
  56. package/dist/gateway/service-key-auth.d.ts +1 -1
  57. package/dist/gateway/service-key-auth.js +1 -1
  58. package/dist/gateway/service-key-repository.d.ts +27 -0
  59. package/dist/gateway/service-key-repository.js +64 -0
  60. package/dist/gateway/types.d.ts +5 -5
  61. package/dist/metering/reconciliation-cron.test.js +9 -8
  62. package/dist/metering/reconciliation-repository.js +12 -10
  63. package/dist/metering/reconciliation-repository.test.js +9 -8
  64. package/dist/monetization/affiliate/affiliate-admin-repository.js +10 -8
  65. package/dist/monetization/affiliate/affiliate-admin-repository.test.js +32 -13
  66. package/dist/monetization/affiliate/credit-match.d.ts +2 -2
  67. package/dist/monetization/affiliate/credit-match.js +4 -1
  68. package/dist/monetization/affiliate/credit-match.test.js +58 -13
  69. package/dist/monetization/affiliate/new-user-bonus.d.ts +2 -2
  70. package/dist/monetization/affiliate/new-user-bonus.js +4 -1
  71. package/dist/monetization/affiliate/new-user-bonus.test.js +4 -3
  72. package/dist/monetization/credits/auto-topup-charge.d.ts +2 -2
  73. package/dist/monetization/credits/auto-topup-charge.js +5 -1
  74. package/dist/monetization/credits/auto-topup-charge.test.js +5 -4
  75. package/dist/monetization/credits/auto-topup-usage.d.ts +2 -2
  76. package/dist/monetization/credits/auto-topup-usage.test.js +53 -12
  77. package/dist/monetization/credits/bot-billing.d.ts +3 -3
  78. package/dist/monetization/credits/bot-billing.test.js +18 -5
  79. package/dist/monetization/credits/credit-expiry-cron.test.js +25 -8
  80. package/dist/monetization/credits/dividend-cron.d.ts +2 -4
  81. package/dist/monetization/credits/dividend-cron.js +7 -4
  82. package/dist/monetization/credits/dividend-cron.test.js +26 -46
  83. package/dist/monetization/credits/dividend-repository.js +15 -24
  84. package/dist/monetization/credits/dividend-repository.test.js +4 -3
  85. package/dist/monetization/credits/index.d.ts +2 -2
  86. package/dist/monetization/credits/index.js +1 -1
  87. package/dist/monetization/credits/member-usage.test.js +23 -10
  88. package/dist/monetization/credits/phone-billing.d.ts +2 -2
  89. package/dist/monetization/credits/phone-billing.js +5 -1
  90. package/dist/monetization/credits/phone-billing.test.js +9 -12
  91. package/dist/monetization/credits/runtime-cron.d.ts +2 -2
  92. package/dist/monetization/credits/runtime-cron.js +32 -8
  93. package/dist/monetization/credits/runtime-cron.test.js +28 -27
  94. package/dist/monetization/credits/runtime-scheduler.d.ts +2 -2
  95. package/dist/monetization/credits/runtime-scheduler.test.js +1 -1
  96. package/dist/monetization/credits/signup-grant.test.js +5 -3
  97. package/dist/monetization/credits/storage-tier-cron.test.js +3 -2
  98. package/dist/monetization/credits/trial-balance-cron.test.js +42 -0
  99. package/dist/monetization/feature-gate.d.ts +3 -3
  100. package/dist/monetization/index.d.ts +3 -3
  101. package/dist/monetization/index.js +1 -1
  102. package/dist/monetization/metering/reconciliation-cron.test.js +9 -8
  103. package/dist/monetization/metering/reconciliation-repository.js +11 -10
  104. package/dist/monetization/metering/reconciliation-repository.test.js +9 -8
  105. package/dist/monetization/payram/webhook.d.ts +2 -2
  106. package/dist/monetization/payram/webhook.js +5 -1
  107. package/dist/monetization/payram/webhook.test.js +5 -4
  108. package/dist/monetization/promotions/engine.d.ts +2 -2
  109. package/dist/monetization/promotions/engine.js +4 -1
  110. package/dist/monetization/promotions/engine.test.js +3 -1
  111. package/dist/monetization/repository-types.d.ts +1 -1
  112. package/dist/monetization/socket/socket.d.ts +3 -3
  113. package/dist/monetization/stripe/stripe-payment-processor.d.ts +2 -2
  114. package/dist/monetization/stripe/stripe-payment-processor.test.js +7 -0
  115. package/dist/monetization/stripe/webhook.d.ts +2 -2
  116. package/dist/monetization/stripe/webhook.js +70 -6
  117. package/dist/monetization/stripe/webhook.test.js +20 -15
  118. package/dist/onboarding/onboarding-service.d.ts +2 -2
  119. package/dist/onboarding/onboarding-service.js +6 -2
  120. package/drizzle/migrations/0002_gateway_service_keys.sql +14 -0
  121. package/drizzle/migrations/0003_double_entry_ledger.sql +82 -0
  122. package/drizzle/migrations/meta/_journal.json +14 -0
  123. package/package.json +1 -1
  124. package/src/api/routes/admin-credits.ts +11 -14
  125. package/src/api/routes/quota.ts +2 -2
  126. package/src/api/routes/verify-email.ts +4 -4
  127. package/src/backup/on-demand-snapshot-service.test.ts +3 -3
  128. package/src/backup/on-demand-snapshot-service.ts +3 -3
  129. package/src/billing/payram/webhook.test.ts +7 -5
  130. package/src/billing/payram/webhook.ts +8 -11
  131. package/src/billing/stripe/stripe-payment-processor.test.ts +10 -3
  132. package/src/billing/stripe/stripe-payment-processor.ts +3 -3
  133. package/src/billing/stripe/tenant-store.ts +1 -1
  134. package/src/credits/auto-topup-charge.test.ts +7 -5
  135. package/src/credits/auto-topup-charge.ts +7 -10
  136. package/src/credits/auto-topup-usage.test.ts +55 -13
  137. package/src/credits/auto-topup-usage.ts +2 -2
  138. package/src/credits/credit-expiry-cron.test.ts +26 -45
  139. package/src/credits/credit-expiry-cron.ts +9 -12
  140. package/src/credits/credit-ledger.ts +3 -3
  141. package/src/credits/dividend-cron.test.ts +38 -45
  142. package/src/credits/dividend-cron.ts +12 -26
  143. package/src/credits/dividend-repository.test.ts +4 -3
  144. package/src/credits/dividend-repository.ts +21 -23
  145. package/src/credits/index.ts +23 -4
  146. package/src/credits/ledger.test.ts +514 -0
  147. package/src/credits/ledger.ts +851 -0
  148. package/src/credits/signup-grant.test.ts +7 -4
  149. package/src/credits/signup-grant.ts +6 -12
  150. package/src/credits/trial-balance-cron.test.ts +68 -0
  151. package/src/credits/trial-balance-cron.ts +46 -0
  152. package/src/db/schema/gateway-service-keys.ts +23 -0
  153. package/src/db/schema/index.ts +2 -0
  154. package/src/db/schema/ledger.ts +94 -0
  155. package/src/gateway/credit-gate-wiring.test.ts +3 -3
  156. package/src/gateway/credit-gate.test.ts +35 -33
  157. package/src/gateway/credit-gate.ts +6 -10
  158. package/src/gateway/gateway-routes.test.ts +6 -6
  159. package/src/gateway/index.ts +2 -0
  160. package/src/gateway/protocol/anthropic.ts +2 -2
  161. package/src/gateway/protocol/deps.ts +5 -5
  162. package/src/gateway/protocol/openai.ts +2 -2
  163. package/src/gateway/proxy.ts +4 -4
  164. package/src/gateway/route-mounting.test.ts +3 -3
  165. package/src/gateway/service-key-auth.ts +4 -2
  166. package/src/gateway/service-key-repository.ts +87 -0
  167. package/src/gateway/types.ts +5 -5
  168. package/src/metering/reconciliation-cron.test.ts +10 -9
  169. package/src/metering/reconciliation-repository.test.ts +10 -9
  170. package/src/metering/reconciliation-repository.ts +14 -11
  171. package/src/monetization/affiliate/affiliate-admin-repository.test.ts +32 -19
  172. package/src/monetization/affiliate/affiliate-admin-repository.ts +16 -8
  173. package/src/monetization/affiliate/credit-match.test.ts +60 -14
  174. package/src/monetization/affiliate/credit-match.ts +6 -9
  175. package/src/monetization/affiliate/new-user-bonus.test.ts +6 -4
  176. package/src/monetization/affiliate/new-user-bonus.ts +6 -9
  177. package/src/monetization/credits/auto-topup-charge.test.ts +7 -5
  178. package/src/monetization/credits/auto-topup-charge.ts +7 -10
  179. package/src/monetization/credits/auto-topup-usage.test.ts +55 -13
  180. package/src/monetization/credits/auto-topup-usage.ts +2 -2
  181. package/src/monetization/credits/bot-billing.test.ts +20 -6
  182. package/src/monetization/credits/bot-billing.ts +3 -3
  183. package/src/monetization/credits/credit-expiry-cron.test.ts +26 -45
  184. package/src/monetization/credits/dividend-cron.test.ts +34 -48
  185. package/src/monetization/credits/dividend-cron.ts +9 -14
  186. package/src/monetization/credits/dividend-repository.test.ts +4 -3
  187. package/src/monetization/credits/dividend-repository.ts +19 -25
  188. package/src/monetization/credits/index.ts +4 -4
  189. package/src/monetization/credits/member-usage.test.ts +25 -11
  190. package/src/monetization/credits/phone-billing.test.ts +18 -26
  191. package/src/monetization/credits/phone-billing.ts +7 -10
  192. package/src/monetization/credits/runtime-cron.test.ts +29 -28
  193. package/src/monetization/credits/runtime-cron.ts +34 -58
  194. package/src/monetization/credits/runtime-scheduler.test.ts +1 -1
  195. package/src/monetization/credits/runtime-scheduler.ts +2 -2
  196. package/src/monetization/credits/signup-grant.test.ts +7 -4
  197. package/src/monetization/credits/storage-tier-cron.test.ts +5 -3
  198. package/src/monetization/credits/trial-balance-cron.test.ts +52 -0
  199. package/src/monetization/feature-gate.ts +3 -3
  200. package/src/monetization/index.ts +4 -4
  201. package/src/monetization/metering/reconciliation-cron.test.ts +10 -9
  202. package/src/monetization/metering/reconciliation-repository.test.ts +11 -9
  203. package/src/monetization/metering/reconciliation-repository.ts +13 -11
  204. package/src/monetization/payram/webhook.test.ts +7 -5
  205. package/src/monetization/payram/webhook.ts +7 -10
  206. package/src/monetization/promotions/engine.test.ts +6 -5
  207. package/src/monetization/promotions/engine.ts +6 -3
  208. package/src/monetization/repository-types.ts +1 -1
  209. package/src/monetization/socket/socket.ts +4 -4
  210. package/src/monetization/stripe/stripe-payment-processor.test.ts +10 -3
  211. package/src/monetization/stripe/stripe-payment-processor.ts +3 -3
  212. package/src/monetization/stripe/webhook.test.ts +22 -16
  213. package/src/monetization/stripe/webhook.ts +75 -50
  214. package/src/onboarding/onboarding-service.ts +8 -11
  215. package/dist/credits/credit-ledger-extra.test.js +0 -40
  216. package/dist/credits/credit-ledger.bench.js +0 -33
  217. package/dist/credits/credit-ledger.test.d.ts +0 -4
  218. package/dist/credits/credit-ledger.test.js +0 -203
  219. package/dist/credits/credit-transaction-repository.test.js +0 -232
  220. package/dist/monetization/credits/credit-ledger-extra.test.d.ts +0 -1
  221. package/dist/monetization/credits/credit-ledger-extra.test.js +0 -39
  222. package/dist/monetization/credits/credit-ledger.bench.d.ts +0 -1
  223. package/dist/monetization/credits/credit-ledger.bench.js +0 -32
  224. package/dist/monetization/credits/credit-ledger.test.d.ts +0 -4
  225. package/dist/monetization/credits/credit-ledger.test.js +0 -202
  226. package/dist/monetization/credits/credit-transaction-repository.test.d.ts +0 -1
  227. package/dist/monetization/credits/credit-transaction-repository.test.js +0 -232
  228. package/src/credits/credit-ledger-extra.test.ts +0 -57
  229. package/src/credits/credit-ledger.bench.ts +0 -56
  230. package/src/credits/credit-ledger.test.ts +0 -276
  231. package/src/credits/credit-transaction-repository.test.ts +0 -274
  232. package/src/monetization/credits/credit-ledger-extra.test.ts +0 -56
  233. package/src/monetization/credits/credit-ledger.bench.ts +0 -55
  234. package/src/monetization/credits/credit-ledger.test.ts +0 -275
  235. package/src/monetization/credits/credit-transaction-repository.test.ts +0 -274
  236. /package/dist/credits/{credit-ledger-extra.test.d.ts → ledger.test.d.ts} +0 -0
  237. /package/dist/credits/{credit-ledger.bench.d.ts → trial-balance-cron.test.d.ts} +0 -0
  238. /package/dist/{credits/credit-transaction-repository.test.d.ts → monetization/credits/trial-balance-cron.test.d.ts} +0 -0
@@ -1,274 +0,0 @@
1
- import crypto from "node:crypto";
2
- import type { PGlite } from "@electric-sql/pglite";
3
- import { Credit } from "@wopr-network/platform-core/credits";
4
- import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
5
- import type { DrizzleDb } from "../../db/index.js";
6
- import { creditTransactions } from "../../db/schema/credits.js";
7
- import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../../test/db.js";
8
- import { DrizzleCreditTransactionRepository } from "./credit-transaction-repository.js";
9
-
10
- let pool: PGlite;
11
- let db: DrizzleDb;
12
-
13
- beforeAll(async () => {
14
- ({ db, pool } = await createTestDb());
15
- await beginTestTransaction(pool);
16
- });
17
-
18
- afterAll(async () => {
19
- await endTestTransaction(pool);
20
- await pool.close();
21
- });
22
-
23
- /** Seed a credit_transactions row directly. */
24
- async function seedTx(opts: {
25
- tenantId: string;
26
- amount: Credit;
27
- type: string;
28
- createdAt?: string;
29
- referenceId?: string;
30
- balanceAfter?: Credit;
31
- }): Promise<void> {
32
- await db.insert(creditTransactions).values({
33
- id: crypto.randomUUID(),
34
- tenantId: opts.tenantId,
35
- amount: opts.amount,
36
- balanceAfter: opts.balanceAfter ?? opts.amount,
37
- type: opts.type,
38
- description: "test",
39
- referenceId: opts.referenceId ?? null,
40
- createdAt: opts.createdAt ?? new Date().toISOString(),
41
- });
42
- }
43
-
44
- describe("DrizzleCreditTransactionRepository", () => {
45
- let repo: DrizzleCreditTransactionRepository;
46
-
47
- beforeEach(async () => {
48
- await rollbackTestTransaction(pool);
49
- repo = new DrizzleCreditTransactionRepository(db);
50
- });
51
-
52
- describe("existsByReferenceIdLike()", () => {
53
- it("returns false when no transactions exist", async () => {
54
- const result = await repo.existsByReferenceIdLike("div-%");
55
- expect(result).toBe(false);
56
- });
57
-
58
- it("returns true when a matching referenceId exists", async () => {
59
- await seedTx({
60
- tenantId: "t1",
61
- amount: Credit.fromCents(100),
62
- type: "purchase",
63
- referenceId: "div-2026-01-01",
64
- });
65
-
66
- const result = await repo.existsByReferenceIdLike("div-%");
67
- expect(result).toBe(true);
68
- });
69
-
70
- it("returns false when no referenceId matches the pattern", async () => {
71
- await seedTx({
72
- tenantId: "t1",
73
- amount: Credit.fromCents(100),
74
- type: "purchase",
75
- referenceId: "purchase-abc",
76
- });
77
-
78
- const result = await repo.existsByReferenceIdLike("div-%");
79
- expect(result).toBe(false);
80
- });
81
-
82
- it("matches partial patterns with wildcards", async () => {
83
- await seedTx({
84
- tenantId: "t1",
85
- amount: Credit.fromCents(50),
86
- type: "community_dividend",
87
- referenceId: "div-2026-02-15-t1",
88
- });
89
-
90
- expect(await repo.existsByReferenceIdLike("div-2026-02-%")).toBe(true);
91
- expect(await repo.existsByReferenceIdLike("div-2026-03-%")).toBe(false);
92
- });
93
- });
94
-
95
- describe("sumPurchasesForPeriod()", () => {
96
- it("returns Credit.ZERO when no transactions exist", async () => {
97
- const sum = await repo.sumPurchasesForPeriod("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
98
- expect(sum.toRaw()).toBe(0);
99
- });
100
-
101
- it("sums only purchase-type transactions", async () => {
102
- await seedTx({
103
- tenantId: "t1",
104
- amount: Credit.fromCents(100),
105
- type: "purchase",
106
- createdAt: "2026-01-15T12:00:00Z",
107
- });
108
- await seedTx({
109
- tenantId: "t1",
110
- amount: Credit.fromCents(200),
111
- type: "signup_grant",
112
- createdAt: "2026-01-15T12:00:00Z",
113
- });
114
-
115
- const sum = await repo.sumPurchasesForPeriod("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
116
- expect(sum.toRaw()).toBe(Credit.fromCents(100).toRaw());
117
- });
118
-
119
- it("respects half-open interval [start, end)", async () => {
120
- // Exactly at start — included
121
- await seedTx({
122
- tenantId: "t1",
123
- amount: Credit.fromCents(10),
124
- type: "purchase",
125
- createdAt: "2026-01-01T00:00:00Z",
126
- });
127
- // Inside window
128
- await seedTx({
129
- tenantId: "t1",
130
- amount: Credit.fromCents(20),
131
- type: "purchase",
132
- createdAt: "2026-01-15T00:00:00Z",
133
- });
134
- // Exactly at end — excluded
135
- await seedTx({
136
- tenantId: "t1",
137
- amount: Credit.fromCents(40),
138
- type: "purchase",
139
- createdAt: "2026-02-01T00:00:00Z",
140
- });
141
-
142
- const sum = await repo.sumPurchasesForPeriod("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
143
- expect(sum.toRaw()).toBe(Credit.fromCents(30).toRaw()); // 10 + 20, not 40
144
- });
145
-
146
- it("sums across all tenants (not tenant-scoped)", async () => {
147
- await seedTx({
148
- tenantId: "t1",
149
- amount: Credit.fromCents(50),
150
- type: "purchase",
151
- createdAt: "2026-01-10T00:00:00Z",
152
- });
153
- await seedTx({
154
- tenantId: "t2",
155
- amount: Credit.fromCents(75),
156
- type: "purchase",
157
- createdAt: "2026-01-10T00:00:00Z",
158
- });
159
-
160
- const sum = await repo.sumPurchasesForPeriod("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
161
- expect(sum.toRaw()).toBe(Credit.fromCents(125).toRaw()); // 50 + 75
162
- });
163
- });
164
-
165
- describe("getActiveTenantIdsInWindow()", () => {
166
- it("returns empty array when no transactions exist", async () => {
167
- const ids = await repo.getActiveTenantIdsInWindow("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
168
- expect(ids).toEqual([]);
169
- });
170
-
171
- it("returns distinct tenantIds with purchase transactions in window", async () => {
172
- await seedTx({
173
- tenantId: "t1",
174
- amount: Credit.fromCents(10),
175
- type: "purchase",
176
- createdAt: "2026-01-10T00:00:00Z",
177
- });
178
- // t1 again — should not duplicate
179
- await seedTx({
180
- tenantId: "t1",
181
- amount: Credit.fromCents(20),
182
- type: "purchase",
183
- createdAt: "2026-01-11T00:00:00Z",
184
- });
185
- await seedTx({
186
- tenantId: "t2",
187
- amount: Credit.fromCents(30),
188
- type: "purchase",
189
- createdAt: "2026-01-12T00:00:00Z",
190
- });
191
-
192
- const ids = await repo.getActiveTenantIdsInWindow("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
193
- expect(ids.sort()).toEqual(["t1", "t2"]);
194
- });
195
-
196
- it("excludes non-purchase transaction types", async () => {
197
- await seedTx({
198
- tenantId: "t1",
199
- amount: Credit.fromCents(100),
200
- type: "signup_grant",
201
- createdAt: "2026-01-10T00:00:00Z",
202
- });
203
-
204
- const ids = await repo.getActiveTenantIdsInWindow("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
205
- expect(ids).toEqual([]);
206
- });
207
-
208
- it("respects half-open interval [start, end)", async () => {
209
- // Before window
210
- await seedTx({
211
- tenantId: "t-before",
212
- amount: Credit.fromCents(10),
213
- type: "purchase",
214
- createdAt: "2025-12-31T23:59:59Z",
215
- });
216
- // At start — included
217
- await seedTx({
218
- tenantId: "t-start",
219
- amount: Credit.fromCents(10),
220
- type: "purchase",
221
- createdAt: "2026-01-01T00:00:00Z",
222
- });
223
- // At end — excluded
224
- await seedTx({
225
- tenantId: "t-end",
226
- amount: Credit.fromCents(10),
227
- type: "purchase",
228
- createdAt: "2026-02-01T00:00:00Z",
229
- });
230
-
231
- const ids = await repo.getActiveTenantIdsInWindow("2026-01-01T00:00:00Z", "2026-02-01T00:00:00Z");
232
- expect(ids).toEqual(["t-start"]);
233
- });
234
- });
235
-
236
- describe("referenceId uniqueness", () => {
237
- it("rejects duplicate referenceId (database unique constraint)", async () => {
238
- await seedTx({
239
- tenantId: "t1",
240
- amount: Credit.fromCents(100),
241
- type: "purchase",
242
- referenceId: "unique-ref-1",
243
- });
244
-
245
- await expect(
246
- seedTx({
247
- tenantId: "t1",
248
- amount: Credit.fromCents(200),
249
- type: "purchase",
250
- referenceId: "unique-ref-1",
251
- }),
252
- ).rejects.toThrow(); // PG unique constraint violation
253
- });
254
-
255
- it("allows null referenceId on multiple rows", async () => {
256
- await seedTx({
257
- tenantId: "t1",
258
- amount: Credit.fromCents(100),
259
- type: "purchase",
260
- referenceId: undefined, // null
261
- });
262
- await seedTx({
263
- tenantId: "t1",
264
- amount: Credit.fromCents(200),
265
- type: "purchase",
266
- referenceId: undefined, // null
267
- });
268
-
269
- // Both inserted — no constraint violation for nulls
270
- const result = await repo.existsByReferenceIdLike("%");
271
- expect(result).toBe(false); // LIKE '%' won't match null referenceIds
272
- });
273
- });
274
- });