backend-manager 5.0.173 → 5.0.175

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.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
14
14
  - `Fixed` for any bug fixes.
15
15
  - `Security` in case of vulnerabilities.
16
16
 
17
+ # [5.0.174] - 2026-03-27
18
+ ### Fixed
19
+ - Payments-orders `metadata.created` timestamp no longer overwritten on subsequent webhook events (renewals, cancellations, payment failures)
20
+
17
21
  # [5.0.168] - 2026-03-21
18
22
  ### Fixed
19
23
  - Immediately suspend subscription on payment denial (PAYMENT.SALE.DENIED, invoice.payment_failed) instead of waiting for the processor to give up retrying — recovery via PAYMENT.SALE.COMPLETED restores active status
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.173",
3
+ "version": "5.0.175",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -314,12 +314,15 @@ async function processPaymentEvent({ category, library, resource, resourceType,
314
314
  const orderRef = admin.firestore().doc(`payments-orders/${orderId}`);
315
315
  const orderSnap = await orderRef.get();
316
316
 
317
- // Initialize requests on first creation only (avoid overwriting cancel/refund data set by endpoints)
318
317
  if (!orderSnap.exists) {
318
+ // Initialize requests on first creation only (avoid overwriting cancel/refund data set by endpoints)
319
319
  order.requests = {
320
320
  cancellation: null,
321
321
  refund: null,
322
322
  };
323
+ } else {
324
+ // Preserve original created timestamp on subsequent webhook events
325
+ order.metadata.created = orderSnap.data().metadata?.created || order.metadata.created;
323
326
  }
324
327
 
325
328
  await orderRef.set(order, { merge: true });
@@ -212,8 +212,8 @@ const SEGMENTS = {
212
212
  engagement_active_30d: { display: 'Engaged Last 30 Days', conditions: [{ type: 'engagement', op: 'opened_or_clicked', value: '30d' }] },
213
213
  engagement_active_90d: { display: 'Engaged Last 90 Days', conditions: [{ type: 'engagement', op: 'opened_or_clicked', value: '90d' }] },
214
214
  engagement_inactive_90d: { display: 'Inactive 90+ Days', conditions: [{ type: 'engagement', op: 'not_opened', value: '90d' }] },
215
- engagement_inactive_5m: { display: 'Inactive 5+ Months', conditions: [{ type: 'engagement', op: 'not_opened', value: '150d' }] },
216
- engagement_inactive_6m: { display: 'Inactive 6+ Months', conditions: [{ type: 'engagement', op: 'not_opened', value: '180d' }] },
215
+ engagement_inactive_5m: { display: 'Inactive 5+ Months', conditions: [{ type: 'engagement', op: 'not_opened', value: '150d' }, { field: 'user_metadata_signup_date', op: 'not_within', value: '150d' }] },
216
+ engagement_inactive_6m: { display: 'Inactive 6+ Months', conditions: [{ type: 'engagement', op: 'not_opened', value: '180d' }, { field: 'user_metadata_signup_date', op: 'not_within', value: '180d' }] },
217
217
 
218
218
  // Test
219
219
  test_admin: { display: 'Test Admin', conditions: [{ type: 'contact', op: 'email_is', value: 'hello@itwcreativeworks.com' }] },
@@ -55,10 +55,12 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
55
55
  return assistant.respond('Frequency is required for subscription products', { code: 400 });
56
56
  }
57
57
 
58
- // Check if user already has an active non-basic subscription
59
- if (user.subscription?.status === 'active' && user.subscription?.product?.id !== 'basic') {
60
- assistant.log(`User ${uid} already has active subscription: product=${user.subscription.product.id}, status=${user.subscription.status}, resourceId=${user.subscription.payment?.resourceId}`);
61
- return assistant.respond('User already has an active subscription', { code: 400 });
58
+ // Block checkout unless user has no subscription or is fully cancelled
59
+ const subProductId = user.subscription?.product?.id || 'basic';
60
+ const subStatus = user.subscription?.status;
61
+ if (subProductId !== 'basic' && subStatus !== 'cancelled') {
62
+ assistant.log(`User ${uid} has existing subscription: product=${subProductId}, status=${subStatus}, resourceId=${user.subscription.payment?.resourceId}`);
63
+ return assistant.respond('You already have a subscription. Please cancel your existing subscription before purchasing a new one.', { code: 400 });
62
64
  }
63
65
 
64
66
  // Resolve trial eligibility: if requested but user has subscription history, silently downgrade
@@ -229,7 +229,8 @@ class TestRunner {
229
229
  // Create fresh test accounts
230
230
  const result = await testAccounts.createTestAccounts(
231
231
  this.options.admin,
232
- this.options.domain
232
+ this.options.domain,
233
+ this.config
233
234
  );
234
235
 
235
236
  if (!result.success) {
@@ -240,7 +241,7 @@ class TestRunner {
240
241
  console.log(chalk.green(`✓ (${result.created} created)`));
241
242
 
242
243
  // Fetch account privateKeys
243
- this.accounts = await testAccounts.fetchPrivateKeys(this.options.admin, this.options.domain);
244
+ this.accounts = await testAccounts.fetchPrivateKeys(this.options.admin, this.options.domain, this.config);
244
245
 
245
246
  // Initialize rules testing context for security rules tests
246
247
  process.stdout.write(chalk.gray(' Initializing rules testing context... '));
@@ -663,30 +664,20 @@ class TestRunner {
663
664
  });
664
665
 
665
666
  // Set default auth
666
- switch (auth) {
667
- case 'admin':
668
- http.setAuth('backendManagerKey', { key: this.options.backendManagerKey });
669
- break;
670
- case 'basic':
671
- case 'user':
672
- if (this.accounts?.basic?.privateKey) {
673
- http.setAuth('privateKey', { privateKey: this.accounts.basic.privateKey });
674
- }
675
- break;
676
- case 'premium-active':
677
- if (this.accounts?.['premium-active']?.privateKey) {
678
- http.setAuth('privateKey', { privateKey: this.accounts['premium-active'].privateKey });
679
- }
680
- break;
681
- case 'premium-expired':
682
- if (this.accounts?.['premium-expired']?.privateKey) {
683
- http.setAuth('privateKey', { privateKey: this.accounts['premium-expired'].privateKey });
684
- }
685
- break;
686
- case 'none':
687
- default:
688
- http.setAuth('none');
689
- break;
667
+ if (auth === 'admin') {
668
+ http.setAuth('backendManagerKey', { key: this.options.backendManagerKey });
669
+ } else if (auth === 'none') {
670
+ http.setAuth('none');
671
+ } else if (auth === 'user') {
672
+ // Alias for basic
673
+ if (this.accounts?.basic?.privateKey) {
674
+ http.setAuth('privateKey', { privateKey: this.accounts.basic.privateKey });
675
+ }
676
+ } else if (this.accounts?.[auth]?.privateKey) {
677
+ // Dynamic lookup — any account type with a privateKey
678
+ http.setAuth('privateKey', { privateKey: this.accounts[auth].privateKey });
679
+ } else {
680
+ http.setAuth('none');
690
681
  }
691
682
 
692
683
  // Create firestore helper (only if admin SDK available)
@@ -1,5 +1,17 @@
1
1
  const uuid = require('uuid');
2
2
 
3
+ /**
4
+ * Resolve the first paid subscription product from config
5
+ * Falls back to 'premium' if no config or no paid products found
6
+ */
7
+ function getFirstPaidProduct(config) {
8
+ const products = config?.payment?.products || [];
9
+ const paid = products.find(p => p.type === 'subscription' && p.id !== 'basic');
10
+ return paid
11
+ ? { id: paid.id, name: paid.name }
12
+ : { id: 'premium', name: 'Premium' };
13
+ }
14
+
3
15
  /**
4
16
  * Helper to create a future expiration date for premium subscriptions
5
17
  * User() checks subscription.expires to determine if subscription is active
@@ -77,6 +89,24 @@ const STATIC_ACCOUNTS = {
77
89
  subscription: { product: { id: 'premium' }, status: 'cancelled', expires: getPastExpires() },
78
90
  },
79
91
  },
92
+ 'premium-suspended': {
93
+ id: 'premium-suspended',
94
+ uid: '_test-premium-suspended',
95
+ email: '_test.premium-suspended@{domain}',
96
+ properties: {
97
+ roles: {},
98
+ subscription: { product: { id: 'premium' }, status: 'suspended', expires: getFutureExpires() },
99
+ },
100
+ },
101
+ 'premium-cancelling': {
102
+ id: 'premium-cancelling',
103
+ uid: '_test-premium-cancelling',
104
+ email: '_test.premium-cancelling@{domain}',
105
+ properties: {
106
+ roles: {},
107
+ subscription: { product: { id: 'premium' }, status: 'active', expires: getFutureExpires(), cancellation: { pending: true } },
108
+ },
109
+ },
80
110
  delete: {
81
111
  id: 'delete',
82
112
  uid: '_test-delete',
@@ -397,6 +427,25 @@ const JOURNEY_ACCOUNTS = {
397
427
  subscription: { product: { id: 'basic' }, status: 'active' },
398
428
  },
399
429
  },
430
+ // Dedicated accounts for user resolve tests — must not be reused by other tests
431
+ 'resolve-premium-active': {
432
+ id: 'resolve-premium-active',
433
+ uid: '_test-resolve-premium-active',
434
+ email: '_test.resolve-premium-active@{domain}',
435
+ properties: {
436
+ roles: {},
437
+ subscription: { product: { id: 'premium' }, status: 'active', expires: getFutureExpires() },
438
+ },
439
+ },
440
+ 'resolve-premium-expired': {
441
+ id: 'resolve-premium-expired',
442
+ uid: '_test-resolve-premium-expired',
443
+ email: '_test.resolve-premium-expired@{domain}',
444
+ properties: {
445
+ roles: {},
446
+ subscription: { product: { id: 'premium' }, status: 'cancelled', expires: getPastExpires() },
447
+ },
448
+ },
400
449
  };
401
450
 
402
451
  /**
@@ -408,19 +457,29 @@ const TEST_ACCOUNTS = {
408
457
  };
409
458
 
410
459
  /**
411
- * Get all test account definitions with resolved emails
460
+ * Get all test account definitions with resolved emails and dynamic product IDs
412
461
  * @param {string} domain - Domain for email addresses (e.g., 'itwcreativeworks.com')
462
+ * @param {object} [config] - BEM config (used to resolve first paid product)
413
463
  * @returns {object} Account definitions with resolved emails
414
464
  */
415
- function getAccountDefinitions(domain) {
465
+ function getAccountDefinitions(domain, config) {
466
+ const paidProduct = getFirstPaidProduct(config);
416
467
  const accounts = {};
417
468
 
418
469
  for (const [key, account] of Object.entries(TEST_ACCOUNTS)) {
470
+ const properties = JSON.parse(JSON.stringify(account.properties));
471
+
472
+ // Replace hardcoded 'premium' product with the actual first paid product from config
473
+ if (properties.subscription?.product?.id === 'premium') {
474
+ properties.subscription.product.id = paidProduct.id;
475
+ properties.subscription.product.name = paidProduct.name;
476
+ }
477
+
419
478
  accounts[key] = {
420
479
  id: account.id,
421
480
  uid: account.uid,
422
481
  email: account.email.replace('{domain}', domain),
423
- properties: account.properties,
482
+ properties,
424
483
  };
425
484
  }
426
485
 
@@ -431,10 +490,11 @@ function getAccountDefinitions(domain) {
431
490
  * Fetch privateKeys for test accounts from Firestore
432
491
  * @param {object} admin - Firebase admin instance
433
492
  * @param {string} domain - Domain for email addresses (e.g., 'itwcreativeworks.com')
493
+ * @param {object} [config] - BEM config (used to resolve first paid product)
434
494
  * @returns {Promise<object>} Account credentials with privateKeys
435
495
  */
436
- async function fetchPrivateKeys(admin, domain) {
437
- const definitions = getAccountDefinitions(domain);
496
+ async function fetchPrivateKeys(admin, domain, config) {
497
+ const definitions = getAccountDefinitions(domain, config);
438
498
  const accounts = {};
439
499
 
440
500
  // Fetch all in parallel
@@ -617,10 +677,11 @@ async function deleteTestUsers(admin) {
617
677
  * Assumes deleteTestUsers() was called first to ensure clean state
618
678
  * @param {object} admin - Firebase admin instance
619
679
  * @param {string} domain - Domain for email addresses (e.g., 'itwcreativeworks.com')
680
+ * @param {object} [config] - BEM config (used to resolve first paid product)
620
681
  * @returns {Promise<object>} Result with created/failed counts
621
682
  */
622
- async function createTestAccounts(admin, domain) {
623
- const definitions = getAccountDefinitions(domain);
683
+ async function createTestAccounts(admin, domain, config) {
684
+ const definitions = getAccountDefinitions(domain, config);
624
685
  const results = { created: [], failed: [] };
625
686
 
626
687
  // Create all accounts in parallel
@@ -716,6 +777,7 @@ module.exports = {
716
777
  JOURNEY_ACCOUNTS,
717
778
  TEST_ACCOUNTS,
718
779
  TEST_DATA,
780
+ getFirstPaidProduct,
719
781
  getAccountDefinitions,
720
782
  fetchPrivateKeys,
721
783
  deleteTestUsers,
@@ -162,22 +162,20 @@ async function seedTestAccounts(accounts) {
162
162
  throw new Error('Test environment not initialized. Call initRulesTestEnv() first.');
163
163
  }
164
164
 
165
- // Get static account definitions for roles/subscription data
166
- const { TEST_ACCOUNTS } = require('../test-accounts.js');
167
-
168
- // Use withSecurityRulesDisabled to write test data
165
+ // Use withSecurityRulesDisabled to write ONLY roles (for isAdmin() rules checks)
166
+ // Do NOT write subscription — that's already set by createAccount with config-resolved product IDs
169
167
  await testEnv.withSecurityRulesDisabled(async (context) => {
170
168
  const db = context.firestore();
169
+ const { TEST_ACCOUNTS } = require('../test-accounts.js');
171
170
 
172
171
  for (const [accountType, account] of Object.entries(accounts)) {
173
172
  if (!account.uid) {
174
173
  continue;
175
174
  }
176
175
 
177
- // Get the static definition for this account type (has roles, subscription)
178
176
  const staticDef = TEST_ACCOUNTS[accountType];
179
177
 
180
- // Build user document with roles for isAdmin() check
178
+ // Only write auth + roles for rules testing — subscription is already correct in the doc
181
179
  const userData = {
182
180
  auth: {
183
181
  uid: account.uid,
@@ -186,11 +184,6 @@ async function seedTestAccounts(accounts) {
186
184
  roles: staticDef?.properties?.roles || {},
187
185
  };
188
186
 
189
- // Add subscription if present in static definition
190
- if (staticDef?.properties?.subscription) {
191
- userData.subscription = staticDef.properties.subscription;
192
- }
193
-
194
187
  await db.doc(`users/${account.uid}`).set(userData, { merge: true });
195
188
  }
196
189
  });
@@ -101,14 +101,14 @@ module.exports = {
101
101
  },
102
102
 
103
103
  {
104
- name: 'localpart-admin-blocked',
104
+ name: 'localpart-admin-allowed',
105
105
  timeout: 5000,
106
106
 
107
107
  async run({ assert }) {
108
108
  const result = await validate('admin@company.com');
109
109
 
110
- assert.equal(result.valid, false, '"admin" local part should be blocked');
111
- assert.propertyEquals(result, 'checks.localPart.blocked', true, 'Should be flagged as blocked');
110
+ assert.equal(result.valid, true, '"admin" local part should be allowed (team/role address)');
111
+ assert.propertyEquals(result, 'checks.localPart.valid', true, 'localPart check should pass');
112
112
  },
113
113
  },
114
114
 
@@ -252,24 +252,16 @@ module.exports = {
252
252
 
253
253
  {
254
254
  name: 'infer-contact-regex-fallback',
255
- async run({ assert }) {
256
- // Temporarily clear OPENAI_API_KEY to force regex path
257
- const originalKey = process.env.OPENAI_API_KEY;
258
- delete process.env.OPENAI_API_KEY;
259
-
260
- try {
261
- const result = await inferContact('alice.wonderland@example.com');
262
-
263
- assert.equal(result.firstName, 'Alice', 'Regex fallback first name');
264
- assert.equal(result.lastName, 'Wonderland', 'Regex fallback last name');
265
- assert.equal(result.company, 'Example', 'Regex fallback company');
266
- assert.equal(result.method, 'regex', 'Should use regex when no API key');
267
- } finally {
268
- // Restore
269
- if (originalKey) {
270
- process.env.OPENAI_API_KEY = originalKey;
271
- }
255
+ async run({ assert, skip }) {
256
+ if (!process.env.TEST_EXTENDED_MODE || !process.env.BACKEND_MANAGER_OPENAI_API_KEY) {
257
+ skip('TEST_EXTENDED_MODE or BACKEND_MANAGER_OPENAI_API_KEY not set');
272
258
  }
259
+
260
+ const result = await inferContact('alice.wonderland@example.com');
261
+
262
+ assert.equal(result.firstName, 'Alice', 'AI inferred first name');
263
+ assert.equal(result.lastName, 'Wonderland', 'AI inferred last name');
264
+ assert.ok(result.method === 'ai', 'Should use AI');
273
265
  },
274
266
  },
275
267
 
@@ -72,10 +72,7 @@ module.exports = {
72
72
  assert.ok(user.api.privateKey.length > 0, 'api.privateKey should not be empty');
73
73
 
74
74
  // Usage
75
- assert.equal(user.usage.requests.monthly, 0, 'usage.requests.monthly should be 0');
76
- assert.equal(user.usage.requests.daily, 0, 'usage.requests.daily should be 0');
77
- assert.equal(user.usage.requests.total, 0, 'usage.requests.total should be 0');
78
- assert.equal(user.usage.requests.last.id, null, 'usage.requests.last.id should be null');
75
+ assert.deepEqual(user.usage, {}, 'usage should be empty object by default');
79
76
 
80
77
  // Personal
81
78
  assert.equal(user.personal.name.first, null, 'personal.name.first should be null');
@@ -231,12 +228,11 @@ module.exports = {
231
228
  },
232
229
 
233
230
  {
234
- name: 'usage-only-requests-when-no-extra-keys',
231
+ name: 'usage-empty-when-no-keys-provided',
235
232
  async run({ assert }) {
236
233
  const user = createUser({});
237
234
 
238
- assert.ok(user.usage.requests, 'usage.requests should exist');
239
- assert.equal(Object.keys(user.usage).length, 1, 'usage should only have requests key');
235
+ assert.deepEqual(user.usage, {}, 'usage should be empty object when no keys provided');
240
236
  },
241
237
  },
242
238
 
@@ -44,6 +44,9 @@ module.exports = {
44
44
  name: 'single-email-returns-result',
45
45
  auth: 'admin',
46
46
  timeout: 30000,
47
+ skip: !process.env.TEST_EXTENDED_MODE
48
+ ? 'TEST_EXTENDED_MODE not set (skipping inference test)'
49
+ : false,
47
50
 
48
51
  async run({ http, assert }) {
49
52
  const response = await http.post('admin/infer-contact', {
@@ -89,12 +92,15 @@ module.exports = {
89
92
  },
90
93
  },
91
94
 
92
- // ─── Name parsing (regex) ───
95
+ // ─── Name parsing (requires AI) ───
93
96
 
94
97
  {
95
98
  name: 'regex-parses-dot-separated-names',
96
99
  auth: 'admin',
97
100
  timeout: 30000,
101
+ skip: !process.env.TEST_EXTENDED_MODE
102
+ ? 'TEST_EXTENDED_MODE not set (skipping inference test)'
103
+ : false,
98
104
 
99
105
  async run({ http, assert }) {
100
106
  const response = await http.post('admin/infer-contact', {
@@ -104,7 +110,6 @@ module.exports = {
104
110
  assert.isSuccess(response);
105
111
  const result = response.data.results[0];
106
112
 
107
- // AI or regex — either way should get the name right
108
113
  assert.equal(result.firstName, 'Alice', 'Should parse first name');
109
114
  assert.equal(result.lastName, 'Wonderland', 'Should parse last name');
110
115
  },
@@ -114,6 +119,9 @@ module.exports = {
114
119
  name: 'infers-company-from-custom-domain',
115
120
  auth: 'admin',
116
121
  timeout: 30000,
122
+ skip: !process.env.TEST_EXTENDED_MODE
123
+ ? 'TEST_EXTENDED_MODE not set (skipping inference test)'
124
+ : false,
117
125
 
118
126
  async run({ http, assert }) {
119
127
  const response = await http.post('admin/infer-contact', {
@@ -130,6 +138,9 @@ module.exports = {
130
138
  name: 'no-company-from-generic-domain',
131
139
  auth: 'admin',
132
140
  timeout: 30000,
141
+ skip: !process.env.TEST_EXTENDED_MODE
142
+ ? 'TEST_EXTENDED_MODE not set (skipping inference test)'
143
+ : false,
133
144
 
134
145
  async run({ http, assert }) {
135
146
  const response = await http.post('admin/infer-contact', {
@@ -32,7 +32,7 @@ module.exports = {
32
32
  name: 'rejects-unknown-provider',
33
33
  auth: 'none',
34
34
  async run({ http, assert }) {
35
- const response = await http.as('none').post(`payments/dispute-alert?alerts=unknown&key=${process.env.BACKEND_MANAGER_KEY}`, {
35
+ const response = await http.as('none').post(`payments/dispute-alert?provider=unknown&key=${process.env.BACKEND_MANAGER_KEY}`, {
36
36
  id: '_test-dispute-unknown-provider',
37
37
  card: '4242',
38
38
  amount: 9.99,
@@ -85,6 +85,54 @@ module.exports = {
85
85
  },
86
86
  },
87
87
 
88
+ {
89
+ name: 'rejects-suspended-user',
90
+ auth: 'premium-suspended',
91
+ async run({ http, assert, config }) {
92
+ const paidProduct = config.payment.products.find(p => p.id !== 'basic' && p.prices);
93
+
94
+ const response = await http.as('premium-suspended').post('payments/intent', {
95
+ processor: 'stripe',
96
+ productId: paidProduct.id,
97
+ frequency: 'monthly',
98
+ });
99
+
100
+ assert.isError(response, 400, 'Should reject user with suspended subscription');
101
+ },
102
+ },
103
+
104
+ {
105
+ name: 'rejects-cancelling-user',
106
+ auth: 'premium-cancelling',
107
+ async run({ http, assert, config }) {
108
+ const paidProduct = config.payment.products.find(p => p.id !== 'basic' && p.prices);
109
+
110
+ const response = await http.as('premium-cancelling').post('payments/intent', {
111
+ processor: 'stripe',
112
+ productId: paidProduct.id,
113
+ frequency: 'monthly',
114
+ });
115
+
116
+ assert.isError(response, 400, 'Should reject user with cancelling subscription');
117
+ },
118
+ },
119
+
120
+ {
121
+ name: 'allows-cancelled-user',
122
+ auth: 'premium-expired',
123
+ async run({ http, assert, config }) {
124
+ const paidProduct = config.payment.products.find(p => p.id !== 'basic' && p.prices);
125
+
126
+ const response = await http.as('premium-expired').post('payments/intent', {
127
+ processor: 'test',
128
+ productId: paidProduct.id,
129
+ frequency: 'monthly',
130
+ });
131
+
132
+ assert.isSuccess(response, 'Should allow user with fully cancelled subscription');
133
+ },
134
+ },
135
+
88
136
  {
89
137
  name: 'rejects-invalid-product',
90
138
  async run({ http, assert }) {
@@ -4,6 +4,8 @@
4
4
  * Returns user account info for authenticated users
5
5
  * Validates that User() correctly structures user data
6
6
  */
7
+ const { getFirstPaidProduct } = require('../../../src/test/test-accounts.js');
8
+
7
9
  module.exports = {
8
10
  description: 'User resolve (account info)',
9
11
  type: 'group',
@@ -104,10 +106,11 @@ module.exports = {
104
106
  // Test 5: Premium active user - verify premium subscription is retained
105
107
  {
106
108
  name: 'premium-active-user-resolved-correctly',
107
- auth: 'premium-active',
109
+ auth: 'resolve-premium-active',
108
110
  timeout: 15000,
109
111
 
110
- async run({ http, assert, accounts }) {
112
+ async run({ http, assert, accounts, config }) {
113
+ const paidProduct = getFirstPaidProduct(config);
111
114
  const response = await http.get('user', {});
112
115
 
113
116
  assert.isSuccess(response, 'Resolve should succeed for premium user');
@@ -115,11 +118,10 @@ module.exports = {
115
118
  const user = response.data.user;
116
119
 
117
120
  // Verify auth properties
118
- assert.equal(user.auth.uid, accounts['premium-active'].uid, 'UID should match premium test account');
119
- assert.equal(user.auth.email, accounts['premium-active'].email, 'Email should match premium test account');
121
+ assert.equal(user.auth.uid, accounts['resolve-premium-active'].uid, 'UID should match premium test account');
120
122
 
121
- // Verify subscription - premium user should retain premium subscription
122
- assert.equal(user.subscription.product.id, 'premium', 'Subscription ID should be premium');
123
+ // Verify subscription - premium user should retain paid subscription
124
+ assert.equal(user.subscription.product.id, paidProduct.id, 'Subscription ID should match first paid product');
123
125
  assert.equal(user.subscription.status, 'active', 'Subscription status should be active');
124
126
 
125
127
  // Verify expires is in the future
@@ -132,10 +134,11 @@ module.exports = {
132
134
  // Test 6: Premium expired user - verify subscription retains product but shows cancelled status
133
135
  {
134
136
  name: 'premium-expired-user-cancelled',
135
- auth: 'premium-expired',
137
+ auth: 'resolve-premium-expired',
136
138
  timeout: 15000,
137
139
 
138
- async run({ http, assert, accounts }) {
140
+ async run({ http, assert, accounts, config }) {
141
+ const paidProduct = getFirstPaidProduct(config);
139
142
  const response = await http.get('user', {});
140
143
 
141
144
  assert.isSuccess(response, 'Resolve should succeed for expired premium user');
@@ -143,10 +146,10 @@ module.exports = {
143
146
  const user = response.data.user;
144
147
 
145
148
  // Verify auth properties
146
- assert.equal(user.auth.uid, accounts['premium-expired'].uid, 'UID should match expired premium test account');
149
+ assert.equal(user.auth.uid, accounts['resolve-premium-expired'].uid, 'UID should match expired premium test account');
147
150
 
148
- // Verify subscription - product.id stays premium, status reflects cancellation
149
- assert.equal(user.subscription.product.id, 'premium', 'Product ID should remain premium');
151
+ // Verify subscription - product.id stays as paid product, status reflects cancellation
152
+ assert.equal(user.subscription.product.id, paidProduct.id, 'Product ID should remain paid product');
150
153
  assert.equal(user.subscription.status, 'cancelled', 'Status should be cancelled');
151
154
  },
152
155
  },