@onesub/server 0.14.0 → 0.16.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 (140) hide show
  1. package/dist/__tests__/google-subscriptions-v2.test.d.ts +1 -1
  2. package/dist/__tests__/receipt-age.test.d.ts +7 -0
  3. package/dist/__tests__/receipt-age.test.d.ts.map +1 -0
  4. package/dist/__tests__/tracing.test.d.ts +2 -0
  5. package/dist/__tests__/tracing.test.d.ts.map +1 -0
  6. package/dist/cache.d.ts +5 -0
  7. package/dist/cache.d.ts.map +1 -1
  8. package/dist/index.cjs +178 -55
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +178 -55
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/apple.d.ts.map +1 -1
  13. package/dist/providers/google.d.ts.map +1 -1
  14. package/dist/providers/mock.d.ts.map +1 -1
  15. package/dist/routes/admin.d.ts.map +1 -1
  16. package/dist/routes/purchase.d.ts.map +1 -1
  17. package/dist/routes/validate.d.ts.map +1 -1
  18. package/dist/routes/webhook-apple.d.ts.map +1 -1
  19. package/dist/routes/webhook-google.d.ts +1 -1
  20. package/dist/routes/webhook-google.d.ts.map +1 -1
  21. package/dist/store.d.ts.map +1 -1
  22. package/dist/stores/postgres.d.ts.map +1 -1
  23. package/dist/stores/redis.d.ts +1 -0
  24. package/dist/stores/redis.d.ts.map +1 -1
  25. package/dist/tracing.d.ts.map +1 -1
  26. package/dist/webhook-events.d.ts +10 -0
  27. package/dist/webhook-events.d.ts.map +1 -1
  28. package/package.json +5 -3
  29. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  30. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  31. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  32. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  33. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  34. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  35. package/dist/__tests__/apple-root-ca.test.js +0 -41
  36. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  37. package/dist/__tests__/apple-status-api.test.js +0 -345
  38. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  39. package/dist/__tests__/cache.test.js +0 -34
  40. package/dist/__tests__/cache.test.js.map +0 -1
  41. package/dist/__tests__/entitlements.test.js +0 -268
  42. package/dist/__tests__/entitlements.test.js.map +0 -1
  43. package/dist/__tests__/error-codes.test.js +0 -173
  44. package/dist/__tests__/error-codes.test.js.map +0 -1
  45. package/dist/__tests__/fetch-hardening.test.js +0 -192
  46. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  47. package/dist/__tests__/google-paused.test.js +0 -126
  48. package/dist/__tests__/google-paused.test.js.map +0 -1
  49. package/dist/__tests__/google-price-change.test.js +0 -186
  50. package/dist/__tests__/google-price-change.test.js.map +0 -1
  51. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  52. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  53. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  54. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  55. package/dist/__tests__/lifecycle-states.test.js +0 -423
  56. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  57. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  58. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  59. package/dist/__tests__/metrics.test.js +0 -340
  60. package/dist/__tests__/metrics.test.js.map +0 -1
  61. package/dist/__tests__/mock-provider.test.js +0 -188
  62. package/dist/__tests__/mock-provider.test.js.map +0 -1
  63. package/dist/__tests__/openapi.test.js +0 -32
  64. package/dist/__tests__/openapi.test.js.map +0 -1
  65. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  66. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  67. package/dist/__tests__/providers.test.js +0 -321
  68. package/dist/__tests__/providers.test.js.map +0 -1
  69. package/dist/__tests__/redis-store.test.js +0 -166
  70. package/dist/__tests__/redis-store.test.js.map +0 -1
  71. package/dist/__tests__/refund-policy.test.js +0 -317
  72. package/dist/__tests__/refund-policy.test.js.map +0 -1
  73. package/dist/__tests__/schema.test.js +0 -46
  74. package/dist/__tests__/schema.test.js.map +0 -1
  75. package/dist/__tests__/store.test.js +0 -172
  76. package/dist/__tests__/store.test.js.map +0 -1
  77. package/dist/__tests__/test-utils.js +0 -30
  78. package/dist/__tests__/test-utils.js.map +0 -1
  79. package/dist/__tests__/webhook-apple.test.js +0 -247
  80. package/dist/__tests__/webhook-apple.test.js.map +0 -1
  81. package/dist/__tests__/webhook-events.test.js +0 -36
  82. package/dist/__tests__/webhook-events.test.js.map +0 -1
  83. package/dist/__tests__/webhook-google.test.js +0 -255
  84. package/dist/__tests__/webhook-google.test.js.map +0 -1
  85. package/dist/__tests__/webhook-queue.test.js +0 -26
  86. package/dist/__tests__/webhook-queue.test.js.map +0 -1
  87. package/dist/__tests__/webhook-refund.test.js +0 -313
  88. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  89. package/dist/cache.js +0 -39
  90. package/dist/cache.js.map +0 -1
  91. package/dist/errors.js +0 -19
  92. package/dist/errors.js.map +0 -1
  93. package/dist/http.js +0 -36
  94. package/dist/http.js.map +0 -1
  95. package/dist/logger.js +0 -20
  96. package/dist/logger.js.map +0 -1
  97. package/dist/openapi.js +0 -196
  98. package/dist/openapi.js.map +0 -1
  99. package/dist/providers/apple-root-ca.js +0 -38
  100. package/dist/providers/apple-root-ca.js.map +0 -1
  101. package/dist/providers/apple.js +0 -630
  102. package/dist/providers/apple.js.map +0 -1
  103. package/dist/providers/google.js +0 -564
  104. package/dist/providers/google.js.map +0 -1
  105. package/dist/providers/mock.js +0 -93
  106. package/dist/providers/mock.js.map +0 -1
  107. package/dist/routes/admin.js +0 -327
  108. package/dist/routes/admin.js.map +0 -1
  109. package/dist/routes/apple-offer.js +0 -72
  110. package/dist/routes/apple-offer.js.map +0 -1
  111. package/dist/routes/entitlements.js +0 -130
  112. package/dist/routes/entitlements.js.map +0 -1
  113. package/dist/routes/metrics.js +0 -281
  114. package/dist/routes/metrics.js.map +0 -1
  115. package/dist/routes/purchase.js +0 -204
  116. package/dist/routes/purchase.js.map +0 -1
  117. package/dist/routes/status.js +0 -55
  118. package/dist/routes/status.js.map +0 -1
  119. package/dist/routes/validate.js +0 -70
  120. package/dist/routes/validate.js.map +0 -1
  121. package/dist/routes/webhook-apple.js +0 -145
  122. package/dist/routes/webhook-apple.js.map +0 -1
  123. package/dist/routes/webhook-google.js +0 -204
  124. package/dist/routes/webhook-google.js.map +0 -1
  125. package/dist/routes/webhook.js +0 -19
  126. package/dist/routes/webhook.js.map +0 -1
  127. package/dist/store.js +0 -145
  128. package/dist/store.js.map +0 -1
  129. package/dist/stores/postgres.js +0 -329
  130. package/dist/stores/postgres.js.map +0 -1
  131. package/dist/stores/redis.js +0 -297
  132. package/dist/stores/redis.js.map +0 -1
  133. package/dist/stores/schema.js +0 -67
  134. package/dist/stores/schema.js.map +0 -1
  135. package/dist/tracing.js +0 -74
  136. package/dist/tracing.js.map +0 -1
  137. package/dist/webhook-events.js +0 -58
  138. package/dist/webhook-events.js.map +0 -1
  139. package/dist/webhook-queue.js +0 -108
  140. package/dist/webhook-queue.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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"}