@onesub/server 0.11.4 → 0.12.1

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 (127) hide show
  1. package/README.md +119 -2
  2. package/dist/__tests__/cache.test.d.ts +2 -0
  3. package/dist/__tests__/cache.test.d.ts.map +1 -0
  4. package/dist/__tests__/openapi.test.d.ts +2 -0
  5. package/dist/__tests__/openapi.test.d.ts.map +1 -0
  6. package/dist/__tests__/redis-store.test.d.ts +2 -0
  7. package/dist/__tests__/redis-store.test.d.ts.map +1 -0
  8. package/dist/__tests__/webhook-apple.test.d.ts +12 -0
  9. package/dist/__tests__/webhook-apple.test.d.ts.map +1 -0
  10. package/dist/__tests__/webhook-events.test.d.ts +2 -0
  11. package/dist/__tests__/webhook-events.test.d.ts.map +1 -0
  12. package/dist/__tests__/webhook-google.test.d.ts +13 -0
  13. package/dist/__tests__/webhook-google.test.d.ts.map +1 -0
  14. package/dist/__tests__/webhook-queue.test.d.ts +2 -0
  15. package/dist/__tests__/webhook-queue.test.d.ts.map +1 -0
  16. package/dist/cache.d.ts +32 -0
  17. package/dist/cache.d.ts.map +1 -0
  18. package/dist/index.cjs +3084 -0
  19. package/dist/index.cjs.map +1 -0
  20. package/dist/index.d.ts +34 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +3045 -118
  23. package/dist/index.js.map +1 -1
  24. package/dist/openapi.d.ts +41 -0
  25. package/dist/openapi.d.ts.map +1 -0
  26. package/dist/providers/apple.d.ts +1 -1
  27. package/dist/providers/apple.d.ts.map +1 -1
  28. package/dist/providers/google.d.ts.map +1 -1
  29. package/dist/routes/admin.d.ts +2 -1
  30. package/dist/routes/admin.d.ts.map +1 -1
  31. package/dist/routes/webhook-apple.d.ts +6 -0
  32. package/dist/routes/webhook-apple.d.ts.map +1 -0
  33. package/dist/routes/webhook-google.d.ts +7 -0
  34. package/dist/routes/webhook-google.d.ts.map +1 -0
  35. package/dist/routes/webhook.d.ts +6 -18
  36. package/dist/routes/webhook.d.ts.map +1 -1
  37. package/dist/stores/redis.d.ts +60 -0
  38. package/dist/stores/redis.d.ts.map +1 -0
  39. package/dist/stores/schema.d.ts.map +1 -1
  40. package/dist/tracing.d.ts +32 -0
  41. package/dist/tracing.d.ts.map +1 -0
  42. package/dist/webhook-events.d.ts +58 -0
  43. package/dist/webhook-events.d.ts.map +1 -0
  44. package/dist/webhook-queue.d.ts +92 -0
  45. package/dist/webhook-queue.d.ts.map +1 -0
  46. package/package.json +33 -4
  47. package/sql/schema.sql +10 -0
  48. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  49. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  50. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  51. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  52. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  53. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  54. package/dist/__tests__/apple-root-ca.test.js +0 -41
  55. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  56. package/dist/__tests__/apple-status-api.test.js +0 -345
  57. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  58. package/dist/__tests__/entitlements.test.js +0 -268
  59. package/dist/__tests__/entitlements.test.js.map +0 -1
  60. package/dist/__tests__/error-codes.test.js +0 -168
  61. package/dist/__tests__/error-codes.test.js.map +0 -1
  62. package/dist/__tests__/fetch-hardening.test.js +0 -192
  63. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  64. package/dist/__tests__/google-paused.test.js +0 -126
  65. package/dist/__tests__/google-paused.test.js.map +0 -1
  66. package/dist/__tests__/google-price-change.test.js +0 -186
  67. package/dist/__tests__/google-price-change.test.js.map +0 -1
  68. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  69. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  70. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  71. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  72. package/dist/__tests__/lifecycle-states.test.js +0 -423
  73. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  74. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  75. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  76. package/dist/__tests__/metrics.test.js +0 -340
  77. package/dist/__tests__/metrics.test.js.map +0 -1
  78. package/dist/__tests__/mock-provider.test.js +0 -188
  79. package/dist/__tests__/mock-provider.test.js.map +0 -1
  80. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  81. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  82. package/dist/__tests__/providers.test.js +0 -321
  83. package/dist/__tests__/providers.test.js.map +0 -1
  84. package/dist/__tests__/refund-policy.test.js +0 -317
  85. package/dist/__tests__/refund-policy.test.js.map +0 -1
  86. package/dist/__tests__/schema.test.js +0 -46
  87. package/dist/__tests__/schema.test.js.map +0 -1
  88. package/dist/__tests__/store.test.js +0 -172
  89. package/dist/__tests__/store.test.js.map +0 -1
  90. package/dist/__tests__/test-utils.js +0 -30
  91. package/dist/__tests__/test-utils.js.map +0 -1
  92. package/dist/__tests__/webhook-refund.test.js +0 -313
  93. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  94. package/dist/errors.js +0 -19
  95. package/dist/errors.js.map +0 -1
  96. package/dist/http.js +0 -36
  97. package/dist/http.js.map +0 -1
  98. package/dist/logger.js +0 -20
  99. package/dist/logger.js.map +0 -1
  100. package/dist/providers/apple-root-ca.js +0 -38
  101. package/dist/providers/apple-root-ca.js.map +0 -1
  102. package/dist/providers/apple.js +0 -499
  103. package/dist/providers/apple.js.map +0 -1
  104. package/dist/providers/google.js +0 -493
  105. package/dist/providers/google.js.map +0 -1
  106. package/dist/providers/mock.js +0 -93
  107. package/dist/providers/mock.js.map +0 -1
  108. package/dist/routes/admin.js +0 -251
  109. package/dist/routes/admin.js.map +0 -1
  110. package/dist/routes/entitlements.js +0 -130
  111. package/dist/routes/entitlements.js.map +0 -1
  112. package/dist/routes/metrics.js +0 -281
  113. package/dist/routes/metrics.js.map +0 -1
  114. package/dist/routes/purchase.js +0 -187
  115. package/dist/routes/purchase.js.map +0 -1
  116. package/dist/routes/status.js +0 -55
  117. package/dist/routes/status.js.map +0 -1
  118. package/dist/routes/validate.js +0 -70
  119. package/dist/routes/validate.js.map +0 -1
  120. package/dist/routes/webhook.js +0 -441
  121. package/dist/routes/webhook.js.map +0 -1
  122. package/dist/store.js +0 -145
  123. package/dist/store.js.map +0 -1
  124. package/dist/stores/postgres.js +0 -329
  125. package/dist/stores/postgres.js.map +0 -1
  126. package/dist/stores/schema.js +0 -57
  127. package/dist/stores/schema.js.map +0 -1
