Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent discriminated models from being created before their base model #244

Merged
merged 3 commits into from
Sep 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 10 additions & 28 deletions src/app/models/form.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,42 +493,24 @@ const compileFormModel = (db: Mongoose): IFormModel => {
return FormModel
}

const compileEmailFormModel = (db: Mongoose) => {
return db.model<IEmailFormSchema, IEmailFormModel>(
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<IEncryptedFormSchema, IEncryptedFormModel>(
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
38 changes: 10 additions & 28 deletions src/app/models/submission.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IEmailSubmissionSchema, IEmailSubmissionModel>(
SubmissionType.Email,
emailSubmissionSchema,
)
}
}

export const getEncryptSubmissionModel = (db: Mongoose) => {
try {
return db.model(SubmissionType.Encrypt) as IEncryptSubmissionModel
} catch {
return db.model<IEncryptedSubmissionSchema, IEncryptSubmissionModel>(
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)
Expand All @@ -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
2 changes: 1 addition & 1 deletion src/types/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export interface IForm {
webhook?: Webhook
msgSrvcName?: string

responseMode?: ResponseMode
responseMode: ResponseMode

// Schema properties
_id: Document['_id']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
},
)

Expand Down
82 changes: 72 additions & 10 deletions tests/unit/backend/models/form.server.model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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 = {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -741,7 +803,7 @@ describe('Form Model', () => {
email: preloadedAdmin.email,
userId: preloadedAdmin._id,
},
msgSrvcName: formParams.msgSrvcName,
msgSrvcName: encryptFormParams.msgSrvcName,
}
expect(actualOtpData).toEqual(expectedOtpData)
})
Expand Down