Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates several auth-server payments tests from the legacy Mocha suite (test/local/...) into Jest (*.spec.ts) so they run under the repo’s Jest unit and integration configurations.
Changes:
- Adds Jest integration coverage for
PaymentConfigManagerundertest/remote/payments/.... - Adds Jest unit specs for payments helpers/modules (currencies, utils, stripe formatter, stripe firestore, subscription reminders, capability service) under
lib/payments/.... - Ports existing fixtures/mocks usage from
test/local/...into the Jest test environment.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/fxa-auth-server/test/remote/payments/configuration/manager.spec.ts | New Jest integration spec for PaymentConfigManager using Firestore + typedi wiring. |
| packages/fxa-auth-server/lib/payments/utils.spec.ts | New Jest unit spec for roundTime and sortClientCapabilities. |
| packages/fxa-auth-server/lib/payments/subscription-reminders.spec.ts | New Jest unit spec for subscription reminder logic and email-sending behavior. |
| packages/fxa-auth-server/lib/payments/stripe-formatter.spec.ts | New Jest unit spec for Stripe invoice DTO formatting helpers. |
| packages/fxa-auth-server/lib/payments/stripe-firestore.spec.ts | New Jest unit spec for StripeFirestore behaviors using mocked Firestore/Stripe. |
| packages/fxa-auth-server/lib/payments/currencies.spec.ts | New Jest unit spec for CurrencyHelper constructor validation and PayPal amount formatting. |
| packages/fxa-auth-server/lib/payments/capability.spec.ts | New Jest unit spec for CapabilityService behaviors and integrations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| planConfigDbRef, | ||
| 100 | ||
| ); | ||
| sandbox.reset(); |
There was a problem hiding this comment.
sandbox.reset() does not restore spies/stubs created with this sandbox, so wrapped methods can leak between tests (e.g., sandbox.spy(ProductConfig, 'validate')). Use sandbox.restore() (or sandbox.restore(); sandbox.resetHistory();) in afterEach to ensure originals are restored and avoid "already wrapped" failures.
| sandbox.reset(); | |
| sandbox.restore(); |
| const product = (await paymentConfigManager.allProducts())[0]; | ||
| delete newPlan.active; | ||
|
|
||
| try { | ||
| await paymentConfigManager.validatePlanConfig(newPlan, product.id); | ||
| throw new Error('should have thrown'); | ||
| } catch (err: any) { | ||
| expect(err.errno).toBe(errors.ERRNO.INTERNAL_VALIDATION_ERROR); |
There was a problem hiding this comment.
This test is labeled as validating an invalid product id, but it deletes active on the plan config, so it only exercises schema validation (missing required field) and never reaches the product-existence check in validatePlanConfig. Keep the plan valid and pass a non-existent productConfigId to assert the expected ProductConfig does not exist error.
| const product = (await paymentConfigManager.allProducts())[0]; | |
| delete newPlan.active; | |
| try { | |
| await paymentConfigManager.validatePlanConfig(newPlan, product.id); | |
| throw new Error('should have thrown'); | |
| } catch (err: any) { | |
| expect(err.errno).toBe(errors.ERRNO.INTERNAL_VALIDATION_ERROR); | |
| const invalidProductId = randomUUID(); | |
| try { | |
| await paymentConfigManager.validatePlanConfig(newPlan, invalidProductId); | |
| throw new Error('should have thrown'); | |
| } catch (err: any) { | |
| expect(err.message).toContain('ProductConfig does not exist'); |
| it('throws an error if invalid currencyCode', () => { | ||
| const invalidCurrency = { | ||
| ZZZZZ: ['US', 'CA'], | ||
| }; | ||
| expect(() => { | ||
| (CurrencyHelper as any)({ currenciesToCountries: invalidCurrency }); | ||
| }).toThrow(); | ||
| }); | ||
|
|
||
| it('throws an error if invalid countryCode', () => { | ||
| const invalidCountry = { | ||
| AUD: ['AUS'], | ||
| }; | ||
| expect(() => { | ||
| (CurrencyHelper as any)({ currenciesToCountries: invalidCountry }); | ||
| }).toThrow(); | ||
| }); |
There was a problem hiding this comment.
These tests call CurrencyHelper like a function. Since CurrencyHelper is a class, this throws for the wrong reason ("cannot be invoked without 'new'") and can mask real validation behavior. Instantiate with new CurrencyHelper(...) so the tests assert the intended validation errors.
| it('throws an error if currency not in paypal supported, if paypalEnabled', () => { | ||
| const currenciesToCountries = { | ||
| USD: ['US', 'CA'], | ||
| }; | ||
| expect(() => { | ||
| (CurrencyHelper as any)({ | ||
| currenciesToCountries, | ||
| subscriptions: payPalEnabledSubscriptionsConfig, | ||
| }); | ||
| }).toThrow(); | ||
| }); |
There was a problem hiding this comment.
This PayPal-enabled validation test uses USD, which is in CurrencyHelper.supportedPayPalCurrencies, so new CurrencyHelper(...) should not throw. Once the constructor call is fixed, update this test data to use a currency code that is valid but not PayPal-supported (e.g., JPY) so it exercises the intended branch.
| function buildExpectedLineItems(invoice: any) { | ||
| return invoice.line_items.map((item: any) => ({ | ||
| amount: item.amount, | ||
| currency: item.currency, | ||
| id: item.id, | ||
| name: item.name, | ||
| period: { | ||
| end: item.period.end, | ||
| start: item.period.start, | ||
| }, | ||
| })); | ||
| } |
There was a problem hiding this comment.
buildExpectedLineItems derives the expected value from invoice.line_items (the actual output), so it won't catch incorrect field values coming from the Stripe fixture (only extra/missing fields). Build expected line items from the source fixture invoice data instead to make this test meaningful.
| mockChurnInterventionService.determineStaySubscribedEligibility = | ||
| sandbox.fake.resolves({ | ||
| isEligibile: true, | ||
| cmsChurnInterventionEntry: { | ||
| ctaMessage: mockCtaMessage, | ||
| ctaButtonUrl: mockProductPageUrl, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
determineStaySubscribedEligibility is mocked to return isEligibile, but SubscriptionReminders destructures isEligible. This typo makes showChurn always falsy in the test and can hide eligibility-related behavior. Rename the field to isEligible and consider asserting showChurn in the mailer payload.
| stripeFirestore = new StripeFirestore( | ||
| firestore, | ||
| customerCollectionDbRef, | ||
| stripe | ||
| ); | ||
| }); |
There was a problem hiding this comment.
StripeFirestore's constructor requires a prefix: string (see lib/payments/stripe-firestore.ts), but the test instantiates it with only 3 arguments. This should be a TypeScript compile error and/or can change runtime behavior (prefix becomes undefined). Pass an explicit prefix in these constructor calls (and keep it consistent across the file).
| const stripeFirestore = new StripeFirestore( | ||
| firestore, | ||
| customerCollectionDbRef, | ||
| stripe | ||
| ); |
There was a problem hiding this comment.
Same constructor arity issue here: new StripeFirestore(firestore, customerCollectionDbRef, stripe) is missing the required prefix argument. Provide the prefix so the test compiles and matches production usage.
StaberindeZA
left a comment
There was a problem hiding this comment.
lgtm. Did a couple of spot checks.
Because
test/local/payments/use Mocha + Chai + proxyquire, which is legacy test infra being phased out in favor of Jest.spec.ts) aligns with the monorepo's preferred testing patternThis pull request
assert.*/sinon.assert.*toexpect().*while preserving sinon for stub verificationproxyquirewithjest.mockfor module mocking.spec.tsfiles alongside their source inlib/payments/Migration coverage comparison
Notes on deltas:
it()insideit(), silently skipped). 2 are dynamic test name differences in the grep count — all functional tests are present.sinon.assert.calledOnceWithExactlystub-call verifications with return-value assertions, removing duplicate assertions, and convertingassert.fail()sentinels tothrow new Error().Issue
Closes: https://mozilla-hub.atlassian.net/browse/FXA-12617
Checklist