From d8a617d6b9854ab1dbdf7a549ba5b09368d5f70c Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 26 Oct 2020 12:34:14 +0800 Subject: [PATCH 1/4] test(BillingRoutes): add some integration tests for GET /billing Missing success scenario --- .../billing/__tests__/billing.routes.spec.ts | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/app/modules/billing/__tests__/billing.routes.spec.ts diff --git a/src/app/modules/billing/__tests__/billing.routes.spec.ts b/src/app/modules/billing/__tests__/billing.routes.spec.ts new file mode 100644 index 0000000000..72fb3d8c5d --- /dev/null +++ b/src/app/modules/billing/__tests__/billing.routes.spec.ts @@ -0,0 +1,134 @@ +import { errAsync } from 'neverthrow' +import supertest, { Session } from 'supertest-session' + +import { IUserSchema } from 'src/types' + +import { createAuthedSession } from 'tests/integration/helpers/express-auth' +import { setupApp } from 'tests/integration/helpers/express-setup' +import { buildCelebrateError } from 'tests/unit/backend/helpers/celebrate' +import dbHandler from 'tests/unit/backend/helpers/jest-db' + +import { DatabaseError } from '../../core/core.errors' +import { BillingFactory } from '../billing.factory' +import { BillingRouter } from '../billing.routes' + +const app = setupApp('/billing', BillingRouter, { + setupWithAuth: true, +}) + +describe('billing.routes', () => { + let request: Session + let defaultUser: IUserSchema + + beforeAll(async () => await dbHandler.connect()) + beforeEach(async () => { + request = supertest(app) + const { user } = await dbHandler.insertFormCollectionReqs() + defaultUser = user + }) + afterEach(async () => { + await dbHandler.clearDatabase() + jest.restoreAllMocks() + }) + afterAll(async () => await dbHandler.closeDatabase()) + + describe('GET /billing', () => { + const VALID_ESRVCID = 'mockEsrvcId' + const VALID_QUERY_YR = 2020 + const VALID_QUERY_MTH = 10 + + it('should return 400 when query.esrvcId is not provided', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/billing').query({ + // No esrvcId provided. + yr: VALID_QUERY_YR, + mth: VALID_QUERY_MTH, + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ query: { key: 'esrvcId' } }), + ) + }) + + it('should return 400 when query.yr is not provided', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/billing').query({ + esrvcId: VALID_ESRVCID, + // No yr provided. + mth: VALID_QUERY_MTH, + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ query: { key: 'yr' } }), + ) + }) + + it('should return 400 when query.mth is not provided', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/billing').query({ + esrvcId: VALID_ESRVCID, + yr: VALID_QUERY_YR, + // No mth provided. + }) + + // Assert + expect(response.status).toEqual(400) + expect(response.body).toEqual( + buildCelebrateError({ query: { key: 'mth' } }), + ) + }) + + it('should return 401 when user session does not exist', async () => { + // Act + // Call endpoint directly without first logging in. + const response = await request.get('/billing').query({ + esrvcId: VALID_ESRVCID, + yr: VALID_QUERY_YR, + mth: VALID_QUERY_MTH, + }) + + // Assert + expect(response.status).toEqual(401) + expect(response.body).toEqual('User is unauthorized.') + }) + + it('should return 500 when error occurs whilst querying the database', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + + // Mock database error from service call. + const retrieveStatsSpy = jest + .spyOn(BillingFactory, 'getSpLoginStats') + .mockReturnValueOnce(errAsync(new DatabaseError())) + + // Act + const response = await session.get('/billing').query({ + esrvcId: VALID_ESRVCID, + yr: VALID_QUERY_YR, + mth: VALID_QUERY_MTH, + }) + + // Assert + expect(retrieveStatsSpy).toBeCalled() + expect(response.status).toEqual(500) + expect(response.body).toEqual('Error in retrieving billing records') + }) + }) +}) From 9f1ef684e624dc0c73585e60e60d6f272f2a5b7d Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 26 Oct 2020 14:27:25 +0800 Subject: [PATCH 2/4] test(BillingRoutes): add success test case --- .../billing/__tests__/billing.routes.spec.ts | 126 ++++++++++++++++-- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/src/app/modules/billing/__tests__/billing.routes.spec.ts b/src/app/modules/billing/__tests__/billing.routes.spec.ts index 72fb3d8c5d..61cd25bdba 100644 --- a/src/app/modules/billing/__tests__/billing.routes.spec.ts +++ b/src/app/modules/billing/__tests__/billing.routes.spec.ts @@ -1,7 +1,11 @@ +import { flatten, sortBy, times } from 'lodash' +import mongoose from 'mongoose' import { errAsync } from 'neverthrow' import supertest, { Session } from 'supertest-session' -import { IUserSchema } from 'src/types' +import getFormModel from 'src/app/models/form.server.model' +import getLoginModel from 'src/app/models/login.server.model' +import { AuthType, IUserSchema, ResponseMode } from 'src/types' import { createAuthedSession } from 'tests/integration/helpers/express-auth' import { setupApp } from 'tests/integration/helpers/express-setup' @@ -16,6 +20,9 @@ const app = setupApp('/billing', BillingRouter, { setupWithAuth: true, }) +const FormModel = getFormModel(mongoose) +const LoginModel = getLoginModel(mongoose) + describe('billing.routes', () => { let request: Session let defaultUser: IUserSchema @@ -33,9 +40,112 @@ describe('billing.routes', () => { afterAll(async () => await dbHandler.closeDatabase()) describe('GET /billing', () => { - const VALID_ESRVCID = 'mockEsrvcId' - const VALID_QUERY_YR = 2020 - const VALID_QUERY_MTH = 10 + const VALID_ESRVCID_1 = 'mockEsrvcId1' + const VALID_ESRVCID_2 = 'mockEsrvcId2' + const INVALID_ESRVCID = 'invalidEsrvcId' + const VALID_QUERY_YR = new Date().getFullYear() + const VALID_QUERY_MTH = new Date().getMonth() + + it('should return 200 with array of results of form SPCP logins of the given esrvcId', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + // Generate login statistics. + // Create 2 random forms. + const formPromises = times(3, (idx) => + FormModel.create({ + title: `example form title ${idx}`, + admin: defaultUser._id, + responseMode: ResponseMode.Email, + emails: [defaultUser.email], + }), + ) + const forms = await Promise.all(formPromises) + + // Login to first two forms a set number of times with the same esrvcId. + const esrvc1LoginTimes = [4, 2] + const loginPromises = flatten( + forms.map((form, idx) => + times(esrvc1LoginTimes[idx], () => + LoginModel.create({ + form: form._id, + admin: defaultUser._id, + agency: defaultUser.agency, + authType: AuthType.SP, + esrvcId: VALID_ESRVCID_1, + }), + ), + ), + ) + // Login to third form with a different esrvcId. + loginPromises.push( + ...times(5, () => + LoginModel.create({ + form: forms[2]._id, + admin: defaultUser._id, + agency: defaultUser.agency, + authType: AuthType.SP, + esrvcId: VALID_ESRVCID_2, + }), + ), + ) + + await Promise.all(loginPromises) + + // Act + const response = await session.get('/billing').query({ + esrvcId: VALID_ESRVCID_1, + yr: VALID_QUERY_YR, + mth: VALID_QUERY_MTH, + }) + + // Assert + // Only first two forms returned, as those have logins with + // VALID_ESRVCID_1. + // Should not contain third form with VALID_ESRVCID_2. + const expectedStats = [ + { + adminEmail: defaultUser.email, + formName: forms[0].title, + total: esrvc1LoginTimes[0], + formId: String(forms[0]._id), + authType: AuthType.SP, + }, + { + adminEmail: defaultUser.email, + formName: forms[1].title, + total: esrvc1LoginTimes[1], + formId: String(forms[1]._id), + authType: AuthType.SP, + }, + ] + expect(response.status).toEqual(200) + // Check shape. + expect(response.body).toEqual({ + loginStats: expect.any(Array), + }) + // Sort by total and compare. + expect(sortBy(response.body.loginStats, ['total'])).toEqual( + sortBy(expectedStats, ['total']), + ) + }) + + it('should return 200 with empty array when no esrvcId matches logins', async () => { + // Arrange + // Log in user. + const session = await createAuthedSession(defaultUser.email, request) + + // Act + const response = await session.get('/billing').query({ + esrvcId: INVALID_ESRVCID, + yr: VALID_QUERY_YR, + mth: VALID_QUERY_MTH, + }) + + // Assert + expect(response.status).toEqual(200) + expect(response.body).toEqual({ loginStats: [] }) + }) it('should return 400 when query.esrvcId is not provided', async () => { // Arrange @@ -63,7 +173,7 @@ describe('billing.routes', () => { // Act const response = await session.get('/billing').query({ - esrvcId: VALID_ESRVCID, + esrvcId: VALID_ESRVCID_1, // No yr provided. mth: VALID_QUERY_MTH, }) @@ -82,7 +192,7 @@ describe('billing.routes', () => { // Act const response = await session.get('/billing').query({ - esrvcId: VALID_ESRVCID, + esrvcId: VALID_ESRVCID_1, yr: VALID_QUERY_YR, // No mth provided. }) @@ -98,7 +208,7 @@ describe('billing.routes', () => { // Act // Call endpoint directly without first logging in. const response = await request.get('/billing').query({ - esrvcId: VALID_ESRVCID, + esrvcId: VALID_ESRVCID_1, yr: VALID_QUERY_YR, mth: VALID_QUERY_MTH, }) @@ -120,7 +230,7 @@ describe('billing.routes', () => { // Act const response = await session.get('/billing').query({ - esrvcId: VALID_ESRVCID, + esrvcId: VALID_ESRVCID_1, yr: VALID_QUERY_YR, mth: VALID_QUERY_MTH, }) From 3eab7383c7bd4c3494400481cbc8e41006126776 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 26 Oct 2020 14:31:19 +0800 Subject: [PATCH 3/4] test(BillingRoutes): correct count of forms in comment --- src/app/modules/billing/__tests__/billing.routes.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/billing/__tests__/billing.routes.spec.ts b/src/app/modules/billing/__tests__/billing.routes.spec.ts index 61cd25bdba..80b94eb08b 100644 --- a/src/app/modules/billing/__tests__/billing.routes.spec.ts +++ b/src/app/modules/billing/__tests__/billing.routes.spec.ts @@ -51,7 +51,7 @@ describe('billing.routes', () => { // Log in user. const session = await createAuthedSession(defaultUser.email, request) // Generate login statistics. - // Create 2 random forms. + // Create 3 random forms. const formPromises = times(3, (idx) => FormModel.create({ title: `example form title ${idx}`, From d6dd5e0fdbbf48572077118a73ef34e3e1d15d7f Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Thu, 29 Oct 2020 12:29:30 +0800 Subject: [PATCH 4/4] test(BillingRoutes): extract helper method to generate login stats --- .../billing/__tests__/billing.routes.spec.ts | 120 +++++++++++------- 1 file changed, 74 insertions(+), 46 deletions(-) diff --git a/src/app/modules/billing/__tests__/billing.routes.spec.ts b/src/app/modules/billing/__tests__/billing.routes.spec.ts index 80b94eb08b..c8865d7271 100644 --- a/src/app/modules/billing/__tests__/billing.routes.spec.ts +++ b/src/app/modules/billing/__tests__/billing.routes.spec.ts @@ -51,46 +51,14 @@ describe('billing.routes', () => { // Log in user. const session = await createAuthedSession(defaultUser.email, request) // Generate login statistics. - // Create 3 random forms. - const formPromises = times(3, (idx) => - FormModel.create({ - title: `example form title ${idx}`, - admin: defaultUser._id, - responseMode: ResponseMode.Email, - emails: [defaultUser.email], - }), - ) - const forms = await Promise.all(formPromises) - - // Login to first two forms a set number of times with the same esrvcId. - const esrvc1LoginTimes = [4, 2] - const loginPromises = flatten( - forms.map((form, idx) => - times(esrvc1LoginTimes[idx], () => - LoginModel.create({ - form: form._id, - admin: defaultUser._id, - agency: defaultUser.agency, - authType: AuthType.SP, - esrvcId: VALID_ESRVCID_1, - }), - ), - ), - ) - // Login to third form with a different esrvcId. - loginPromises.push( - ...times(5, () => - LoginModel.create({ - form: forms[2]._id, - admin: defaultUser._id, - agency: defaultUser.agency, - authType: AuthType.SP, - esrvcId: VALID_ESRVCID_2, - }), - ), - ) - - await Promise.all(loginPromises) + const { + generatedLoginTimes, + generatedForms, + } = await generateLoginStatistics({ + user: defaultUser, + esrvcIdToCheck: VALID_ESRVCID_1, + altEsrvcId: VALID_ESRVCID_2, + }) // Act const response = await session.get('/billing').query({ @@ -106,16 +74,16 @@ describe('billing.routes', () => { const expectedStats = [ { adminEmail: defaultUser.email, - formName: forms[0].title, - total: esrvc1LoginTimes[0], - formId: String(forms[0]._id), + formName: generatedForms[0].title, + total: generatedLoginTimes[0], + formId: String(generatedForms[0]._id), authType: AuthType.SP, }, { adminEmail: defaultUser.email, - formName: forms[1].title, - total: esrvc1LoginTimes[1], - formId: String(forms[1]._id), + formName: generatedForms[1].title, + total: generatedLoginTimes[1], + formId: String(generatedForms[1]._id), authType: AuthType.SP, }, ] @@ -242,3 +210,63 @@ describe('billing.routes', () => { }) }) }) + +/** + * Helper method to generate login statistics for testing. + */ +const generateLoginStatistics = async ({ + user, + esrvcIdToCheck, + altEsrvcId, +}: { + user: IUserSchema + esrvcIdToCheck: string + altEsrvcId: string +}) => { + // Generate login statistics. + // Create 3 random forms. + const formPromises = times(3, (idx) => + FormModel.create({ + title: `example form title ${idx}`, + admin: user._id, + responseMode: ResponseMode.Email, + emails: [user.email], + }), + ) + const forms = await Promise.all(formPromises) + + // Login to first two forms a set number of times with the same esrvcId. + const esrvc1LoginTimes = [4, 2] + const loginPromises = flatten( + forms.map((form, idx) => + times(esrvc1LoginTimes[idx], () => + LoginModel.create({ + form: form._id, + admin: user._id, + agency: user.agency, + authType: AuthType.SP, + esrvcId: esrvcIdToCheck, + }), + ), + ), + ) + // Login to third form with a different esrvcId. + loginPromises.push( + ...times(5, () => + LoginModel.create({ + form: forms[2]._id, + admin: user._id, + agency: user.agency, + authType: AuthType.SP, + esrvcId: altEsrvcId, + }), + ), + ) + + await Promise.all(loginPromises) + + return { + generatedLoginTimes: esrvc1LoginTimes, + generatedForms: forms, + } +}