From 3efe6eea0e197f8a0ee77b8bf33c1a43557ca5e1 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 1 Sep 2020 12:05:21 +0800 Subject: [PATCH 1/3] fix(FormModel): return discriminated model only after compiling base --- src/app/models/form.server.model.ts | 38 +++------ src/types/form.ts | 2 +- .../backend/models/form.server.model.spec.ts | 82 ++++++++++++++++--- 3 files changed, 83 insertions(+), 39 deletions(-) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index 5880a6059e..3603d00847 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -493,42 +493,24 @@ const compileFormModel = (db: Mongoose): IFormModel => { return FormModel } -const compileEmailFormModel = (db: Mongoose) => { - return db.model( - ResponseMode.Email, - EmailFormSchema, - ) -} - -export const getEmailFormModel = (db: Mongoose) => { +const getFormModel = (db: Mongoose) => { try { - return db.model(ResponseMode.Email) as IEmailFormModel + return db.model(FORM_SCHEMA_ID) as IFormModel } catch { - return compileEmailFormModel(db) + return compileFormModel(db) } } -const compileEncryptedFormModel = (db: Mongoose) => { - return db.model( - ResponseMode.Encrypt, - EncryptedFormSchema, - ) +export const getEmailFormModel = (db: Mongoose) => { + // Load or build base model first + getFormModel(db) + return db.model(ResponseMode.Email) as IEmailFormModel } export const getEncryptedFormModel = (db: Mongoose) => { - try { - return db.model(ResponseMode.Encrypt) as IEncryptedFormModel - } catch { - return compileEncryptedFormModel(db) - } -} - -const getFormModel = (db: Mongoose) => { - try { - return db.model(FORM_SCHEMA_ID) as IFormModel - } catch { - return compileFormModel(db) - } + // Load or build base model first + getFormModel(db) + return db.model(ResponseMode.Encrypt) as IEncryptedFormModel } export default getFormModel diff --git a/src/types/form.ts b/src/types/form.ts index 3d21fb1b98..f4df6785c1 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -91,7 +91,7 @@ export interface IForm { webhook?: Webhook msgSrvcName?: string - responseMode?: ResponseMode + responseMode: ResponseMode // Schema properties _id: Document['_id'] diff --git a/tests/unit/backend/models/form.server.model.spec.ts b/tests/unit/backend/models/form.server.model.spec.ts index 4b26c9155f..2ddf825d5c 100644 --- a/tests/unit/backend/models/form.server.model.spec.ts +++ b/tests/unit/backend/models/form.server.model.spec.ts @@ -6,7 +6,12 @@ import getFormModel, { getEmailFormModel, getEncryptedFormModel, } from 'src/app/models/form.server.model' -import { IAgencySchema, IEncryptedForm, IUserSchema } from 'src/types' +import { + IAgencySchema, + IEncryptedForm, + IUserSchema, + ResponseMode, +} from 'src/types' import dbHandler from '../helpers/jest-db' @@ -25,12 +30,12 @@ const MOCK_FORM_PARAMS = { const MOCK_ENCRYPTED_FORM_PARAMS = { ...MOCK_FORM_PARAMS, publicKey: 'mockPublicKey', - responseMode: 'encrypt', + responseMode: ResponseMode.Encrypt, } const MOCK_EMAIL_FORM_PARAMS = { ...MOCK_FORM_PARAMS, emails: [MOCK_ADMIN_EMAIL], - responseMode: 'email', + responseMode: ResponseMode.Email, } const FORM_DEFAULTS = { @@ -676,13 +681,44 @@ describe('Form Model', () => { expect(form).toBeNull() }) - it('should return the populated form when formId is valid', async () => { + it('should return the populated email form when formId is valid', async () => { // Arrange - const formParams = merge({}, MOCK_FORM_PARAMS, { + const emailFormParams = merge({}, MOCK_EMAIL_FORM_PARAMS, { + admin: preloadedAdmin, + }) + // Create a form + const form = (await Form.create(emailFormParams)).toObject() + + // Act + const actualForm = (await Form.getFullFormById(form._id)).toObject() + + // Assert + // Form should be returned + expect(actualForm).not.toBeNull() + // Omit admin key since it is populated is not ObjectId anymore. + expect(omit(actualForm, 'admin')).toEqual(omit(form, 'admin')) + // Verify populated admin shape + expect(actualForm.admin).not.toBeNull() + expect(actualForm.admin.email).toEqual(preloadedAdmin.email) + // Remove indeterministic keys + const expectedAgency = omit(preloadedAgency.toObject(), [ + '_id', + 'created', + 'lastModified', + '__v', + ]) + expect(actualForm.admin.agency).toEqual( + expect.objectContaining(expectedAgency), + ) + }) + + it('should return the populated encrypt form when formId is valid', async () => { + // Arrange + const encryptFormParams = merge({}, MOCK_ENCRYPTED_FORM_PARAMS, { admin: preloadedAdmin, }) // Create a form - const form = (await Form.create(formParams)).toObject() + const form = (await Form.create(encryptFormParams)).toObject() // Act const actualForm = (await Form.getFullFormById(form._id)).toObject() @@ -720,13 +756,39 @@ describe('Form Model', () => { expect(form).toBeNull() }) - it('should return otpData when formId is valid', async () => { + it('should return otpData of an email form when formId is valid', async () => { // Arrange - const formParams = merge({}, MOCK_FORM_PARAMS, { + const emailFormParams = merge({}, MOCK_EMAIL_FORM_PARAMS, { + msgSrvcName: 'mockSrvcName', + }) + // Create a form with msgSrvcName + const form = await Form.create(emailFormParams) + + // Act + const actualOtpData = await Form.getOtpData(form._id) + + // Assert + // OtpData should be returned + expect(actualOtpData).not.toBeNull() + // Check shape + const expectedOtpData = { + form: form._id, + formAdmin: { + email: preloadedAdmin.email, + userId: preloadedAdmin._id, + }, + msgSrvcName: emailFormParams.msgSrvcName, + } + expect(actualOtpData).toEqual(expectedOtpData) + }) + + it('should return otpData of an encrypt form when formId is valid', async () => { + // Arrange + const encryptFormParams = merge({}, MOCK_ENCRYPTED_FORM_PARAMS, { msgSrvcName: 'mockSrvcName', }) // Create a form with msgSrvcName - const form = await Form.create(formParams) + const form = await Form.create(encryptFormParams) // Act const actualOtpData = await Form.getOtpData(form._id) @@ -741,7 +803,7 @@ describe('Form Model', () => { email: preloadedAdmin.email, userId: preloadedAdmin._id, }, - msgSrvcName: formParams.msgSrvcName, + msgSrvcName: encryptFormParams.msgSrvcName, } expect(actualOtpData).toEqual(expectedOtpData) }) From 741a5c03cf27ba05a2aed7767bd544b8ef089fd9 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 1 Sep 2020 12:09:07 +0800 Subject: [PATCH 2/3] fix(SubmissionModel): return discriminated model after compiling base --- src/app/models/submission.server.model.ts | 38 ++++++----------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index bc5cb243dc..ffe0a2b50e 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -152,34 +152,6 @@ encryptSubmissionSchema.methods.getWebhookView = function ( } } -export const getEmailSubmissionModel = (db: Mongoose) => { - try { - return db.model(SubmissionType.Email) as IEmailSubmissionModel - } catch { - return db.model( - SubmissionType.Email, - emailSubmissionSchema, - ) - } -} - -export const getEncryptSubmissionModel = (db: Mongoose) => { - try { - return db.model(SubmissionType.Encrypt) as IEncryptSubmissionModel - } catch { - return db.model( - SubmissionType.Encrypt, - encryptSubmissionSchema, - ) - } -} - -/** - * Form Submission Schema - * @param {Object} db - Active DB Connection - * @return {Object} Mongoose Model - */ - const compileSubmissionModel = (db: Mongoose) => { const Submission = db.model('Submission', SubmissionSchema) Submission.discriminator(SubmissionType.Email, emailSubmissionSchema) @@ -195,4 +167,14 @@ const getSubmissionModel = (db: Mongoose) => { } } +export const getEmailSubmissionModel = (db: Mongoose) => { + getSubmissionModel(db) + return db.model(SubmissionType.Email) as IEmailSubmissionModel +} + +export const getEncryptSubmissionModel = (db: Mongoose) => { + getSubmissionModel(db) + return db.model(SubmissionType.Encrypt) as IEncryptSubmissionModel +} + export default getSubmissionModel From 5e07ebc790b561d491f0d5d695c3220e7d9ea738 Mon Sep 17 00:00:00 2001 From: Arshad Ali Date: Tue, 1 Sep 2020 17:40:07 +0800 Subject: [PATCH 3/3] Fix tests --- .../email-submissions.server.controller.spec.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/unit/backend/controllers/email-submissions.server.controller.spec.js b/tests/unit/backend/controllers/email-submissions.server.controller.spec.js index 139d949553..01170d5230 100644 --- a/tests/unit/backend/controllers/email-submissions.server.controller.spec.js +++ b/tests/unit/backend/controllers/email-submissions.server.controller.spec.js @@ -656,18 +656,16 @@ describe('Email Submissions Controller', () => { it('errors with 400 if submission fail', (done) => { const badSubmission = jasmine.createSpyObj('Submission', ['save']) badSubmission.save.and.callFake((callback) => callback(new Error('boom'))) - const badSubmissionModel = jasmine.createSpy() badSubmissionModel.and.returnValue(badSubmission) - const mongoose = jasmine.createSpyObj('mongoose', ['model']) - mongoose.model - .withArgs('emailSubmission') - .and.returnValue(badSubmissionModel) - + const getEmailSubmissionModel = jasmine.createSpy( + 'getEmailSubmissionModel', + ) + getEmailSubmissionModel.and.returnValue(badSubmissionModel) const badController = spec( 'dist/backend/app/controllers/email-submissions.server.controller', { - mongoose, + '../models/submission.server.model': { getEmailSubmissionModel }, }, )