From 459161f2f7929396c1bd267da822df0b53073cbc Mon Sep 17 00:00:00 2001 From: Nicholas Shirley Date: Fri, 5 Jun 2026 14:15:33 -0600 Subject: [PATCH] test(auth): migrate log/statsd/fxaMailer mocks to typed Jest fixtures Because: - test/mocks.js's untyped shared factories obscure intent and miss type drift (FXA-13708). This commit: - Swaps mocks.mockLog()/mockStatsd() for inline createMock() and adds test/fixtures/fxa-mailer.ts (installMockFxaMailer) across ~60 specs. - Completes the AuthLogger interface and removes the now-dead mockStatsd and mockFxaMailer slices from test/mocks.js. - Breaks a validators <-> cloud-tasks circular dependency the migration exposed (validators now imports ReasonForDeletion from @fxa/shared/cloud-tasks). --- .../lib/account-delete.spec.ts | 8 +- .../lib/cad-reminders.in.spec.ts | 8 +- packages/fxa-auth-server/lib/db.spec.ts | 16 ++-- packages/fxa-auth-server/lib/devices.spec.ts | 7 +- .../fxa-auth-server/lib/email/bounces.spec.ts | 8 +- .../lib/email/delivery-delay.spec.ts | 36 ++++---- .../lib/email/delivery.spec.ts | 20 +++-- .../lib/email/notifications.spec.ts | 5 +- .../lib/email/utils/helpers.spec.ts | 13 +-- .../lib/google-maps-services.spec.ts | 7 +- .../lib/inactive-accounts/index.spec.ts | 16 +++- .../lib/metrics/amplitude.spec.ts | 4 +- .../lib/metrics/context.spec.ts | 7 +- .../lib/payments/capability.spec.ts | 8 +- .../apple-app-store/app-store-helper.spec.ts | 5 +- .../iap/apple-app-store/apple-iap.spec.ts | 5 +- .../apple-app-store/purchase-manager.spec.ts | 5 +- .../iap/google-play/play-billing.spec.ts | 5 +- .../iap/google-play/purchase-manager.spec.ts | 5 +- .../iap/google-play/user-manager.spec.ts | 5 +- .../lib/payments/iap/iap-config.spec.ts | 5 +- .../lib/payments/paypal/helper.spec.ts | 5 +- .../lib/payments/paypal/processor.spec.ts | 4 +- .../lib/payments/stripe.spec.ts | 15 ++-- .../payments/subscription-reminders.spec.ts | 4 +- .../lib/profile/updates.spec.ts | 8 +- packages/fxa-auth-server/lib/push.spec.ts | 13 +-- .../fxa-auth-server/lib/pushbox/index.spec.ts | 31 +++---- packages/fxa-auth-server/lib/redis.in.spec.ts | 8 +- .../lib/routes/account.spec.ts | 48 +++++++---- .../lib/routes/attached-clients.spec.ts | 10 ++- .../lib/routes/cloud-tasks.spec.ts | 7 +- .../fxa-auth-server/lib/routes/cms.spec.ts | 13 ++- .../lib/routes/devices-and-sessions.spec.ts | 26 +++--- .../fxa-auth-server/lib/routes/emails.spec.ts | 42 ++++++---- .../lib/routes/geo-location.spec.ts | 5 +- .../lib/routes/ip-profiling.spec.ts | 12 ++- .../lib/routes/linked-accounts.spec.ts | 43 +++++----- .../fxa-auth-server/lib/routes/mfa.spec.ts | 10 ++- .../lib/routes/newsletters.spec.ts | 13 +-- .../lib/routes/oauth/index.spec.ts | 5 +- .../lib/routes/password.spec.ts | 54 +++++------- .../lib/routes/passwordless.spec.ts | 32 +++++--- .../lib/routes/recovery-codes.spec.ts | 7 +- .../lib/routes/recovery-key.spec.ts | 16 +++- .../lib/routes/recovery-phone.spec.ts | 13 +-- .../lib/routes/security-events.spec.ts | 5 +- .../lib/routes/session.spec.ts | 38 +++++---- .../lib/routes/subscriptions/apple.spec.ts | 6 +- .../lib/routes/subscriptions/google.spec.ts | 5 +- .../lib/routes/subscriptions/mozilla.spec.ts | 5 +- .../paypal-notifications.spec.ts | 4 +- .../routes/subscriptions/play-pubsub.spec.ts | 5 +- .../subscriptions/stripe-webhooks.spec.ts | 27 ++---- .../lib/routes/subscriptions/stripe.spec.ts | 11 ++- .../lib/routes/subscriptions/support.spec.ts | 4 +- .../fxa-auth-server/lib/routes/totp.spec.ts | 10 ++- .../lib/routes/unblock-codes.spec.ts | 24 ++++-- .../lib/routes/utils/clients.spec.ts | 10 +-- .../lib/routes/utils/oauth.spec.ts | 9 +- .../lib/routes/utils/signin.spec.ts | 11 ++- .../lib/routes/utils/signup.spec.ts | 15 +++- .../fxa-auth-server/lib/routes/validators.js | 6 +- .../fxa-auth-server/lib/senders/index.spec.ts | 4 +- .../fxa-auth-server/lib/server.in.spec.ts | 4 +- .../subscription-account-reminders.in.spec.ts | 8 +- .../fxa-auth-server/lib/tokens/token.spec.ts | 82 +++++++++++++------ packages/fxa-auth-server/lib/types.ts | 8 ++ packages/fxa-auth-server/package.json | 1 + .../fxa-auth-server/test/fixtures/README.md | 73 +++++++++++++++++ .../test/fixtures/fxa-mailer.ts | 54 ++++++++++++ packages/fxa-auth-server/test/mocks.js | 81 ------------------ yarn.lock | 1 + 73 files changed, 699 insertions(+), 454 deletions(-) create mode 100644 packages/fxa-auth-server/test/fixtures/README.md create mode 100644 packages/fxa-auth-server/test/fixtures/fxa-mailer.ts diff --git a/packages/fxa-auth-server/lib/account-delete.spec.ts b/packages/fxa-auth-server/lib/account-delete.spec.ts index a6d49a9c2fc..092ea541d32 100644 --- a/packages/fxa-auth-server/lib/account-delete.spec.ts +++ b/packages/fxa-auth-server/lib/account-delete.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Container from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import { v4 as uuidv4 } from 'uuid'; import { AppError, ERRNO } from '@fxa/accounts/errors'; import { AppleIAP } from './payments/iap/apple-app-store/apple-iap'; @@ -47,7 +49,7 @@ describe('AccountDeleteManager', () => { let mockOAuthDb: any; let mockPush: any; let mockPushbox: any; - let mockStatsd: any; + let mockStatsd: DeepMocked; let mockGlean: any; let mockMailer: any; let mockStripeHelper: any; @@ -89,11 +91,11 @@ describe('AccountDeleteManager', () => { mockOAuthDb = {}; mockPush = mocks.mockPush(); mockPushbox = mocks.mockPushbox(); - mockStatsd = { increment: jest.fn() }; + mockStatsd = createMock(); mockGlean = mocks.mockGlean(); mockMailer = mocks.mockMailer(); mockStripeHelper = {}; - mockLog = mocks.mockLog(); + mockLog = createMock(); mockAppleIap = { purchaseManager: { deletePurchases: jest.fn().mockResolvedValue(undefined), diff --git a/packages/fxa-auth-server/lib/cad-reminders.in.spec.ts b/packages/fxa-auth-server/lib/cad-reminders.in.spec.ts index 07ec074dffc..6c635cf540c 100644 --- a/packages/fxa-auth-server/lib/cad-reminders.in.spec.ts +++ b/packages/fxa-auth-server/lib/cad-reminders.in.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; + const REMINDERS = ['first', 'second', 'third']; const EXPECTED_CREATE_DELETE_RESULT = REMINDERS.reduce( (expected: any, reminder) => { @@ -12,14 +15,13 @@ const EXPECTED_CREATE_DELETE_RESULT = REMINDERS.reduce( ); const config = require('../config').default.getProperties(); -const mocks = require('../test/mocks'); describe('#integration - lib/cad-reminders', () => { let log: any, mockConfig: any, redis: any, cadReminders: any; beforeEach(() => { jest.resetModules(); - log = mocks.mockLog(); + log = createMock(); mockConfig = { redis: config.redis, cadReminders: { @@ -40,7 +42,7 @@ describe('#integration - lib/cad-reminders', () => { ...mockConfig.cadReminders.redis, enabled: true, }, - mocks.mockLog() + createMock() ); cadReminders = require('./cad-reminders')(mockConfig, log); }); diff --git a/packages/fxa-auth-server/lib/db.spec.ts b/packages/fxa-auth-server/lib/db.spec.ts index 59020fa86e2..529837919e2 100644 --- a/packages/fxa-auth-server/lib/db.spec.ts +++ b/packages/fxa-auth-server/lib/db.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; + // Mock fxa-shared/db to prevent real DB connections jest.mock('fxa-shared/db', () => ({ setupAuthDatabase: jest.fn(), @@ -59,7 +62,6 @@ jest.mock('fxa-shared/connected-services', () => { }); // Import after mocks -const mocks = require('../test/mocks'); const config = require('../config').default.getProperties(); const models: any = require('fxa-shared/db/models/auth'); const { createDB } = require('./db'); @@ -73,7 +75,7 @@ describe('db, session tokens expire:', () => { beforeEach(async () => { redisMockFactory = () => undefined; - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); const DB = createDB( { @@ -131,7 +133,7 @@ describe('db, session tokens do not expire:', () => { beforeEach(async () => { redisMockFactory = () => undefined; - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); const DB = createDB( { @@ -189,7 +191,7 @@ describe('db with redis disabled:', () => { beforeEach(async () => { redisMockFactory = () => undefined; - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); const DB = createDB( { redis: {}, tokenLifetimes, tokenPruning: {} }, @@ -249,7 +251,7 @@ describe('redis enabled, token-pruning enabled:', () => { expect(args[0].blee).toBeUndefined(); return redis; }; - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); const DB = createDB( { @@ -489,7 +491,7 @@ describe('redis enabled, token-pruning disabled:', () => { expect(args[0].blee).toBeUndefined(); return redis; }; - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); const DB = createDB( { @@ -539,7 +541,7 @@ describe('db.deviceFromRefreshTokenId:', () => { mergeDevicesAndSessionTokens: jest.Mock; beforeEach(async () => { - log = mocks.mockLog(); + log = createMock(); tokens = require('../lib/tokens')(log, { tokenLifetimes }); models.Device.findByUidAndRefreshTokenId = jest.fn(); diff --git a/packages/fxa-auth-server/lib/devices.spec.ts b/packages/fxa-auth-server/lib/devices.spec.ts index 9fb20641cd4..23b9560eba4 100644 --- a/packages/fxa-auth-server/lib/devices.spec.ts +++ b/packages/fxa-auth-server/lib/devices.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; + const crypto = require('crypto'); const mocks = require('../test/mocks'); const { AppError: error } = require('@fxa/accounts/errors'); @@ -36,7 +39,7 @@ interface DevicesModule { describe('lib/devices:', () => { describe('instantiate:', () => { - let log: ReturnType, + let log: AuthLogger, deviceCreatedAt: number, deviceId: string, device: Record, @@ -46,7 +49,7 @@ describe('lib/devices:', () => { pushbox: ReturnType; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); deviceCreatedAt = Date.now(); deviceId = crypto.randomBytes(16).toString('hex'); device = { diff --git a/packages/fxa-auth-server/lib/email/bounces.spec.ts b/packages/fxa-auth-server/lib/email/bounces.spec.ts index f0b8927a30f..3c373b22550 100644 --- a/packages/fxa-auth-server/lib/email/bounces.spec.ts +++ b/packages/fxa-auth-server/lib/email/bounces.spec.ts @@ -4,10 +4,12 @@ import { EventEmitter } from 'events'; import Container from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; const bounces = require('./bounces'); const { AppError: error } = require('@fxa/accounts/errors'); -const { mockLog, mockStatsd } = require('../../test/mocks'); const { StripeHelper } = require('../payments/stripe'); const emailHelpers = require('./utils/helpers'); @@ -34,8 +36,8 @@ describe('bounce messages', () => { } beforeEach(() => { - log = mockLog(); - statsd = mockStatsd(); + log = createMock(); + statsd = createMock(); mockConfig = { smtp: { bounces: { diff --git a/packages/fxa-auth-server/lib/email/delivery-delay.spec.ts b/packages/fxa-auth-server/lib/email/delivery-delay.spec.ts index a732482288e..6d9438d33ab 100644 --- a/packages/fxa-auth-server/lib/email/delivery-delay.spec.ts +++ b/packages/fxa-auth-server/lib/email/delivery-delay.spec.ts @@ -3,8 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { EventEmitter } from 'events'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; -const { mockLog, mockStatsd } = require('../../test/mocks'); const emailHelpers = require('./utils/helpers'); const deliveryDelay = require('./delivery-delay'); @@ -48,8 +50,8 @@ describe('delivery delay messages', () => { }); it('should not log an error for headers', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); await mockedDeliveryDelay(log, statsd).handleDeliveryDelay( mockMessage({ junk: 'message' }) ); @@ -57,8 +59,8 @@ describe('delivery delay messages', () => { }); it('should log an error for missing headers', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const message = mockMessage({ junk: 'message' }); message.headers = undefined; await mockedDeliveryDelay(log, statsd).handleDeliveryDelay(message); @@ -66,8 +68,8 @@ describe('delivery delay messages', () => { }); it('should log delivery delay with all fields', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage({ deliveryDelay: { delayType: 'TransientCommunicationFailure', @@ -118,8 +120,8 @@ describe('delivery delay messages', () => { }); it('should handle delivery delay with notificationType', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage({ notificationType: 'DeliveryDelay', eventType: undefined, @@ -150,8 +152,8 @@ describe('delivery delay messages', () => { jest .spyOn(emailHelpers, 'logAccountEventFromMessage') .mockReturnValue(Promise.resolve()); - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage({ deliveryDelay: { delayType: 'SpamDetected', @@ -169,8 +171,8 @@ describe('delivery delay messages', () => { }); it('should handle popular email domain', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage({ deliveryDelay: { delayType: 'RecipientServerError', @@ -187,8 +189,8 @@ describe('delivery delay messages', () => { }); it('should handle missing delayedRecipients gracefully', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage({ deliveryDelay: { delayType: 'Undetermined', @@ -204,8 +206,8 @@ describe('delivery delay messages', () => { }); it('should handle errors and still delete message', async () => { - const log = mockLog(); - const statsd = mockStatsd(); + const log = createMock(); + const statsd = createMock(); const mockMsg = createDeliveryDelayMessage(); jest diff --git a/packages/fxa-auth-server/lib/email/delivery.spec.ts b/packages/fxa-auth-server/lib/email/delivery.spec.ts index a6daabe0c6f..178a0751daa 100644 --- a/packages/fxa-auth-server/lib/email/delivery.spec.ts +++ b/packages/fxa-auth-server/lib/email/delivery.spec.ts @@ -3,8 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { EventEmitter } from 'events'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; -const { mockLog, mockGlean } = require('../../test/mocks'); +const { mockGlean } = require('../../test/mocks'); const emailHelpers = require('./utils/helpers'); const delivery = require('./delivery'); const { requestForGlean } = require('../inactive-accounts'); @@ -32,7 +34,7 @@ describe('delivery messages', () => { }); it('should not log an error for headers', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); await mockedDelivery(log, glean).handleDelivery( mockMessage({ junk: 'message' }) @@ -41,7 +43,7 @@ describe('delivery messages', () => { }); it('should log an error for missing headers', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const message = mockMessage({ junk: 'message', @@ -52,7 +54,7 @@ describe('delivery messages', () => { }); it('should ignore unknown message types', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); await mockedDelivery(log, glean).handleDelivery( mockMessage({ @@ -67,7 +69,7 @@ describe('delivery messages', () => { }); it('should log delivery', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const mockMsg = mockMessage({ notificationType: 'Delivery', @@ -112,7 +114,7 @@ describe('delivery messages', () => { }); it('should emit flow metrics', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const mockMsg = mockMessage({ notificationType: 'Delivery', @@ -175,7 +177,7 @@ describe('delivery messages', () => { }); it('should log popular email domain', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const mockMsg = mockMessage({ notificationType: 'Delivery', @@ -242,7 +244,7 @@ describe('delivery messages', () => { jest .spyOn(emailHelpers, 'logAccountEventFromMessage') .mockReturnValue(Promise.resolve()); - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const mockMsg = mockMessage({ notificationType: 'Delivery', @@ -285,7 +287,7 @@ describe('delivery messages', () => { }); it('should log glean event for successful email delivery', async () => { - const log = mockLog(); + const log = createMock(); const glean = mockGlean(); const mockMsg = mockMessage({ notificationType: 'Delivery', diff --git a/packages/fxa-auth-server/lib/email/notifications.spec.ts b/packages/fxa-auth-server/lib/email/notifications.spec.ts index 5265a5a132b..ab206508036 100644 --- a/packages/fxa-auth-server/lib/email/notifications.spec.ts +++ b/packages/fxa-auth-server/lib/email/notifications.spec.ts @@ -3,10 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Container from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; import { StripeHelper } from '../payments/stripe'; +import { AuthLogger } from '../types'; const { AppError: error } = require('@fxa/accounts/errors'); -const { mockLog } = require('../../test/mocks'); const notifications = require('./notifications'); const SIX_HOURS = 1000 * 60 * 60 * 6; @@ -28,7 +29,7 @@ describe('lib/email/notifications:', () => { now = Date.now(); jest.spyOn(Date, 'now').mockImplementation(() => now); del = jest.fn(); - log = mockLog(); + log = createMock(); queue = { start: jest.fn(), on: jest.fn(), diff --git a/packages/fxa-auth-server/lib/email/utils/helpers.spec.ts b/packages/fxa-auth-server/lib/email/utils/helpers.spec.ts index baf24d884a0..b792e4d2e6b 100644 --- a/packages/fxa-auth-server/lib/email/utils/helpers.spec.ts +++ b/packages/fxa-auth-server/lib/email/utils/helpers.spec.ts @@ -3,13 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Container from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; import { AccountEventsManager } from '../../account-events'; +import { AuthLogger } from '../../types'; const amplitude = jest.fn(); jest.mock('../../../lib/metrics/amplitude', () => () => amplitude); -const { mockLog } = require('../../../test/mocks'); const emailHelpers = require('./helpers'); describe('email utils helpers', () => { @@ -49,7 +50,7 @@ describe('email utils helpers', () => { describe('logEmailEventSent', () => { it('should check headers case-insensitively', () => { - const log = mockLog(); + const log = createMock(); const message = { email: 'user@example.domain', template: 'verifyEmail', @@ -66,7 +67,7 @@ describe('email utils helpers', () => { }); it('should log an event per CC email', () => { - const log = mockLog(); + const log = createMock(); const message = { email: 'user@example.domain', ccEmails: ['noreply@gmail.com', 'noreply@yahoo.com'], @@ -93,7 +94,7 @@ describe('email utils helpers', () => { }); it('logEmailEventSent should call amplitude correctly', async () => { - emailHelpers.logEmailEventSent(mockLog(), { + emailHelpers.logEmailEventSent(createMock(), { email: 'foo@example.com', ccEmails: ['bar@example.com', 'baz@example.com'], template: 'verifyEmail', @@ -141,7 +142,7 @@ describe('email utils helpers', () => { it('logEmailEventFromMessage should call amplitude correctly', async () => { emailHelpers.logEmailEventFromMessage( - mockLog(), + createMock(), { email: 'foo@example.com', ccEmails: ['bar@example.com', 'baz@example.com'], @@ -196,7 +197,7 @@ describe('email utils helpers', () => { let log: any; beforeEach(() => { - log = mockLog(); + log = createMock(); }); it('logs an error if message.mail is missing', () => { diff --git a/packages/fxa-auth-server/lib/google-maps-services.spec.ts b/packages/fxa-auth-server/lib/google-maps-services.spec.ts index 656b87c5383..84bb59c977d 100644 --- a/packages/fxa-auth-server/lib/google-maps-services.spec.ts +++ b/packages/fxa-auth-server/lib/google-maps-services.spec.ts @@ -4,11 +4,12 @@ import Sentry from '@sentry/node'; import { default as Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; const sentryModule = require('./sentry'); -const { mockLog } = require('../test/mocks'); const { GoogleMapsService } = require('./google-maps-services'); -const { AuthLogger, AppConfig } = require('./types'); +const { AppConfig } = require('./types'); function deepCopy(object: any) { return JSON.parse(JSON.stringify(object)); @@ -110,7 +111,7 @@ describe('GoogleMapsServices', () => { let log: any; beforeEach(() => { - log = mockLog(); + log = createMock(); Container.set(AuthLogger, log); Container.set(AppConfig, mockConfig); googleMapsServices = new GoogleMapsService(); diff --git a/packages/fxa-auth-server/lib/inactive-accounts/index.spec.ts b/packages/fxa-auth-server/lib/inactive-accounts/index.spec.ts index 1be0efa2183..7954dff7c90 100644 --- a/packages/fxa-auth-server/lib/inactive-accounts/index.spec.ts +++ b/packages/fxa-auth-server/lib/inactive-accounts/index.spec.ts @@ -3,12 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import { EmailTypes, DeleteAccountTasks, ReasonForDeletion, } from '@fxa/shared/cloud-tasks'; -import { AppConfig } from '../types'; +import { AppConfig, AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; // Mock fxa-shared/db/models/auth with real exports plus mock EmailBounce for chaining. // Must be before any require() that pulls this module (including test/mocks.js). @@ -52,8 +58,8 @@ const mockAccount = { }; const mockFxaDb = mocks.mockDB(mockAccount); const mockMailer = mocks.mockMailer(); -const mockFxaMailer = mocks.mockFxaMailer(); -const mockStatsd = { increment: jest.fn() }; +const mockFxaMailer = installMockFxaMailer(); +const mockStatsd: DeepMocked = createMock(); const mockGlean = { inactiveAccountDeletion: { firstEmailSkipped: jest.fn(), @@ -74,7 +80,7 @@ const mockGlean = { deletionScheduled: jest.fn(), }, }; -const mockLog = mocks.mockLog(); +const mockLog = createMock(); const mockOAuthDb = { getRefreshTokensByUid: jest.fn().mockResolvedValue([]), getAccessTokensByUid: jest.fn().mockResolvedValue([]), @@ -130,6 +136,8 @@ describe('InactiveAccountsManager', () => { jest.restoreAllMocks(); }); + afterAll(() => uninstallMockFxaMailer()); + describe('first email notification', () => { beforeEach(() => { jest.spyOn(Date, 'now').mockReturnValue(now); diff --git a/packages/fxa-auth-server/lib/metrics/amplitude.spec.ts b/packages/fxa-auth-server/lib/metrics/amplitude.spec.ts index c86cd305258..1114c03cd4e 100644 --- a/packages/fxa-auth-server/lib/metrics/amplitude.spec.ts +++ b/packages/fxa-auth-server/lib/metrics/amplitude.spec.ts @@ -4,6 +4,8 @@ import { Container } from 'typedi'; import { StatsD } from 'hot-shots'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; // --------------------------------------------------------------------------- // Ensure StatsD is available in the Container before any module-level code @@ -49,7 +51,7 @@ describe('metrics/amplitude', () => { let amplitude: (...args: any[]) => Promise; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); mockAmplitudeConfig.rawEvents = false; amplitude = amplitudeModule(log, { amplitude: mockAmplitudeConfig, diff --git a/packages/fxa-auth-server/lib/metrics/context.spec.ts b/packages/fxa-auth-server/lib/metrics/context.spec.ts index 34a38170596..58c50ee506b 100644 --- a/packages/fxa-auth-server/lib/metrics/context.spec.ts +++ b/packages/fxa-auth-server/lib/metrics/context.spec.ts @@ -4,7 +4,8 @@ import crypto from 'crypto'; import { MetricsRedis } from '../metricsCache'; -import mocks from '../../test/mocks'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; import contextFactory from './context'; const cache = { @@ -42,7 +43,7 @@ describe('metricsContext', () => { cache.get.mockReset().mockImplementation(() => results.get); (MetricsRedis as jest.Mock).mockClear(); - log = mocks.mockLog(); + log = createMock(); config = { redis: { metrics: { @@ -674,7 +675,7 @@ describe('metricsContext', () => { // graph; just call the already-loaded factory with a fresh config // instead of re-requiring `./context`. function makeValidateContext(flowIdKey = 'test') { - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockConfig = { redis: { metrics: {} }, metrics: { diff --git a/packages/fxa-auth-server/lib/payments/capability.spec.ts b/packages/fxa-auth-server/lib/payments/capability.spec.ts index 2a287445681..7596b5ea256 100644 --- a/packages/fxa-auth-server/lib/payments/capability.spec.ts +++ b/packages/fxa-auth-server/lib/payments/capability.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; const mockAuthEvents: any = {}; jest.mock('../events', () => ({ @@ -15,10 +17,9 @@ const config = require('../../config').default.getProperties(); const { mockCMSClients, - mockLog, mockCMSPlanIdsToClientCapabilities, } = require('../../test/mocks'); -const { AppConfig, AuthLogger } = require('../types'); +const { AppConfig } = require('../types'); const { StripeHelper } = require('./stripe'); const { PlayBilling } = require('./iap/google-play'); const { AppleIAP } = require('./iap/apple-app-store'); @@ -138,7 +139,7 @@ describe('CapabilityService', () => { .mockResolvedValue(mockCMSPlanIdsToClientCapabilities), }; mockConfig = { ...config }; - log = mockLog(); + log = createMock(); Container.set(AppConfig, mockConfig); Container.set(AuthLogger, log); @@ -818,7 +819,6 @@ describe('CapabilityService', () => { }); }); }); - }); describe('processPriceIdDiff', () => { diff --git a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/app-store-helper.spec.ts b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/app-store-helper.spec.ts index 3695fdaf03a..d48ad5337d9 100644 --- a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/app-store-helper.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/app-store-helper.spec.ts @@ -2,12 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthLogger, AppConfig } from '../../../types'; -const { mockLog } = require('../../../../test/mocks'); - // Mock AppStoreServerAPI to avoid PKCS#8 key parsing const mockAppStoreServerAPI: any = {}; jest.mock('app-store-server-api', () => { @@ -45,7 +44,7 @@ describe('AppStoreHelper', () => { let log: any; beforeEach(() => { - log = mockLog(); + log = createMock(); Container.set(AuthLogger, log); Container.set(AppConfig, mockConfig); appStoreHelper = Container.get(AppStoreHelper); diff --git a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/apple-iap.spec.ts b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/apple-iap.spec.ts index 85ac9ded58e..2b349f46b09 100644 --- a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/apple-iap.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/apple-iap.spec.ts @@ -7,13 +7,12 @@ jest.mock('./app-store-helper', () => ({ AppStoreHelper: class MockAppStoreHelper {}, })); +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthFirestore, AuthLogger, AppConfig } from '../../../types'; import { AppleIAP } from '.'; -const { mockLog } = require('../../../../test/mocks'); - const mockConfig = { authFirestore: { prefix: 'mock-fxa-', @@ -38,7 +37,7 @@ describe('AppleIAP', () => { let log: any; beforeEach(() => { - log = mockLog(); + log = createMock(); collectionMock = jest.fn(); firestore = { collection: collectionMock, diff --git a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/purchase-manager.spec.ts b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/purchase-manager.spec.ts index 68a1503531c..b297c5335d9 100644 --- a/packages/fxa-auth-server/lib/payments/iap/apple-app-store/purchase-manager.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/apple-app-store/purchase-manager.spec.ts @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { NotificationType, @@ -18,8 +19,6 @@ const { AppStoreSubscriptionPurchase } = jest.requireActual( 'fxa-shared/payments/iap/apple-app-store/subscription-purchase' ); -const { mockLog } = require('../../../../test/mocks'); - const mockBundleId = 'testBundleId'; const mockOriginalTransactionId = 'testOriginalTransactionId'; const mockSubscriptionPurchase: any = {}; @@ -118,7 +117,7 @@ describe('PurchaseManager', () => { beforeEach(() => { mockAppStoreHelper = {}; - log = mockLog(); + log = createMock(); Container.set(AuthLogger, log); Container.set(AppConfig, mockConfig); }); diff --git a/packages/fxa-auth-server/lib/payments/iap/google-play/play-billing.spec.ts b/packages/fxa-auth-server/lib/payments/iap/google-play/play-billing.spec.ts index 72dc2fa387d..9831a4c54df 100644 --- a/packages/fxa-auth-server/lib/payments/iap/google-play/play-billing.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/google-play/play-billing.spec.ts @@ -2,13 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthFirestore, AuthLogger, AppConfig } from '../../../types'; import { PlayBilling } from '.'; -const { mockLog } = require('../../../../test/mocks'); - const mockConfig = { authFirestore: { prefix: 'mock-fxa-', @@ -35,7 +34,7 @@ describe('PlayBilling', () => { firestore = { collection: collectionMock, }; - log = mockLog(); + log = createMock(); Container.set(AuthFirestore, firestore); Container.set(AuthLogger, log); Container.set(AppConfig, mockConfig); diff --git a/packages/fxa-auth-server/lib/payments/iap/google-play/purchase-manager.spec.ts b/packages/fxa-auth-server/lib/payments/iap/google-play/purchase-manager.spec.ts index 807eee418d2..bd1b29c69ac 100644 --- a/packages/fxa-auth-server/lib/payments/iap/google-play/purchase-manager.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/google-play/purchase-manager.spec.ts @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthLogger } from '../../../types'; @@ -12,8 +13,6 @@ import { NotificationType, } from './types'; -const { mockLog } = require('../../../../test/mocks'); - // Mock subscription purchase module used by both auth-server and fxa-shared. // Use a delegating function pattern so the mock factory (hoisted by Jest) // can reference variables defined later. @@ -44,7 +43,7 @@ describe('PurchaseManager', () => { let mockApiClient: any; beforeEach(() => { - log = mockLog(); + log = createMock(); mockPurchaseDbRef = {}; mockApiClient = {}; Container.set(AuthLogger, log); diff --git a/packages/fxa-auth-server/lib/payments/iap/google-play/user-manager.spec.ts b/packages/fxa-auth-server/lib/payments/iap/google-play/user-manager.spec.ts index 8990dbc839a..4de3f952be3 100644 --- a/packages/fxa-auth-server/lib/payments/iap/google-play/user-manager.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/google-play/user-manager.spec.ts @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthLogger } from '../../../types'; @@ -9,8 +10,6 @@ import { UserManager } from './user-manager'; import { PlayStoreSubscriptionPurchase } from './subscription-purchase'; import { PurchaseQueryError } from './types'; -const { mockLog } = require('../../../../test/mocks'); - const USER_ID = 'testUser'; const VALID_SUB_API_RESPONSE = { kind: 'androidpublisher#subscriptionPurchase', @@ -33,7 +32,7 @@ describe('UserManager', () => { let queryResult: any; beforeEach(() => { - log = mockLog(); + log = createMock(); queryResult = { docs: [], }; diff --git a/packages/fxa-auth-server/lib/payments/iap/iap-config.spec.ts b/packages/fxa-auth-server/lib/payments/iap/iap-config.spec.ts index c4efcb66b67..91b89479281 100644 --- a/packages/fxa-auth-server/lib/payments/iap/iap-config.spec.ts +++ b/packages/fxa-auth-server/lib/payments/iap/iap-config.spec.ts @@ -2,13 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; import { Container } from 'typedi'; import { AuthFirestore, AuthLogger, AppConfig } from '../../types'; import { IAPConfig } from './iap-config'; -const { mockLog } = require('../../../test/mocks'); - const mockConfig = { authFirestore: { prefix: 'mock-fxa-', @@ -28,7 +27,7 @@ describe('IAPConfig', () => { firestore = { collection: collectionMock, }; - log = mockLog(); + log = createMock(); Container.set(AuthFirestore, firestore); Container.set(AuthLogger, log); Container.set(AppConfig, mockConfig); diff --git a/packages/fxa-auth-server/lib/payments/paypal/helper.spec.ts b/packages/fxa-auth-server/lib/payments/paypal/helper.spec.ts index 516d2efa28e..e1d0f774ed9 100644 --- a/packages/fxa-auth-server/lib/payments/paypal/helper.spec.ts +++ b/packages/fxa-auth-server/lib/payments/paypal/helper.spec.ts @@ -26,7 +26,10 @@ import { PAYPAL_RETRY_ERRORS, } from './error-codes'; -const { mockLog } = require('../../../test/mocks'); +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; + +const mockLog = createMock(); const successfulDoReferenceTransactionResponse = require('../../../test/local/payments/fixtures/paypal/do_reference_transaction_success.json'); const successfulRefundTransactionResponse = require('../../../test/local/payments/fixtures/paypal/refund_transaction_success.json'); diff --git a/packages/fxa-auth-server/lib/payments/paypal/processor.spec.ts b/packages/fxa-auth-server/lib/payments/paypal/processor.spec.ts index 59ec4966e2e..fc2beeb161a 100644 --- a/packages/fxa-auth-server/lib/payments/paypal/processor.spec.ts +++ b/packages/fxa-auth-server/lib/payments/paypal/processor.spec.ts @@ -21,8 +21,10 @@ import { } from './error-codes'; import { CurrencyHelper } from '../currencies'; import { CapabilityService } from '../capability'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; -const { mockLog } = require('../../../test/mocks'); +const mockLog = createMock(); const paidInvoice = require('../../../test/local/payments/fixtures/stripe/invoice_paid.json'); const unpaidInvoice = require('../../../test/local/payments/fixtures/stripe/invoice_open.json'); diff --git a/packages/fxa-auth-server/lib/payments/stripe.spec.ts b/packages/fxa-auth-server/lib/payments/stripe.spec.ts index 3da86a93712..755e49b3020 100644 --- a/packages/fxa-auth-server/lib/payments/stripe.spec.ts +++ b/packages/fxa-auth-server/lib/payments/stripe.spec.ts @@ -4,6 +4,9 @@ /* eslint-disable no-undef */ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; + const Sentry = require('@sentry/node'); const Chance = require('chance'); const { Container } = require('typedi'); @@ -18,7 +21,7 @@ try { sentryModule = { reportSentryMessage: () => {} }; } -const { mockLog, asyncIterable } = require('../../test/mocks'); +const { asyncIterable } = require('../../test/mocks'); const { AppError: error } = require('@fxa/accounts/errors'); const stripeError = require('stripe').Stripe.errors; const uuidv4 = require('uuid').v4; @@ -333,7 +336,7 @@ describe('StripeHelper', () => { let listStripePlans: any; let log: any; let existingCustomer: any; - let mockStatsd: any; + let mockStatsd: DeepMocked; const existingUid = '40cc397def2d487b9b8ba0369079a267'; let stripeFirestore: any; let mockGoogleMapsService: any; @@ -350,12 +353,8 @@ describe('StripeHelper', () => { beforeEach(() => { mockRedis = createMockRedis(); (globalThis as any).__testMockRedis = mockRedis; - log = mockLog(); - mockStatsd = { - increment: jest.fn().mockReturnValue({}), - timing: jest.fn().mockReturnValue({}), - close: jest.fn().mockReturnValue({}), - }; + log = createMock(); + mockStatsd = createMock(); const currencyHelper = new CurrencyHelper(mockConfig); Container.set(CurrencyHelper, currencyHelper); Container.set(AuthFirestore, { diff --git a/packages/fxa-auth-server/lib/payments/subscription-reminders.spec.ts b/packages/fxa-auth-server/lib/payments/subscription-reminders.spec.ts index ccc48d836df..4d9543c0579 100644 --- a/packages/fxa-auth-server/lib/payments/subscription-reminders.spec.ts +++ b/packages/fxa-auth-server/lib/payments/subscription-reminders.spec.ts @@ -4,8 +4,10 @@ import { Container } from 'typedi'; import { DateTime, Duration, Interval } from 'luxon'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; -const { mockLog } = require('../../test/mocks'); +const mockLog = createMock(); const { CurrencyHelper } = require('./currencies'); const { StripeHelper } = require('./stripe'); const { SentEmail } = require('fxa-shared/db/models/auth/sent-email'); diff --git a/packages/fxa-auth-server/lib/profile/updates.spec.ts b/packages/fxa-auth-server/lib/profile/updates.spec.ts index 313cb6a9077..f9e5bd7f2da 100644 --- a/packages/fxa-auth-server/lib/profile/updates.spec.ts +++ b/packages/fxa-auth-server/lib/profile/updates.spec.ts @@ -3,8 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { EventEmitter } from 'events'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; -const { mockDB, mockLog } = require('../../test/mocks'); +const { mockDB } = require('../../test/mocks'); const profileUpdates = require('./updates'); const mockDeliveryQueue = new EventEmitter(); @@ -38,7 +40,7 @@ describe('profile updates', () => { it('should log errors', async () => { pushShouldThrow = true; - const log = mockLog(); + const log = createMock(); await mockProfileUpdates(log).handleProfileUpdated( mockMessage({ uid: 'bogusuid', @@ -49,7 +51,7 @@ describe('profile updates', () => { }); it('should send notifications', async () => { - const log = mockLog(); + const log = createMock(); const uid = '1e2122ba'; const email = 'foo@mozilla.com'; const locale = 'en-US'; diff --git a/packages/fxa-auth-server/lib/push.spec.ts b/packages/fxa-auth-server/lib/push.spec.ts index fd46facf7af..f5d33423149 100644 --- a/packages/fxa-auth-server/lib/push.spec.ts +++ b/packages/fxa-auth-server/lib/push.spec.ts @@ -6,6 +6,9 @@ import Ajv from 'ajv'; import fs from 'fs'; import path from 'path'; import mocks from '../test/mocks'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from './types'; const ajv = new Ajv(); const mockUid = 'deadbeef'; @@ -88,9 +91,9 @@ const mockSendNotification = webPush.sendNotification as jest.Mock; describe('push', () => { let mockDb: ReturnType, - mockLog: ReturnType, + mockLog: DeepMocked, mockConfig: Record, - mockStatsD: { increment: jest.Mock }, + mockStatsD: DeepMocked, mockDevices: MockDevice[]; function loadMockedPushModule() { @@ -99,7 +102,7 @@ describe('push', () => { beforeEach(() => { mockDb = mocks.mockDB(); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockConfig = {}; mockDevices = [ { @@ -136,9 +139,7 @@ describe('push', () => { pushEndpointExpired: false, }, ]; - mockStatsD = { - increment: jest.fn(), - }; + mockStatsD = createMock(); mockSendNotification.mockReset().mockImplementation(async () => {}); }); diff --git a/packages/fxa-auth-server/lib/pushbox/index.spec.ts b/packages/fxa-auth-server/lib/pushbox/index.spec.ts index 08c9348fd15..b3e1683a38a 100644 --- a/packages/fxa-auth-server/lib/pushbox/index.spec.ts +++ b/packages/fxa-auth-server/lib/pushbox/index.spec.ts @@ -2,10 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; + const { pushboxApi } = require('./index'); const { AppError: error } = require('@fxa/accounts/errors'); -const { mockLog } = require('../../test/mocks'); -let mockStatsD: { increment: jest.Mock; timing: jest.Mock }; +let mockStatsD: DeepMocked; const mockConfig = { publicUrl: 'https://accounts.example.com', @@ -43,7 +46,7 @@ describe('pushbox', () => { let stubConstructor: jest.Mock; beforeEach(() => { - mockStatsD = { increment: jest.fn(), timing: jest.fn() }; + mockStatsD = createMock(); // Create a mock instance with the methods we need stubDbModule = { store: jest.fn(), @@ -62,7 +65,7 @@ describe('pushbox', () => { jest.spyOn(Date, 'now').mockReturnValue(1000534); stubDbModule.store.mockResolvedValue({ idx: 12 }); const pushbox = pushboxApi( - mockLog(), + createMock(), mockConfig, mockStatsD, stubConstructor @@ -92,7 +95,7 @@ describe('pushbox', () => { jest.spyOn(Date, 'now').mockReturnValue(1000534); stubDbModule.store.mockResolvedValue({ idx: 12 }); const pushbox = pushboxApi( - mockLog(), + createMock(), mockConfig, mockStatsD, stubConstructor @@ -115,7 +118,7 @@ describe('pushbox', () => { jest.spyOn(Date, 'now').mockReturnValue(1000432); stubDbModule.store.mockResolvedValue({ idx: 12 }); const pushbox = pushboxApi( - mockLog(), + createMock(), mockConfig, mockStatsD, stubConstructor @@ -136,7 +139,7 @@ describe('pushbox', () => { it('logs an error when failed to store', async () => { stubDbModule.store.mockRejectedValue(new Error('db is a mess right now')); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); try { await pushbox.store( @@ -173,7 +176,7 @@ describe('pushbox', () => { ], }); const pushbox = pushboxApi( - mockLog(), + createMock(), mockConfig, mockStatsD, stubConstructor @@ -200,7 +203,7 @@ describe('pushbox', () => { stubDbModule.retrieve.mockRejectedValue( new Error('db is a mess right now') ); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); try { await pushbox.retrieve(mockUid, mockDeviceIds[0], 50, 10); @@ -222,7 +225,7 @@ describe('pushbox', () => { it('deletes records of a device', async () => { stubDbModule.deleteDevice.mockResolvedValue(undefined); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); const res = await pushbox.deleteDevice(mockUid, mockDeviceIds[0]); expect(res).toBeUndefined(); @@ -240,7 +243,7 @@ describe('pushbox', () => { stubDbModule.deleteDevice.mockRejectedValue( new Error('db is a mess right now') ); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); try { await pushbox.deleteDevice(mockUid, mockDeviceIds[0]); @@ -262,7 +265,7 @@ describe('pushbox', () => { it('deletes all records for an account', async () => { stubDbModule.deleteAccount.mockResolvedValue(undefined); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); const res = await pushbox.deleteAccount(mockUid); expect(res).toBeUndefined(); @@ -280,7 +283,7 @@ describe('pushbox', () => { stubDbModule.deleteAccount.mockRejectedValue( new Error('someone deleted the pushboxv1 table') ); - const log = mockLog(); + const log = createMock(); const pushbox = pushboxApi(log, mockConfig, mockStatsD, stubConstructor); try { await pushbox.deleteAccount(mockUid); @@ -303,7 +306,7 @@ describe('pushbox', () => { it('feature disabled', async () => { const config = { ...mockConfig, pushbox: { enabled: false } }; - const pushbox = pushboxApi(mockLog(), config); + const pushbox = pushboxApi(createMock(), config); try { await pushbox.store(mockUid, mockDeviceIds[0], 'sendtab', mockUid); diff --git a/packages/fxa-auth-server/lib/redis.in.spec.ts b/packages/fxa-auth-server/lib/redis.in.spec.ts index 43b2b098f8e..4a1f53ac524 100644 --- a/packages/fxa-auth-server/lib/redis.in.spec.ts +++ b/packages/fxa-auth-server/lib/redis.in.spec.ts @@ -2,10 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; + const AccessToken = require('./oauth/db/accessToken'); const RefreshTokenMetadata = require('./oauth/db/refreshTokenMetadata'); const config = require('../config').default.getProperties(); -const mocks = require('../test/mocks'); const recordLimit = 20; const prefix = 'test:'; @@ -19,12 +21,12 @@ const redis = require('./redis')( recordLimit, maxttl, }, - mocks.mockLog() + createMock() ); const downRedis = require('./redis')( { enabled: true, port: 1, timeoutMs: 10, lazyConnect: true }, - mocks.mockLog() + createMock() ); downRedis.redis.on('error', () => {}); diff --git a/packages/fxa-auth-server/lib/routes/account.spec.ts b/packages/fxa-auth-server/lib/routes/account.spec.ts index 66b45a083a9..506f5ba0d4e 100644 --- a/packages/fxa-auth-server/lib/routes/account.spec.ts +++ b/packages/fxa-auth-server/lib/routes/account.spec.ts @@ -2,6 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger as AuthLoggerType } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; + const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); const { @@ -95,7 +103,10 @@ jest.mock('../oauth/jwt', () => { const glean = mocks.mockGlean(); const profile = mocks.mockProfile(); -const statsd = mocks.mockStatsd(); +const statsd = createMock(); + +afterAll(() => uninstallMockFxaMailer()); + const rpCmsConfig = { clientId: '00f00f', shared: { @@ -154,7 +165,7 @@ const makeRoutes = function (options: any = {}, requireMocks: any = {}) { config.cloudTasks = mocks.mockCloudTasksConfig; config.accountDestroy = defaultConfig.accountDestroy; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); Container.set(AuthLogger, log); Container.set(AppConfig, config); @@ -306,7 +317,7 @@ describe('/account/reset', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockMetricsContext = mocks.mockMetricsContext(); mockRequest = mocks.mockRequest({ credentials: { @@ -344,7 +355,7 @@ describe('/account/reset', () => { mockCustoms = mocks.mockCustoms(); mockPush = mocks.mockPush(); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); mocks.mockOAuthClientInfo(); oauth = { removeTokensAndCodes: jest.fn() }; accountRoutes = makeRoutes({ @@ -892,7 +903,7 @@ describe('/account/create', () => { } ); const mockMailer = mocks.mockMailer(); - const mockFxaMailer = mocks.mockFxaMailer(); + const mockFxaMailer = installMockFxaMailer(); const mockPush = mocks.mockPush(); const verificationReminders = mocks.mockVerificationReminders(); const subscriptionAccountReminders = mocks.mockVerificationReminders(); @@ -1449,7 +1460,7 @@ describe('/account/stub', () => { } ); const mockMailer = mocks.mockMailer(); - mocks.mockFxaMailer(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); const mockPush = mocks.mockPush(); const verificationReminders = mocks.mockVerificationReminders(); @@ -1666,7 +1677,7 @@ describe('/account/status', () => { } ); const mockMailer = mocks.mockMailer(); - mocks.mockFxaMailer(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); const mockPush = mocks.mockPush(); const mockCustoms = mocks.mockCustoms(); @@ -2168,7 +2179,7 @@ describe('/account/set_password', () => { } ); const mockMailer = mocks.mockMailer(); - const mockFxaMailer = mocks.mockFxaMailer(); + const mockFxaMailer = installMockFxaMailer(); const mockPush = mocks.mockPush(); const verificationReminders = mocks.mockVerificationReminders(); const subscriptionAccountReminders = mocks.mockVerificationReminders(); @@ -2461,7 +2472,7 @@ describe('/account/login', () => { uid: uid, }); const mockMailer = mocks.mockMailer(); - const mockFxaMailer = mocks.mockFxaMailer(); + const mockFxaMailer = installMockFxaMailer(); const mockOAuthClientInfo = mocks.mockOAuthClientInfo(); const mockPush = mocks.mockPush(); @@ -4054,7 +4065,7 @@ describe('/account/login', () => { describe('/account/keys', () => { const keyFetchTokenId = hexString(16); const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ credentials: { emailVerified: true, @@ -4138,7 +4149,7 @@ describe('/account/destroy', () => { mockDB = { ...mocks.mockDB({ email: email, uid: uid }), }; - mockLog = mocks.mockLog(); + mockLog = createMock(); mockCustoms = mocks.mockCustoms(); mockRequest = mocks.mockRequest({ credentials: { uid, email, tokenVerified }, @@ -4148,7 +4159,7 @@ describe('/account/destroy', () => { authPW: new Array(65).join('f'), }, }); - mocks.mockFxaMailer(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); }); @@ -4243,7 +4254,10 @@ describe('/account/destroy', () => { const accountRoutes = makeRoutes({ checkPassword: () => Promise.reject(error.unknownAccount()), config: { - subscriptions: { enabled: true, paypalNvpSigCredentials: { enabled: true } }, + subscriptions: { + enabled: true, + paypalNvpSigCredentials: { enabled: true }, + }, accountDestroy: { requireVerifiedAccount: false }, domain: 'wibble', }, @@ -4376,7 +4390,7 @@ describe('/account', () => { }; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); request = mocks.mockRequest({ credentials: { uid, email }, log: log, @@ -4391,7 +4405,7 @@ describe('/account', () => { 'subscriptionsToResponse', 'removeFirestoreCustomer', ]); - mockFxaMailerLocal = mocks.mockFxaMailer(); + mockFxaMailerLocal = installMockFxaMailer(); mockOAuthClientInfoLocal = mocks.mockOAuthClientInfo(); mockStripeHelper.fetchCustomer = jest.fn( async (uid: any, email: any) => mockCustomer @@ -5019,7 +5033,7 @@ describe('/account/email_bounce_status', () => { const email = 'test@example.com'; function buildRoute(dbOverrides: any = {}) { - log = mocks.mockLog(); + log = createMock(); mockDB = { emailBounces: jest.fn(() => Promise.resolve([])), ...dbOverrides, @@ -5073,7 +5087,7 @@ describe('/account/metrics_opt', () => { const email = 'test@example.com'; function buildRoute(setMetricsOptStub: any) { - log = mocks.mockLog(); + log = createMock(); mockCustoms = { check: jest.fn(() => Promise.resolve()), checkAuthenticated: jest.fn(() => Promise.resolve()), diff --git a/packages/fxa-auth-server/lib/routes/attached-clients.spec.ts b/packages/fxa-auth-server/lib/routes/attached-clients.spec.ts index fdf6310ad46..8c4bb2c994f 100644 --- a/packages/fxa-auth-server/lib/routes/attached-clients.spec.ts +++ b/packages/fxa-auth-server/lib/routes/attached-clients.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import crypto from 'crypto'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); @@ -35,7 +37,7 @@ function makeRoutes(options: any = {}) { }; config.publicUrl = 'https://public.url'; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const push = options.push || require('../push')(log, db, {}); const devices = options.devices || require('../devices')(log, db, push); @@ -66,7 +68,7 @@ describe('/account/attached_clients', () => { beforeEach(() => { config = {}; uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB(); request = mocks.mockRequest({ credentials: { @@ -412,7 +414,7 @@ describe('/account/attached_client/destroy', () => { beforeEach(() => { config = {}; uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB(); devices = mocks.mockDevices({}); request = mocks.mockRequest({ @@ -627,7 +629,7 @@ describe('/account/attached_oauth_clients', () => { beforeEach(() => { config = {}; uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB(); request = mocks.mockRequest({ credentials: { diff --git a/packages/fxa-auth-server/lib/routes/cloud-tasks.spec.ts b/packages/fxa-auth-server/lib/routes/cloud-tasks.spec.ts index f7b9e6ca64d..3cbc7290c8b 100644 --- a/packages/fxa-auth-server/lib/routes/cloud-tasks.spec.ts +++ b/packages/fxa-auth-server/lib/routes/cloud-tasks.spec.ts @@ -4,8 +4,9 @@ import { Container } from 'typedi'; import { ReasonForDeletion, EmailTypes } from '@fxa/shared/cloud-tasks'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; -const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); const { cloudTaskRoutes } = require('./cloud-tasks'); const { AccountDeleteManager } = require('../account-delete'); @@ -35,7 +36,7 @@ describe('/cloud-tasks/accounts/delete', () => { let route: any, routes: any; beforeEach(() => { - mockLog = mocks.mockLog(); + mockLog = createMock(); jest.clearAllMocks(); Container.set(AccountDeleteManager, { @@ -67,7 +68,7 @@ describe('/cloud-tasks/emails/notify-inactive', () => { beforeEach(() => { jest.clearAllMocks(); - mockLog = mocks.mockLog(); + mockLog = createMock(); Container.set(AccountDeleteManager, { deleteAccount: deleteAccountStub, diff --git a/packages/fxa-auth-server/lib/routes/cms.spec.ts b/packages/fxa-auth-server/lib/routes/cms.spec.ts index 2df60883969..e7ffab2f446 100644 --- a/packages/fxa-auth-server/lib/routes/cms.spec.ts +++ b/packages/fxa-auth-server/lib/routes/cms.spec.ts @@ -4,13 +4,15 @@ import crypto from 'crypto'; import { Container } from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; -const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); let log: any, mockConfig: any, - mockStatsD: any, + mockStatsD: DeepMocked, routes: any, route: any, request: any; @@ -86,11 +88,8 @@ jest.mock('./utils/cms', () => ({ describe('cms', () => { beforeEach(() => { - log = mocks.mockLog(); - mockStatsD = { - increment: jest.fn(), - timing: jest.fn(), - }; + log = createMock(); + mockStatsD = createMock(); mockConfig = { cms: { diff --git a/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts b/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts index df0acef05b2..d8620d0f1c1 100644 --- a/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts +++ b/packages/fxa-auth-server/lib/routes/devices-and-sessions.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import crypto from 'crypto'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; const Joi = require('joi'); const { AppError: error } = require('@fxa/accounts/errors'); @@ -30,7 +32,7 @@ function makeRoutes(options: any = {}) { }; config.publicUrl = 'https://public.url'; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const oauth = options.oauth || { getRefreshTokensByUid: jest.fn(async () => []), @@ -121,7 +123,7 @@ describe('/account/device', () => { }); devicesData = {}; mockDevices = mocks.mockDevices(devicesData); - mockLog = mocks.mockLog(); + mockLog = createMock(); accountRoutes = makeRoutes({ config: config, devices: mockDevices, @@ -306,7 +308,7 @@ describe('/account/devices/notify', () => { const config: any = {}; const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); const deviceId = crypto.randomBytes(16).toString('hex'); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ log: mockLog, devices: [ @@ -546,7 +548,7 @@ describe('/account/devices/notify', () => { payload: pushPayload, }; - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockPush = mocks.mockPush({ sendPush: () => Promise.reject('devices empty'), }); @@ -640,7 +642,7 @@ describe('/account/device/commands', () => { let mockLog: any, mockRequest: any, mockCustoms: any; beforeEach(() => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockRequest = mocks.mockRequest({ log: mockLog, credentials: { @@ -886,7 +888,7 @@ describe('/account/devices/invoke_command', () => { mockCustoms: any; beforeEach(() => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ devices: mockDevices, }); @@ -1330,7 +1332,7 @@ describe('/account/device/destroy', () => { deviceId = crypto.randomBytes(16).toString('hex'); deviceId2 = crypto.randomBytes(16).toString('hex'); mockDevices = mocks.mockDevices({ deviceId }); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB(); mockPush = mocks.mockPush(); }); @@ -1413,7 +1415,7 @@ describe('/account/devices', () => { }); const mockDB = mocks.mockDB(); const mockDevices = mocks.mockDevices(); - const log = mocks.mockLog(); + const log = createMock(); const accountRoutes = makeRoutes({ db: mockDB, devices: mockDevices, @@ -1524,7 +1526,7 @@ describe('/account/devices', () => { }); const db = mocks.mockDB(); const devices = mocks.mockDevices(); - const log = mocks.mockLog(); + const log = createMock(); const accountRoutes = makeRoutes({ db, devices, log }); const route = getRoute(accountRoutes, '/account/devices'); @@ -1621,7 +1623,7 @@ describe('/account/devices', () => { payload: {}, }); const db = mocks.mockDB(); - const log = mocks.mockLog(); + const log = createMock(); const oauth = { getRefreshTokensByUid: jest.fn(async () => { return [ @@ -1688,7 +1690,7 @@ describe('/account/devices', () => { }); const mockDB = mocks.mockDB(); const mockDevices = mocks.mockDevices(); - const log = mocks.mockLog(); + const log = createMock(); const accountRoutes = makeRoutes({ db: mockDB, devices: mockDevices, @@ -1735,7 +1737,7 @@ describe('/account/devices', () => { }); const mockDB = mocks.mockDB(); const mockDevices = mocks.mockDevices(); - const log = mocks.mockLog(); + const log = createMock(); const accountRoutes = makeRoutes({ db: mockDB, devices: mockDevices, diff --git a/packages/fxa-auth-server/lib/routes/emails.spec.ts b/packages/fxa-auth-server/lib/routes/emails.spec.ts index 1597cc12da0..af74e39912c 100644 --- a/packages/fxa-auth-server/lib/routes/emails.spec.ts +++ b/packages/fxa-auth-server/lib/routes/emails.spec.ts @@ -2,6 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; + const crypto = require('crypto'); const { AppError: error } = require('@fxa/accounts/errors'); const getRoute = require('../../test/routes_helpers').getRoute; @@ -161,7 +169,7 @@ const makeRoutes = function (options: any = {}) { config.otp = otpOptions; config.gleanMetrics = gleanConfig; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); db = options.db || mocks.mockDB(); const customs = options.customs || { check: () => Promise.resolve(), @@ -173,7 +181,7 @@ const makeRoutes = function (options: any = {}) { options.verificationReminders || mocks.mockVerificationReminders(); cadReminders = options.cadReminders || mocks.mockCadReminders(); glean = gleanMetrics(config); - const statsd = mocks.mockStatsd(); + const statsd = createMock(); const signupUtils = options.signupUtils || @@ -217,6 +225,8 @@ function runTest(route: any, request: any, assertions?: any) { return route.handler(request).then(assertions); } +afterAll(() => uninstallMockFxaMailer()); + // Called in /recovery_email/set_primary, however the promise is not waited for // so we test the function independently as it doesn't affect the route success. describe('update zendesk primary email', () => { @@ -349,7 +359,7 @@ describe('/recovery_email/status', () => { const config: any = {}; const mockDB = mocks.mockDB(); let pushCalled: boolean; - const mockLog = mocks.mockLog({ + const mockLog = createMock({ info: jest.fn((op: any, data: any) => { if (data.name === 'recovery_email_reason.push') { pushCalled = true; @@ -571,7 +581,7 @@ describe('/recovery_email/resend_code', () => { secondEmailCode: secondEmailCode, email: TEST_EMAIL, }); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); mockLog.flowEvent = jest.fn(() => { return Promise.resolve(); }); @@ -579,7 +589,7 @@ describe('/recovery_email/resend_code', () => { mocks.mockOAuthClientInfo({ fetch: jest.fn().mockResolvedValue({ name: 'Firefox' }), }); - const mockFxaMailer = mocks.mockFxaMailer(); + const mockFxaMailer = installMockFxaMailer(); const mockMetricsContext = mocks.mockMetricsContext(); const accountRoutes = makeRoutes({ config: config, @@ -713,7 +723,7 @@ describe('/recovery_email/resend_code', () => { describe('/recovery_email/verify_code', () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ log: mockLog, metricsContext: mocks.mockMetricsContext({ @@ -750,7 +760,7 @@ describe('/recovery_email/verify_code', () => { const mockDB = mocks.mockDB(dbData, dbErrors); const mockMailer = mocks.mockMailer(); mocks.mockOAuthClientInfo(); - const mockFxaMailer = mocks.mockFxaMailer(); + const mockFxaMailer = installMockFxaMailer(); const mockPush = mocks.mockPush(); const mockCustoms = mocks.mockCustoms(); const verificationReminders = mocks.mockVerificationReminders(); @@ -971,7 +981,7 @@ describe('/recovery_email/verify_code', () => { describe('/recovery_email', () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); let dbData: any, accountRoutes: any, mockDB: any, @@ -1045,7 +1055,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { let fxaMailer: any; beforeEach(() => { mocks.mockOAuthClientInfo(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); }); afterEach(() => { fxaMailer.sendVerifySecondaryCodeEmail.mockClear(); @@ -1053,7 +1063,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { it('resends code when redis reservation exists for this uid', async () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); const email = TEST_EMAIL_ADDITIONAL; - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockMailer = mocks.mockMailer(); const mockDB = mocks.mockDB({ uid, @@ -1108,7 +1118,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { const email = TEST_EMAIL_ADDITIONAL; const normalized = normalizeEmail(email); const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockDB = mocks.mockDB({ email: TEST_EMAIL, emailVerified: true, @@ -1191,7 +1201,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { const email = TEST_EMAIL_ADDITIONAL; const normalized = normalizeEmail(email); const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockDB = mocks.mockDB({ email: TEST_EMAIL, emailVerified: true, @@ -1324,7 +1334,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); const email = TEST_EMAIL_ADDITIONAL; const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockDB = mocks.mockDB({ email: TEST_EMAIL, emailVerified: true, @@ -1370,7 +1380,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); const email = TEST_EMAIL_ADDITIONAL; const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1424,7 +1434,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { const email = TEST_EMAIL_ADDITIONAL; const secret = 'existingsecret1234567890123456'; const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1472,7 +1482,7 @@ describe('/mfa/recovery_email/secondary/resend_code', () => { }); describe('/emails/reminders/cad', () => { - const mockLog = mocks.mockLog(); + const mockLog = createMock(); let accountRoutes: any, mockRequest: any, route: any, uid: string; beforeEach(() => { diff --git a/packages/fxa-auth-server/lib/routes/geo-location.spec.ts b/packages/fxa-auth-server/lib/routes/geo-location.spec.ts index a487e42e3ff..811b600cc49 100644 --- a/packages/fxa-auth-server/lib/routes/geo-location.spec.ts +++ b/packages/fxa-auth-server/lib/routes/geo-location.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; + const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); @@ -22,7 +25,7 @@ function setup({ rules?: Record; geoMissing?: boolean; } = {}) { - log = mocks.mockLog(); + log = createMock(); const config = { geoEligibility: { rules }, diff --git a/packages/fxa-auth-server/lib/routes/ip-profiling.spec.ts b/packages/fxa-auth-server/lib/routes/ip-profiling.spec.ts index 391a3cbb98c..a849bd65e2c 100644 --- a/packages/fxa-auth-server/lib/routes/ip-profiling.spec.ts +++ b/packages/fxa-auth-server/lib/routes/ip-profiling.spec.ts @@ -13,12 +13,16 @@ import crypto from 'crypto'; import { Container } from 'typedi'; import { v4 as uuid } from 'uuid'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); const { ProfileClient } = require('@fxa/profile/client'); const { AccountDeleteManager } = require('../account-delete'); -const { AppConfig, AuthLogger } = require('../types'); +const { AppConfig } = require('../types'); const TEST_EMAIL = 'foo@gmail.com'; const MS_ONE_DAY = 1000 * 60 * 60 * 24; @@ -103,7 +107,7 @@ function makeRoutes(options: { db: any; mailer: any }) { signinConfirmation: {}, smtp: {}, }; - const log = mocks.mockLog(); + const log = createMock(); mocks.mockAccountEventsManager(); Container.set(AccountDeleteManager, { enqueue: jest.fn() }); Container.set(AppConfig, config); @@ -144,7 +148,7 @@ function makeRoutes(options: { db: any; mailer: any }) { null, glean, authServerCacheRedis, - mocks.mockStatsd() + createMock() ); } @@ -183,7 +187,7 @@ describe('IP Profiling', () => { ], }); jest.clearAllMocks(); - mockFxaMailerInstance = mocks.mockFxaMailer({ + mockFxaMailerInstance = installMockFxaMailer({ canSend: jest.fn().mockResolvedValue(true), }); mocks.mockOAuthClientInfo(); diff --git a/packages/fxa-auth-server/lib/routes/linked-accounts.spec.ts b/packages/fxa-auth-server/lib/routes/linked-accounts.spec.ts index 8624e88b6e2..22943a36731 100644 --- a/packages/fxa-auth-server/lib/routes/linked-accounts.spec.ts +++ b/packages/fxa-auth-server/lib/routes/linked-accounts.spec.ts @@ -2,7 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { Container } from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; // Mutable mock implementations — changed per-test via makeRoutes(options, requireMocks) // eslint-disable-next-line no-var @@ -51,13 +57,8 @@ const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); const { AppError: error } = require('@fxa/accounts/errors'); -// Set up mock FxaMailer in Container before loading linked-accounts -const mockFxaMailer: any = { - canSend: jest.fn().mockReturnValue(true), - sendPostAddLinkedAccountEmail: jest.fn().mockResolvedValue(undefined), -}; -const { FxaMailer } = require('../senders/fxa-mailer'); -Container.set(FxaMailer, mockFxaMailer); +// Install a typed mock FxaMailer in the Container before loading linked-accounts +const mockFxaMailer = installMockFxaMailer(); const { linkedAccountRoutes } = require('./linked-accounts'); @@ -70,11 +71,11 @@ const makeRoutes = function (options: any = {}, requireMocks?: any) { const config = options.config || {}; config.signinConfirmation = config.signinConfirmation || {}; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const mailer = options.mailer || mocks.mockMailer(); const profile = options.profile || mocks.mockProfile(); - const statsd = options.statsd || { increment: jest.fn() }; + const statsd = options.statsd || createMock(); // Apply per-test mock implementations if (requireMocks) { @@ -112,6 +113,8 @@ function runTest(route: any, request: any, assertions?: any) { }).then(assertions); } +afterAll(() => uninstallMockFxaMailer()); + describe('/linked_account', () => { let mockLog: any, mockDB: any, @@ -120,7 +123,7 @@ describe('/linked_account', () => { mockRequest: any, route: any, axiosMock: any, - statsd: any; + statsd: DeepMocked; const UID = 'fxauid'; @@ -132,7 +135,7 @@ describe('/linked_account', () => { }; beforeEach(async () => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ email: mockGoogleUser.email, uid: UID, @@ -141,7 +144,7 @@ describe('/linked_account', () => { googleAuthConfig: { clientId: 'OooOoo' }, }; mockMailer = mocks.mockMailer(); - mockFxaMailer = mocks.mockFxaMailer(); + mockFxaMailer = installMockFxaMailer(); mocks.mockOAuthClientInfo(); mockRequest = mocks.mockRequest({ log: mockLog, @@ -151,7 +154,7 @@ describe('/linked_account', () => { service: 'sync', }, }); - statsd = { increment: jest.fn() }; + statsd = createMock(); const OAuth2ClientMock = class OAuth2Client { verifyIdToken() { @@ -425,7 +428,7 @@ describe('/linked_account', () => { -----END PRIVATE KEY-----`; beforeEach(async () => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ email: mockAppleUser.email, uid: UID, @@ -650,7 +653,7 @@ describe('/linked_account', () => { }; beforeEach(async () => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ email: mockGoogleUser.email, uid: UID, @@ -799,7 +802,7 @@ describe('/linked_account', () => { } function setupTest(options: any) { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid: UID, sessions: [ @@ -826,7 +829,7 @@ describe('/linked_account', () => { mockRequest = mocks.mockRequest({ payload: [], }); - statsd = { increment: jest.fn() }; + statsd = createMock(); route = getRoute( makeRoutes( @@ -1159,7 +1162,7 @@ describe('/linked_account', () => { } function setupTest(options: any) { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid: UID, sessions: [ @@ -1182,7 +1185,7 @@ describe('/linked_account', () => { mockRequest = mocks.mockRequest({ payload: [], }); - statsd = { increment: jest.fn() }; + statsd = createMock(); route = getRoute( makeRoutes( diff --git a/packages/fxa-auth-server/lib/routes/mfa.spec.ts b/packages/fxa-auth-server/lib/routes/mfa.spec.ts index c3c5babe9cf..01deaa44389 100644 --- a/packages/fxa-auth-server/lib/routes/mfa.spec.ts +++ b/packages/fxa-auth-server/lib/routes/mfa.spec.ts @@ -4,7 +4,11 @@ import { Container } from 'typedi'; import { AppError } from '@fxa/accounts/errors'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import { strategy } from './auth-schemes/mfa'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; const mocks = require('../../test/mocks'); const getRoute = require('../../test/routes_helpers').getRoute; @@ -94,11 +98,11 @@ describe('mfa', () => { const mockAccountEventsManager = { recordSecurityEvent: jest.fn(), }; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); mailer = mocks.mockMailer(); - const fxaMailer = mocks.mockFxaMailer(); - statsd = mocks.mockStatsd(); + const fxaMailer = installMockFxaMailer(); + statsd = createMock(); db = mocks.mockDB({ uid: UID, email: TEST_EMAIL, diff --git a/packages/fxa-auth-server/lib/routes/newsletters.spec.ts b/packages/fxa-auth-server/lib/routes/newsletters.spec.ts index eb5f51274df..49626de9817 100644 --- a/packages/fxa-auth-server/lib/routes/newsletters.spec.ts +++ b/packages/fxa-auth-server/lib/routes/newsletters.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; + const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); const ScopeSet = require('fxa-shared/oauth/scopes').scopeSetHelpers; @@ -10,7 +13,7 @@ const { AppError: error } = require('@fxa/accounts/errors'); const { INVALID_PARAMETER, MISSING_PARAMETER } = error.ERRNO; function makeRoutes(options: any = {}) { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); return require('./newsletters')(log, db); } @@ -33,7 +36,7 @@ describe('/newsletters should emit newsletters update message', () => { describe('using session token', () => { beforeEach(async () => { - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB({ email, uid, @@ -91,7 +94,7 @@ describe('/newsletters should emit newsletters update message', () => { describe('using access token', () => { beforeEach(async () => { - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB({ email, uid, @@ -153,7 +156,7 @@ describe('/newsletters should emit newsletters update message', () => { describe('using access token without the required scope', () => { it('throws an unauthorized error', async () => { - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB({ email, uid, @@ -187,7 +190,7 @@ describe('/newsletters should emit newsletters update message', () => { describe('request errors', () => { beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB({ email, uid, diff --git a/packages/fxa-auth-server/lib/routes/oauth/index.spec.ts b/packages/fxa-auth-server/lib/routes/oauth/index.spec.ts index 6b1a86cb1c7..bc431167d80 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/index.spec.ts +++ b/packages/fxa-auth-server/lib/routes/oauth/index.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; + // Mock the OAuth DB to prevent real MySQL connections jest.mock('../../oauth/db', () => ({ getClient: jest.fn().mockResolvedValue(null), @@ -74,7 +77,7 @@ describe('/oauth/ routes', () => { } beforeEach(() => { - mockLog = mocks.mockLog(); + mockLog = createMock(); mockConfig = { oauth: {}, oauthServer: { diff --git a/packages/fxa-auth-server/lib/routes/password.spec.ts b/packages/fxa-auth-server/lib/routes/password.spec.ts index 4cf0ef13c18..d163e07cf7a 100644 --- a/packages/fxa-auth-server/lib/routes/password.spec.ts +++ b/packages/fxa-auth-server/lib/routes/password.spec.ts @@ -4,6 +4,10 @@ import crypto from 'crypto'; import { Container } from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); @@ -24,7 +28,7 @@ function makeRoutes(options: any = {}) { digits: 8, }, }; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || {}; const mailer = options.mailer || {}; const Password = require('../../lib/crypto/password')(log, config); @@ -70,7 +74,7 @@ describe('/password', () => { // otherwise it won't pickup the mock we define because // of module caching mocks.mockOAuthClientInfo(); - mockFxaMailer = mocks.mockFxaMailer(); + mockFxaMailer = installMockFxaMailer(); mockAccountEventsManager = mocks.mockAccountEventsManager(); glean.resetPassword.emailSent.mockClear(); }); @@ -96,16 +100,7 @@ describe('/password', () => { }); const mockMailer = mocks.mockMailer(); const mockMetricsContext = mocks.mockMetricsContext(); - const mockLog = mocks.mockLog('ERROR', 'test', { - stdout: { - on: jest.fn(), - write: jest.fn(), - }, - stderr: { - on: jest.fn(), - write: jest.fn(), - }, - }); + const mockLog = createMock(); mockLog.flowEvent = jest.fn(() => { return Promise.resolve(); }); @@ -114,7 +109,7 @@ describe('/password', () => { get: jest.fn(), del: jest.fn(), }; - const mockStatsd = { increment: jest.fn() }; + const mockStatsd: DeepMocked = createMock(); it('sends an OTP when enabled', () => { const passwordRoutes = makeRoutes({ @@ -268,16 +263,7 @@ describe('/password', () => { }); const mockMailer = mocks.mockMailer(); const mockMetricsContext = mocks.mockMetricsContext(); - const mockLog = mocks.mockLog('ERROR', 'test', { - stdout: { - on: jest.fn(), - write: jest.fn(), - }, - stderr: { - on: jest.fn(), - write: jest.fn(), - }, - }); + const mockLog = createMock(); mockLog.flowEvent = jest.fn(() => { return Promise.resolve(); }); @@ -287,7 +273,7 @@ describe('/password', () => { get: jest.fn().mockReturnValue(code), del: jest.fn(), }; - const mockStatsd = { increment: jest.fn() }; + const mockStatsd: DeepMocked = createMock(); const mockRequest = mocks.mockRequest({ log: mockLog, @@ -510,7 +496,7 @@ describe('/password', () => { }); const mockPush = mocks.mockPush(); const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockSessionToken = await mockDB.createSessionToken({}); const mockRequest = mocks.mockRequest({ payload: { @@ -530,7 +516,7 @@ describe('/password', () => { uaOSVersion: '10.11', }); const mockCustoms = mocks.mockCustoms(); - const mockStatsd = mocks.mockStatsd(); + const mockStatsd = createMock(); const passwordRoutes = makeRoutes({ db: mockDB, push: mockPush, @@ -577,8 +563,8 @@ describe('/password', () => { const mockSession = await mockDB.createSessionToken({}); const mockPush = mocks.mockPush(); const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); - const mockStatsd = mocks.mockStatsd(); + const mockLog = createMock(); + const mockStatsd = createMock(); const mockRequest = mocks.mockRequest({ credentials: mockSession, payload: { @@ -646,7 +632,7 @@ describe('/password', () => { }); const mockPush = mocks.mockPush(); const mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ credentials: { uid: uid, @@ -757,7 +743,7 @@ describe('/password', () => { return Promise.reject(error.emailBouncedHard()); }), }; - const mockLog = mocks.mockLog(); + const mockLog = createMock(); // Configure mockFxaMailer to reject for this test mockFxaMailer.sendPasswordChangedEmail.mockRejectedValue( @@ -844,7 +830,7 @@ describe('/password', () => { return Promise.resolve(); }), }; - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ credentials: { uid: uid, @@ -910,7 +896,7 @@ describe('/password', () => { verifierSetAt: 0, }); mockMailer = mocks.mockMailer(); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const authPW = await random.hex(32); passwordRoutes = makeRoutes({ db: mockDB, @@ -1020,8 +1006,8 @@ describe('/password', () => { }); mockMailer = mocks.mockMailer(); mockPush = mocks.mockPush(); - mockLog = mocks.mockLog(); - mockStatsd = mocks.mockStatsd(); + mockLog = createMock(); + mockStatsd = createMock(); mockCustoms = mocks.mockCustoms(); }); diff --git a/packages/fxa-auth-server/lib/routes/passwordless.spec.ts b/packages/fxa-auth-server/lib/routes/passwordless.spec.ts index bedbe952694..8a36cb404f6 100644 --- a/packages/fxa-auth-server/lib/routes/passwordless.spec.ts +++ b/packages/fxa-auth-server/lib/routes/passwordless.spec.ts @@ -4,7 +4,14 @@ import crypto from 'crypto'; import * as uuid from 'uuid'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import { AppError as error } from '@fxa/accounts/errors'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); @@ -23,6 +30,7 @@ beforeAll(() => { }); afterAll(() => { jest.restoreAllMocks(); + uninstallMockFxaMailer(); }); // --- Module-level stubs for mocked dependencies --- @@ -113,14 +121,14 @@ function makeRoutes(options: any = {}) { enabled: true, }; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const customs = options.customs || { check: () => Promise.resolve(true), v2Enabled: () => true, }; const glean = options.glean || mocks.mockGlean(); - const statsd = options.statsd || mocks.mockStatsd(); + const statsd = options.statsd || createMock(); const redis = options.authServerCacheRedis || { get: async () => null, set: async () => 'OK', @@ -145,7 +153,7 @@ function makeRoutes(options: any = {}) { getOptionalCmsEmailConfigStub = options.getOptionalCmsEmailConfig || jest.fn().mockResolvedValue({}); - mocks.mockFxaMailer(); + installMockFxaMailer(); // Now require the module under test. Because all mocks above are wired // through jest.mock() at the module level, the fresh `require` picks them @@ -179,7 +187,7 @@ describe('/account/passwordless/send_code', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -343,7 +351,7 @@ describe('/account/passwordless/confirm_code', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -784,7 +792,7 @@ describe('passwordless CMS customization', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1070,7 +1078,7 @@ describe('passwordless security events', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1297,7 +1305,7 @@ describe('passwordless statsd metrics', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1309,7 +1317,7 @@ describe('passwordless statsd metrics', () => { check: jest.fn(() => Promise.resolve()), v2Enabled: () => true, }; - mockStatsd = mocks.mockStatsd(); + mockStatsd = createMock(); mockRequest = mocks.mockRequest({ log: mockLog, payload: { @@ -1520,7 +1528,7 @@ describe('/account/passwordless/resend_code', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -1696,7 +1704,7 @@ describe('passwordless service validation', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, @@ -2000,7 +2008,7 @@ describe('existing passwordless accounts bypass flag and allowlist', () => { beforeEach(() => { uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); - mockLog = mocks.mockLog(); + mockLog = createMock(); mockDB = mocks.mockDB({ uid, email: TEST_EMAIL, diff --git a/packages/fxa-auth-server/lib/routes/recovery-codes.spec.ts b/packages/fxa-auth-server/lib/routes/recovery-codes.spec.ts index 63e994eef0c..585857b9f73 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-codes.spec.ts +++ b/packages/fxa-auth-server/lib/routes/recovery-codes.spec.ts @@ -3,8 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; import { AppError as error } from '@fxa/accounts/errors'; import { BackupCodeManager } from '@fxa/accounts/two-factor'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; const mocks = require('../../test/mocks'); const getRoute = require('../../test/routes_helpers').getRoute; @@ -48,10 +51,10 @@ function runTest(routePath: string, requestOptions: any, method?: string) { describe('backup authentication codes', () => { beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); db = mocks.mockDB({ uid: UID, email: TEST_EMAIL, diff --git a/packages/fxa-auth-server/lib/routes/recovery-key.spec.ts b/packages/fxa-auth-server/lib/routes/recovery-key.spec.ts index 77989f77e1c..c65846f23bc 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-key.spec.ts +++ b/packages/fxa-auth-server/lib/routes/recovery-key.spec.ts @@ -3,6 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { AppError as errors } from '@fxa/accounts/errors'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; const uuid = require('uuid'); const mocks = require('../../test/mocks'); @@ -37,7 +43,7 @@ function setup(results: any, _errors: any, path: string, requestOptions: any) { results = results || {}; _errors = _errors || {}; - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB(results.db, _errors.db); customs = mocks.mockCustoms(_errors.customs); mailer = mocks.mockMailer(); @@ -59,7 +65,7 @@ function setup(results: any, _errors: any, path: string, requestOptions: any) { } function makeRoutes(options: any = {}) { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const customs = options.customs || mocks.mockCustoms(); const config = options.config || { signinConfirmation: {} }; @@ -79,7 +85,7 @@ function makeRoutes(options: any = {}) { describe('POST /recoveryKey', () => { beforeEach(() => { mockAccountEventsManager = mocks.mockAccountEventsManager(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); }); afterEach(() => { @@ -672,7 +678,7 @@ describe('POST /recoveryKey/exists', () => { describe('DELETE /recoveryKey', () => { beforeEach(() => { mockAccountEventsManager = mocks.mockAccountEventsManager(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); }); afterEach(() => { @@ -779,3 +785,5 @@ describe('POST /recoveryKey/hint', () => { }); }); }); + +afterAll(() => uninstallMockFxaMailer()); diff --git a/packages/fxa-auth-server/lib/routes/recovery-phone.spec.ts b/packages/fxa-auth-server/lib/routes/recovery-phone.spec.ts index 3ec609df132..c395790b8fb 100644 --- a/packages/fxa-auth-server/lib/routes/recovery-phone.spec.ts +++ b/packages/fxa-auth-server/lib/routes/recovery-phone.spec.ts @@ -3,8 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import { AppError } from '@fxa/accounts/errors'; import { AccountManager } from '@fxa/shared/account/account'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; import { RecoveryNumberNotSupportedError, SmsSendRateLimitExceededError, @@ -26,7 +30,7 @@ describe('/recovery_phone', () => { const nationalFormat = '(555) 000-5555'; const code = '000000'; const mockDb = mocks.mockDB({ uid: uid, email: email }); - const mockLog = mocks.mockLog(); + const mockLog = createMock(); let mockMailer: any; let mockFxaMailer: any; @@ -34,10 +38,7 @@ describe('/recovery_phone', () => { check: jest.fn(), checkAuthenticated: jest.fn(), }; - const mockStatsd = { - increment: jest.fn(), - histogram: jest.fn(), - }; + const mockStatsd: DeepMocked = createMock(); const mockGlean = { login: { recoveryPhoneSuccess: jest.fn(), @@ -89,7 +90,7 @@ describe('/recovery_phone', () => { Container.set(AccountManager, mockAccountManager); Container.set(AccountEventsManager, mockAccountEventsManager); mockMailer = mocks.mockMailer(); - mockFxaMailer = mocks.mockFxaMailer(); + mockFxaMailer = installMockFxaMailer(); // Ensure RecoveryPhoneHandler resolves OtpUtils with our mocked db/statsd otpUtils = new OtpUtils(mockDb, mockStatsd); Container.set(OtpUtils, otpUtils); diff --git a/packages/fxa-auth-server/lib/routes/security-events.spec.ts b/packages/fxa-auth-server/lib/routes/security-events.spec.ts index dd7638c9d57..4c44294630a 100644 --- a/packages/fxa-auth-server/lib/routes/security-events.spec.ts +++ b/packages/fxa-auth-server/lib/routes/security-events.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; + const uuid = require('uuid'); const mocks = require('../../test/mocks'); @@ -12,7 +15,7 @@ const TEST_EMAIL = 'foo@gmail.com'; const UID = uuid.v4({}, Buffer.alloc(16)).toString('hex'); function makeRoutes(options: any = {}) { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const config = options.config || {}; const db = options.db || mocks.mockDB(); return require('./security-events')(log, db, config); diff --git a/packages/fxa-auth-server/lib/routes/session.spec.ts b/packages/fxa-auth-server/lib/routes/session.spec.ts index 50d795db343..37001acd405 100644 --- a/packages/fxa-auth-server/lib/routes/session.spec.ts +++ b/packages/fxa-auth-server/lib/routes/session.spec.ts @@ -2,6 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; + const crypto = require('crypto'); const { getRoute } = require('../../test/routes_helpers'); const knownIpLocation = require('../../test/known-ip-location'); @@ -25,11 +33,11 @@ function makeRoutes(options: any = {}) { config.oauth = config.oauth || {}; config.smtp = config.smtp || {}; const db = options.db || mocks.mockDB(); - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const mailer = options.mailer || mocks.mockMailer(); const cadReminders = options.cadReminders || mocks.mockCadReminders(); const glean = options.glean || gleanMock; - const statsd = options.statsd || mocks.mockStatsd(); + const statsd = options.statsd || createMock(); Container.set( AccountEventsManager, @@ -108,13 +116,15 @@ function getExpectedOtpCode(options: any = {}, secret = 'abcdef') { return authenticator.generate(); } +afterAll(() => uninstallMockFxaMailer()); + describe('/session/status', () => { let log: any, db: any, config: any, routes: any, route: any; beforeEach(() => { jest.clearAllMocks(); - log = mocks.mockLog(); - mocks.mockFxaMailer(); + log = createMock(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); db = { account: () => {}, @@ -417,7 +427,7 @@ describe('/session/reauth', () => { SessionToken: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); config = {}; customs = { checkAuthenticated: () => { @@ -429,7 +439,7 @@ describe('/session/reauth', () => { uid: TEST_UID, }); mailer = mocks.mockMailer(); - mocks.mockFxaMailer(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); signinUtils = require('./utils/signin')(log, config, customs, db, mailer); SessionToken = require('../tokens/index')(log, config).SessionToken; @@ -723,7 +733,7 @@ describe('/session/destroy', () => { beforeEach(() => { db = mocks.mockDB(); - log = mocks.mockLog(); + log = createMock(); const config = {}; securityEventStub = jest.fn(); const routes = makeRoutes({ @@ -823,8 +833,8 @@ describe('/session/duplicate', () => { beforeEach(async () => { db = mocks.mockDB({}); - log = mocks.mockLog(); - mocks.mockFxaMailer(); + log = createMock(); + installMockFxaMailer(); mocks.mockOAuthClientInfo(); const config = {}; const routes = makeRoutes({ log, config, db }); @@ -963,15 +973,15 @@ describe('/session/verify_code', () => { function setup(options: any = {}) { db = mocks.mockDB({ ...signupCodeAccount, ...options }); - log = mocks.mockLog(); + log = createMock(); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); mocks.mockOAuthClientInfo(); push = mocks.mockPush(); customs = mocks.mockCustoms(); customs.check = jest.fn(() => Promise.resolve(true)); cadReminders = mocks.mockCadReminders(); - const statsd = mocks.mockStatsd(); + const statsd = createMock(); const config = {}; const routes = makeRoutes({ @@ -1124,9 +1134,9 @@ describe('/session/resend_code', () => { beforeEach(() => { db = mocks.mockDB({ ...signupCodeAccount }); - log = mocks.mockLog(); + log = createMock(); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); oauthClientInfo = mocks.mockOAuthClientInfo(); push = mocks.mockPush(); customs = { diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/apple.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/apple.spec.ts index d37ca99a766..a572809f923 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/apple.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/apple.spec.ts @@ -3,14 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; -const mocks = require('../../../test/mocks'); const { AppleIapHandler } = require('./apple'); const { PurchaseUpdateError, } = require('../../payments/iap/apple-app-store/types/errors'); const { AppError: error } = require('@fxa/accounts/errors'); -const { AuthLogger } = require('../../types'); const { AppleIAP } = require('../../payments/iap/apple-app-store/apple-iap'); const { IAPConfig } = require('../../payments/iap/iap-config'); const { OAUTH_SCOPE_SUBSCRIPTIONS_IAP } = require('fxa-shared/oauth/constants'); @@ -38,7 +38,7 @@ describe('AppleIapHandler', () => { let mockCapabilityService: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); appleIap = {}; Container.set(AuthLogger, log); iapConfig = {}; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/google.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/google.spec.ts index ec05ca7d9c3..b899a8211b2 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/google.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/google.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const uuid = require('uuid'); const mocks = require('../../../test/mocks'); @@ -11,7 +13,6 @@ const { PurchaseUpdateError, } = require('../../payments/iap/google-play/types/errors'); const { AppError: error } = require('@fxa/accounts/errors'); -const { AuthLogger } = require('../../types'); const { PlayBilling } = require('../../payments/iap/google-play'); const { IAPConfig } = require('../../payments/iap/iap-config'); const { OAUTH_SCOPE_SUBSCRIPTIONS_IAP } = require('fxa-shared/oauth/constants'); @@ -40,7 +41,7 @@ describe('GoogleIapHandler', () => { let db: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); playBilling = {}; Container.set(AuthLogger, log); iapConfig = {}; diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.spec.ts index bf66f8a6155..cd0273be30b 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/mozilla.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; + const { MozillaSubscriptionTypes } = require('fxa-shared/subscriptions/types'); const { ERRNO } = require('@fxa/accounts/errors'); const uuid = require('uuid'); @@ -164,7 +167,7 @@ const mockIapOffering = { }; const mocks = require('../../../test/mocks'); -const log = mocks.mockLog(); +const log = createMock(); const db = mocks.mockDB({ uid: UID, email: TEST_EMAIL, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.spec.ts index ba5b25390ef..3df41635d54 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/paypal-notifications.spec.ts @@ -4,6 +4,8 @@ import * as uuid from 'uuid'; import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const dbStub = { getPayPalBAByBAId: jest.fn(), @@ -58,7 +60,7 @@ describe('PayPalNotificationHandler', () => { }, }; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); db = mocks.mockDB({ diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.spec.ts index edee235464a..439a1d0bad4 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/play-pubsub.spec.ts @@ -3,11 +3,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const uuid = require('uuid'); const mocks = require('../../../test/mocks'); const { PlayPubsubHandler } = require('./play-pubsub'); -const { AuthLogger } = require('../../types'); const { PlayBilling } = require('../../payments/iap/google-play'); const { CapabilityService } = require('../../payments/capability'); @@ -26,7 +27,7 @@ describe('PlayPubsubHandler', () => { let mockPurchase: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); db = mocks.mockDB({ uid: UID, email: TEST_EMAIL, diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhooks.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhooks.spec.ts index 9c8cf9d027a..e4047ba827f 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhooks.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/stripe-webhooks.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { default as Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const uuid = require('uuid'); const mocks = require('../../../test/mocks'); @@ -102,7 +104,7 @@ describe('StripeWebhookHandler', () => { }, }; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); profile = mocks.mockProfile({ deleteCache: jest.fn(async (uid: any) => ({})), @@ -2040,10 +2042,7 @@ describe('StripeWebhookHandler', () => { it('silently no-ops when SetupIntent has no subscription_id metadata', async () => { const event = makeEvent(null); const dispatchStub = jest - .spyOn( - StripeWebhookHandlerInstance, - 'sendSubscriptionInvoiceEmail' - ) + .spyOn(StripeWebhookHandlerInstance, 'sendSubscriptionInvoiceEmail') .mockResolvedValue(undefined); const sentryMod = require('../../sentry'); const reportMessage = jest @@ -2062,10 +2061,7 @@ describe('StripeWebhookHandler', () => { it('dispatches the welcome emails when subscription is active and invoice is paid', async () => { const event = makeEvent({ subscription_id: mockSubscription.id }); const dispatchStub = jest - .spyOn( - StripeWebhookHandlerInstance, - 'sendSubscriptionInvoiceEmail' - ) + .spyOn(StripeWebhookHandlerInstance, 'sendSubscriptionInvoiceEmail') .mockResolvedValue(undefined); const customer = deepCopy(customerFixture); @@ -2092,10 +2088,7 @@ describe('StripeWebhookHandler', () => { it('reports a Sentry warning when the subscription is not active', async () => { const event = makeEvent({ subscription_id: mockSubscription.id }); const dispatchStub = jest - .spyOn( - StripeWebhookHandlerInstance, - 'sendSubscriptionInvoiceEmail' - ) + .spyOn(StripeWebhookHandlerInstance, 'sendSubscriptionInvoiceEmail') .mockResolvedValue(undefined); const sentryMod = require('../../sentry'); const reportMessage = jest @@ -2121,10 +2114,7 @@ describe('StripeWebhookHandler', () => { it('reports a Sentry warning when the latest invoice is not paid', async () => { const event = makeEvent({ subscription_id: mockSubscription.id }); const dispatchStub = jest - .spyOn( - StripeWebhookHandlerInstance, - 'sendSubscriptionInvoiceEmail' - ) + .spyOn(StripeWebhookHandlerInstance, 'sendSubscriptionInvoiceEmail') .mockResolvedValue(undefined); const sentryMod = require('../../sentry'); const reportMessage = jest @@ -2135,7 +2125,8 @@ describe('StripeWebhookHandler', () => { .fn() .mockImplementation((id) => { if (id === mockSubscription.id) return mockSubscription; - if (id === mockInvoice.id) return { ...mockInvoice, status: 'open' }; + if (id === mockInvoice.id) + return { ...mockInvoice, status: 'open' }; return undefined; }); diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/stripe.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/stripe.spec.ts index 624a0eaa43c..68db612ef24 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/stripe.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/stripe.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; + const { Container } = require('typedi'); const uuid = require('uuid'); const mocks = require('../../../test/mocks'); @@ -31,7 +34,7 @@ jest.mock('./utils', () => ({ const { StripeHandler: DirectStripeRoutes } = require('./stripe'); const { buildTaxAddress: buildTaxAddressStub } = require('./utils'); -const { AuthLogger, AppConfig } = require('../../types'); +const { AppConfig } = require('../../types'); const { CapabilityService } = require('../../payments/capability'); const { PlayBilling } = require('../../payments/iap/google-play'); const subscription2 = require('../../../test/local/payments/fixtures/stripe/subscription2.json'); @@ -164,7 +167,7 @@ describe('subscriptions stripeRoutes', () => { mockCapabilityService.getClients.mockResolvedValue(mockCMSClients); Container.set(CapabilityService, mockCapabilityService); - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); Container.set(AuthLogger, log); @@ -385,7 +388,7 @@ describe('DirectStripeRoutes', () => { }, }; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); profile = mocks.mockProfile({ deleteCache: jest.fn(async (uid: any) => ({})), @@ -551,7 +554,7 @@ describe('DirectStripeRoutes', () => { describe('buildTaxAddress', () => { beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); }); it('returns tax location if complete', () => { diff --git a/packages/fxa-auth-server/lib/routes/subscriptions/support.spec.ts b/packages/fxa-auth-server/lib/routes/subscriptions/support.spec.ts index 24258d8575a..6ab514fe656 100644 --- a/packages/fxa-auth-server/lib/routes/subscriptions/support.spec.ts +++ b/packages/fxa-auth-server/lib/routes/subscriptions/support.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import nock from 'nock'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const uuid = require('uuid'); const mocks = require('../../../test/mocks'); @@ -143,7 +145,7 @@ describe('support', () => { }, }; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(); db = mocks.mockDB({ diff --git a/packages/fxa-auth-server/lib/routes/totp.spec.ts b/packages/fxa-auth-server/lib/routes/totp.spec.ts index d332f5d31ef..67eae9fd345 100644 --- a/packages/fxa-auth-server/lib/routes/totp.spec.ts +++ b/packages/fxa-auth-server/lib/routes/totp.spec.ts @@ -6,7 +6,11 @@ import { Container } from 'typedi'; import { AppError as authErrors } from '@fxa/accounts/errors'; import { RecoveryPhoneService } from '@fxa/accounts/recovery-phone'; import { BackupCodeManager } from '@fxa/accounts/two-factor'; +import { createMock } from '@golevelup/ts-jest'; +import { StatsD } from 'hot-shots'; import crypto from 'crypto'; +import { AuthLogger } from '../types'; +import { installMockFxaMailer } from '../../test/fixtures/fxa-mailer'; const otplib = require('otplib'); @@ -43,7 +47,7 @@ const sessionId = 'id'; function setup(results: any, errors: any, routePath: string, reqOpts: any) { results = results || {}; errors = errors || {}; - log = mocks.mockLog(); + log = createMock(); customs = mocks.mockCustoms(errors.customs); mailer = mocks.mockMailer(); db = mocks.mockDB(results.db, errors.db); @@ -97,7 +101,7 @@ function setup(results: any, errors: any, routePath: string, reqOpts: any) { } return Promise.resolve(); }); - const statsd = mocks.mockStatsd(); + const statsd = createMock(); routes = makeRoutes({ log, db, @@ -172,7 +176,7 @@ describe('totp', () => { }; mocks.mockOAuthClientInfo(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); Container.set(RecoveryPhoneService, mockRecoveryPhoneService); Container.set(BackupCodeManager, mockBackupCodeManager); diff --git a/packages/fxa-auth-server/lib/routes/unblock-codes.spec.ts b/packages/fxa-auth-server/lib/routes/unblock-codes.spec.ts index f9e677c664e..f9f033c967d 100644 --- a/packages/fxa-auth-server/lib/routes/unblock-codes.spec.ts +++ b/packages/fxa-auth-server/lib/routes/unblock-codes.spec.ts @@ -2,14 +2,28 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../test/fixtures/fxa-mailer'; + const uuid = require('uuid'); const mocks = require('../../test/mocks'); const { getRoute } = require('../../test/routes_helpers'); +// FxaMailer is resolved from the TypeDI Container at route-factory construction +// time, so it must be installed before makeRoutes() runs (see the describe-scope +// install below). Tear it down once for the whole file to keep cross-file isolation. +afterAll(() => { + uninstallMockFxaMailer(); +}); + function makeRoutes(options: any = {}) { const config = options.config || {}; - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const customs = options.customs || { check: function () { @@ -36,7 +50,7 @@ function runTest(route: any, request: any, assertions?: (res: any) => void) { describe('/account/login/send_unblock_code', () => { const uid = uuid.v4({}, Buffer.alloc(16)).toString('hex'); const email = 'unblock@example.com'; - const mockLog = mocks.mockLog(); + const mockLog = createMock(); const mockRequest = mocks.mockRequest({ log: mockLog, payload: { @@ -48,8 +62,9 @@ describe('/account/login/send_unblock_code', () => { }, }, }); - const mockMailer = mocks.mockMailer(); - const mockFxaMailer = mocks.mockFxaMailer(); + // Mock the modern FxaMailer (the legacy `mailer` else-branch is not exercised: + // canSend defaults to true, so makeRoutes leaves `mailer` as the default stub). + const mockFxaMailer = installMockFxaMailer(); const mockDb = mocks.mockDB({ uid: uid, email: email, @@ -61,7 +76,6 @@ describe('/account/login/send_unblock_code', () => { config: config, db: mockDb, log: mockLog, - mailer: mockMailer, }); const route = getRoute(accountRoutes, '/account/login/send_unblock_code'); diff --git a/packages/fxa-auth-server/lib/routes/utils/clients.spec.ts b/packages/fxa-auth-server/lib/routes/utils/clients.spec.ts index 675e9faec70..a0894e6b00b 100644 --- a/packages/fxa-auth-server/lib/routes/utils/clients.spec.ts +++ b/packages/fxa-auth-server/lib/routes/utils/clients.spec.ts @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import moment from 'moment'; - -const mocks = require('../../../test/mocks'); +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; const EARLIEST_SANE_TIMESTAMP = 31536000000; @@ -62,7 +62,7 @@ function mockRequest(data: any) { } const makeClientUtils = (options: any) => { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const config = options.config || {}; config.lastAccessTimeUpdates = config.lastAccessTimeUpdates || { earliestSaneTimestamp: EARLIEST_SANE_TIMESTAMP, @@ -78,7 +78,7 @@ describe('clientUtils.formatLocation', () => { let log: any, clientUtils: any, request: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); clientUtils = makeClientUtils({ log }); request = mockRequest({}); }); @@ -163,7 +163,7 @@ describe('clientUtils.formatTimestamps', () => { let log: any, clientUtils: any, request: any; beforeEach(() => { - log = mocks.mockLog(); + log = createMock(); clientUtils = makeClientUtils({ log }); request = mockRequest({}); }); diff --git a/packages/fxa-auth-server/lib/routes/utils/oauth.spec.ts b/packages/fxa-auth-server/lib/routes/utils/oauth.spec.ts index 1d7127344a3..4b914012510 100644 --- a/packages/fxa-auth-server/lib/routes/utils/oauth.spec.ts +++ b/packages/fxa-auth-server/lib/routes/utils/oauth.spec.ts @@ -30,6 +30,11 @@ jest.mock('../../oauth/client', () => ({ }, })); +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../../test/fixtures/fxa-mailer'; + const mocks = require('../../../test/mocks'); const oauthUtils = require('./oauth'); @@ -95,6 +100,8 @@ describe('newTokenNotification', () => { let credentials: any; let grant: any; + afterAll(() => uninstallMockFxaMailer()); + beforeEach(() => { db = mocks.mockDB({ email: TEST_EMAIL, @@ -102,7 +109,7 @@ describe('newTokenNotification', () => { uid: MOCK_UID, }); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); mocks.mockOAuthClientInfo(); devices = mocks.mockDevices(); credentials = { diff --git a/packages/fxa-auth-server/lib/routes/utils/signin.spec.ts b/packages/fxa-auth-server/lib/routes/utils/signin.spec.ts index 3d9313d61c7..c54ca1700fb 100644 --- a/packages/fxa-auth-server/lib/routes/utils/signin.spec.ts +++ b/packages/fxa-auth-server/lib/routes/utils/signin.spec.ts @@ -3,6 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; +import { installMockFxaMailer } from '../../../test/fixtures/fxa-mailer'; const mocks = require('../../../test/mocks'); const Password = require('../../crypto/password')({}, {}); @@ -24,7 +27,7 @@ const otpOptions = { }; function makeSigninUtils(options: any) { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const config = options.config || {}; config.authFirestore = config.authFirestore || {}; config.securityHistory = config.securityHistory || {}; @@ -240,7 +243,7 @@ describe('checkCustomsAndLoadAccount', () => { uid: TEST_UID, email: TEST_EMAIL, }); - log = mocks.mockLog(); + log = createMock(); customs = { v2Enabled: jest.fn(() => true), check: jest.fn(() => Promise.resolve()), @@ -695,10 +698,10 @@ describe('sendSigninNotifications', () => { clock = jest.useFakeTimers({ now: 1769555935958 }); db = mocks.mockDB(); - log = mocks.mockLog(); + log = createMock(); mailer = mocks.mockMailer(); mocks.mockOAuthClientInfo(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); metricsContext = mocks.mockMetricsContext(); request = mocks.mockRequest(defaultMockRequestData(log, metricsContext)); accountRecord = { diff --git a/packages/fxa-auth-server/lib/routes/utils/signup.spec.ts b/packages/fxa-auth-server/lib/routes/utils/signup.spec.ts index 166e3dbd951..36d9c9c1786 100644 --- a/packages/fxa-auth-server/lib/routes/utils/signup.spec.ts +++ b/packages/fxa-auth-server/lib/routes/utils/signup.spec.ts @@ -2,6 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../../types'; +import { + installMockFxaMailer, + uninstallMockFxaMailer, +} from '../../../test/fixtures/fxa-mailer'; + const mocks = require('../../../test/mocks'); const { gleanMetrics } = require('../../metrics/glean'); @@ -17,7 +24,7 @@ const gleanConfig = { const glean = gleanMetrics({ gleanMetrics: gleanConfig }); async function setup(options: any) { - const log = options.log || mocks.mockLog(); + const log = options.log || createMock(); const db = options.db || mocks.mockDB(); const mailer = options.mailer || {}; const verificationReminders = @@ -44,6 +51,8 @@ describe('verifyAccount', () => { let utils: any; let account: any; + afterAll(() => uninstallMockFxaMailer()); + beforeEach(() => { account = { uid: TEST_UID, @@ -54,9 +63,9 @@ describe('verifyAccount', () => { }, }; db = mocks.mockDB(account); - log = mocks.mockLog(); + log = createMock(); mailer = mocks.mockMailer(); - fxaMailer = mocks.mockFxaMailer(); + fxaMailer = installMockFxaMailer(); push = mocks.mockPush(); verificationReminders = mocks.mockVerificationReminders(); request = mocks.mockRequest({ diff --git a/packages/fxa-auth-server/lib/routes/validators.js b/packages/fxa-auth-server/lib/routes/validators.js index 6fdfc842a28..a6dfd3d450a 100644 --- a/packages/fxa-auth-server/lib/routes/validators.js +++ b/packages/fxa-auth-server/lib/routes/validators.js @@ -34,7 +34,11 @@ const { const { VX_REGEX: CLIENT_SALT_STRING, } = require('../../lib/routes/utils/client-key-stretch'); -const { ReasonForDeletion } = require('./cloud-tasks'); +// Import ReasonForDeletion from its source package rather than the local +// ./cloud-tasks route module, which re-exports it: ./cloud-tasks imports this +// validators module, so requiring it here created a circular dependency that +// left ReasonForDeletion undefined depending on module load order. +const { ReasonForDeletion } = require('@fxa/shared/cloud-tasks'); // Match any non-empty hex-encoded string. const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/; diff --git a/packages/fxa-auth-server/lib/senders/index.spec.ts b/packages/fxa-auth-server/lib/senders/index.spec.ts index e75af9c1e0a..c71c9d77bdd 100644 --- a/packages/fxa-auth-server/lib/senders/index.spec.ts +++ b/packages/fxa-auth-server/lib/senders/index.spec.ts @@ -4,6 +4,8 @@ import crypto from 'crypto'; import { Container } from 'typedi'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; const config = require('../../config').default.getProperties(); const mocks = require('../../test/mocks'); @@ -12,7 +14,7 @@ const { ProductConfigurationManager, } = require('../../../../libs/shared/cms/src'); -const nullLog = mocks.mockLog(); +const nullLog = createMock(); describe('lib/senders/index', () => { afterEach(() => { diff --git a/packages/fxa-auth-server/lib/server.in.spec.ts b/packages/fxa-auth-server/lib/server.in.spec.ts index 8fd0b7e9715..17e0a027871 100644 --- a/packages/fxa-auth-server/lib/server.in.spec.ts +++ b/packages/fxa-auth-server/lib/server.in.spec.ts @@ -3,6 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { Account } from 'fxa-shared/db/models/auth/account'; +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; const mockReportValidationError = jest.fn(); jest.mock('fxa-shared/sentry/report-validation-error', () => ({ @@ -110,7 +112,7 @@ describe('lib/server', () => { beforeEach(() => { config = getConfig(); - log = mocks.mockLog(); + log = createMock(); routes = getRoutes(); statsd = { timing: jest.fn(), increment: jest.fn() }; }); diff --git a/packages/fxa-auth-server/lib/subscription-account-reminders.in.spec.ts b/packages/fxa-auth-server/lib/subscription-account-reminders.in.spec.ts index 2b2a0641090..d2f3c715a47 100644 --- a/packages/fxa-auth-server/lib/subscription-account-reminders.in.spec.ts +++ b/packages/fxa-auth-server/lib/subscription-account-reminders.in.spec.ts @@ -2,6 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from './types'; + const REMINDERS = ['first', 'second', 'third']; const EXPECTED_CREATE_DELETE_RESULT = REMINDERS.reduce( (expected: any, reminder) => { @@ -12,7 +15,6 @@ const EXPECTED_CREATE_DELETE_RESULT = REMINDERS.reduce( ); const config = require('../config').default.getProperties(); -const mocks = require('../test/mocks'); const TEST_PREFIX = `test-lib-subscription-account-reminders:${ process.env.JEST_WORKER_ID || '1' }:`; @@ -22,7 +24,7 @@ describe('#integration - lib/subscription-account-reminders', () => { beforeEach(async () => { jest.resetModules(); - log = mocks.mockLog(); + log = createMock(); mockConfig = { redis: config.redis, subscriptionAccountReminders: { @@ -43,7 +45,7 @@ describe('#integration - lib/subscription-account-reminders', () => { ...mockConfig.subscriptionAccountReminders.redis, enabled: true, }, - mocks.mockLog() + createMock() ); await Promise.all([ redis.del('first'), diff --git a/packages/fxa-auth-server/lib/tokens/token.spec.ts b/packages/fxa-auth-server/lib/tokens/token.spec.ts index ac0f47042dd..2223eb553fb 100644 --- a/packages/fxa-auth-server/lib/tokens/token.spec.ts +++ b/packages/fxa-auth-server/lib/tokens/token.spec.ts @@ -2,9 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { createMock } from '@golevelup/ts-jest'; +import { AuthLogger } from '../types'; + const config = require('../../config').default.getProperties(); -const mocks = require('../../test/mocks'); -const log = mocks.mockLog(); +const log = createMock(); interface TokenInstance { createdAt: number; @@ -45,41 +47,61 @@ describe('Token', () => { it('Token.createNewToken defaults createdAt to the current time', async () => { const now = Date.now(); const token: TokenInstance = await Token.createNewToken(Token, {}); - expect(token.createdAt >= now && token.createdAt <= Date.now()).toBeTruthy(); + expect( + token.createdAt >= now && token.createdAt <= Date.now() + ).toBeTruthy(); }); it('Token.createNewToken ignores an override for createdAt', async () => { const now = Date.now() - 1; - const token: TokenInstance = await Token.createNewToken(Token, { createdAt: now }); + const token: TokenInstance = await Token.createNewToken(Token, { + createdAt: now, + }); expect(token.createdAt).not.toBe(now); }); it('Token.createNewToken ignores a negative value for createdAt', async () => { const now = Date.now(); const notNow = -now; - const token: TokenInstance = await Token.createNewToken(Token, { createdAt: notNow }); - expect(token.createdAt >= now && token.createdAt <= Date.now()).toBeTruthy(); + const token: TokenInstance = await Token.createNewToken(Token, { + createdAt: notNow, + }); + expect( + token.createdAt >= now && token.createdAt <= Date.now() + ).toBeTruthy(); }); it('Token.createNewToken ignores a createdAt timestamp in the future', async () => { const now = Date.now(); const notNow = Date.now() + 1000; - const token: TokenInstance = await Token.createNewToken(Token, { createdAt: notNow }); - expect(token.createdAt >= now && token.createdAt <= Date.now()).toBeTruthy(); + const token: TokenInstance = await Token.createNewToken(Token, { + createdAt: notNow, + }); + expect( + token.createdAt >= now && token.createdAt <= Date.now() + ).toBeTruthy(); }); it('Token.createTokenFromHexData accepts a value for createdAt', async () => { const now = Date.now() - 20; - const token: TokenInstance = await Token.createTokenFromHexData(Token, 'ABCD', { - createdAt: now, - }); + const token: TokenInstance = await Token.createTokenFromHexData( + Token, + 'ABCD', + { + createdAt: now, + } + ); expect(token.createdAt).toBe(now); }); it('Token.createTokenFromHexData defaults to zero if not given a value for createdAt', async () => { - const token: TokenInstance = await Token.createTokenFromHexData(Token, 'ABCD', { - other: 'data', - }); + const token: TokenInstance = await Token.createTokenFromHexData( + Token, + 'ABCD', + { + other: 'data', + } + ); expect(token.createdAt).toBe(0); }); }); @@ -94,27 +116,41 @@ describe('Token', () => { it('Token.createNewToken defaults createdAt to the current time', async () => { const now = Date.now(); const token: TokenInstance = await Token.createNewToken(Token, {}); - expect(token.createdAt >= now && token.createdAt <= Date.now()).toBeTruthy(); + expect( + token.createdAt >= now && token.createdAt <= Date.now() + ).toBeTruthy(); }); it('Token.createNewToken does not accept an override for createdAt', async () => { const now = Date.now() - 1; - const token: TokenInstance = await Token.createNewToken(Token, { createdAt: now }); - expect(token.createdAt > now && token.createdAt <= Date.now()).toBeTruthy(); + const token: TokenInstance = await Token.createNewToken(Token, { + createdAt: now, + }); + expect( + token.createdAt > now && token.createdAt <= Date.now() + ).toBeTruthy(); }); it('Token.createTokenFromHexData accepts a value for createdAt', async () => { const now = Date.now() - 20; - const token: TokenInstance = await Token.createTokenFromHexData(Token, 'ABCD', { - createdAt: now, - }); + const token: TokenInstance = await Token.createTokenFromHexData( + Token, + 'ABCD', + { + createdAt: now, + } + ); expect(token.createdAt).toBe(now); }); it('Token.createTokenFromHexData defaults to zero if not given a value for createdAt', async () => { - const token: TokenInstance = await Token.createTokenFromHexData(Token, 'ABCD', { - other: 'data', - }); + const token: TokenInstance = await Token.createTokenFromHexData( + Token, + 'ABCD', + { + other: 'data', + } + ); expect(token.createdAt).toBe(0); }); }); diff --git a/packages/fxa-auth-server/lib/types.ts b/packages/fxa-auth-server/lib/types.ts index 89a6aa25b20..6e723b01b98 100644 --- a/packages/fxa-auth-server/lib/types.ts +++ b/packages/fxa-auth-server/lib/types.ts @@ -116,6 +116,14 @@ export interface AuthLogger extends Logger { request: AuthRequest, data: Record ): Promise; + + activityEvent(data: Record): void; + + amplitudeEvent(data: Record): void; + + flowEvent(data: Record): void; + + summary(request: AuthRequest, response: unknown): void; } export interface AuthClientInfoService { diff --git a/packages/fxa-auth-server/package.json b/packages/fxa-auth-server/package.json index f73a829a805..3f2b02c323c 100644 --- a/packages/fxa-auth-server/package.json +++ b/packages/fxa-auth-server/package.json @@ -119,6 +119,7 @@ "web-push": "3.4.4" }, "devDependencies": { + "@golevelup/ts-jest": "^0.5.0", "@types/async-retry": "^1", "@types/dedent": "^0", "@types/hapi__hapi": "^20.0.10", diff --git a/packages/fxa-auth-server/test/fixtures/README.md b/packages/fxa-auth-server/test/fixtures/README.md new file mode 100644 index 00000000000..bb26737a92e --- /dev/null +++ b/packages/fxa-auth-server/test/fixtures/README.md @@ -0,0 +1,73 @@ + + +# Test fixtures (typed Jest mocks) + +Replacement for the legacy `test/mocks.js` monolith (**FXA-13708**). Most specs +need no fixture file at all — this directory exists only for the cases where a +typed inline `createMock()` is not enough. + +## Default: inline `createMock()` + +For any collaborator with a real TypeScript interface (NestJS managers, +`AuthLogger`, `StatsD`, repositories, …), mock it inline — don't hand-roll a +mock and don't add a file here: + +```ts +import { createMock } from '@golevelup/ts-jest'; + +const log = createMock(); +const statsd = createMock(); +``` + +Why this over a hand-rolled mock: + +- **Complete method set.** Every method exists as a spy for free, so the + "mock is missing a method" failure (`undefined is not a function`) disappears. + The legacy `mockObject(STATSD_METHOD_NAMES)` stubbed 3 of `StatsD`'s ~13. +- **Tracks the production type.** The mock is `DeepMocked`; if `T` changes, + the spec fails to compile (enforced once specs are type-checked — **FXA-13782**). +- **Deep mocking.** Nested calls return their own deep mocks, so chained APIs + work unwired: `ctx.switchToHttp().getRequest()`. +- **No shared method-name list** for consumers to couple to and accrete onto. + +The payoff scales with the collaborator — minimal for flat ones (`statsd`, +`log`, use it anyway for one idiom), large for complex surfaces (`db`, `request`, +NestJS managers). + +**Caveat — auto-return.** Methods return nested deep mocks, not `undefined`. For +data-bearing reads (`db.account`, …) a forgotten stub yields a silent proxy +rather than a crash, so stub the reads your assertions depend on. Supply data +from the `libs/` factories: + +```ts +db.account.mockResolvedValue(AccountFactory({ email })); +``` + +## When to add a file here + +Only when inline `createMock()` is not enough: + +1. **Container / DI-injected collaborators.** Things production resolves from + the TypeDI `Container` need the mock installed _and torn down_ (`clearMocks` + does not reset TypeDI). See `fxa-mailer.ts`: `installMockFxaMailer()` pairs + `createMock()` + `Container.set(...)` with a matching + `uninstallMockFxaMailer()` for `afterEach`. +2. **Shared presets.** A collaborator most consumers configure identically + (a `request` builder, a pre-wired `db` factory). Export a factory that wraps + `createMock()` and applies them. +3. **No production type at all (rare).** Currently none — notably the legacy + `mailer` is deliberately not given one; mock `FxaMailer` and pass a throwaway + stub for the legacy `mailer` argument. + +If it is none of these, it is pure indirection — don't add it. + +## Conventions for files that belong here + +- One file per domain, named after it (`mailer.ts`, `db.ts`, …). +- Named factory `createMock()`; take a `Partial<...>` overrides argument + when presets are involved, mirroring the `*.factories.ts` style in `libs/`. +- Reuse `libs/` data factories (`AccountFactory`, …); never re-invent shapes. +- Pair every `Container.set(...)` with a matching reset so state cannot leak + between specs. diff --git a/packages/fxa-auth-server/test/fixtures/fxa-mailer.ts b/packages/fxa-auth-server/test/fixtures/fxa-mailer.ts new file mode 100644 index 00000000000..f44553902f0 --- /dev/null +++ b/packages/fxa-auth-server/test/fixtures/fxa-mailer.ts @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { createMock, DeepMocked, PartialFuncReturn } from '@golevelup/ts-jest'; +import { Container } from 'typedi'; +import { FxaMailer } from '../../lib/senders/fxa-mailer'; + +/** + * Installs a typed mock {@link FxaMailer} into the TypeDI Container, the way + * production resolves it (`Container.get(FxaMailer)`), and returns the mock for + * spying. + * + * `FxaMailer` is a real class, so the mock is `createMock()` — it + * tracks the production type (a renamed/removed/retyped `send*` method surfaces + * as a compile error in the spec) with no hand-maintained method list. This + * replaces the legacy `mocks.mockFxaMailer()`. + * + * The only reason this is a fixture and not an inline `createMock()` + * is the Container wiring + the need for matching teardown. **Always pair it + * with {@link uninstallMockFxaMailer} in `afterEach`** — TypeDI state is not + * reset by Jest's `clearMocks`, so an un-removed mock leaks into later specs. + * + * `canSend` defaults to `true` so the modern FxaMailer code path is exercised; + * override it (or any method) via the argument or `.mockReturnValue(...)`. + * + * Prefer mocking `FxaMailer` over the legacy `mailer`. Tests that only exercise + * the FxaMailer path can pass a throwaway stub for the legacy `mailer` argument + * rather than mocking its untyped surface. + * + * @example + * let mailer: DeepMocked; + * beforeEach(() => { mailer = installMockFxaMailer(); }); + * afterEach(() => { uninstallMockFxaMailer(); }); + * // ... + * expect(mailer.sendPostNewRecoveryCodesEmail).toHaveBeenCalledTimes(1); + */ +export function installMockFxaMailer( + overrides?: PartialFuncReturn +): DeepMocked { + const mailer = createMock({ + canSend: () => true, + ...overrides, + }); + Container.set(FxaMailer, mailer); + return mailer; +} + +/** + * Removes the mock {@link FxaMailer} from the Container. Call in `afterEach`. + */ +export function uninstallMockFxaMailer(): void { + Container.remove(FxaMailer); +} diff --git a/packages/fxa-auth-server/test/mocks.js b/packages/fxa-auth-server/test/mocks.js index c436fd5bf00..1777b919e51 100644 --- a/packages/fxa-auth-server/test/mocks.js +++ b/packages/fxa-auth-server/test/mocks.js @@ -19,7 +19,6 @@ const { AccountEventsManager } = require('../lib/account-events'); const { gleanMetrics } = require('../lib/metrics/glean'); const { PriceManager } = require('@fxa/payments/customer'); const { ProductConfigurationManager } = require('@fxa/shared/cms'); -const { FxaMailer } = require('../lib/senders/fxa-mailer'); // Patch Account.metricsEnabled before loading amplitude (replicates what // proxyquire used to do without replacing the entire auth models module). @@ -238,8 +237,6 @@ const SUBHUB_METHOD_NAMES = [ 'createSubscription', ]; -const STATSD_METHOD_NAMES = ['increment', 'timing', 'histogram']; - const PROFILE_METHOD_NAMES = ['deleteCache', 'updateDisplayName']; const MOCK_CMS_CLIENTS = [ @@ -349,7 +346,6 @@ module.exports = { mockPushbox, mockRequest, mockSubHub, - mockStatsd: mockObject(STATSD_METHOD_NAMES), mockProfile, mockVerificationReminders, mockCadReminders, @@ -361,7 +357,6 @@ module.exports = { unMockAccountEventsManager, mockPriceManager, mockProductConfigurationManager, - mockFxaMailer, mockOAuthClientInfo, }; @@ -1140,82 +1135,6 @@ function mockProductConfigurationManager() { return productConfigurationManager; } -/** - * Used to mock the FxaMailer, injecting the mock into the Container. Be sure - * to call this before the code under test requests an FxaMailer instance from - * the Container. - * - * `canSend` is defaulted to a stub that resolves to `false`, so email - * sending is disabled by default in tests. Call mock setup with an override to enable - * sending for specific tests. - * ``` - * const mockFxaMailer = mocks.mockFxaMailer({ canSend: jest.fn().mockResolvedValue(true) }); - * // or, if you don't need to spy on the method: - * const mockFxaMailer = mocks.mockFxaMailer({ canSend: true }); - * ``` - * @param {*} overrides - * @returns {object} The mock FxaMailer instance for spy and assertion use. - * @usage - * - * ``` ts - * const mockFxaMailer = mocks.mockFxaMailer(); - * // arrange, act, assert - * expect(mockFxaMailer.sendRecoveryEmail).toHaveBeenCalledTimes(1); - * ``` - */ -function mockFxaMailer(overrides) { - const mockFxaMailer = { - // add new email methods here! - canSend: jest.fn().mockReturnValue(true), - sendRecoveryEmail: jest.fn().mockResolvedValue(), - sendPasswordForgotOtpEmail: jest.fn().mockResolvedValue(), - sendPasswordlessSigninOtpEmail: jest.fn().mockResolvedValue(), - sendPasswordlessSignupOtpEmail: jest.fn().mockResolvedValue(), - sendPostVerifySecondaryEmail: jest.fn().mockResolvedValue(), - sendPostChangePrimaryEmail: jest.fn().mockResolvedValue(), - sendPostRemoveSecondaryEmail: jest.fn().mockResolvedValue(), - sendPostAddLinkedAccountEmail: jest.fn().mockResolvedValue(), - sendNewDeviceLoginEmail: jest.fn().mockResolvedValue(), - sendPostAddTwoStepAuthenticationEmail: jest.fn().mockResolvedValue(), - sendPostChangeTwoStepAuthenticationEmail: jest.fn().mockResolvedValue(), - sendPostNewRecoveryCodesEmail: jest.fn().mockResolvedValue(), - sendPostConsumeRecoveryCodeEmail: jest.fn().mockResolvedValue(), - sendLowRecoveryCodesEmail: jest.fn().mockResolvedValue(), - sendPostSigninRecoveryCodeEmail: jest.fn().mockResolvedValue(), - sendPostAddRecoveryPhoneEmail: jest.fn().mockResolvedValue(), - sendPostChangeRecoveryPhoneEmail: jest.fn().mockResolvedValue(), - sendPostRemoveRecoveryPhoneEmail: jest.fn().mockResolvedValue(), - sendPasswordResetRecoveryPhoneEmail: jest.fn().mockResolvedValue(), - sendPostSigninRecoveryPhoneEmail: jest.fn().mockResolvedValue(), - sendPostAddAccountRecoveryEmail: jest.fn().mockResolvedValue(), - sendPostChangeAccountRecoveryEmail: jest.fn().mockResolvedValue(), - sendPostRemoveAccountRecoveryEmail: jest.fn().mockResolvedValue(), - sendPasswordResetAccountRecoveryEmail: jest.fn().mockResolvedValue(), - sendPasswordResetWithRecoveryKeyPromptEmail: jest.fn().mockResolvedValue(), - sendPostVerifyEmail: jest.fn().mockResolvedValue(), - sendVerifyLoginCodeEmail: jest.fn().mockResolvedValue(), - sendVerifyShortCodeEmail: jest.fn().mockResolvedValue(), - sendVerifySecondaryCodeEmail: jest.fn().mockResolvedValue(), - sendVerifyLoginEmail: jest.fn().mockResolvedValue(), - sendVerifyEmail: jest.fn().mockResolvedValue(), - sendVerifyAccountChangeEmail: jest.fn().mockResolvedValue(), - sendUnblockCodeEmail: jest.fn().mockResolvedValue(), - sendPasswordResetEmail: jest.fn().mockResolvedValue(), - sendPasswordChangedEmail: jest.fn().mockResolvedValue(), - sendInactiveAccountFirstWarningEmail: jest.fn().mockResolvedValue(), - sendInactiveAccountSecondWarningEmail: jest.fn().mockResolvedValue(), - sendInactiveAccountFinalWarningEmail: jest.fn().mockResolvedValue(), - sendVerificationReminderFirstEmail: jest.fn().mockResolvedValue(), - sendVerificationReminderSecondEmail: jest.fn().mockResolvedValue(), - sendVerificationReminderFinalEmail: jest.fn().mockResolvedValue(), - sendCadReminderFirstEmail: jest.fn().mockResolvedValue(), - sendCadReminderSecondEmail: jest.fn().mockResolvedValue(), - ...overrides, - }; - Container.set(FxaMailer, mockFxaMailer); - return mockFxaMailer; -} - function mockOAuthClientInfo(overrides) { const mock = { fetch: jest.fn().mockResolvedValue({ name: 'sync' }), diff --git a/yarn.lock b/yarn.lock index f06706aa6d1..96110b69fba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30982,6 +30982,7 @@ __metadata: "@fluent/bundle": "npm:^0.18.0" "@fluent/dom": "npm:^0.10.0" "@fluent/langneg": "npm:^0.7.0" + "@golevelup/ts-jest": "npm:^0.5.0" "@google-cloud/tasks": "npm:^5.5.0" "@googlemaps/google-maps-services-js": "npm:^3.4.0" "@hapi/hapi": "npm:^20.2.1"