@@ -1,227 +0,0 @@
1
- /**
2
- * Tests for Google linkedPurchaseToken upgrade/downgrade chain handling.
3
- *
4
- * - validateGoogleReceipt surfaces linkedPurchaseToken from v2 response
5
- * - webhook for an unknown new token with linkedPurchaseToken inherits the
6
- * userId from the previous record (continuity across plan changes)
7
- */
8
- import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
9
- import express from 'express';
10
- import { generateKeyPairSync } from 'crypto';
11
- import { SUBSCRIPTION_STATUS } from '@onesub/shared';
12
- import { validateGoogleReceipt } from '../providers/google.js';
13
- import { createWebhookRouter } from '../routes/webhook.js';
14
- import { InMemorySubscriptionStore, InMemoryPurchaseStore } from '../store.js';
15
- import { isLocalhostUrl, urlHost } from './test-utils.js';
16
- let testPrivateKey;
17
- beforeAll(() => {
18
- const { privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
19
- testPrivateKey = privateKey.export({ type: 'pkcs8', format: 'pem' });
20
- });
21
- beforeEach(() => {
22
- vi.restoreAllMocks();
23
- });
24
- function googleConfig(extra) {
25
- return {
26
- packageName: 'com.example.app',
27
- serviceAccountKey: JSON.stringify({
28
- client_email: `test-${Math.random()}@test.iam.gserviceaccount.com`,
29
- private_key: testPrivateKey,
30
- token_uri: 'https://oauth2.googleapis.com/token',
31
- }),
32
- ...extra,
33
- };
34
- }
35
- function v2Response(opts) {
36
- return {
37
- startTime: '2026-01-01T00:00:00Z',
38
- subscriptionState: 'SUBSCRIPTION_STATE_ACTIVE',
39
- latestOrderId: 'GPA.new_order',
40
- lineItems: [{
41
- productId: opts.productId ?? 'pro_yearly',
42
- expiryTime: '2027-01-01T00:00:00Z',
43
- autoRenewingPlan: { autoRenewEnabled: true },
44
- }],
45
- ...opts,
46
- };
47
- }
48
- function mockV2Fetch(responseBody) {
49
- const originalFetch = global.fetch;
50
- vi.spyOn(global, 'fetch').mockImplementation(async (url, init) => {
51
- if (isLocalhostUrl(url))
52
- return originalFetch(url, init);
53
- const host = urlHost(url);
54
- if (host === 'oauth2.googleapis.com') {
55
- return {
56
- ok: true,
57
- json: async () => ({ access_token: 'tok', expires_in: 3600 }),
58
- text: async () => '',
59
- };
60
- }
61
- if (host === 'androidpublisher.googleapis.com') {
62
- return {
63
- ok: true,
64
- json: async () => responseBody,
65
- text: async () => JSON.stringify(responseBody),
66
- };
67
- }
68
- throw new Error(`[test] Unexpected URL: ${String(url)}`);
69
- });
70
- }
71
- // ── validateGoogleReceipt surfaces linkedPurchaseToken ──────────────────────
72
- describe('validateGoogleReceipt — linkedPurchaseToken passthrough', () => {
73
- it('returns linkedPurchaseToken from the v2 response', async () => {
74
- mockV2Fetch(v2Response({
75
- productId: 'pro_yearly',
76
- linkedPurchaseToken: 'tok_old_monthly',
77
- }));
78
- const result = await validateGoogleReceipt('tok_new_yearly', 'pro_yearly', googleConfig());
79
- expect(result?.linkedPurchaseToken).toBe('tok_old_monthly');
80
- });
81
- it('returns undefined linkedPurchaseToken for first-purchase (no chain)', async () => {
82
- mockV2Fetch(v2Response({ productId: 'pro_monthly' })); // no linkedPurchaseToken
83
- const result = await validateGoogleReceipt('tok_first', 'pro_monthly', googleConfig());
84
- expect(result?.linkedPurchaseToken).toBeUndefined();
85
- });
86
- });
87
- function spinUp(handler) {
88
- return {
89
- async request(path, body) {
90
- const httpServer = handler.listen(0);
91
- const address = httpServer.address();
92
- const port = typeof address === 'object' && address ? address.port : 0;
93
- try {
94
- const resp = await fetch(`http://127.0.0.1:${port}${path}`, {
95
- method: 'POST',
96
- headers: { 'Content-Type': 'application/json' },
97
- body: JSON.stringify(body),
98
- });
99
- const text = await resp.text();
100
- let parsed = text;
101
- try {
102
- parsed = JSON.parse(text);
103
- }
104
- catch { /* keep as text */ }
105
- return { status: resp.status, body: parsed };
106
- }
107
- finally {
108
- await new Promise((resolve) => httpServer.close(() => resolve()));
109
- }
110
- },
111
- };
112
- }
113
- function buildServer(config) {
114
- const store = new InMemorySubscriptionStore();
115
- const purchaseStore = new InMemoryPurchaseStore();
116
- const app = express();
117
- app.use(express.json());
118
- app.use(createWebhookRouter(config, store, purchaseStore));
119
- return { store, server: spinUp(app) };
120
- }
121
- function purchasedNotificationBody(purchaseToken, productId = 'pro_yearly') {
122
- const json = JSON.stringify({
123
- version: '1.0',
124
- packageName: 'com.example.app',
125
- eventTimeMillis: String(Date.now()),
126
- subscriptionNotification: {
127
- version: '1.0',
128
- notificationType: 4, // SUBSCRIPTION_PURCHASED
129
- purchaseToken,
130
- subscriptionId: productId,
131
- },
132
- });
133
- return {
134
- message: { data: Buffer.from(json).toString('base64'), messageId: '1' },
135
- subscription: 's',
136
- };
137
- }
138
- const sampleSub = (overrides) => ({
139
- userId: 'real_user_42',
140
- productId: 'pro_monthly',
141
- platform: 'google',
142
- status: 'active',
143
- expiresAt: '2026-12-01T00:00:00.000Z',
144
- originalTransactionId: 'tok_old_monthly',
145
- purchasedAt: '2026-01-01T00:00:00.000Z',
146
- willRenew: true,
147
- ...overrides,
148
- });
149
- // Note: webhook saves the new record indexed by fresh.originalTransactionId
150
- // (= v2 response's latestOrderId), not by purchaseToken. So the assertions
151
- // look up new records via the latestOrderId from the mocked v2 response.
152
- //
153
- // History assertions (previous record) use the previous original_transaction_id
154
- // (= the linkedPurchaseToken value, which is itself the prior latestOrderId).
155
- describe('Google webhook — linkedPurchaseToken userId inheritance', () => {
156
- it('inherits userId from the previous (linked) record on plan change', async () => {
157
- mockV2Fetch(v2Response({
158
- productId: 'pro_yearly',
159
- latestOrderId: 'GPA.new_yearly_order',
160
- linkedPurchaseToken: 'tok_old_monthly',
161
- }));
162
- const { store, server } = buildServer({
163
- google: googleConfig(),
164
- database: { url: '' },
165
- });
166
- // Previous monthly subscription owned by real_user_42
167
- await store.save(sampleSub({ originalTransactionId: 'tok_old_monthly' }));
168
- const resp = await server.request('/onesub/webhook/google', purchasedNotificationBody('tok_new_yearly'));
169
- expect(resp.status).toBe(200);
170
- const newRecord = await store.getByTransactionId('GPA.new_yearly_order');
171
- expect(newRecord).not.toBeNull();
172
- expect(newRecord?.userId).toBe('real_user_42'); // inherited, not placeholder
173
- expect(newRecord?.productId).toBe('pro_yearly');
174
- expect(newRecord?.linkedPurchaseToken).toBe('tok_old_monthly');
175
- expect(newRecord?.status).toBe(SUBSCRIPTION_STATUS.ACTIVE);
176
- });
177
- it('falls back to placeholder userId when linkedPurchaseToken record is unknown', async () => {
178
- mockV2Fetch(v2Response({
179
- productId: 'pro_yearly',
180
- latestOrderId: 'GPA.orphan_order',
181
- linkedPurchaseToken: 'tok_never_seen',
182
- }));
183
- const { store, server } = buildServer({
184
- google: googleConfig(),
185
- database: { url: '' },
186
- });
187
- // No previous record exists
188
- await server.request('/onesub/webhook/google', purchasedNotificationBody('tok_new_orphan'));
189
- const newRecord = await store.getByTransactionId('GPA.orphan_order');
190
- expect(newRecord?.userId).toBe('tok_new_orphan'); // placeholder = purchaseToken
191
- });
192
- it('uses placeholder userId when the new subscription has no linkedPurchaseToken (first purchase)', async () => {
193
- mockV2Fetch(v2Response({
194
- productId: 'pro_monthly',
195
- latestOrderId: 'GPA.first_order',
196
- })); // no linkedPurchaseToken
197
- const { store, server } = buildServer({
198
- google: googleConfig(),
199
- database: { url: '' },
200
- });
201
- await server.request('/onesub/webhook/google', purchasedNotificationBody('tok_first_buy', 'pro_monthly'));
202
- const newRecord = await store.getByTransactionId('GPA.first_order');
203
- expect(newRecord?.userId).toBe('tok_first_buy');
204
- });
205
- it('preserves the previous record (history) when issuing the new one', async () => {
206
- mockV2Fetch(v2Response({
207
- productId: 'pro_yearly',
208
- latestOrderId: 'GPA.keep_new_order',
209
- linkedPurchaseToken: 'tok_old_keep',
210
- }));
211
- const { store, server } = buildServer({
212
- google: googleConfig(),
213
- database: { url: '' },
214
- });
215
- await store.save(sampleSub({ originalTransactionId: 'tok_old_keep', userId: 'user_keep' }));
216
- await server.request('/onesub/webhook/google', purchasedNotificationBody('tok_new_keep'));
217
- // Previous record still findable by its own originalTransactionId
218
- const previous = await store.getByTransactionId('tok_old_keep');
219
- expect(previous?.userId).toBe('user_keep');
220
- expect(previous?.productId).toBe('pro_monthly');
221
- // New record carries the inherited userId AND the link backward
222
- const next = await store.getByTransactionId('GPA.keep_new_order');
223
- expect(next?.userId).toBe('user_keep');
224
- expect(next?.linkedPurchaseToken).toBe('tok_old_keep');
225
- });
226
- });
227
- //# sourceMappingURL=linked-purchase-token.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"linked-purchase-token.test.js","sourceRoot":"","sources":["../../src/__tests__/linked-purchase-token.test.ts"],"names":[],"mappings":"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"}
@@ -1,340 +0,0 @@
1
- /**
2
- * Read-only metrics endpoints — admin-gated aggregations over Subscription +
3
- * PurchaseStore data.
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- import express from 'express';
7
- import { createOneSubMiddleware, InMemorySubscriptionStore, InMemoryPurchaseStore } from '../index.js';
8
- // ── helpers ─────────────────────────────────────────────────────────────────
9
- function sub(overrides) {
10
- return {
11
- userId: 'u',
12
- productId: 'pro_monthly',
13
- platform: 'apple',
14
- status: 'active',
15
- expiresAt: '2099-01-01T00:00:00.000Z',
16
- purchasedAt: '2026-04-01T00:00:00.000Z',
17
- originalTransactionId: `orig_${Math.random()}`,
18
- willRenew: true,
19
- ...overrides,
20
- };
21
- }
22
- function purchase(overrides) {
23
- return {
24
- userId: 'u',
25
- productId: 'lifetime_pass',
26
- platform: 'apple',
27
- type: 'non_consumable',
28
- transactionId: `tx_${Math.random()}`,
29
- purchasedAt: '2026-04-01T00:00:00.000Z',
30
- quantity: 1,
31
- ...overrides,
32
- };
33
- }
34
- function spinUp(handler) {
35
- return {
36
- async get(path, headers) {
37
- const httpServer = handler.listen(0);
38
- const port = httpServer.address().port;
39
- try {
40
- const resp = await fetch(`http://127.0.0.1:${port}${path}`, { headers });
41
- const text = await resp.text();
42
- let parsed = text;
43
- try {
44
- parsed = JSON.parse(text);
45
- }
46
- catch { /* keep as text */ }
47
- return { status: resp.status, body: parsed };
48
- }
49
- finally {
50
- await new Promise((r) => httpServer.close(() => r()));
51
- }
52
- },
53
- };
54
- }
55
- function buildServer(opts) {
56
- const store = new InMemorySubscriptionStore();
57
- const purchaseStore = new InMemoryPurchaseStore();
58
- const config = {
59
- apple: { bundleId: 'com.example.app', skipJwsVerification: true },
60
- database: { url: '' },
61
- adminSecret: opts.adminSecret,
62
- };
63
- const app = express();
64
- app.use(createOneSubMiddleware({ ...config, store, purchaseStore }));
65
- return { store, purchaseStore, server: spinUp(app) };
66
- }
67
- const SECRET = 's3cr3t';
68
- const AUTH = { 'x-admin-secret': SECRET };
69
- // ─────────────────────────────────────────────────────────────────────────────
70
- // Auth gate
71
- // ─────────────────────────────────────────────────────────────────────────────
72
- describe('Metrics auth', () => {
73
- it('returns 404 (router not mounted) when adminSecret is unset', async () => {
74
- const { server } = buildServer({});
75
- const resp = await server.get('/onesub/metrics/active');
76
- expect(resp.status).toBe(404);
77
- });
78
- it('returns 401 INVALID_ADMIN_SECRET when header is missing or wrong', async () => {
79
- const { server } = buildServer({ adminSecret: SECRET });
80
- const noHeader = await server.get('/onesub/metrics/active');
81
- expect(noHeader.status).toBe(401);
82
- expect(noHeader.body.errorCode).toBe('INVALID_ADMIN_SECRET');
83
- const wrongHeader = await server.get('/onesub/metrics/active', { 'x-admin-secret': 'wrong' });
84
- expect(wrongHeader.status).toBe(401);
85
- });
86
- });
87
- // ─────────────────────────────────────────────────────────────────────────────
88
- // /onesub/metrics/active
89
- // ─────────────────────────────────────────────────────────────────────────────
90
- describe('GET /onesub/metrics/active', () => {
91
- it('returns zeros for an empty store', async () => {
92
- const { server } = buildServer({ adminSecret: SECRET });
93
- const resp = await server.get('/onesub/metrics/active', AUTH);
94
- expect(resp.status).toBe(200);
95
- expect(resp.body).toEqual({
96
- total: 0,
97
- activeSubscriptions: 0,
98
- gracePeriodSubscriptions: 0,
99
- nonConsumablePurchases: 0,
100
- byProduct: {},
101
- byProductPurchases: {},
102
- byPlatform: {},
103
- });
104
- });
105
- it('counts active subs + non-consumable purchases', async () => {
106
- const { store, purchaseStore, server } = buildServer({ adminSecret: SECRET });
107
- await store.save(sub({ userId: 'a', productId: 'pro_monthly', platform: 'apple' }));
108
- await store.save(sub({ userId: 'b', productId: 'pro_yearly', platform: 'google' }));
109
- await purchaseStore.savePurchase(purchase({ userId: 'c', platform: 'apple' }));
110
- const resp = await server.get('/onesub/metrics/active', AUTH);
111
- expect(resp.body.total).toBe(3);
112
- expect(resp.body.activeSubscriptions).toBe(2);
113
- expect(resp.body.nonConsumablePurchases).toBe(1);
114
- expect(resp.body.byProduct).toEqual({ pro_monthly: 1, pro_yearly: 1 }); // purchases not in byProduct
115
- expect(resp.body.byPlatform).toEqual({ apple: 2, google: 1 });
116
- });
117
- it('grace_period subs counted in activeSubscriptions AND gracePeriodSubscriptions', async () => {
118
- const { store, server } = buildServer({ adminSecret: SECRET });
119
- await store.save(sub({ userId: 'a', status: 'active' }));
120
- await store.save(sub({ userId: 'b', status: 'grace_period' }));
121
- const resp = await server.get('/onesub/metrics/active', AUTH);
122
- expect(resp.body.activeSubscriptions).toBe(2);
123
- expect(resp.body.gracePeriodSubscriptions).toBe(1);
124
- });
125
- it('on_hold / paused / canceled / expired subs NOT counted', async () => {
126
- const { store, server } = buildServer({ adminSecret: SECRET });
127
- for (const status of ['on_hold', 'paused', 'canceled', 'expired']) {
128
- await store.save(sub({ userId: status, status }));
129
- }
130
- await store.save(sub({ userId: 'real_active', status: 'active' }));
131
- const resp = await server.get('/onesub/metrics/active', AUTH);
132
- expect(resp.body.activeSubscriptions).toBe(1);
133
- });
134
- it('expired-by-time subs (status=active but expiresAt < now) NOT counted', async () => {
135
- const { store, server } = buildServer({ adminSecret: SECRET });
136
- await store.save(sub({ userId: 'past', status: 'active', expiresAt: '2024-01-01T00:00:00.000Z' }));
137
- await store.save(sub({ userId: 'future', status: 'active', expiresAt: '2099-01-01T00:00:00.000Z' }));
138
- const resp = await server.get('/onesub/metrics/active', AUTH);
139
- expect(resp.body.activeSubscriptions).toBe(1);
140
- });
141
- it('consumable purchases NOT counted (only non_consumable contribute)', async () => {
142
- const { purchaseStore, server } = buildServer({ adminSecret: SECRET });
143
- await purchaseStore.savePurchase(purchase({ userId: 'a', type: 'consumable' }));
144
- await purchaseStore.savePurchase(purchase({ userId: 'b', type: 'non_consumable' }));
145
- const resp = await server.get('/onesub/metrics/active', AUTH);
146
- expect(resp.body.nonConsumablePurchases).toBe(1);
147
- expect(resp.body.total).toBe(1);
148
- });
149
- });
150
- // ─────────────────────────────────────────────────────────────────────────────
151
- // /onesub/metrics/started
152
- // ─────────────────────────────────────────────────────────────────────────────
153
- describe('GET /onesub/metrics/started', () => {
154
- it('counts subs with purchasedAt within the [from, to] window', async () => {
155
- const { store, server } = buildServer({ adminSecret: SECRET });
156
- await store.save(sub({ userId: 'before', purchasedAt: '2026-03-15T00:00:00Z' }));
157
- await store.save(sub({ userId: 'in1', purchasedAt: '2026-04-01T00:00:00Z' }));
158
- await store.save(sub({ userId: 'in2', purchasedAt: '2026-04-15T00:00:00Z' }));
159
- await store.save(sub({ userId: 'after', purchasedAt: '2026-05-15T00:00:00Z' }));
160
- const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
161
- expect(resp.body.total).toBe(2);
162
- expect(resp.body.from).toBe('2026-04-01T00:00:00Z');
163
- expect(resp.body.to).toBe('2026-04-30T23:59:59Z');
164
- });
165
- it('aggregates by product and platform', async () => {
166
- const { store, server } = buildServer({ adminSecret: SECRET });
167
- await store.save(sub({ userId: 'a', productId: 'pro_monthly', platform: 'apple', purchasedAt: '2026-04-10T00:00:00Z' }));
168
- await store.save(sub({ userId: 'b', productId: 'pro_yearly', platform: 'google', purchasedAt: '2026-04-20T00:00:00Z' }));
169
- await store.save(sub({ userId: 'c', productId: 'pro_monthly', platform: 'google', purchasedAt: '2026-04-25T00:00:00Z' }));
170
- const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
171
- expect(resp.body.byProduct).toEqual({ pro_monthly: 2, pro_yearly: 1 });
172
- expect(resp.body.byPlatform).toEqual({ apple: 1, google: 2 });
173
- });
174
- it('400 INVALID_INPUT when from/to are missing or malformed', async () => {
175
- const { server } = buildServer({ adminSecret: SECRET });
176
- const missing = await server.get('/onesub/metrics/started', AUTH);
177
- expect(missing.status).toBe(400);
178
- const malformed = await server.get('/onesub/metrics/started?from=not-a-date&to=2026-04-30T23:59:59Z', AUTH);
179
- expect(malformed.status).toBe(400);
180
- const reversed = await server.get('/onesub/metrics/started?from=2026-05-01T00:00:00Z&to=2026-04-01T00:00:00Z', AUTH);
181
- expect(reversed.status).toBe(400);
182
- });
183
- });
184
- // ─────────────────────────────────────────────────────────────────────────────
185
- // /onesub/metrics/expired
186
- // ─────────────────────────────────────────────────────────────────────────────
187
- describe('GET /onesub/metrics/expired', () => {
188
- it('counts only subs where status ∈ {expired, canceled} AND expiresAt in window', async () => {
189
- const { store, server } = buildServer({ adminSecret: SECRET });
190
- // status active but expiresAt in window — NOT counted (not actually ended)
191
- await store.save(sub({ userId: 'still_active', status: 'active', expiresAt: '2026-04-15T00:00:00Z' }));
192
- // status expired, expiresAt in window — counted
193
- await store.save(sub({ userId: 'expired_in', status: 'expired', expiresAt: '2026-04-10T00:00:00Z' }));
194
- // status canceled (refund), expiresAt in window — counted
195
- await store.save(sub({ userId: 'canceled_in', status: 'canceled', expiresAt: '2026-04-20T00:00:00Z' }));
196
- // status expired but expiresAt before window
197
- await store.save(sub({ userId: 'expired_before', status: 'expired', expiresAt: '2026-03-01T00:00:00Z' }));
198
- const resp = await server.get('/onesub/metrics/expired?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
199
- expect(resp.body.total).toBe(2); // expired_in + canceled_in
200
- });
201
- it('returns 0 when nothing matches', async () => {
202
- const { store, server } = buildServer({ adminSecret: SECRET });
203
- await store.save(sub({ status: 'active' }));
204
- const resp = await server.get('/onesub/metrics/expired?from=2020-01-01T00:00:00Z&to=2020-12-31T23:59:59Z', AUTH);
205
- expect(resp.body.total).toBe(0);
206
- });
207
- });
208
- // ─────────────────────────────────────────────────────────────────────────────
209
- // /onesub/metrics/{started,expired}?groupBy=day — daily bucketing
210
- // ─────────────────────────────────────────────────────────────────────────────
211
- describe('GET /onesub/metrics/started?groupBy=day', () => {
212
- it('omits buckets when groupBy is not set (backwards compatible)', async () => {
213
- const { store, server } = buildServer({ adminSecret: SECRET });
214
- await store.save(sub({ purchasedAt: '2026-04-15T00:00:00Z' }));
215
- const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
216
- expect(resp.body.total).toBe(1);
217
- expect(resp.body.buckets).toBeUndefined();
218
- });
219
- it('returns one zero-filled bucket per UTC day across the window', async () => {
220
- const { store, server } = buildServer({ adminSecret: SECRET });
221
- // 3-day window: 2026-04-01 .. 2026-04-03 inclusive
222
- await store.save(sub({ userId: 'a', purchasedAt: '2026-04-01T03:00:00Z', originalTransactionId: 't1' }));
223
- await store.save(sub({ userId: 'b', purchasedAt: '2026-04-01T18:00:00Z', originalTransactionId: 't2' }));
224
- await store.save(sub({ userId: 'c', purchasedAt: '2026-04-03T12:00:00Z', originalTransactionId: 't3' }));
225
- const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-03T23:59:59Z&groupBy=day', AUTH);
226
- expect(resp.body.total).toBe(3);
227
- expect(resp.body.buckets).toEqual([
228
- { date: '2026-04-01', count: 2 },
229
- { date: '2026-04-02', count: 0 }, // gap day, zero-filled
230
- { date: '2026-04-03', count: 1 },
231
- ]);
232
- });
233
- it('snaps the first bucket to UTC midnight even when from is mid-day', async () => {
234
- const { store, server } = buildServer({ adminSecret: SECRET });
235
- await store.save(sub({ purchasedAt: '2026-04-01T20:00:00Z' }));
236
- const resp = await server.get(
237
- // from is 2026-04-01T15:00:00Z — same UTC day as the record
238
- '/onesub/metrics/started?from=2026-04-01T15:00:00Z&to=2026-04-02T15:00:00Z&groupBy=day', AUTH);
239
- expect(resp.body.buckets?.[0]?.date).toBe('2026-04-01');
240
- expect(resp.body.buckets?.[0]?.count).toBe(1);
241
- });
242
- it('400 when groupBy is not one of the allowed values', async () => {
243
- const { server } = buildServer({ adminSecret: SECRET });
244
- const resp = await server.get('/onesub/metrics/started?from=2026-04-01T00:00:00Z&to=2026-04-02T00:00:00Z&groupBy=hour', AUTH);
245
- expect(resp.status).toBe(400);
246
- });
247
- });
248
- describe('GET /onesub/metrics/expired?groupBy=day', () => {
249
- it('buckets expired/canceled by expiresAt UTC date', async () => {
250
- const { store, server } = buildServer({ adminSecret: SECRET });
251
- await store.save(sub({ userId: 'e1', status: 'expired', expiresAt: '2026-04-01T03:00:00Z', originalTransactionId: 'x1' }));
252
- await store.save(sub({ userId: 'e2', status: 'canceled', expiresAt: '2026-04-03T20:00:00Z', originalTransactionId: 'x2' }));
253
- // active record in window — not counted
254
- await store.save(sub({ userId: 'a', status: 'active', expiresAt: '2026-04-02T00:00:00Z', originalTransactionId: 'x3' }));
255
- const resp = await server.get('/onesub/metrics/expired?from=2026-04-01T00:00:00Z&to=2026-04-03T23:59:59Z&groupBy=day', AUTH);
256
- expect(resp.body.total).toBe(2);
257
- expect(resp.body.buckets).toEqual([
258
- { date: '2026-04-01', count: 1 },
259
- { date: '2026-04-02', count: 0 },
260
- { date: '2026-04-03', count: 1 },
261
- ]);
262
- });
263
- });
264
- // ─────────────────────────────────────────────────────────────────────────────
265
- // /onesub/metrics/active — byProductPurchases regression
266
- // ─────────────────────────────────────────────────────────────────────────────
267
- describe('GET /onesub/metrics/active byProductPurchases', () => {
268
- it('reports non-consumable purchases under byProductPurchases (separate from byProduct)', async () => {
269
- const { store, purchaseStore, server } = buildServer({ adminSecret: SECRET });
270
- // Active subscription — should land in byProduct (subs-only)
271
- await store.save(sub({ userId: 'a', productId: 'pro_monthly', status: 'active' }));
272
- // Non-consumable purchases — must land in byProductPurchases, NOT byProduct
273
- await purchaseStore.savePurchase(purchase({ userId: 'b', productId: 'lifetime_pass', transactionId: 'p1' }));
274
- await purchaseStore.savePurchase(purchase({ userId: 'c', productId: 'lifetime_pass', transactionId: 'p2' }));
275
- await purchaseStore.savePurchase(purchase({ userId: 'd', productId: 'event_pack', transactionId: 'p3' }));
276
- // Consumable — must NOT show in byProductPurchases (consumables aren't entitlement-relevant)
277
- await purchaseStore.savePurchase(purchase({ userId: 'e', productId: 'coin_500', type: 'consumable', transactionId: 'p4' }));
278
- const resp = await server.get('/onesub/metrics/active', AUTH);
279
- expect(resp.body.byProduct).toEqual({ pro_monthly: 1 });
280
- expect(resp.body.byProductPurchases).toEqual({ lifetime_pass: 2, event_pack: 1 });
281
- });
282
- it('byProductPurchases empty when there are no non-consumable purchases', async () => {
283
- const { server } = buildServer({ adminSecret: SECRET });
284
- const resp = await server.get('/onesub/metrics/active', AUTH);
285
- expect(resp.body.byProductPurchases).toEqual({});
286
- });
287
- });
288
- // ─────────────────────────────────────────────────────────────────────────────
289
- // /onesub/metrics/purchases/started — non-consumable purchase timeseries
290
- // ─────────────────────────────────────────────────────────────────────────────
291
- describe('GET /onesub/metrics/purchases/started', () => {
292
- it('counts non-consumable purchases with purchasedAt in window', async () => {
293
- const { purchaseStore, server } = buildServer({ adminSecret: SECRET });
294
- await purchaseStore.savePurchase(purchase({ userId: 'a', purchasedAt: '2026-03-15T00:00:00Z', transactionId: 'before' }));
295
- await purchaseStore.savePurchase(purchase({ userId: 'b', purchasedAt: '2026-04-10T00:00:00Z', transactionId: 'in1' }));
296
- await purchaseStore.savePurchase(purchase({ userId: 'c', purchasedAt: '2026-04-20T00:00:00Z', transactionId: 'in2' }));
297
- const resp = await server.get('/onesub/metrics/purchases/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
298
- expect(resp.body.total).toBe(2);
299
- });
300
- it('excludes consumables (entitlement-irrelevant)', async () => {
301
- const { purchaseStore, server } = buildServer({ adminSecret: SECRET });
302
- await purchaseStore.savePurchase(purchase({ userId: 'a', type: 'non_consumable', purchasedAt: '2026-04-10T00:00:00Z', transactionId: 'nc1' }));
303
- await purchaseStore.savePurchase(purchase({ userId: 'b', type: 'consumable', purchasedAt: '2026-04-15T00:00:00Z', transactionId: 'co1' }));
304
- const resp = await server.get('/onesub/metrics/purchases/started?from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z', AUTH);
305
- expect(resp.body.total).toBe(1);
306
- expect(resp.body.byProduct).toEqual({ lifetime_pass: 1 });
307
- });
308
- it('returns zero-filled buckets with groupBy=day', async () => {
309
- const { purchaseStore, server } = buildServer({ adminSecret: SECRET });
310
- await purchaseStore.savePurchase(purchase({ userId: 'a', purchasedAt: '2026-04-01T05:00:00Z', transactionId: 't1' }));
311
- await purchaseStore.savePurchase(purchase({ userId: 'b', purchasedAt: '2026-04-03T18:00:00Z', transactionId: 't2' }));
312
- const resp = await server.get('/onesub/metrics/purchases/started?from=2026-04-01T00:00:00Z&to=2026-04-03T23:59:59Z&groupBy=day', AUTH);
313
- expect(resp.body.buckets).toEqual([
314
- { date: '2026-04-01', count: 1 },
315
- { date: '2026-04-02', count: 0 },
316
- { date: '2026-04-03', count: 1 },
317
- ]);
318
- });
319
- });
320
- // ─────────────────────────────────────────────────────────────────────────────
321
- // Store listAll regression
322
- // ─────────────────────────────────────────────────────────────────────────────
323
- describe('Store listAll', () => {
324
- it('SubscriptionStore.listAll returns every record', async () => {
325
- const store = new InMemorySubscriptionStore();
326
- await store.save(sub({ userId: 'a', originalTransactionId: 't1' }));
327
- await store.save(sub({ userId: 'b', originalTransactionId: 't2' }));
328
- await store.save(sub({ userId: 'a', originalTransactionId: 't3' }));
329
- const all = await store.listAll();
330
- expect(all).toHaveLength(3);
331
- });
332
- it('PurchaseStore.listAll returns every record', async () => {
333
- const purchaseStore = new InMemoryPurchaseStore();
334
- await purchaseStore.savePurchase(purchase({ userId: 'a', transactionId: 'p1' }));
335
- await purchaseStore.savePurchase(purchase({ userId: 'b', transactionId: 'p2' }));
336
- const all = await purchaseStore.listAll();
337
- expect(all).toHaveLength(2);
338
- });
339
- });
340
- //# sourceMappingURL=metrics.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"metrics.test.js","sourceRoot":"","sources":["../../src/__tests__/metrics.test.ts"],"names":[],"mappings":"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"}