@onesub/server 0.8.0 → 0.9.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 (36) hide show
  1. package/dist/__tests__/fetch-hardening.test.d.ts +7 -0
  2. package/dist/__tests__/fetch-hardening.test.d.ts.map +1 -0
  3. package/dist/__tests__/fetch-hardening.test.js +192 -0
  4. package/dist/__tests__/fetch-hardening.test.js.map +1 -0
  5. package/dist/__tests__/lifecycle-scenarios.test.d.ts +21 -0
  6. package/dist/__tests__/lifecycle-scenarios.test.d.ts.map +1 -0
  7. package/dist/__tests__/lifecycle-scenarios.test.js +579 -0
  8. package/dist/__tests__/lifecycle-scenarios.test.js.map +1 -0
  9. package/dist/__tests__/paused-auto-resume.test.d.ts +7 -0
  10. package/dist/__tests__/paused-auto-resume.test.d.ts.map +1 -0
  11. package/dist/__tests__/paused-auto-resume.test.js +162 -0
  12. package/dist/__tests__/paused-auto-resume.test.js.map +1 -0
  13. package/dist/__tests__/schema.test.js +4 -0
  14. package/dist/__tests__/schema.test.js.map +1 -1
  15. package/dist/http.d.ts +17 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +36 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/providers/apple.d.ts +5 -0
  20. package/dist/providers/apple.d.ts.map +1 -1
  21. package/dist/providers/apple.js +48 -10
  22. package/dist/providers/apple.js.map +1 -1
  23. package/dist/providers/google.d.ts.map +1 -1
  24. package/dist/providers/google.js +14 -6
  25. package/dist/providers/google.js.map +1 -1
  26. package/dist/routes/webhook.d.ts.map +1 -1
  27. package/dist/routes/webhook.js +7 -0
  28. package/dist/routes/webhook.js.map +1 -1
  29. package/dist/stores/postgres.d.ts.map +1 -1
  30. package/dist/stores/postgres.js +6 -2
  31. package/dist/stores/postgres.js.map +1 -1
  32. package/dist/stores/schema.d.ts.map +1 -1
  33. package/dist/stores/schema.js +3 -1
  34. package/dist/stores/schema.js.map +1 -1
  35. package/package.json +2 -2
  36. package/sql/schema.sql +3 -1
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for outbound fetch hardening:
3
+ * - Apple Server API JWT cache (re-use within TTL, mint dedup under concurrent calls)
4
+ * - fetchWithTimeout: AbortController fires when upstream hangs longer than the budget
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=fetch-hardening.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-hardening.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/fetch-hardening.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Tests for outbound fetch hardening:
3
+ * - Apple Server API JWT cache (re-use within TTL, mint dedup under concurrent calls)
4
+ * - fetchWithTimeout: AbortController fires when upstream hangs longer than the budget
5
+ */
6
+ import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest';
7
+ import { generateKeyPairSync } from 'crypto';
8
+ import { fetchAppleSubscriptionStatus, __testing as appleTesting } from '../providers/apple.js';
9
+ import { fetchWithTimeout } from '../http.js';
10
+ import { isLocalhostUrl } from './test-utils.js';
11
+ let testEcKey;
12
+ beforeAll(() => {
13
+ const { privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' });
14
+ testEcKey = privateKey.export({ type: 'pkcs8', format: 'pem' });
15
+ });
16
+ beforeEach(() => {
17
+ vi.restoreAllMocks();
18
+ appleTesting.clearAppleJwtCacheForTests();
19
+ });
20
+ function appleConfig() {
21
+ return {
22
+ bundleId: 'com.example.app',
23
+ skipJwsVerification: true,
24
+ keyId: 'KEY1',
25
+ issuerId: 'iss-uuid',
26
+ privateKey: testEcKey,
27
+ };
28
+ }
29
+ function makeJws(payload) {
30
+ const header = Buffer.from(JSON.stringify({ alg: 'ES256' })).toString('base64url');
31
+ const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
32
+ return `${header}.${body}.fakesig`;
33
+ }
34
+ function statusResponse(orig) {
35
+ return {
36
+ bundleId: 'com.example.app',
37
+ environment: 'Production',
38
+ data: [{
39
+ lastTransactions: [{
40
+ originalTransactionId: orig,
41
+ status: 1,
42
+ signedTransactionInfo: makeJws({
43
+ bundleId: 'com.example.app',
44
+ productId: 'pro_monthly',
45
+ transactionId: 'tx',
46
+ originalTransactionId: orig,
47
+ purchaseDate: Date.now(),
48
+ originalPurchaseDate: Date.now(),
49
+ expiresDate: Date.now() + 86400000,
50
+ }),
51
+ signedRenewalInfo: makeJws({ autoRenewStatus: 1 }),
52
+ }],
53
+ }],
54
+ };
55
+ }
56
+ // ── Apple JWT cache ─────────────────────────────────────────────────────────
57
+ describe('makeAppleApiJwt cache', () => {
58
+ it('mints a JWT once per cache window when called sequentially', async () => {
59
+ const originalFetch = global.fetch;
60
+ const calls = [];
61
+ vi.spyOn(global, 'fetch').mockImplementation(async (url, init) => {
62
+ if (isLocalhostUrl(url))
63
+ return originalFetch(url, init);
64
+ const headers = init?.headers;
65
+ calls.push({ url: String(url), auth: headers?.Authorization });
66
+ return {
67
+ ok: true,
68
+ status: 200,
69
+ json: async () => statusResponse('orig_a'),
70
+ text: async () => '',
71
+ };
72
+ });
73
+ await fetchAppleSubscriptionStatus('orig_a', appleConfig());
74
+ await fetchAppleSubscriptionStatus('orig_a', appleConfig());
75
+ await fetchAppleSubscriptionStatus('orig_a', appleConfig());
76
+ // 3 outbound API calls, but the Authorization header (Bearer <jwt>) should
77
+ // be the same JWT in all of them — cache hit on calls 2 and 3.
78
+ expect(calls).toHaveLength(3);
79
+ expect(calls[0].auth).toBeDefined();
80
+ expect(calls[1].auth).toBe(calls[0].auth);
81
+ expect(calls[2].auth).toBe(calls[0].auth);
82
+ });
83
+ it('dedups concurrent JWT mints (single in-flight Promise under burst)', async () => {
84
+ // Use a real key pair sign spy through jose? Indirect — just count fetch
85
+ // Authorization headers across many concurrent calls. With dedup, all
86
+ // share the same JWT.
87
+ const originalFetch = global.fetch;
88
+ const auths = [];
89
+ vi.spyOn(global, 'fetch').mockImplementation(async (url, init) => {
90
+ if (isLocalhostUrl(url))
91
+ return originalFetch(url, init);
92
+ const headers = init?.headers;
93
+ auths.push(headers?.Authorization ?? '');
94
+ return {
95
+ ok: true,
96
+ status: 200,
97
+ json: async () => statusResponse('orig_burst'),
98
+ text: async () => '',
99
+ };
100
+ });
101
+ await Promise.all(Array.from({ length: 10 }, () => fetchAppleSubscriptionStatus('orig_burst', appleConfig())));
102
+ expect(auths).toHaveLength(10);
103
+ const unique = new Set(auths);
104
+ expect(unique.size).toBe(1); // all 10 used the same JWT
105
+ });
106
+ it('mints a fresh JWT after the cache is cleared (e.g. credential rotation)', async () => {
107
+ const originalFetch = global.fetch;
108
+ const auths = [];
109
+ vi.spyOn(global, 'fetch').mockImplementation(async (url, init) => {
110
+ if (isLocalhostUrl(url))
111
+ return originalFetch(url, init);
112
+ const headers = init?.headers;
113
+ auths.push(headers?.Authorization ?? '');
114
+ return {
115
+ ok: true,
116
+ status: 200,
117
+ json: async () => statusResponse('orig_clear'),
118
+ text: async () => '',
119
+ };
120
+ });
121
+ await fetchAppleSubscriptionStatus('orig_clear', appleConfig());
122
+ appleTesting.clearAppleJwtCacheForTests();
123
+ await fetchAppleSubscriptionStatus('orig_clear', appleConfig());
124
+ expect(auths).toHaveLength(2);
125
+ expect(auths[0]).not.toBe(auths[1]); // cache miss → fresh JWT
126
+ });
127
+ });
128
+ // ── fetchWithTimeout ────────────────────────────────────────────────────────
129
+ describe('fetchWithTimeout', () => {
130
+ it('returns the response when upstream replies before the timeout', async () => {
131
+ vi.spyOn(global, 'fetch').mockImplementation(async () => {
132
+ return { ok: true, status: 200, text: async () => 'hi' };
133
+ });
134
+ const resp = await fetchWithTimeout('https://example.com', undefined, 1000);
135
+ expect(resp.ok).toBe(true);
136
+ });
137
+ it('aborts when upstream hangs longer than the timeout', async () => {
138
+ vi.spyOn(global, 'fetch').mockImplementation((_input, init) => {
139
+ // Simulate a hung server — never resolves until the AbortController fires
140
+ return new Promise((_resolve, reject) => {
141
+ const signal = init?.signal;
142
+ if (signal) {
143
+ signal.addEventListener('abort', () => {
144
+ const reason = signal.reason;
145
+ reject(reason instanceof Error ? reason : new Error('aborted'));
146
+ });
147
+ }
148
+ });
149
+ });
150
+ const start = Date.now();
151
+ await expect(fetchWithTimeout('https://hung.example', undefined, 100)).rejects.toThrow(/timed out|aborted/i);
152
+ const elapsed = Date.now() - start;
153
+ expect(elapsed).toBeGreaterThanOrEqual(90);
154
+ expect(elapsed).toBeLessThan(500);
155
+ });
156
+ it('respects a caller-provided AbortSignal (composes with timeout)', async () => {
157
+ vi.spyOn(global, 'fetch').mockImplementation((_input, init) => {
158
+ return new Promise((_resolve, reject) => {
159
+ init?.signal?.addEventListener('abort', () => reject(new Error('aborted')));
160
+ });
161
+ });
162
+ const ac = new AbortController();
163
+ setTimeout(() => ac.abort(new Error('caller cancelled')), 30);
164
+ await expect(fetchWithTimeout('https://hung.example', { signal: ac.signal }, 5000)).rejects.toThrow();
165
+ });
166
+ it('clears its timer on success (no leaked handles)', async () => {
167
+ vi.spyOn(global, 'fetch').mockImplementation(async () => {
168
+ return { ok: true, status: 200, text: async () => 'ok' };
169
+ });
170
+ // 100s timeout, but resolves immediately. If the timer leaked, vitest
171
+ // would hold the process open after the test — vitest's `--detectLeaks`
172
+ // would catch that. Smoke test here is just that the call returns and the
173
+ // process doesn't hang the test runner.
174
+ await fetchWithTimeout('https://example.com', undefined, 100_000);
175
+ // Reaching this line means the timer either fired (it shouldn't) or was
176
+ // cleared. We can't easily inspect `setTimeout` internals, but the suite
177
+ // completing in normal time is the implicit assertion.
178
+ expect(true).toBe(true);
179
+ });
180
+ it('uses the default 10s budget when no timeoutMs is passed', async () => {
181
+ let observedTimeout;
182
+ vi.spyOn(global, 'fetch').mockImplementation((_input, init) => {
183
+ // Snapshot any abort behaviour: we can't read the timeout directly,
184
+ // but we can verify a signal is attached.
185
+ observedTimeout = init?.signal ? 1 : 0;
186
+ return Promise.resolve({ ok: true, status: 200, text: async () => '' });
187
+ });
188
+ await fetchWithTimeout('https://example.com');
189
+ expect(observedTimeout).toBe(1); // signal attached → timeout active
190
+ });
191
+ });
192
+ //# sourceMappingURL=fetch-hardening.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-hardening.test.js","sourceRoot":"","sources":["../../src/__tests__/fetch-hardening.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,EAAE,4BAA4B,EAAE,SAAS,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAW,MAAM,iBAAiB,CAAC;AAE1D,IAAI,SAAiB,CAAC;AAEtB,SAAS,CAAC,GAAG,EAAE;IACb,MAAM,EAAE,UAAU,EAAE,GAAG,mBAAmB,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1E,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAW,CAAC;AAC5E,CAAC,CAAC,CAAC;AAEH,UAAU,CAAC,GAAG,EAAE;IACd,EAAE,CAAC,eAAe,EAAE,CAAC;IACrB,YAAY,CAAC,0BAA0B,EAAE,CAAC;AAC5C,CAAC,CAAC,CAAC;AAEH,SAAS,WAAW;IAClB,OAAO;QACL,QAAQ,EAAE,iBAAiB;QAC3B,mBAAmB,EAAE,IAAI;QACzB,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,UAAU;QACpB,UAAU,EAAE,SAAS;KACtB,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,OAAgC;IAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACxE,OAAO,GAAG,MAAM,IAAI,IAAI,UAAU,CAAC;AACrC,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO;QACL,QAAQ,EAAE,iBAAiB;QAC3B,WAAW,EAAE,YAAY;QACzB,IAAI,EAAE,CAAC;gBACL,gBAAgB,EAAE,CAAC;wBACjB,qBAAqB,EAAE,IAAI;wBAC3B,MAAM,EAAE,CAAC;wBACT,qBAAqB,EAAE,OAAO,CAAC;4BAC7B,QAAQ,EAAE,iBAAiB;4BAC3B,SAAS,EAAE,aAAa;4BACxB,aAAa,EAAE,IAAI;4BACnB,qBAAqB,EAAE,IAAI;4BAC3B,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;4BACxB,oBAAoB,EAAE,IAAI,CAAC,GAAG,EAAE;4BAChC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ;yBACnC,CAAC;wBACF,iBAAiB,EAAE,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;qBACnD,CAAC;aACH,CAAC;KACH,CAAC;AACJ,CAAC;AAED,+EAA+E;AAE/E,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC;QACnC,MAAM,KAAK,GAAqC,EAAE,CAAC;QACnD,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAC/D,IAAI,cAAc,CAAC,GAAG,CAAC;gBAAE,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,EAAE,OAA6C,CAAC;YACpE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;YAC/D,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC;gBAC1C,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;aACT,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,MAAM,4BAA4B,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5D,MAAM,4BAA4B,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5D,MAAM,4BAA4B,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;QAE5D,2EAA2E;QAC3E,+DAA+D;QAC/D,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACpC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;QAClF,yEAAyE;QACzE,sEAAsE;QACtE,sBAAsB;QACtB,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC;QACnC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAC/D,IAAI,cAAc,CAAC,GAAG,CAAC;gBAAE,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,EAAE,OAA6C,CAAC;YACpE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;YACzC,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC;gBAC9C,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;aACT,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,4BAA4B,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAC5F,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,2BAA2B;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,aAAa,GAAG,MAAM,CAAC,KAAK,CAAC;QACnC,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YAC/D,IAAI,cAAc,CAAC,GAAG,CAAC;gBAAE,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,EAAE,OAA6C,CAAC;YACpE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;YACzC,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC;gBAC9C,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;aACT,CAAC;QAChB,CAAC,CAAC,CAAC;QAEH,MAAM,4BAA4B,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAChE,YAAY,CAAC,0BAA0B,EAAE,CAAC;QAC1C,MAAM,4BAA4B,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC;QAEhE,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,yBAAyB;IACjE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,+EAA+E;AAE/E,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;YACtD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,EAAc,CAAC;QACvE,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,qBAAqB,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC5E,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5D,0EAA0E;YAC1E,OAAO,IAAI,OAAO,CAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;gBAChD,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC;gBAC5B,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;wBACpC,MAAM,MAAM,GAAI,MAA6C,CAAC,MAAM,CAAC;wBACrE,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;oBAClE,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,CAAC,gBAAgB,CAAC,sBAAsB,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;QAC7G,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACnC,MAAM,CAAC,OAAO,CAAC,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;QAC9E,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5D,OAAO,IAAI,OAAO,CAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;gBAChD,IAAI,EAAE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC9E,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAE9D,MAAM,MAAM,CACV,gBAAgB,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CACtE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;YACtD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,EAAc,CAAC;QACvE,CAAC,CAAC,CAAC;QACH,sEAAsE;QACtE,wEAAwE;QACxE,0EAA0E;QAC1E,wCAAwC;QACxC,MAAM,gBAAgB,CAAC,qBAAqB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAClE,wEAAwE;QACxE,yEAAyE;QACzE,uDAAuD;QACvD,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,IAAI,eAAmC,CAAC;QACxC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5D,oEAAoE;YACpE,0CAA0C;YAC1C,eAAe,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvC,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,EAAc,CAAC,CAAC;QACtF,CAAC,CAAC,CAAC;QACH,MAAM,gBAAgB,CAAC,qBAAqB,CAAC,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,mCAAmC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * End-to-end lifecycle scenarios — exercise multi-notification sequences against
3
+ * the real webhook router + status route + stores, the way Apple/Google would
4
+ * deliver them in production.
5
+ *
6
+ * Single-notification correctness is covered by the per-feature unit tests; this
7
+ * file catches issues that only show up when notifications arrive in a sequence:
8
+ * stale state bleeding across transitions, idempotency under replay, recovery
9
+ * paths after a missed webhook, etc.
10
+ *
11
+ * Why scenarios instead of more unit tests:
12
+ * - The webhook handler has many branches (per-notification mapping, fresh
13
+ * re-fetch, refundPolicy gating, hooks). Each is unit-tested in isolation,
14
+ * but a transition like SUBSCRIBED → DID_FAIL_TO_RENEW(GRACE) → DID_RENEW
15
+ * touches three of those branches in turn — a regression in any one only
16
+ * surfaces when run together.
17
+ * - These scenarios mirror the runbook for sandbox verification, so the
18
+ * mock results here can be cross-checked against real sandbox behaviour.
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=lifecycle-scenarios.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle-scenarios.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/lifecycle-scenarios.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}