backend-manager 5.0.173 → 5.0.176

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.
@@ -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,
@@ -115,6 +115,14 @@ module.exports = {
115
115
  amount: 29.99,
116
116
  transactionDate: '2026-03-07 14:30:00',
117
117
  processor: 'stripe',
118
+ alertType: 'FRAUD',
119
+ customerEmail: 'test@example.com',
120
+ externalOrder: 'ch_test123',
121
+ metadata: 'pi_test456',
122
+ externalUrl: 'https://dashboard.stripe.com/charges/ch_test123',
123
+ reasonCode: 'WIP',
124
+ subprovider: 'Ethoca',
125
+ isRefunded: false,
118
126
  });
119
127
 
120
128
  assert.isSuccess(response, 'Should accept valid Chargeblast alert');
@@ -129,13 +137,23 @@ module.exports = {
129
137
  'Status should be pending or processing',
130
138
  );
131
139
 
132
- // Verify normalized alert data
140
+ // Verify core normalized alert data
133
141
  assert.equal(doc.alert.card.last4, '4242', 'Should extract last4 from full card number');
134
142
  assert.equal(doc.alert.card.brand, 'visa', 'Should lowercase card brand');
135
143
  assert.equal(doc.alert.amount, 29.99, 'Amount should be preserved');
136
144
  assert.equal(doc.alert.transactionDate, '2026-03-07', 'Should extract date without time');
137
145
  assert.equal(doc.alert.processor, 'stripe', 'Processor should be stripe');
138
146
 
147
+ // Verify new normalized fields
148
+ assert.equal(doc.alert.alertType, 'FRAUD', 'Alert type should be preserved');
149
+ assert.equal(doc.alert.customerEmail, 'test@example.com', 'Customer email should be preserved');
150
+ assert.equal(doc.alert.chargeId, 'ch_test123', 'Charge ID should be extracted from externalOrder');
151
+ assert.equal(doc.alert.paymentIntentId, 'pi_test456', 'Payment intent ID should be extracted from metadata');
152
+ assert.equal(doc.alert.stripeUrl, 'https://dashboard.stripe.com/charges/ch_test123', 'Stripe URL should be preserved');
153
+ assert.equal(doc.alert.reasonCode, 'WIP', 'Reason code should be preserved');
154
+ assert.equal(doc.alert.subprovider, 'Ethoca', 'Subprovider should be preserved');
155
+ assert.equal(doc.alert.isRefunded, false, 'isRefunded should be preserved');
156
+
139
157
  // Verify raw payload is preserved
140
158
  assert.ok(doc.raw, 'Raw payload should be preserved');
141
159
  assert.equal(doc.raw.id, alertId, 'Raw id should match');
@@ -145,6 +163,73 @@ module.exports = {
145
163
  },
146
164
  },
147
165
 
166
+ {
167
+ name: 'accepts-alert-with-alertId-field',
168
+ auth: 'none',
169
+ async run({ http, assert, firestore }) {
170
+ const alertId = '_test-dispute-alertid-field';
171
+
172
+ // Clean up any existing doc
173
+ await firestore.delete(`payments-disputes/${alertId}`);
174
+
175
+ // Chargeblast alert.created events use alertId instead of id
176
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
177
+ alertId: alertId,
178
+ card: '546616******5805',
179
+ cardBrand: 'Mastercard',
180
+ amount: 8,
181
+ transactionDate: '2026-03-19 00:00:00.000000Z',
182
+ });
183
+
184
+ assert.isSuccess(response, 'Should accept alert using alertId field');
185
+
186
+ const doc = await firestore.get(`payments-disputes/${alertId}`);
187
+ assert.ok(doc, 'Dispute doc should exist in Firestore');
188
+ assert.equal(doc.id, alertId, 'Doc ID should match alertId');
189
+ assert.equal(doc.alert.id, alertId, 'Alert id should be set from alertId');
190
+ assert.equal(doc.alert.card.last4, '5805', 'Should extract last4 from masked card');
191
+
192
+ // Clean up
193
+ await firestore.delete(`payments-disputes/${alertId}`);
194
+ },
195
+ },
196
+
197
+ {
198
+ name: 'accepts-alert-without-optional-fields',
199
+ auth: 'none',
200
+ async run({ http, assert, firestore }) {
201
+ const alertId = '_test-dispute-minimal';
202
+
203
+ // Clean up any existing doc
204
+ await firestore.delete(`payments-disputes/${alertId}`);
205
+
206
+ // Send minimal alert (alert.created shape — no externalOrder, metadata, etc.)
207
+ const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
208
+ id: alertId,
209
+ card: '9124',
210
+ amount: 10,
211
+ transactionDate: '2026-03-21 00:01:02',
212
+ });
213
+
214
+ assert.isSuccess(response, 'Should accept minimal alert');
215
+
216
+ const doc = await firestore.get(`payments-disputes/${alertId}`);
217
+ assert.equal(doc.alert.card.last4, '9124', 'Should use card as last4');
218
+ assert.equal(doc.alert.processor, 'stripe', 'Processor should default to stripe');
219
+ assert.equal(doc.alert.chargeId, null, 'Charge ID should be null when not provided');
220
+ assert.equal(doc.alert.paymentIntentId, null, 'Payment intent should be null when not provided');
221
+ assert.equal(doc.alert.customerEmail, null, 'Customer email should be null when not provided');
222
+ assert.equal(doc.alert.alertType, null, 'Alert type should be null when not provided');
223
+ assert.equal(doc.alert.stripeUrl, null, 'Stripe URL should be null when not provided');
224
+ assert.equal(doc.alert.reasonCode, null, 'Reason code should be null when not provided');
225
+ assert.equal(doc.alert.subprovider, null, 'Subprovider should be null when not provided');
226
+ assert.equal(doc.alert.isRefunded, false, 'isRefunded should default to false');
227
+
228
+ // Clean up
229
+ await firestore.delete(`payments-disputes/${alertId}`);
230
+ },
231
+ },
232
+
148
233
  {
149
234
  name: 'accepts-alert-with-last4-only',
150
235
  auth: 'none',
@@ -242,7 +327,7 @@ module.exports = {
242
327
  },
243
328
 
244
329
  {
245
- name: 'defaults-alerts-to-chargeblast',
330
+ name: 'defaults-provider-to-chargeblast',
246
331
  auth: 'none',
247
332
  async run({ http, assert, firestore }) {
248
333
  const alertId = '_test-dispute-default-provider';
@@ -250,7 +335,7 @@ module.exports = {
250
335
  // Clean up any existing doc
251
336
  await firestore.delete(`payments-disputes/${alertId}`);
252
337
 
253
- // Send without alerts query param
338
+ // Send without provider query param
254
339
  const response = await http.as('none').post(`payments/dispute-alert?key=${process.env.BACKEND_MANAGER_KEY}`, {
255
340
  id: alertId,
256
341
  card: '4242',
@@ -258,7 +343,7 @@ module.exports = {
258
343
  transactionDate: '2026-01-15',
259
344
  });
260
345
 
261
- assert.isSuccess(response, 'Should accept without explicit alerts param');
346
+ assert.isSuccess(response, 'Should accept without explicit provider param');
262
347
 
263
348
  const doc = await firestore.get(`payments-disputes/${alertId}`);
264
349
  assert.equal(doc.provider, 'chargeblast', 'Provider should default to chargeblast');
@@ -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
  },