From a3dde59b1c92f52c422f1b6734c6cb06f5ef2591 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2020 15:53:38 +0800 Subject: [PATCH 01/31] chore(deps-dev): bump @typescript-eslint/parser from 3.3.0 to 3.10.1 (#247) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 3.3.0 to 3.10.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v3.10.1/packages/parser) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 60 +++++++++++++++++++++++++++++++++++++---------- package.json | 2 +- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index e28c2df037..24b866c504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5260,27 +5260,45 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.3.0.tgz", - "integrity": "sha512-d4pGIAbu/tYsrPrdHCQ5xfadJGvlkUxbeBB56nO/VGmEDi/sKmfa5fGty5t5veL1OyJBrUmSiRn1R1qfVDydrg==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "3.3.0", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true + } } }, "@typescript-eslint/parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.3.0.tgz", - "integrity": "sha512-a7S0Sqn/+RpOOWTcaLw6RD4obsharzxmgMfdK24l364VxuBODXjuJM7ImCkSXEN7oz52aiZbXSbc76+2EsE91w==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", "dev": true, "requires": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.3.0", - "@typescript-eslint/typescript-estree": "3.3.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true + } } }, "@typescript-eslint/types": { @@ -5290,13 +5308,14 @@ "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.3.0.tgz", - "integrity": "sha512-3SqxylENltEvJsjjMSDCUx/edZNSC7wAqifUU1Ywp//0OWEZwMZJfecJud9XxJ/40rAKEbJMKBOQzeOjrLJFzQ==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", "dev": true, "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", "debug": "^4.1.1", - "eslint-visitor-keys": "^1.1.0", "glob": "^7.1.6", "is-glob": "^4.0.1", "lodash": "^4.17.15", @@ -5304,6 +5323,21 @@ "tsutils": "^3.17.1" }, "dependencies": { + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", diff --git a/package.json b/package.json index 3a22ffd27d..629d17a4e0 100644 --- a/package.json +++ b/package.json @@ -189,7 +189,7 @@ "@types/uuid": "^8.0.0", "@types/validator": "^13.0.0", "@typescript-eslint/eslint-plugin": "^3.9.1", - "@typescript-eslint/parser": "^3.3.0", + "@typescript-eslint/parser": "^3.10.1", "axios-mock-adapter": "^1.18.1", "babel-loader": "^8.0.5", "concurrently": "^3.6.1", From f6bc8b5d7d2b1c26ee504351f8d3d47ad8ec8496 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2020 16:54:40 +0800 Subject: [PATCH 02/31] fix(deps): remove ajv as dependency (#248) * fix(deps): bump ajv from 5.5.2 to 6.12.4 Bumps [ajv](https://github.com/ajv-validator/ajv) from 5.5.2 to 6.12.4. - [Release notes](https://github.com/ajv-validator/ajv/releases) - [Commits](https://github.com/ajv-validator/ajv/compare/v5.5.2...v6.12.4) Signed-off-by: dependabot[bot] * chore(deps): remove ajv as dependency Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Antariksh Mahajan --- package-lock.json | 23 ----------------------- package.json | 1 - 2 files changed, 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24b866c504..d464e759d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5649,29 +5649,6 @@ "indent-string": "^4.0.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - }, - "dependencies": { - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - } - } - }, "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", diff --git a/package.json b/package.json index 629d17a4e0..3f969a494b 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "@opengovsg/spcp-auth-client": "^1.3.0", "@stablelib/base64": "^1.0.0", "JSONStream": "^1.3.5", - "ajv": "^5.2.3", "angular": "~1.8.0", "angular-animate": "^1.8.0", "angular-aria": "^1.8.0", From c72d433beda406166ae8d01cd1fdd703a09b6973 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 1 Sep 2020 18:03:21 +0800 Subject: [PATCH 03/31] fix: prevent discriminated models from being created before their base model (#244) * fix(FormModel): return discriminated model only after compiling base * fix(SubmissionModel): return discriminated model after compiling base * Fix tests Co-authored-by: Arshad Ali --- src/app/models/form.server.model.ts | 38 +++------ src/app/models/submission.server.model.ts | 38 +++------ src/types/form.ts | 2 +- ...mail-submissions.server.controller.spec.js | 12 ++- .../backend/models/form.server.model.spec.ts | 82 ++++++++++++++++--- 5 files changed, 98 insertions(+), 74 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/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 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/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 }, }, ) 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 0c475fe954c569f0786e808fe92f542994efd0c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 13:10:03 +0800 Subject: [PATCH 04/31] chore(deps-dev): bump prettier from 2.0.5 to 2.1.1 (#249) * chore(deps-dev): bump prettier from 2.0.5 to 2.1.1 Bumps [prettier](https://github.com/prettier/prettier) from 2.0.5 to 2.1.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.0.5...2.1.1) Signed-off-by: dependabot[bot] * chore: lint Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Antariksh Mahajan --- package-lock.json | 6 +++--- package.json | 2 +- .../core/componentViews/avatar-dropdown.html | 4 +--- .../views/edit-contact-number-modal.view.html | 8 ++------ .../response-title.client.view.html | 4 +--- .../configure-form.client.view.html | 16 ++++------------ .../directiveViews/edit-form.client.view.html | 8 ++------ .../settings-form.client.view.html | 4 +--- .../admin/views/activate-form.client.modal.html | 8 ++------ .../admin/views/admin-form.client.view.html | 4 +--- .../views/delete-field-warning.client.modal.html | 4 +--- .../admin/views/delete-form.client.modal.html | 4 +--- .../admin/views/edit-fields.client.modal.html | 8 +++----- .../views/edit-myinfo-field.client.modal.html | 8 ++------ .../admin/views/list-forms.client.view.html | 12 +++--------- .../views/mobile-edit-fields.client.modal.html | 4 +--- .../forms/admin/views/pop-up.client.modal.html | 4 +--- .../admin/views/view-responses.client.view.html | 8 ++------ .../feedback-form.client.view.html | 4 +--- .../field-dropdown.client.view.html | 4 +--- .../base/componentViews/verifiable-field.html | 4 +--- .../base/views/submit-progress.client.modal.html | 4 +--- .../modules/users/views/billing.client.view.html | 8 ++------ .../users/views/examples-card.client.view.html | 12 +++--------- 24 files changed, 41 insertions(+), 111 deletions(-) diff --git a/package-lock.json b/package-lock.json index d464e759d1..d2829c6084 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20842,9 +20842,9 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prettier": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", - "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.1.tgz", + "integrity": "sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw==", "dev": true }, "prettier-linter-helpers": { diff --git a/package.json b/package.json index 3f969a494b..da78aec36c 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ "mongodb-memory-server-core": "^5.1.5", "ngrok": "^3.2.7", "optimize-css-assets-webpack-plugin": "^5.0.1", - "prettier": "^2.0.5", + "prettier": "^2.1.1", "regenerator": "^0.14.4", "sinon": "^9.0.3", "stylelint": "^13.3.3", diff --git a/src/public/modules/core/componentViews/avatar-dropdown.html b/src/public/modules/core/componentViews/avatar-dropdown.html index 4f92c89fdb..874653b06a 100644 --- a/src/public/modules/core/componentViews/avatar-dropdown.html +++ b/src/public/modules/core/componentViews/avatar-dropdown.html @@ -10,9 +10,7 @@ ng-mouseenter="vm.isDropdownHover=true" ng-mouseleave="vm.isDropdownHover=false" > - +