diff --git a/package-lock.json b/package-lock.json index a12decb1e1..52c4f145b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4716,23 +4716,6 @@ "@sinonjs/commons": "^1.7.0" } }, - "@sinonjs/samsam": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", - "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.6.0", - "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, "@stablelib/base64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.0.tgz", @@ -15822,12 +15805,6 @@ "set-immediate-shim": "~1.0.1" } }, - "just-extend": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", - "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", - "dev": true - }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -16346,12 +16323,6 @@ "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-4.6.0.tgz", "integrity": "sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E=" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, "lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -17883,36 +17854,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "nise": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", - "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - } - } - }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -21203,46 +21144,6 @@ "is-arrayish": "^0.3.1" } }, - "sinon": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-10.0.0.tgz", - "integrity": "sha512-XAn5DxtGVJBlBWYrcYKEhWCz7FLwZGdyvANRyK06419hyEpdT0dMc5A8Vcxg5SCGHc40CsqoKsc1bt1CbJPfNw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.8.1", - "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.1", - "diff": "^4.0.2", - "nise": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -24165,12 +24066,6 @@ } } }, - "ts-mock-imports": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/ts-mock-imports/-/ts-mock-imports-1.3.3.tgz", - "integrity": "sha512-zCAcb89Y+f3Bhw5VaHrHMh5tiuwAQEl5D3e0r5ELCdLl9EnZpb8Oeei/S60Qf4LORIfmJEXb3TpR5kxtL6j2cg==", - "dev": true - }, "ts-node": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", diff --git a/package.json b/package.json index 1a2e1228c0..5255f2400a 100644 --- a/package.json +++ b/package.json @@ -232,7 +232,6 @@ "proxyquire": "^2.1.3", "regenerator": "^0.14.4", "rimraf": "^3.0.2", - "sinon": "^10.0.0", "stylelint": "^13.12.0", "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^21.0.0", @@ -244,7 +243,6 @@ "ts-essentials": "^7.0.1", "ts-jest": "^26.5.4", "ts-loader": "^7.0.5", - "ts-mock-imports": "^1.3.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", "type-fest": "^0.20.2", diff --git a/src/app/modules/auth/__tests__/auth.service.spec.ts b/src/app/modules/auth/__tests__/auth.service.spec.ts index 8557464bd9..a84e3c4551 100644 --- a/src/app/modules/auth/__tests__/auth.service.spec.ts +++ b/src/app/modules/auth/__tests__/auth.service.spec.ts @@ -2,14 +2,13 @@ import { ObjectId } from 'bson-ext' import mongoose from 'mongoose' import { err, errAsync, ok, okAsync } from 'neverthrow' import { mocked } from 'ts-jest/utils' -import { ImportMock } from 'ts-mock-imports' import getTokenModel from 'src/app/models/token.server.model' -import * as OtpUtils from 'src/app/utils/otp' import { IAgencySchema, IPopulatedForm, IPopulatedUser } from 'src/types' import dbHandler from 'tests/unit/backend/helpers/jest-db' +import * as OtpUtils from '../../../utils/otp' import { DatabaseError } from '../../core/core.errors' import { PermissionLevel } from '../../form/admin-form/admin-form.types' import * as AdminFormUtils from '../../form/admin-form/admin-form.utils' @@ -34,9 +33,6 @@ const VALID_EMAIL_DOMAIN = 'test.gov.sg' const VALID_EMAIL = `valid@${VALID_EMAIL_DOMAIN}` const MOCK_OTP = '123456' -// All calls to generateOtp will return MOCK_OTP. -ImportMock.mockFunction(OtpUtils, 'generateOtp', MOCK_OTP) - describe('auth.service', () => { let defaultAgency: IAgencySchema @@ -50,7 +46,7 @@ describe('auth.service', () => { // Only need to clear Token collection, and ignore other collections. beforeEach(async () => { await dbHandler.clearCollection(TokenModel.collection.collectionName) - jest.resetAllMocks() + jest.clearAllMocks() }) afterAll(async () => await dbHandler.closeDatabase()) @@ -97,6 +93,7 @@ describe('auth.service', () => { // Arrange // Should have no documents prior to this. await expect(TokenModel.countDocuments()).resolves.toEqual(0) + jest.spyOn(OtpUtils, 'generateOtp').mockReturnValueOnce(MOCK_OTP) // Act const actualResult = await AuthService.createLoginOtp(VALID_EMAIL) @@ -124,6 +121,7 @@ describe('auth.service', () => { describe('verifyLoginOtp', () => { it('should successfully return true and delete Token document when OTP hash matches', async () => { // Arrange + jest.spyOn(OtpUtils, 'generateOtp').mockReturnValueOnce(MOCK_OTP) // Add a Token document to verify against. await AuthService.createLoginOtp(VALID_EMAIL) await expect(TokenModel.countDocuments()).resolves.toEqual(1) diff --git a/src/app/modules/user/__tests__/user.service.spec.ts b/src/app/modules/user/__tests__/user.service.spec.ts index 66f84e0824..91b86e802c 100644 --- a/src/app/modules/user/__tests__/user.service.spec.ts +++ b/src/app/modules/user/__tests__/user.service.spec.ts @@ -3,7 +3,6 @@ import { zipWith } from 'lodash' import MockDate from 'mockdate' import mongoose, { Query } from 'mongoose' import { errAsync, okAsync } from 'neverthrow' -import { ImportMock } from 'ts-mock-imports' import getAdminVerificationModel from 'src/app/models/admin_verification.server.model' import getUserModel from 'src/app/models/user.server.model' @@ -37,7 +36,6 @@ describe('user.service', () => { beforeAll(async () => { await dbHandler.connect() - ImportMock.mockFunction(OtpUtils, 'generateOtp', MOCK_OTP) }) beforeEach(async () => { // Insert user into collections. @@ -46,8 +44,8 @@ describe('user.service', () => { mailDomain: ALLOWED_DOMAIN, }) - defaultAgency = agency.toObject() - defaultUser = user.toObject() + defaultAgency = agency + defaultUser = user }) afterEach(async () => { await dbHandler.clearDatabase() @@ -93,6 +91,7 @@ describe('user.service', () => { it('should create a new AdminVerification document and return otp', async () => { // Arrange // Should have no documents prior to this. + jest.spyOn(OtpUtils, 'generateOtp').mockReturnValueOnce(MOCK_OTP) await expect(AdminVerification.countDocuments()).resolves.toEqual(0) // Act @@ -149,6 +148,7 @@ describe('user.service', () => { describe('verifyContactOtp', () => { it('should successfully verify otp', async () => { // Arrange + jest.spyOn(OtpUtils, 'generateOtp').mockReturnValueOnce(MOCK_OTP) // Add a AdminVerification document to verify against. await UserService.createContactOtp(USER_ID, MOCK_CONTACT) await expect(AdminVerification.countDocuments()).resolves.toEqual(1) @@ -248,6 +248,7 @@ describe('user.service', () => { it('should return InvalidOtpError when contact hash does not match', async () => { // Arrange + jest.spyOn(OtpUtils, 'generateOtp').mockReturnValueOnce(MOCK_OTP) // Insert new AdminVerification document. await UserService.createContactOtp(USER_ID, MOCK_CONTACT) const invalidContact = '123456' @@ -291,7 +292,7 @@ describe('user.service', () => { const actualUser = actualResult._unsafeUnwrap() expect(actualUser.contact).toEqual(MOCK_CONTACT) // Returned document's agency should be populated. - expect(actualUser.agency.toObject()).toEqual(defaultAgency) + expect(actualUser.agency.toObject()).toEqual(defaultAgency.toObject()) }) it('should return MissingUserError if userId is invalid', async () => { @@ -314,8 +315,8 @@ describe('user.service', () => { it('should return populated user successfully', async () => { // Arrange const expected = { - ...defaultUser, - agency: defaultAgency, + ...defaultUser.toObject(), + agency: defaultAgency.toObject(), } // Act @@ -369,7 +370,7 @@ describe('user.service', () => { // Assert const expectedUser: Partial = { - agency: defaultAgency, + agency: defaultAgency.toObject() as IAgencySchema, email: newUserEmail, lastAccessed: MOCKED_DATE, } @@ -395,8 +396,8 @@ describe('user.service', () => { // Assert const expectedUser: Partial = { - ...defaultUser, - agency: defaultAgency, + ...defaultUser.toObject(), + agency: defaultAgency.toObject() as IAgencySchema, lastAccessed: MOCKED_DATE, } expect(actualResult.isOk()).toBe(true) @@ -412,12 +413,9 @@ describe('user.service', () => { it('should return admin successfully', async () => { // Arrange const mockUserId = new ObjectID().toHexString() - const findSpy = jest.spyOn(UserModel, 'findById').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockResolvedValue(defaultUser), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce(({ + exec: jest.fn().mockResolvedValue(defaultUser), + } as unknown) as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -431,12 +429,9 @@ describe('user.service', () => { it('should return DatabaseError when query throws an error', async () => { // Arrange const mockUserId = new ObjectID().toHexString() - const findSpy = jest.spyOn(UserModel, 'findById').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockRejectedValue(new Error('database bad!')), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce(({ + exec: jest.fn().mockRejectedValue(new Error('database bad')), + } as unknown) as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -450,12 +445,9 @@ describe('user.service', () => { it('should return MissingUserError when query returns null', async () => { // Arrange const mockUserId = new ObjectID().toHexString() - const findSpy = jest.spyOn(UserModel, 'findById').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockResolvedValue(null), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce(({ + exec: jest.fn().mockResolvedValue(null), + } as unknown) as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -471,12 +463,9 @@ describe('user.service', () => { it('should return admin successfully', async () => { // Arrange const mockEmail = 'someemail@example.com' - const findSpy = jest.spyOn(UserModel, 'findOne').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockResolvedValue(defaultUser), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce(({ + exec: jest.fn().mockResolvedValue(defaultUser), + } as unknown) as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) @@ -490,12 +479,9 @@ describe('user.service', () => { it('should return DatabaseError when query throws an error', async () => { // Arrange const mockEmail = 'another@example.com' - const findSpy = jest.spyOn(UserModel, 'findOne').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockRejectedValue(new Error('database bad!')), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce(({ + exec: jest.fn().mockRejectedValue(new Error('database bad!')), + } as unknown) as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) @@ -509,12 +495,9 @@ describe('user.service', () => { it('should return MissingUserError when query returns null', async () => { // Arrange const mockEmail = 'mockEmail@example.com' - const findSpy = jest.spyOn(UserModel, 'findOne').mockImplementationOnce( - () => - (({ - exec: jest.fn().mockResolvedValue(null), - } as unknown) as Query), - ) + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce(({ + exec: jest.fn().mockResolvedValue(null), + } as unknown) as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) diff --git a/src/app/services/mail/__tests__/mail.service.spec.ts b/src/app/services/mail/__tests__/mail.service.spec.ts index accab8108c..e2d107a784 100644 --- a/src/app/services/mail/__tests__/mail.service.spec.ts +++ b/src/app/services/mail/__tests__/mail.service.spec.ts @@ -2,7 +2,6 @@ import { cloneDeep } from 'lodash' import moment from 'moment-timezone' import { err, ok, okAsync } from 'neverthrow' import Mail, { Attachment } from 'nodemailer/lib/mailer' -import { ImportMock } from 'ts-mock-imports' import { MailSendError } from 'src/app/services/mail/mail.errors' import { MailService } from 'src/app/services/mail/mail.service' @@ -23,7 +22,7 @@ const MOCK_SENDER_EMAIL = 'from@example.com' const MOCK_APP_NAME = 'mockApp' const MOCK_APP_URL = 'mockApp.example.com' const MOCK_SENDER_STRING = `${MOCK_APP_NAME} <${MOCK_SENDER_EMAIL}>` -const MOCK_PDF = 'fake pdf' +const MOCK_PDF = Buffer.from('fake pdf') const MOCK_RETRY_COUNT = 10 @@ -35,14 +34,11 @@ describe('mail.service', () => { // Set up mocks for MailUtils beforeAll(() => { - ImportMock.mockFunction( - MailUtils, - 'generateAutoreplyPdf', - okAsync(MOCK_PDF), - ) + jest + .spyOn(MailUtils, 'generateAutoreplyPdf') + .mockReturnValue(okAsync(MOCK_PDF)) }) beforeEach(() => sendMailSpy.mockReset()) - afterAll(() => ImportMock.restore()) const mailService = new MailService({ transporter: mockTransporter,