From 2a3b79d70ad793c61281ba6772670e039bbfd1fa Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 1 Jun 2021 22:00:32 +0800 Subject: [PATCH 01/37] docs(scripts): add scripts to set student logos to selected forms (#2048) * docs(scripts): add scripts to set student logos to selected forms * doc(script): update script for completeness * fix: script comments --- .../set-rp-logos.js | 51 +++++++++++++++++++ .../set-sp-logos.js | 42 +++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 scripts/20210601_set-rp-sp-student-logos/set-rp-logos.js create mode 100644 scripts/20210601_set-rp-sp-student-logos/set-sp-logos.js diff --git a/scripts/20210601_set-rp-sp-student-logos/set-rp-logos.js b/scripts/20210601_set-rp-sp-student-logos/set-rp-logos.js new file mode 100644 index 0000000000..432b04be14 --- /dev/null +++ b/scripts/20210601_set-rp-sp-student-logos/set-rp-logos.js @@ -0,0 +1,51 @@ +/* eslint-disable */ + +// BEFORE +// A: Count number of forms belonging to RP students. +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds } }).count() +} +// Should be 0 +{ + db.forms.count({ + 'startPage.logo.fileId': { $eq: '1622549887610-rp%20student%20logo.png' }, + }) +} + +// UPDATE +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + let rpStudentFormIds = db.forms.find({ admin: { $in: rpStudentUserIds } }).map(b => b._id) + + db.forms.updateMany( + { + _id: { $in: rpStudentFormIds }, + }, + { + $set: { + 'startPage.logo': { + state: 'CUSTOM', + fileId: '1622549887610-rp%20student%20logo.png', + fileName: 'RP Student Logo.png', + fileSizeInBytes: 15242, + }, + }, + } + ) +} + +// AFTER + +// Number of forms RP's logo fileId. Should be equal to A. +{ + db.forms.count({ + 'startPage.logo.fileId': { $eq: '1622549887610-rp%20student%20logo.png' }, + }) +} +// Count number of forms belonging to RP students. +// Should still remain the same as A. +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds } }).count() +} \ No newline at end of file diff --git a/scripts/20210601_set-rp-sp-student-logos/set-sp-logos.js b/scripts/20210601_set-rp-sp-student-logos/set-sp-logos.js new file mode 100644 index 0000000000..a1b60f53ab --- /dev/null +++ b/scripts/20210601_set-rp-sp-student-logos/set-sp-logos.js @@ -0,0 +1,42 @@ +/* eslint-disable */ + +// BEFORE +// A: Count number of forms belonging to SP students. +{ + let rpStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds } }).count() +} +// Should be 0 +{ + db.forms.count({ 'startPage.logo.fileId': { $eq: '1622549643061-sp%20student%20logo.png' } }) +} + +// UPDATE +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + let spStudentFormIds = db.forms.find({ admin: { $in: spStudentUserIds } }).map(b => b._id) + + db.forms.updateMany( + { + _id: { $in: spStudentFormIds } + }, + { $set: { 'startPage.logo': { + "state" : "CUSTOM", + "fileId" : "1622549643061-sp%20student%20logo.png", + "fileName" : "SP Student Logo.png", + "fileSizeInBytes" : 14189 + } } } + ) +} + +// AFTER + +// Number of forms SP's logo fileId. Should be equal to A. +{ + db.forms.count({ 'startPage.logo.fileId': { $eq: '1622549643061-sp%20student%20logo.png' } }) +} +// Should still remain the same as A. +{ + let rpStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: spStudentUserIds } }).count() +} From 9aacdc61ecee67025628c4188927750a9746811b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jun 2021 17:23:32 +0000 Subject: [PATCH 02/37] chore(deps-dev): bump @opengovsg/mockpass from 2.7.2 to 2.7.3 (#2050) Bumps [@opengovsg/mockpass](https://github.com/opengovsg/mockpass) from 2.7.2 to 2.7.3. - [Release notes](https://github.com/opengovsg/mockpass/releases) - [Commits](https://github.com/opengovsg/mockpass/compare/v2.7.2...v2.7.3) --- updated-dependencies: - dependency-name: "@opengovsg/mockpass" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23d2b198cc..c38ad97be5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4861,9 +4861,9 @@ } }, "@opengovsg/mockpass": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@opengovsg/mockpass/-/mockpass-2.7.2.tgz", - "integrity": "sha512-m4484lKKfYCv4yiyKfCdphgLEHmNOtwVsbjSO9A2Dnfx7qhDLkPMTifziW85NecaUFnHlI1Q7Rg+S1mYaF9Xtg==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@opengovsg/mockpass/-/mockpass-2.7.3.tgz", + "integrity": "sha512-e/973c+IhMjCjx7TpiAwt6Sv8crr9utsDeirFnIdwvb6vRFD4enObdnQUE2lNnXSTxH3e9tLDJCuTV7mmATomA==", "dev": true, "requires": { "base-64": "^1.0.0", diff --git a/package.json b/package.json index 1ca105b797..f2ddb0b5de 100644 --- a/package.json +++ b/package.json @@ -160,7 +160,7 @@ "@babel/core": "^7.14.3", "@babel/plugin-transform-runtime": "^7.14.3", "@babel/preset-env": "^7.14.4", - "@opengovsg/mockpass": "^2.7.2", + "@opengovsg/mockpass": "^2.7.3", "@types/bcrypt": "^5.0.0", "@types/bluebird": "^3.5.35", "@types/busboy": "^0.2.3", From 9b3b7a8cb48779527b97965e526c44fba8178572 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:50:22 +0800 Subject: [PATCH 03/37] chore(deps-dev): bump type-fest from 0.20.2 to 1.2.0 (#2049) * chore(deps-dev): bump type-fest from 0.20.2 to 1.2.0 Bumps [type-fest](https://github.com/sindresorhus/type-fest) from 0.20.2 to 1.2.0. - [Release notes](https://github.com/sindresorhus/type-fest/releases) - [Commits](https://github.com/sindresorhus/type-fest/compare/v0.20.2...v1.2.0) --- updated-dependencies: - dependency-name: type-fest dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * fix: remove circular reference from PublicForm type seems to be introduced by the new Merge implementation in type-fest, go back to using Omit Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kar Rui Lau --- package-lock.json | 14 +++++++++++--- package.json | 2 +- src/types/form.ts | 11 ++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index c8d334cd55..98365b5409 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11422,6 +11422,14 @@ "dev": true, "requires": { "type-fest": "^0.20.2" + }, + "dependencies": { + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } } }, "has-flag": { @@ -25214,9 +25222,9 @@ "dev": true }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.2.0.tgz", + "integrity": "sha512-++0N6KyAj0t2webXst0PE0xuXb4Dv3z1Z+4SGzK+j/epeWBZCfkQbkW/ezscZwpinmBQ5wu/l4TqagKSVcAGCA==", "dev": true }, "type-is": { diff --git a/package.json b/package.json index bc09c7d37b..4522ae960e 100644 --- a/package.json +++ b/package.json @@ -245,7 +245,7 @@ "ts-loader": "^7.0.5", "ts-node": "^10.0.0", "ts-node-dev": "^1.1.6", - "type-fest": "^0.20.2", + "type-fest": "^1.2.0", "typescript": "^4.3.2", "url-loader": "^1.1.2", "webpack": "^4.46.0", diff --git a/src/types/form.ts b/src/types/form.ts index 2af5e60737..2c2ffa0829 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -1,6 +1,6 @@ import range from 'lodash/range' import { Document, LeanDocument, Model, ToObjectOptions, Types } from 'mongoose' -import { Merge, SetRequired } from 'type-fest' +import { SetRequired } from 'type-fest' import { OverrideProps } from '../app/modules/form/admin-form/admin-form.types' @@ -63,12 +63,9 @@ export type PublicFormValues = Pick< | 'responseMode' > -export type PublicForm = Merge< - PublicFormValues, - { - admin: PublicUser - } -> +export type PublicForm = Omit & { + admin: PublicUser +} export type FormOtpData = { form: IFormSchema['_id'] From ca621e93454aec893afa6babf19066b3ee95b68a Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Wed, 2 Jun 2021 13:15:13 +0800 Subject: [PATCH 04/37] fix(deps): update mongoose to 5.12.12, update model types (#2046) * fix(deps): update mongoose to 5.12.12 * fix: correct types of IEmail/EncryptedSubmissionSchema * refactor: remove explicit `this` in mongoose models --- package-lock.json | 16 ++++---- package.json | 2 +- .../models/admin_verification.server.model.ts | 2 - src/app/models/agency.server.model.ts | 4 +- src/app/models/form.server.model.ts | 40 ++++--------------- src/app/models/form_feedback.server.model.ts | 11 ++--- .../form_statistics_total.server.model.ts | 1 - src/app/models/login.server.model.ts | 2 - src/app/models/submission.server.model.ts | 14 +------ src/app/models/token.server.model.ts | 8 +--- src/app/models/user.server.model.ts | 4 +- src/app/modules/bounce/bounce.model.ts | 15 ++----- src/app/modules/myinfo/myinfo_hash.model.ts | 2 - .../verification/verification.model.ts | 10 +---- .../services/sms/sms_count.server.model.ts | 10 +++-- src/types/submission.ts | 7 ++-- 16 files changed, 40 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98365b5409..8f28b95b00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18458,14 +18458,14 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "5.12.7", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.7.tgz", - "integrity": "sha512-BniNwACn7uflK2h+M3juvyLH5nn9JDFgnB5KE2EwWFwSrRyhSpPnCtanRKJW3OtMCJyPccMIjtGZxHNW7JfnIw==", + "version": "5.12.12", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.12.tgz", + "integrity": "sha512-n+ZmGApaL5x/r92w6S4pb+c075i+YKzg1F9YWkznSzQMtvetj/2dSjj2cqsITpd6z60k3K7ZaosIl6hzHwUA9g==", "requires": { "@types/mongodb": "^3.5.27", "bson": "^1.1.4", "kareem": "2.3.2", - "mongodb": "3.6.6", + "mongodb": "3.6.8", "mongoose-legacy-pluralize": "1.0.2", "mpath": "0.8.3", "mquery": "3.2.5", @@ -18482,14 +18482,14 @@ "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, "mongodb": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.6.tgz", - "integrity": "sha512-WlirMiuV1UPbej5JeCMqE93JRfZ/ZzqE7nJTwP85XzjAF4rRSeq2bGCb1cjfoHLOF06+HxADaPGqT0g3SbVT1w==", + "version": "3.6.8", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.8.tgz", + "integrity": "sha512-sDjJvI73WjON1vapcbyBD3Ao9/VN3TKYY8/QX9EPbs22KaCSrQ5rXo5ZZd44tWJ3wl3FlnrFZ+KyUtNH6+1ZPQ==", "requires": { "bl": "^2.2.1", "bson": "^1.1.4", "denque": "^1.4.1", - "optional-require": "^1.0.2", + "optional-require": "^1.0.3", "safe-buffer": "^5.1.2", "saslprep": "^1.0.0" } diff --git a/package.json b/package.json index 4522ae960e..84c0e5a8ca 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "lodash": "^4.17.21", "moment-timezone": "0.5.33", "mongodb-uri": "^0.9.7", - "mongoose": "^5.12.7", + "mongoose": "^5.12.12", "multiparty": ">=4.2.2", "neverthrow": "^4.2.1", "ng-infinite-scroll": "^1.3.0", diff --git a/src/app/models/admin_verification.server.model.ts b/src/app/models/admin_verification.server.model.ts index 3f5d675999..e6ff3e895c 100644 --- a/src/app/models/admin_verification.server.model.ts +++ b/src/app/models/admin_verification.server.model.ts @@ -54,7 +54,6 @@ AdminVerificationSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 }) * Upserts given OTP into AdminVerification collection. */ AdminVerificationSchema.statics.upsertOtp = async function ( - this: IAdminVerificationModel, upsertParams: UpsertOtpParams, ) { return this.findOneAndUpdate( @@ -73,7 +72,6 @@ AdminVerificationSchema.statics.upsertOtp = async function ( * @returns the incremented document */ AdminVerificationSchema.statics.incrementAttemptsByAdminId = async function ( - this: IAdminVerificationModel, adminId: IUserSchema['_id'], ) { return this.findOneAndUpdate( diff --git a/src/app/models/agency.server.model.ts b/src/app/models/agency.server.model.ts index 31614def79..0930252b24 100644 --- a/src/app/models/agency.server.model.ts +++ b/src/app/models/agency.server.model.ts @@ -50,9 +50,7 @@ const AgencySchema = new Schema( ) // Methods -AgencySchema.methods.getPublicView = function ( - this: IAgencySchema, -): PublicAgency { +AgencySchema.methods.getPublicView = function (): PublicAgency { return pick(this, AGENCY_PUBLIC_FIELDS) as PublicAgency } diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index 0f0e8e9f95..709dd79b4a 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -435,10 +435,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { FormLogicPath.discriminator(LogicType.PreventSubmit, PreventSubmitLogicSchema) // Methods - FormSchema.methods.getDashboardView = function ( - this: IFormSchema, - admin: IPopulatedUser, - ) { + FormSchema.methods.getDashboardView = function (admin: IPopulatedUser) { return { _id: this._id, title: this.title, @@ -450,7 +447,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { } // Method to return myInfo attributes - FormSchema.methods.getUniqueMyInfoAttrs = function (this: IFormSchema) { + FormSchema.methods.getUniqueMyInfoAttrs = function () { if (this.authType !== AuthType.MyInfo) { return [] } @@ -461,7 +458,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Return essential form creation parameters with the given properties FormSchema.methods.getDuplicateParams = function ( - this: IFormSchema, overrideProps: OverrideProps, ) { const newForm = pick(this, [ @@ -477,7 +473,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { return { ...newForm, ...overrideProps } } - FormSchema.methods.getPublicView = function (this: IFormSchema): PublicForm { + FormSchema.methods.getPublicView = function (): PublicForm { const basePublicView = pick(this, FORM_PUBLIC_FIELDS) as PublicFormValues // Return non-populated public fields of form if not populated. @@ -493,7 +489,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { } // Archives form. - FormSchema.methods.archive = function (this: IFormSchema) { + FormSchema.methods.archive = function () { // Return instantly when form is already archived. if (this.status === Status.Archived) { return Promise.resolve(this) @@ -505,15 +501,12 @@ const compileFormModel = (db: Mongoose): IFormModel => { const FormDocumentSchema = (FormSchema as unknown) as Schema - FormDocumentSchema.methods.getSettings = function ( - this: IFormDocument, - ): FormSettings { + FormDocumentSchema.methods.getSettings = function (): FormSettings { return pick(this, FORM_SETTING_FIELDS) } // Transfer ownership of the form to another user FormDocumentSchema.methods.transferOwner = async function ( - this: IFormDocument, currentOwner: IUserSchema, newOwner: IUserSchema, ) { @@ -530,7 +523,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormDocumentSchema.methods.updateFormCollaborators = async function ( - this: IFormDocument, updatedPermissions: Permission[], ) { this.permissionList = updatedPermissions @@ -538,7 +530,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormDocumentSchema.methods.updateFormFieldById = function ( - this: IFormDocument, fieldId: string, newField: FormFieldWithId, ) { @@ -554,17 +545,13 @@ const compileFormModel = (db: Mongoose): IFormModel => { return this.save() } - FormDocumentSchema.methods.insertFormField = function ( - this: IFormDocument, - newField: FormField, - ) { + FormDocumentSchema.methods.insertFormField = function (newField: FormField) { // eslint-disable-next-line @typescript-eslint/no-extra-semi ;(this.form_fields as Types.DocumentArray).push(newField) return this.save() } FormDocumentSchema.methods.duplicateFormFieldById = function ( - this: IFormDocument, fieldId: string, ) { const fieldToDuplicate = getFormFieldById(this.form_fields, fieldId) @@ -579,7 +566,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormDocumentSchema.methods.reorderFormFieldById = function ( - this: IFormDocument, fieldId: string, newPosition: number, ): Promise { @@ -601,10 +587,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Statics // Method to retrieve data for OTP verification - FormSchema.statics.getOtpData = async function ( - this: IFormModel, - formId: string, - ) { + FormSchema.statics.getOtpData = async function (formId: string) { try { const data = await this.findById(formId, 'msgSrvcName admin').populate({ path: 'admin', @@ -627,7 +610,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Returns the form with populated admin details FormSchema.statics.getFullFormById = async function ( - this: IFormModel, formId: string, fields?: (keyof IPopulatedForm)[], ): Promise { @@ -641,7 +623,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Deactivate form by ID FormSchema.statics.deactivateById = async function ( - this: IFormModel, formId: string, ): Promise { const form = await this.findById(formId) @@ -653,7 +634,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormSchema.statics.getMetaByUserIdOrEmail = async function ( - this: IFormModel, userId: IUserSchema['_id'], userEmail: IUserSchema['email'], ): Promise { @@ -684,7 +664,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Deletes specified form logic. FormSchema.statics.deleteFormLogic = async function ( - this: IFormModel, formId: string, logicId: string, ): Promise { @@ -702,7 +681,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Creates specified form logic. FormSchema.statics.createFormLogic = async function ( - this: IFormModel, formId: string, createLogicBody: LogicDto, ): Promise { @@ -718,7 +696,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Deletes specified form field by id. FormSchema.statics.deleteFormFieldById = async function ( - this: IFormModel, formId: string, fieldId: string, ): Promise { @@ -731,7 +708,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Updates specified form logic. FormSchema.statics.updateFormLogic = async function ( - this: IFormModel, formId: string, logicId: string, updatedLogic: LogicDto, @@ -750,7 +726,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormSchema.statics.updateEndPageById = async function ( - this: IFormModel, formId: string, newEndPage: EndPage, ) { @@ -762,7 +737,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } FormSchema.statics.updateStartPageById = async function ( - this: IFormModel, formId: string, newStartPage: StartPage, ) { diff --git a/src/app/models/form_feedback.server.model.ts b/src/app/models/form_feedback.server.model.ts index 3f42609a83..2197c4f980 100644 --- a/src/app/models/form_feedback.server.model.ts +++ b/src/app/models/form_feedback.server.model.ts @@ -7,7 +7,7 @@ import { FORM_SCHEMA_ID } from './form.server.model' export const FORM_FEEDBACK_SCHEMA_ID = 'FormFeedback' export const FORM_FEEDBACK_COLLECTION_NAME = 'formfeedback' -const FormFeedbackSchema = new Schema( +const FormFeedbackSchema = new Schema( { formId: { type: Schema.Types.ObjectId, @@ -39,15 +39,10 @@ const FormFeedbackSchema = new Schema( * @param formId the form id to return the submissions cursor for * @returns a cursor to the feedback retrieved */ -const getFeedbackCursorByFormId: IFormFeedbackModel['getFeedbackCursorByFormId'] = function ( - this: IFormFeedbackModel, - formId, -) { +FormFeedbackSchema.statics.getFeedbackCursorByFormId = function (formId) { return this.find({ formId }).batchSize(2000).read('secondary').lean().cursor() } -FormFeedbackSchema.statics.getFeedbackCursorByFormId = getFeedbackCursorByFormId - /** * Form Feedback Schema * @param db Active DB Connection @@ -57,7 +52,7 @@ const getFormFeedbackModel = (db: Mongoose): IFormFeedbackModel => { try { return db.model(FORM_FEEDBACK_SCHEMA_ID) as IFormFeedbackModel } catch { - return db.model( + return db.model( FORM_FEEDBACK_SCHEMA_ID, FormFeedbackSchema, FORM_FEEDBACK_COLLECTION_NAME, diff --git a/src/app/models/form_statistics_total.server.model.ts b/src/app/models/form_statistics_total.server.model.ts index 212eaec6b7..76aa6962c7 100644 --- a/src/app/models/form_statistics_total.server.model.ts +++ b/src/app/models/form_statistics_total.server.model.ts @@ -51,7 +51,6 @@ const compileFormStatisticsTotalModel = (db: Mongoose) => { // Static functions FormStatisticsTotalSchema.statics.aggregateFormCount = function ( - this: IFormStatisticsTotalModel, minSubCount: number, ): Promise { return this.aggregate([ diff --git a/src/app/models/login.server.model.ts b/src/app/models/login.server.model.ts index b39a4b812b..d9276d302d 100644 --- a/src/app/models/login.server.model.ts +++ b/src/app/models/login.server.model.ts @@ -54,7 +54,6 @@ const LoginSchema = new Schema( ) LoginSchema.statics.addLoginFromForm = function ( - this: ILoginModel, form: IPopulatedForm, ): Promise { if (!form.authType || !form.esrvcId) { @@ -72,7 +71,6 @@ LoginSchema.statics.addLoginFromForm = function ( } LoginSchema.statics.aggregateLoginStats = function ( - this: ILoginModel, esrvcId: string, gte: Date, lte: Date, diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index 4d0e434d4f..64a6b072f1 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -71,7 +71,6 @@ SubmissionSchema.index({ // Base schema static methods SubmissionSchema.statics.findFormsWithSubsAbove = function ( - this: ISubmissionModel, minSubCount: number, ): Promise { return this.aggregate([ @@ -179,9 +178,7 @@ const EncryptSubmissionSchema = new Schema< * Returns an object which represents the encrypted submission * which will be posted to the webhook URL. */ -EncryptSubmissionSchema.methods.getWebhookView = function ( - this: IEncryptedSubmissionSchema, -): WebhookView { +EncryptSubmissionSchema.methods.getWebhookView = function (): WebhookView { const webhookData: WebhookData = { formId: String(this.form), submissionId: String(this._id), @@ -197,7 +194,6 @@ EncryptSubmissionSchema.methods.getWebhookView = function ( } EncryptSubmissionSchema.statics.addWebhookResponse = function ( - this: IEncryptSubmissionModel, submissionId: string, webhookResponse: IWebhookResponse, ): Promise { @@ -209,7 +205,6 @@ EncryptSubmissionSchema.statics.addWebhookResponse = function ( } EncryptSubmissionSchema.statics.findSingleMetadata = function ( - this: IEncryptSubmissionModel, formId: string, submissionId: string, ): Promise { @@ -254,7 +249,6 @@ type MetadataAggregateResult = { } EncryptSubmissionSchema.statics.findAllMetadataByFormId = function ( - this: IEncryptSubmissionModel, formId: string, { page = 1, @@ -319,8 +313,7 @@ EncryptSubmissionSchema.statics.findAllMetadataByFormId = function ( ) } -const getSubmissionCursorByFormId: IEncryptSubmissionModel['getSubmissionCursorByFormId'] = function ( - this: IEncryptSubmissionModel, +EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = function ( formId, dateRange = {}, ) { @@ -345,10 +338,7 @@ const getSubmissionCursorByFormId: IEncryptSubmissionModel['getSubmissionCursorB ) } -EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = getSubmissionCursorByFormId - EncryptSubmissionSchema.statics.findEncryptedSubmissionById = function ( - this: IEncryptSubmissionModel, formId: string, submissionId: string, ) { diff --git a/src/app/models/token.server.model.ts b/src/app/models/token.server.model.ts index c07699a2b5..ba954686a5 100644 --- a/src/app/models/token.server.model.ts +++ b/src/app/models/token.server.model.ts @@ -33,7 +33,6 @@ TokenSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 }) * Upserts given OTP into Token collection. */ TokenSchema.statics.upsertOtp = async function ( - this: ITokenModel, upsertParams: Omit, ) { return this.findOneAndUpdate( @@ -51,10 +50,7 @@ TokenSchema.statics.upsertOtp = async function ( * given email. * @param email the email to retrieve the related Token document */ -TokenSchema.statics.incrementAttemptsByEmail = async function ( - this: ITokenModel, - email: string, -) { +TokenSchema.statics.incrementAttemptsByEmail = async function (email: string) { return this.findOneAndUpdate( { email: email }, { $inc: { numOtpAttempts: 1 } }, @@ -67,7 +63,7 @@ TokenSchema.statics.incrementAttemptsByEmail = async function ( * @param db - Active DB Connection * @returns Token model */ -const getTokenModel = (db: Mongoose) => { +const getTokenModel = (db: Mongoose): ITokenModel => { try { return db.model(TOKEN_SCHEMA_ID) as ITokenModel } catch { diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts index 84c334b4e7..07977a2980 100644 --- a/src/app/models/user.server.model.ts +++ b/src/app/models/user.server.model.ts @@ -104,7 +104,7 @@ const compileUserModel = (db: Mongoose) => { ) // Methods - UserSchema.methods.getPublicView = function (this: IUserSchema): PublicUser { + UserSchema.methods.getPublicView = function (): PublicUser { // Return public view of nested agency document if populated. return { agency: this.populated('agency') @@ -118,7 +118,6 @@ const compileUserModel = (db: Mongoose) => { * Upserts given user details into User collection. */ UserSchema.statics.upsertUser = async function ( - this: IUserModel, upsertParams: Pick, ) { return this.findOneAndUpdate( @@ -141,7 +140,6 @@ const compileUserModel = (db: Mongoose) => { * User collection. */ UserSchema.statics.findContactNumbersByEmails = async function ( - this: IUserModel, emails: string[], ) { return this.find() diff --git a/src/app/modules/bounce/bounce.model.ts b/src/app/modules/bounce/bounce.model.ts index da20085f46..10df96d501 100644 --- a/src/app/modules/bounce/bounce.model.ts +++ b/src/app/modules/bounce/bounce.model.ts @@ -77,7 +77,6 @@ BounceSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 }) * @returns the created Bounce document */ BounceSchema.statics.fromSnsNotification = function ( - this: IBounceModel, snsInfo: IEmailNotification, formId: string, ): IBounceSchema { @@ -103,7 +102,6 @@ BounceSchema.statics.fromSnsNotification = function ( * @returns the updated document */ BounceSchema.methods.updateBounceInfo = function ( - this: IBounceSchema, snsInfo: IEmailNotification, ): IBounceSchema { // First, get rid of outdated emails @@ -141,9 +139,7 @@ BounceSchema.methods.updateBounceInfo = function ( * bounced), false otherwise. * @returns true if all recipients bounced */ -BounceSchema.methods.isCriticalBounce = function ( - this: IBounceSchema, -): boolean { +BounceSchema.methods.isCriticalBounce = function (): boolean { return this.bounces.every((emailInfo) => emailInfo.hasBounced) } @@ -152,9 +148,7 @@ BounceSchema.methods.isCriticalBounce = function ( * all bounces were permanent, false otherwise. * @returns true if all bounecs were permanent */ -BounceSchema.methods.areAllPermanentBounces = function ( - this: IBounceSchema, -): boolean { +BounceSchema.methods.areAllPermanentBounces = function (): boolean { return this.bounces.every( (emailInfo) => emailInfo.hasBounced && emailInfo.bounceType === BounceType.Permanent, @@ -165,7 +159,7 @@ BounceSchema.methods.areAllPermanentBounces = function ( * Returns the list of email recipients for this form * @returns Array of email addresses */ -BounceSchema.methods.getEmails = function (this: IBounceSchema): string[] { +BounceSchema.methods.getEmails = function (): string[] { // Return a regular array to prevent unexpected bugs with mongoose // CoreDocumentArray return Array.from(this.bounces.map((emailInfo) => emailInfo.email)) @@ -177,7 +171,6 @@ BounceSchema.methods.getEmails = function (this: IBounceSchema): string[] { * @returns void. Modifies document in place. */ BounceSchema.methods.setNotificationState = function ( - this: IBounceSchema, emailRecipients: string[], smsRecipients: UserContactView[], ): void { @@ -194,7 +187,7 @@ BounceSchema.methods.setNotificationState = function ( * false otherwise. * @returns true if at least one admin or collaborator has been notified */ -BounceSchema.methods.hasNotified = function (this: IBounceSchema): boolean { +BounceSchema.methods.hasNotified = function (): boolean { return this.hasAutoEmailed || this.hasAutoSmsed } diff --git a/src/app/modules/myinfo/myinfo_hash.model.ts b/src/app/modules/myinfo/myinfo_hash.model.ts index bf4c168a8c..28bbdb4d5d 100644 --- a/src/app/modules/myinfo/myinfo_hash.model.ts +++ b/src/app/modules/myinfo/myinfo_hash.model.ts @@ -45,7 +45,6 @@ MyInfoHashSchema.index({ MyInfoHashSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 }) MyInfoHashSchema.statics.updateHashes = async function ( - this: IMyInfoHashModel, uinFin: string, formId: string, readOnlyHashes: IHashes, @@ -71,7 +70,6 @@ MyInfoHashSchema.statics.updateHashes = async function ( } MyInfoHashSchema.statics.findHashes = async function ( - this: IMyInfoHashModel, uinFin: string, formId: string, ): Promise { diff --git a/src/app/modules/verification/verification.model.ts b/src/app/modules/verification/verification.model.ts index 53d96c3d82..c090a5864f 100644 --- a/src/app/modules/verification/verification.model.ts +++ b/src/app/modules/verification/verification.model.ts @@ -69,14 +69,11 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { }) // Instance methods - VerificationSchema.methods.getPublicView = function ( - this: IVerificationSchema, - ): PublicTransaction { + VerificationSchema.methods.getPublicView = function (): PublicTransaction { return pick(this, VERIFICATION_PUBLIC_FIELDS) as PublicTransaction } VerificationSchema.methods.getField = function ( - this: IVerificationSchema, fieldId: string, ): IVerificationFieldSchema | undefined { return this.fields.find((field) => field._id === fieldId) @@ -85,7 +82,6 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { // Static methods // Method to return non-sensitive fields VerificationSchema.statics.getPublicViewById = async function ( - this: IVerificationModel, id: IVerificationSchema['_id'], ): Promise { const document = await this.findById(id) @@ -94,7 +90,6 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { } VerificationSchema.statics.createTransactionFromForm = async function ( - this: IVerificationModel, form: IFormSchema, ): Promise { const { form_fields } = form @@ -108,7 +103,6 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { } VerificationSchema.statics.incrementFieldRetries = async function ( - this: IVerificationModel, transactionId: string, fieldId: string, ): Promise { @@ -131,7 +125,6 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { } VerificationSchema.statics.resetField = async function ( - this: IVerificationModel, transactionId: string, fieldId: string, ): Promise { @@ -157,7 +150,6 @@ const compileVerificationModel = (db: Mongoose): IVerificationModel => { } VerificationSchema.statics.updateHashForField = async function ( - this: IVerificationModel, updateData: UpdateFieldData, ): Promise { return this.findOneAndUpdate( diff --git a/src/app/services/sms/sms_count.server.model.ts b/src/app/services/sms/sms_count.server.model.ts index e7638e2ac8..9073ca0df2 100644 --- a/src/app/services/sms/sms_count.server.model.ts +++ b/src/app/services/sms/sms_count.server.model.ts @@ -109,10 +109,12 @@ const compileSmsCountModel = (db: Mongoose) => { }, ) - SmsCountSchema.statics.logSms = async function ( - this: ISmsCountModel, - { smsData, msgSrvcSid, smsType, logType }: LogSmsParams, - ) { + SmsCountSchema.statics.logSms = async function ({ + smsData, + msgSrvcSid, + smsType, + logType, + }: LogSmsParams) { const schemaData: Omit = { ...smsData, msgSrvcSid, diff --git a/src/types/submission.ts b/src/types/submission.ts index 150a833b36..0af21c0e26 100644 --- a/src/types/submission.ts +++ b/src/types/submission.ts @@ -83,7 +83,7 @@ export interface IEmailSubmission extends ISubmission { getWebhookView(): null } -export type IEmailSubmissionSchema = IEmailSubmission & ISubmissionSchema +export interface IEmailSubmissionSchema extends IEmailSubmission, Document {} export interface IEncryptedSubmission extends ISubmission { recipientEmails: never @@ -98,8 +98,9 @@ export interface IEncryptedSubmission extends ISubmission { getWebhookView(): WebhookView } -export type IEncryptedSubmissionSchema = IEncryptedSubmission & - ISubmissionSchema +export interface IEncryptedSubmissionSchema + extends IEncryptedSubmission, + Document {} export interface IWebhookResponse { webhookUrl: string From 23f9a9fe9675eab1d25c1983a08a7c76e0139d52 Mon Sep 17 00:00:00 2001 From: LoneRifle Date: Wed, 2 Jun 2021 15:06:21 +0800 Subject: [PATCH 05/37] test(betas): provide coverage In preparation to porting betas.client.factory to TypeScript, write a suite of unit tests to ensure functionality does not change during the migration. Restructure the AngularJS factory a bit to make code paths easy to reach. - Restructure `betas.client.factory` so that its functions are accessible both via module import and `angular.module().factory()` - Allow the functions to take in other beta feature fields for test purposes, defaulting to `BETA_FEATURES_FIELD` - Provide test coverage via jest, crowbar-ing a mock into the angular global at runtime, to minimise changes to the module under test --- .../forms/services/betas.client.factory.js | 86 +++++++++++-------- .../services/__tests__/BetaService.test.ts | 86 +++++++++++++++++++ 2 files changed, 138 insertions(+), 34 deletions(-) create mode 100644 src/public/services/__tests__/BetaService.test.ts diff --git a/src/public/modules/forms/services/betas.client.factory.js b/src/public/modules/forms/services/betas.client.factory.js index da580bae8e..2cdd7d08e2 100644 --- a/src/public/modules/forms/services/betas.client.factory.js +++ b/src/public/modules/forms/services/betas.client.factory.js @@ -2,49 +2,67 @@ const { get, forEach } = require('lodash') angular.module('forms').factory('Betas', [Betas]) -function Betas() { - const BETA_FEATURES_FIELD = { - // This is an example of how to add fields to this object - // featureName: { - // flag: 'betaFlagName', - // matches: (field) => - // field.fieldType === 'mobile' && - // (field.isVerifiable === true), - // }, - } +const BETA_FEATURES_FIELD = { + // This is an example of how to add fields to this object + // featureName: { + // flag: 'betaFlagName', + // matches: (field) => + // field.fieldType === 'mobile' && + // (field.isVerifiable === true), + // }, +} - const getBetaFeaturesForFields = (formFields) => { - let betaFeatures = new Set() - forEach(formFields, (field) => { - forEach(BETA_FEATURES_FIELD, (feature, name) => { - if (feature.matches(field)) betaFeatures.add(name) - }) +const getBetaFeaturesForFields = (formFields, betaFeaturesField) => { + let betaFeatures = new Set() + forEach(formFields, (field) => { + forEach(betaFeaturesField, (feature, name) => { + if (feature.matches(field)) betaFeatures.add(name) }) - return Array.from(betaFeatures) - } + }) + return Array.from(betaFeatures) +} - const getMissingFieldPermissions = (user, form) => { - const betaFeatures = getBetaFeaturesForFields(form.form_fields) - return betaFeatures.filter((name) => { - const flag = get(BETA_FEATURES_FIELD, [name, 'flag']) - return flag && !get(user, ['betaFlags', flag], false) - }) - } +const getMissingFieldPermissions = ( + user, + form, + betaFeaturesField = BETA_FEATURES_FIELD, +) => { + const betaFeatures = getBetaFeaturesForFields( + form.form_fields, + betaFeaturesField, + ) + return betaFeatures.filter((name) => { + const flag = get(betaFeaturesField, [name, 'flag']) + return flag && !get(user, ['betaFlags', flag], false) + }) +} - const isBetaField = (fieldType) => { - return BETA_FEATURES_FIELD[fieldType] - } +const isBetaField = (fieldType, betaFeaturesField = BETA_FEATURES_FIELD) => { + return betaFeaturesField[fieldType] +} - const userHasAccessToFieldType = (user, fieldType) => { - const flag = get(BETA_FEATURES_FIELD, [fieldType, 'flag']) - return ( - !isBetaField(fieldType) || (flag && get(user, ['betaFlags', flag], false)) - ) - } +const userHasAccessToFieldType = ( + user, + fieldType, + betaFeaturesField = BETA_FEATURES_FIELD, +) => { + const flag = get(betaFeaturesField, [fieldType, 'flag']) + return ( + !isBetaField(fieldType, betaFeaturesField) || + (flag && get(user, ['betaFlags', flag], false)) + ) +} +function Betas() { return { getMissingFieldPermissions, isBetaField, userHasAccessToFieldType, } } + +module.exports = { + getMissingFieldPermissions, + isBetaField, + userHasAccessToFieldType, +} diff --git a/src/public/services/__tests__/BetaService.test.ts b/src/public/services/__tests__/BetaService.test.ts new file mode 100644 index 0000000000..7c07bebe5e --- /dev/null +++ b/src/public/services/__tests__/BetaService.test.ts @@ -0,0 +1,86 @@ +import { IFieldSchema, IFormDocument } from '../../../types' + +window.angular = { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + module: jest.fn(() => ({ + factory: jest.fn(), + })), +} + +// eslint-disable-next-line import/first +import * as BetaService from '../../modules/forms/services/betas.client.factory' + +describe('BetaService', () => { + const nonBetaFeature = 'nonBetaFeature' + const featureOne = 'featureOne' + const featureTwo = 'featureTwo' + const betaFeaturesField = { + [featureOne]: { + flag: 'betaFlagOne', + matches(field: IFieldSchema) { + return field.title === featureOne + }, + }, + [featureTwo]: { + flag: 'betaFlagTwo', + matches(field: IFieldSchema) { + return field.title === featureTwo + }, + }, + } + const userWithFeatureOne = { + betaFlags: { + [betaFeaturesField[featureOne].flag]: true, + }, + } + describe('isBetaField', () => { + it('returns truthy for defined beta fields', () => { + const result = BetaService.isBetaField(featureOne, betaFeaturesField) + expect(result).toBeTruthy() + }) + it('returns falsy for fields not defined as beta', () => { + const result = BetaService.isBetaField(nonBetaFeature, betaFeaturesField) + expect(result).toBeFalsy() + }) + }) + describe('userHasAccessToFieldType', () => { + it('returns true for features not defined as beta', () => { + const result = BetaService.userHasAccessToFieldType( + userWithFeatureOne, + nonBetaFeature, + betaFeaturesField, + ) + expect(result).toBeTrue() + }) + it('returns true for beta features that the user has access to', () => { + const result = BetaService.userHasAccessToFieldType( + userWithFeatureOne, + featureOne, + betaFeaturesField, + ) + expect(result).toBeTrue() + }) + it('returns false for beta features that the user lacks access to', () => { + const result = BetaService.userHasAccessToFieldType( + userWithFeatureOne, + featureTwo, + betaFeaturesField, + ) + expect(result).toBeFalse() + }) + }) + describe('getBetaFeaturesForFields', () => { + const form = { + form_fields: [{ title: featureTwo }], + } as IFormDocument + it('lists the beta features that the user lacks', () => { + const result = BetaService.getMissingFieldPermissions( + userWithFeatureOne, + form, + betaFeaturesField, + ) + expect(result).toStrictEqual([featureTwo]) + }) + }) +}) From 1d9e67d65988446732e31d2de94dcf93672f6a92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 17:21:02 +0000 Subject: [PATCH 06/37] chore(deps-dev): bump @types/node from 14.17.1 to 14.17.2 (#2059) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 14.17.1 to 14.17.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f28b95b00..de85c47468 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5534,9 +5534,9 @@ "dev": true }, "@types/node": { - "version": "14.17.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.1.tgz", - "integrity": "sha512-/tpUyFD7meeooTRwl3sYlihx2BrJE7q9XF71EguPFIySj9B7qgnRtHsHTho+0AUm4m1SvWGm6uSncrR94q6Vtw==" + "version": "14.17.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.2.tgz", + "integrity": "sha512-sld7b/xmFum66AAKuz/rp/CUO8+98fMpyQ3SBfzzBNGMd/1iHBTAg9oyAvcYlAj46bpc74r91jSw2iFdnx29nw==" }, "@types/nodemailer": { "version": "6.4.2", diff --git a/package.json b/package.json index 84c0e5a8ca..b889ce5dcd 100644 --- a/package.json +++ b/package.json @@ -180,7 +180,7 @@ "@types/json-stringify-safe": "^5.0.0", "@types/mongodb": "^3.6.17", "@types/mongodb-uri": "^0.9.0", - "@types/node": "^14.17.1", + "@types/node": "^14.17.2", "@types/nodemailer": "^6.4.2", "@types/opossum": "^4.1.1", "@types/promise-retry": "^1.1.3", From ceeeb91e87a6c882ba61431b40d1694bb9592db8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jun 2021 17:25:48 +0000 Subject: [PATCH 07/37] fix(deps): bump aws-sdk from 2.918.0 to 2.919.0 (#2060) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.918.0 to 2.919.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.918.0...v2.919.0) --- updated-dependencies: - dependency-name: aws-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index de85c47468..8c7adebad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7098,9 +7098,9 @@ "integrity": "sha512-24q5Rh3bno7ldoyCq99d6hpnLI+PAMocdeVaaGt/5BTQMprvDwQToHfNnruqN11odCHZZIQbRBw+nZo1lTCH9g==" }, "aws-sdk": { - "version": "2.918.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.918.0.tgz", - "integrity": "sha512-ZjWanOA1Zo664EyWLCnbUlkwCjoRPmSIMx529W4gk1418qo3oCEcvUy1HeibGGIClYnZZ7J4FMQvVDm2+JtHLQ==", + "version": "2.919.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.919.0.tgz", + "integrity": "sha512-9hPKuw3w7iDeDByIeoMspKgmiljTtYGM3bDkICEIDeJuPAlEMu+z3jOTFu5gHzZ95noXRqhRL1eeXI7o1F/hpQ==", "requires": { "buffer": "4.9.2", "events": "1.1.1", diff --git a/package.json b/package.json index b889ce5dcd..29c7cbe10e 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "angular-ui-bootstrap": "~2.5.6", "angular-ui-router": "~1.0.29", "aws-info": "^1.2.0", - "aws-sdk": "^2.918.0", + "aws-sdk": "^2.919.0", "axios": "^0.21.1", "bcrypt": "^5.0.1", "bluebird": "^3.5.2", From f88ad8997cc1b754ef7f22e6596835d7f3da4dc1 Mon Sep 17 00:00:00 2001 From: Chow Yi Yin <54089291+chowyiyin@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:30:35 +0800 Subject: [PATCH 08/37] refactor: replace set hook with validator hook in emailField model (#1971) * refactor: replace set hook with validator hook in emailField model * refactor: edit error message and remove repeated pre-validate hook * fix: allow update if includeFormSummary is false * refactor: eedit validator and add comments --- src/app/models/field/emailField.ts | 31 +++++++++++++----------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/app/models/field/emailField.ts b/src/app/models/field/emailField.ts index f2793282fd..3913ca6f33 100644 --- a/src/app/models/field/emailField.ts +++ b/src/app/models/field/emailField.ts @@ -29,9 +29,19 @@ const createEmailFieldSchema = (): Schema => { includeFormSummary: { type: Boolean, default: false, - set: function (this: IEmailFieldSchema, v: boolean) { - // PDF response not allowed for encrypt forms - return this.parent().responseMode === ResponseMode.Encrypt ? false : v + validate: { + validator: function (this: IEmailFieldSchema, v: boolean) { + // always ok to set to false + return ( + !v || + // either true or false is okay if not in storage mode + this.parent().responseMode !== ResponseMode.Encrypt || + // in storage mode, we can ignore this field if email confirmation is not enabled anyway + !this.autoReplyOptions.hasAutoReply + ) + }, + message: + 'PDF response summaries are not allowed for email confirmations in storage mode forms', }, }, }, @@ -62,21 +72,6 @@ const createEmailFieldSchema = (): Schema => { }, }) - // PDF response not allowed if autoreply is set in encrypted forms. If - // autoreply is not set, then we don't care whether the pdf is set. - EmailFieldSchema.pre('validate', function (next) { - if (this.parent().responseMode === ResponseMode.Encrypt) { - const { hasAutoReply, includeFormSummary } = this.autoReplyOptions || {} - if (hasAutoReply && includeFormSummary) { - return next( - Error('Autoreply PDF is not allowed for storage mode forms'), - ) - } - } - - return next() - }) - return EmailFieldSchema } From 74a2ec8406f215eecaf787e24edd150962eb627d Mon Sep 17 00:00:00 2001 From: LoneRifle Date: Wed, 2 Jun 2021 16:04:44 +0800 Subject: [PATCH 09/37] refactor(beta): migrate to TypeScript --- src/public/main.js | 1 - .../edit-fields-modal.client.controller.js | 2 - .../list-forms.client.controller.js | 5 +- .../directives/edit-form.client.directive.js | 7 +- .../forms/services/betas.client.factory.js | 68 ----------------- .../examples-card.client.directive.js | 5 +- src/public/services/BetaService.ts | 75 +++++++++++++++++++ .../services/__tests__/BetaService.test.ts | 22 ++---- 8 files changed, 88 insertions(+), 97 deletions(-) delete mode 100644 src/public/modules/forms/services/betas.client.factory.js create mode 100644 src/public/services/BetaService.ts diff --git a/src/public/main.js b/src/public/main.js index 61c622bb0a..e29766efe4 100644 --- a/src/public/main.js +++ b/src/public/main.js @@ -264,7 +264,6 @@ require('./modules/forms/services/spcp-session.client.factory.js') require('./modules/forms/services/submissions.client.factory.js') require('./modules/forms/services/toastr.client.factory.js') require('./modules/forms/services/attachment.client.service.js') -require('./modules/forms/services/betas.client.factory.js') require('./modules/forms/services/captcha.client.service.js') require('./modules/forms/services/mailto.client.factory.js') diff --git a/src/public/modules/forms/admin/controllers/edit-fields-modal.client.controller.js b/src/public/modules/forms/admin/controllers/edit-fields-modal.client.controller.js index baea64ef3d..f576c7d167 100644 --- a/src/public/modules/forms/admin/controllers/edit-fields-modal.client.controller.js +++ b/src/public/modules/forms/admin/controllers/edit-fields-modal.client.controller.js @@ -28,7 +28,6 @@ angular 'Attachment', 'FormFields', '$q', - 'Betas', 'Auth', '$state', 'Toastr', @@ -43,7 +42,6 @@ function EditFieldsModalController( Attachment, FormFields, $q, - Betas, Auth, $state, Toastr, diff --git a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js index 250e790582..5ad9d65bac 100644 --- a/src/public/modules/forms/admin/controllers/list-forms.client.controller.js +++ b/src/public/modules/forms/admin/controllers/list-forms.client.controller.js @@ -1,6 +1,7 @@ 'use strict' const get = require('lodash/get') +const BetaService = require('../../../../services/BetaService') // Forms controller angular @@ -15,7 +16,6 @@ angular '$timeout', '$window', 'Toastr', - 'Betas', ListFormsController, ]) @@ -29,7 +29,6 @@ function ListFormsController( $timeout, $window, Toastr, - Betas, ) { const vm = this @@ -172,7 +171,7 @@ function ListFormsController( } vm.duplicateForm = function (formIndex) { - const missingBetaPermissions = Betas.getMissingFieldPermissions( + const missingBetaPermissions = BetaService.getMissingFieldPermissions( vm.user, vm.myforms[formIndex], ) diff --git a/src/public/modules/forms/admin/directives/edit-form.client.directive.js b/src/public/modules/forms/admin/directives/edit-form.client.directive.js index 4ec0fd20ee..c7cf72af69 100644 --- a/src/public/modules/forms/admin/directives/edit-form.client.directive.js +++ b/src/public/modules/forms/admin/directives/edit-form.client.directive.js @@ -3,6 +3,7 @@ const { groupLogicUnitsByField } = require('shared/util/logic') const { reorder } = require('shared/util/immutable-array-fns') const FieldFactory = require('../../helpers/field-factory') const { UPDATE_FORM_TYPES } = require('../constants/update-form-types') +const BetaService = require('../../../../services/BetaService') const newFields = new Set() // Adding a fieldTypes will add a "new" label. @@ -35,7 +36,6 @@ function editFormDirective() { 'FormFields', '$uibModal', 'responseModeEnum', - 'Betas', '$window', 'Attachment', editFormController, @@ -48,7 +48,6 @@ function editFormController( FormFields, $uibModal, responseModeEnum, - Betas, $window, Attachment, ) { @@ -56,7 +55,7 @@ function editFormController( $scope.responseModeEnum = responseModeEnum $scope.isBetaField = function (fieldType) { - return Betas.isBetaField(fieldType) + return BetaService.isBetaField(fieldType) } $scope.isNewField = function (fieldType) { @@ -65,7 +64,7 @@ function editFormController( $scope.adminHasAccess = function (fieldType) { const user = $scope.myform.admin - return Betas.userHasAccessToFieldType(user, fieldType) + return BetaService.userHasAccessToFieldType(user, fieldType) } $scope.getFieldTitle = FormFields.getFieldTitle diff --git a/src/public/modules/forms/services/betas.client.factory.js b/src/public/modules/forms/services/betas.client.factory.js deleted file mode 100644 index 2cdd7d08e2..0000000000 --- a/src/public/modules/forms/services/betas.client.factory.js +++ /dev/null @@ -1,68 +0,0 @@ -const { get, forEach } = require('lodash') - -angular.module('forms').factory('Betas', [Betas]) - -const BETA_FEATURES_FIELD = { - // This is an example of how to add fields to this object - // featureName: { - // flag: 'betaFlagName', - // matches: (field) => - // field.fieldType === 'mobile' && - // (field.isVerifiable === true), - // }, -} - -const getBetaFeaturesForFields = (formFields, betaFeaturesField) => { - let betaFeatures = new Set() - forEach(formFields, (field) => { - forEach(betaFeaturesField, (feature, name) => { - if (feature.matches(field)) betaFeatures.add(name) - }) - }) - return Array.from(betaFeatures) -} - -const getMissingFieldPermissions = ( - user, - form, - betaFeaturesField = BETA_FEATURES_FIELD, -) => { - const betaFeatures = getBetaFeaturesForFields( - form.form_fields, - betaFeaturesField, - ) - return betaFeatures.filter((name) => { - const flag = get(betaFeaturesField, [name, 'flag']) - return flag && !get(user, ['betaFlags', flag], false) - }) -} - -const isBetaField = (fieldType, betaFeaturesField = BETA_FEATURES_FIELD) => { - return betaFeaturesField[fieldType] -} - -const userHasAccessToFieldType = ( - user, - fieldType, - betaFeaturesField = BETA_FEATURES_FIELD, -) => { - const flag = get(betaFeaturesField, [fieldType, 'flag']) - return ( - !isBetaField(fieldType, betaFeaturesField) || - (flag && get(user, ['betaFlags', flag], false)) - ) -} - -function Betas() { - return { - getMissingFieldPermissions, - isBetaField, - userHasAccessToFieldType, - } -} - -module.exports = { - getMissingFieldPermissions, - isBetaField, - userHasAccessToFieldType, -} diff --git a/src/public/modules/users/controllers/examples-card.client.directive.js b/src/public/modules/users/controllers/examples-card.client.directive.js index 7b57c7b823..2331969a89 100644 --- a/src/public/modules/users/controllers/examples-card.client.directive.js +++ b/src/public/modules/users/controllers/examples-card.client.directive.js @@ -1,4 +1,5 @@ 'use strict' +const BetaService = require('../../../services/BetaService') angular.module('users').directive('examplesCard', [examplesCard]) @@ -21,7 +22,6 @@ function examplesCard() { 'GTag', 'Auth', '$location', - 'Betas', 'Toastr', examplesCardController, ], @@ -37,7 +37,6 @@ function examplesCardController( GTag, Auth, $location, - Betas, Toastr, ) { $scope.user = Auth.getUser() @@ -79,7 +78,7 @@ function examplesCardController( */ $scope.useTemplate = function () { - const missingBetaPermissions = Betas.getMissingFieldPermissions( + const missingBetaPermissions = BetaService.getMissingFieldPermissions( $scope.user, $scope.form, ) diff --git a/src/public/services/BetaService.ts b/src/public/services/BetaService.ts new file mode 100644 index 0000000000..b7cdf24928 --- /dev/null +++ b/src/public/services/BetaService.ts @@ -0,0 +1,75 @@ +import { get } from 'lodash' + +// Re-use the backend types for now so that we have some type safety. +// Given that this is used mostly by JavaScript modules, the lack of +// mongo-specific types should not present a problem. +// Change the types to frontend equivalents as and when available. +import { IField, IForm, IUser } from '../../types' + +type BetaFeature = { + flag: string + matches: (field: IField) => boolean +} + +type BetaFeaturesDictionary = { [featureName: string]: BetaFeature } + +const BETA_FEATURES_FIELD: BetaFeaturesDictionary = { + // This is an example of how to add fields to this object + // featureName: { + // flag: 'betaFlagName', + // matches: (field) => + // field.fieldType === 'mobile' && + // (field.isVerifiable === true), + // }, +} + +const getBetaFeaturesForFields = ( + formFields: IField[] | undefined, + betaFeaturesField: BetaFeaturesDictionary, +): string[] => { + const betaFeatures = new Set() + if (formFields) { + for (const field of formFields) { + for (const [name, feature] of Object.entries(betaFeaturesField)) { + if (feature.matches(field)) { + betaFeatures.add(name) + } + } + } + } + return Array.from(betaFeatures) +} + +export const getMissingFieldPermissions = ( + user: IUser, + form: IForm, + betaFeaturesField = BETA_FEATURES_FIELD, +): string[] => { + const betaFeatures = getBetaFeaturesForFields( + form.form_fields, + betaFeaturesField, + ) + return betaFeatures.filter((name) => { + const flag = get(betaFeaturesField, [name, 'flag']) + return flag && !get(user, ['betaFlags', flag], false) + }) +} + +export const isBetaField = ( + fieldType: string, + betaFeaturesField = BETA_FEATURES_FIELD, +): BetaFeature | undefined => { + return betaFeaturesField[fieldType] +} + +export const userHasAccessToFieldType = ( + user: IUser, + fieldType: string, + betaFeaturesField = BETA_FEATURES_FIELD, +): boolean => { + const flag = get(betaFeaturesField, [fieldType, 'flag']) + return ( + !isBetaField(fieldType, betaFeaturesField) || + (Boolean(flag) && get(user, ['betaFlags', flag], false)) + ) +} diff --git a/src/public/services/__tests__/BetaService.test.ts b/src/public/services/__tests__/BetaService.test.ts index 7c07bebe5e..e404f1d1e0 100644 --- a/src/public/services/__tests__/BetaService.test.ts +++ b/src/public/services/__tests__/BetaService.test.ts @@ -1,15 +1,5 @@ -import { IFieldSchema, IFormDocument } from '../../../types' - -window.angular = { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - module: jest.fn(() => ({ - factory: jest.fn(), - })), -} - -// eslint-disable-next-line import/first -import * as BetaService from '../../modules/forms/services/betas.client.factory' +import { IField, IForm, IUser } from '../../../types' +import * as BetaService from '../BetaService' describe('BetaService', () => { const nonBetaFeature = 'nonBetaFeature' @@ -18,13 +8,13 @@ describe('BetaService', () => { const betaFeaturesField = { [featureOne]: { flag: 'betaFlagOne', - matches(field: IFieldSchema) { + matches(field: IField) { return field.title === featureOne }, }, [featureTwo]: { flag: 'betaFlagTwo', - matches(field: IFieldSchema) { + matches(field: IField) { return field.title === featureTwo }, }, @@ -33,7 +23,7 @@ describe('BetaService', () => { betaFlags: { [betaFeaturesField[featureOne].flag]: true, }, - } + } as IUser describe('isBetaField', () => { it('returns truthy for defined beta fields', () => { const result = BetaService.isBetaField(featureOne, betaFeaturesField) @@ -73,7 +63,7 @@ describe('BetaService', () => { describe('getBetaFeaturesForFields', () => { const form = { form_fields: [{ title: featureTwo }], - } as IFormDocument + } as IForm it('lists the beta features that the user lacks', () => { const result = BetaService.getMissingFieldPermissions( userWithFeatureOne, From b761699cce011aaf2a6e25fd3ba71c9b95a3a887 Mon Sep 17 00:00:00 2001 From: LoneRifle Date: Thu, 3 Jun 2021 15:25:50 +0800 Subject: [PATCH 10/37] docs(readme): remove active contributors (#2064) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c25bbf1d43..0c09001278 100755 --- a/README.md +++ b/README.md @@ -206,11 +206,9 @@ FormSG acknowledges the work done by [Arielle Baldwynn](https://github.com/white Contributions have also been made by: [@RyanAngJY](https://github.com/RyanAngJY) [@jeantanzy](https://github.com/jeantanzy) -[@yong-jie](https://github.com/yong-jie) [@pregnantboy](https://github.com/pregnantboy) [@namnguyen08](https://github.com/namnguyen08) [@zioul123](https://github.com/zioul123) [@JoelWee](https://github.com/JoelWee) [@limli](https://github.com/limli) [@tankevan](https://github.com/tankevan) -[@LoneRifle](https://github.com/LoneRifle) From 9f34c8bd1e8aadd5ace57322705a5a28f9e4f4a0 Mon Sep 17 00:00:00 2001 From: Chow Yi Yin <54089291+chowyiyin@users.noreply.github.com> Date: Thu, 3 Jun 2021 17:15:07 +0800 Subject: [PATCH 11/37] refactor(formApiClientFactory): rearrange types (#2061) * refactor: shift EditFormFieldParams to types/api * refactor: shift IPossiblyPrefilledField to types * refactor: add DuplicateFormBody, FormUpdateParmas and PublicFormViewDto to types/api * refactor: rreimport dependencies based on previous changes * fix: remove CreateFormService left over from rebase --- .../__tests__/admin-form.controller.spec.ts | 8 +-- .../__tests__/admin-form.service.spec.ts | 8 +-- .../__tests__/admin-form.utils.spec.ts | 7 +- .../form/admin-form/admin-form.controller.ts | 8 +-- .../form/admin-form/admin-form.routes.ts | 2 +- .../form/admin-form/admin-form.service.ts | 8 +-- .../form/admin-form/admin-form.types.ts | 44 ------------- .../form/admin-form/admin-form.utils.ts | 3 +- .../public-form/public-form.controller.ts | 3 +- .../form/public-form/public-form.types.ts | 18 ------ .../myinfo/__tests__/myinfo.service.spec.ts | 3 +- src/app/modules/myinfo/myinfo.factory.ts | 7 +- src/app/modules/myinfo/myinfo.service.ts | 2 +- src/app/modules/myinfo/myinfo.types.ts | 7 -- src/app/modules/myinfo/myinfo.util.ts | 2 +- src/types/api/field.ts | 18 ++++++ src/types/api/form.ts | 64 ++++++++++++++++++- src/types/api/index.ts | 1 + src/types/field/index.ts | 1 + src/types/field/myinfoField.ts | 7 ++ 20 files changed, 113 insertions(+), 108 deletions(-) create mode 100644 src/types/api/field.ts create mode 100644 src/types/field/myinfoField.ts diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index d81ecf70a6..5867d3b566 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -61,6 +61,8 @@ import { Status, } from 'src/types' import { + DuplicateFormBody, + EditFormFieldParams, EncryptSubmissionDto, FieldCreateDto, FieldUpdateDto, @@ -92,11 +94,7 @@ import { InvalidFileTypeError, } from '../admin-form.errors' import * as AdminFormService from '../admin-form.service' -import { - DuplicateFormBody, - EditFormFieldParams, - PermissionLevel, -} from '../admin-form.types' +import { PermissionLevel } from '../admin-form.types' jest.mock('src/app/modules/auth/auth.service') const MockAuthService = mocked(AuthService) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts index b6d40fed84..1caf142f5a 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts @@ -44,6 +44,8 @@ import { Status, } from 'src/types' import { + DuplicateFormBody, + EditFormFieldParams, FieldCreateDto, FieldUpdateDto, SettingsUpdateDto, @@ -86,11 +88,7 @@ import { updateFormSettings, updateStartPage, } from '../admin-form.service' -import { - DuplicateFormBody, - EditFormFieldParams, - OverrideProps, -} from '../admin-form.types' +import { OverrideProps } from '../admin-form.types' import * as AdminFormUtils from '../admin-form.utils' const FormModel = getFormModel(mongoose) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts index 6c8f30068c..6842b1a9ae 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts @@ -12,16 +12,13 @@ import { ResponseMode, Status, } from 'src/types' +import { DuplicateFormBody, EditFormFieldParams } from 'src/types/api' import { generateDefaultField } from 'tests/unit/backend/helpers/generate-form-data' import { ForbiddenFormError } from '../../form.errors' import { EditFieldError } from '../admin-form.errors' -import { - DuplicateFormBody, - EditFormFieldParams, - OverrideProps, -} from '../admin-form.types' +import { OverrideProps } from '../admin-form.types' import { assertHasDeletePermissions, assertHasReadPermissions, diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index 424dbbb43d..ee1f95aadc 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -26,12 +26,14 @@ import { ResponseMode, } from '../../../../types' import { + DuplicateFormBody, EncryptSubmissionDto, EndPageUpdateDto, ErrorDto, FieldCreateDto, FieldUpdateDto, FormFieldDto, + FormUpdateParams, PermissionsUpdateDto, SettingsUpdateDto, StartPageUpdateDto, @@ -75,11 +77,7 @@ import { } from './admin-form.constants' import { EditFieldError } from './admin-form.errors' import * as AdminFormService from './admin-form.service' -import { - DuplicateFormBody, - FormUpdateParams, - PermissionLevel, -} from './admin-form.types' +import { PermissionLevel } from './admin-form.types' import { mapRouteError } from './admin-form.utils' // NOTE: Refer to this for documentation: https://github.com/sideway/joi-date/blob/master/API.md diff --git a/src/app/modules/form/admin-form/admin-form.routes.ts b/src/app/modules/form/admin-form/admin-form.routes.ts index e9594cb1cb..415fb45f35 100644 --- a/src/app/modules/form/admin-form/admin-form.routes.ts +++ b/src/app/modules/form/admin-form/admin-form.routes.ts @@ -7,11 +7,11 @@ import { celebrate, Joi as BaseJoi, Segments } from 'celebrate' import { Router } from 'express' import { ResponseMode } from '../../../../types' +import { DuplicateFormBody } from '../../../../types/api' import { withUserAuthentication } from '../../auth/auth.middlewares' import * as EncryptSubmissionController from '../../submission/encrypt-submission/encrypt-submission.controller' import * as AdminFormController from './admin-form.controller' -import { DuplicateFormBody } from './admin-form.types' export const AdminFormsRouter = Router() diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts index 5f5ea9e6a0..eb5c64a9bf 100644 --- a/src/app/modules/form/admin-form/admin-form.service.ts +++ b/src/app/modules/form/admin-form/admin-form.service.ts @@ -24,9 +24,12 @@ import { Permission, } from '../../../../types' import { + DuplicateFormBody, + EditFormFieldParams, EndPageUpdateDto, FieldCreateDto, FieldUpdateDto, + FormUpdateParams, SettingsUpdateDto, StartPageUpdateDto, } from '../../../../types/api' @@ -62,11 +65,6 @@ import { FieldNotFoundError, InvalidFileTypeError, } from './admin-form.errors' -import { - DuplicateFormBody, - EditFormFieldParams, - FormUpdateParams, -} from './admin-form.types' import { getUpdatedFormFields, processDuplicateOverrideProps, diff --git a/src/app/modules/form/admin-form/admin-form.types.ts b/src/app/modules/form/admin-form/admin-form.types.ts index 5378a59bf5..bde8301348 100644 --- a/src/app/modules/form/admin-form/admin-form.types.ts +++ b/src/app/modules/form/admin-form/admin-form.types.ts @@ -1,6 +1,5 @@ import { Result } from 'neverthrow' -import { EditFieldActions } from '../../../../shared/constants' import { IFieldSchema, IForm, @@ -23,19 +22,6 @@ export type AssertFormFn = ( form: IPopulatedForm, ) => Result -export type DuplicateFormBody = { - title: string -} & ( - | { - responseMode: ResponseMode.Email - emails: string | string[] - } - | { - responseMode: ResponseMode.Encrypt - publicKey: string - } -) - export type OverrideProps = { endPage?: IForm['endPage'] startPage?: IForm['startPage'] @@ -46,34 +32,4 @@ export type OverrideProps = { publicKey?: string } -export type EditFormFieldParams = { - field: IFieldSchema -} & ( - | { - action: { - name: Exclude - } - } - | { - action: { - name: EditFieldActions.Reorder - position: number - } - } -) - -export type FormUpdateParams = { - editFormField?: EditFormFieldParams - authType?: IForm['authType'] - emails?: IForm['emails'] - esrvcId?: IForm['esrvcId'] - form_logics?: IForm['form_logics'] - hasCaptcha?: IForm['hasCaptcha'] - inactiveMessage?: IForm['inactiveMessage'] - permissionList?: IForm['permissionList'] - status?: IForm['status'] - title?: IForm['title'] - webhook?: IForm['webhook'] -} - export type EditFormFieldResult = Result diff --git a/src/app/modules/form/admin-form/admin-form.utils.ts b/src/app/modules/form/admin-form/admin-form.utils.ts index 57c6788265..b51d6d2dc5 100644 --- a/src/app/modules/form/admin-form/admin-form.utils.ts +++ b/src/app/modules/form/admin-form/admin-form.utils.ts @@ -9,6 +9,7 @@ import { ResponseMode, Status, } from '../../../../types' +import { DuplicateFormBody, EditFormFieldParams } from '../../../../types/api' import { createLoggerWithLabel } from '../../../config/logger' import { isPossibleEmailFieldSchema } from '../../../utils/field-validation/field-validation.guards' import { @@ -38,8 +39,6 @@ import { } from './admin-form.errors' import { AssertFormFn, - DuplicateFormBody, - EditFormFieldParams, EditFormFieldResult, OverrideProps, PermissionLevel, diff --git a/src/app/modules/form/public-form/public-form.controller.ts b/src/app/modules/form/public-form/public-form.controller.ts index 586014eac8..722d1b06f5 100644 --- a/src/app/modules/form/public-form/public-form.controller.ts +++ b/src/app/modules/form/public-form/public-form.controller.ts @@ -10,6 +10,7 @@ import { PrivateFormErrorDto, PublicFormAuthRedirectDto, PublicFormAuthValidateEsrvcIdDto, + PublicFormViewDto, } from '../../../../types/api' import { createLoggerWithLabel } from '../../../config/logger' import { isMongoError } from '../../../utils/handle-mongo-error' @@ -36,7 +37,7 @@ import { AuthTypeMismatchError, PrivateFormError } from '../form.errors' import * as FormService from '../form.service' import * as PublicFormService from './public-form.service' -import { PublicFormViewDto, RedirectParams } from './public-form.types' +import { RedirectParams } from './public-form.types' import { mapFormAuthError, mapRouteError } from './public-form.utils' const logger = createLoggerWithLabel(module) diff --git a/src/app/modules/form/public-form/public-form.types.ts b/src/app/modules/form/public-form/public-form.types.ts index 8b138e85e0..14c2f66a93 100644 --- a/src/app/modules/form/public-form/public-form.types.ts +++ b/src/app/modules/form/public-form/public-form.types.ts @@ -1,8 +1,3 @@ -import { IFieldSchema, PublicForm } from 'src/types' - -import { SpcpSession } from '../../../../types/spcp' -import { IPossiblyPrefilledField } from '../../myinfo/myinfo.types' - export type Metatags = { title: string description?: string @@ -16,16 +11,3 @@ export type RedirectParams = { // TODO(#144): Rename Id to formId after all routes have been updated. Id: string } - -// NOTE: This is needed because PublicForm inherits from IFormDocument (where form_fields has type of IFieldSchema). -// However, the form returned back to the client has form_field of two possible types -interface PossiblyPrefilledPublicForm extends Omit { - form_fields: IPossiblyPrefilledField[] | IFieldSchema[] -} - -export type PublicFormViewDto = { - form: PossiblyPrefilledPublicForm - spcpSession?: SpcpSession - isIntranetUser?: boolean - myInfoError?: true -} diff --git a/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts b/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts index 418ec67bac..e2d8c08a83 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts @@ -12,6 +12,7 @@ import { IHashes, IMyInfoHashSchema, IPopulatedForm, + IPossiblyPrefilledField, MyInfoAttribute, } from 'src/types' @@ -27,7 +28,7 @@ import { MyInfoMissingAccessTokenError, MyInfoParseRelayStateError, } from '../myinfo.errors' -import { IPossiblyPrefilledField, MyInfoRelayState } from '../myinfo.types' +import { MyInfoRelayState } from '../myinfo.types' import { MOCK_ACCESS_TOKEN, diff --git a/src/app/modules/myinfo/myinfo.factory.ts b/src/app/modules/myinfo/myinfo.factory.ts index d4e66be084..a62fe99902 100644 --- a/src/app/modules/myinfo/myinfo.factory.ts +++ b/src/app/modules/myinfo/myinfo.factory.ts @@ -6,6 +6,7 @@ import { IHashes, IMyInfoHashSchema, IPopulatedForm, + IPossiblyPrefilledField, } from '../../../types' import config from '../../config/config' import FeatureManager, { @@ -33,11 +34,7 @@ import { MyInfoParseRelayStateError, } from './myinfo.errors' import { MyInfoService } from './myinfo.service' -import { - IMyInfoRedirectURLArgs, - IPossiblyPrefilledField, - MyInfoParsedRelayState, -} from './myinfo.types' +import { IMyInfoRedirectURLArgs, MyInfoParsedRelayState } from './myinfo.types' interface IMyInfoFactory { createRedirectURL: ( diff --git a/src/app/modules/myinfo/myinfo.service.ts b/src/app/modules/myinfo/myinfo.service.ts index dae865966a..a0107861e1 100644 --- a/src/app/modules/myinfo/myinfo.service.ts +++ b/src/app/modules/myinfo/myinfo.service.ts @@ -16,6 +16,7 @@ import { IHashes, IMyInfoHashSchema, IPopulatedForm, + IPossiblyPrefilledField, MyInfoAttribute, } from '../../../types' import { createLoggerWithLabel } from '../../config/logger' @@ -47,7 +48,6 @@ import { import { IMyInfoRedirectURLArgs, IMyInfoServiceConfig, - IPossiblyPrefilledField, MyInfoParsedRelayState, } from './myinfo.types' import { diff --git a/src/app/modules/myinfo/myinfo.types.ts b/src/app/modules/myinfo/myinfo.types.ts index d28b697f7c..bea4ab40db 100644 --- a/src/app/modules/myinfo/myinfo.types.ts +++ b/src/app/modules/myinfo/myinfo.types.ts @@ -1,9 +1,6 @@ -import { LeanDocument } from 'mongoose' - import { AuthType, Environment, - IFieldSchema, IFormSchema, IMyInfo, MyInfoAttribute, @@ -23,10 +20,6 @@ export interface IMyInfoRedirectURLArgs { requestedAttributes: MyInfoAttribute[] } -export interface IPossiblyPrefilledField extends LeanDocument { - fieldValue?: string -} - export type MyInfoHashPromises = Partial< Record> > diff --git a/src/app/modules/myinfo/myinfo.util.ts b/src/app/modules/myinfo/myinfo.util.ts index ecb97642a0..6f3febbaee 100644 --- a/src/app/modules/myinfo/myinfo.util.ts +++ b/src/app/modules/myinfo/myinfo.util.ts @@ -12,6 +12,7 @@ import { IFormSchema, IHashes, IMyInfo, + IPossiblyPrefilledField, MapRouteError, } from '../../../types' import { createLoggerWithLabel } from '../../config/logger' @@ -39,7 +40,6 @@ import { MyInfoMissingHashError, } from './myinfo.errors' import { - IPossiblyPrefilledField, MyInfoComparePromises, MyInfoCookiePayload, MyInfoCookieState, diff --git a/src/types/api/field.ts b/src/types/api/field.ts new file mode 100644 index 0000000000..49f0acb370 --- /dev/null +++ b/src/types/api/field.ts @@ -0,0 +1,18 @@ +import { EditFieldActions } from '../../shared/constants' +import { IFieldSchema } from '../field' + +export type EditFormFieldParams = { + field: IFieldSchema +} & ( + | { + action: { + name: Exclude + } + } + | { + action: { + name: EditFieldActions.Reorder + position: number + } + } +) diff --git a/src/types/api/form.ts b/src/types/api/form.ts index d6f0b238f4..256e99d122 100644 --- a/src/types/api/form.ts +++ b/src/types/api/form.ts @@ -1,8 +1,26 @@ import { LeanDocument } from 'mongoose' import { ConditionalPick, Primitive } from 'type-fest' -import { FormField, FormFieldSchema, FormFieldWithId } from '../field' -import { EndPage, FormSettings, Permission, StartPage } from '../form' +import { + FormField, + FormFieldSchema, + FormFieldWithId, + IFieldSchema, + IPossiblyPrefilledField, +} from '../field' +import { + EndPage, + FormSettings, + IForm, + IPopulatedForm, + Permission, + PublicForm, + ResponseMode, + StartPage, +} from '../form' +import { SpcpSession } from '../spcp' + +import { EditFormFieldParams } from './field' export type SettingsUpdateDto = Partial @@ -23,3 +41,45 @@ export type PermissionsUpdateDto = Permission[] export type EndPageUpdateDto = EndPage export type StartPageUpdateDto = StartPage + +export type FormViewDto = { form: IPopulatedForm } + +export type DuplicateFormBody = { + title: string +} & ( + | { + responseMode: ResponseMode.Email + emails: string | string[] + } + | { + responseMode: ResponseMode.Encrypt + publicKey: string + } +) + +export type FormUpdateParams = { + editFormField?: EditFormFieldParams + authType?: IForm['authType'] + emails?: IForm['emails'] + esrvcId?: IForm['esrvcId'] + form_logics?: IForm['form_logics'] + hasCaptcha?: IForm['hasCaptcha'] + inactiveMessage?: IForm['inactiveMessage'] + permissionList?: IForm['permissionList'] + status?: IForm['status'] + title?: IForm['title'] + webhook?: IForm['webhook'] +} + +// NOTE: This is needed because PublicForm inherits from IFormDocument (where form_fields has type of IFieldSchema). +// However, the form returned back to the client has form_field of two possible types +interface PossiblyPrefilledPublicForm extends Omit { + form_fields: IPossiblyPrefilledField[] | IFieldSchema[] +} + +export type PublicFormViewDto = { + form: PossiblyPrefilledPublicForm + spcpSession?: SpcpSession + isIntranetUser?: boolean + myInfoError?: true +} diff --git a/src/types/api/index.ts b/src/types/api/index.ts index 80e863b7f3..483fa1912e 100644 --- a/src/types/api/index.ts +++ b/src/types/api/index.ts @@ -1,5 +1,6 @@ export * from './core' export * from './auth' +export * from './field' export * from './form' export * from './billing' export * from './examples' diff --git a/src/types/field/index.ts b/src/types/field/index.ts index 1a32232cfa..52df4bc36f 100644 --- a/src/types/field/index.ts +++ b/src/types/field/index.ts @@ -30,6 +30,7 @@ export * from './homeNoField' export * from './imageField' export * from './longTextField' export * from './mobileField' +export * from './myinfoField' export * from './nricField' export * from './numberField' export * from './radioField' diff --git a/src/types/field/myinfoField.ts b/src/types/field/myinfoField.ts new file mode 100644 index 0000000000..ae09575b47 --- /dev/null +++ b/src/types/field/myinfoField.ts @@ -0,0 +1,7 @@ +import { LeanDocument } from 'mongoose' + +import { IFieldSchema } from '../field' + +export interface IPossiblyPrefilledField extends LeanDocument { + fieldValue?: string +} From df72bf632b852fe620120634085025a54df871d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 17:25:53 +0000 Subject: [PATCH 12/37] fix(deps): bump @sentry/browser from 6.5.0 to 6.5.1 (#2069) Bumps [@sentry/browser](https://github.com/getsentry/sentry-javascript) from 6.5.0 to 6.5.1. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/6.5.0...6.5.1) --- updated-dependencies: - dependency-name: "@sentry/browser" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 96 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c7adebad1..895af89dd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4927,81 +4927,81 @@ } }, "@sentry/browser": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.5.0.tgz", - "integrity": "sha512-n1e8hNKwuVP4bLqRK5J0DHFqnnnrbv6h6+Bc1eNRbf32/e6eZ3Cb36PTplqDCxwnMnnIEEowd5F4ZWeTLPPY3A==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.5.1.tgz", + "integrity": "sha512-iVLCdEFwsoWAzE/hNknexPQjjDpMQV7mmaq9Z1P63bD6MfhwVTx4hG4pHn8HEvC38VvCVf1wv0v/LxtoODAYXg==", "requires": { - "@sentry/core": "6.5.0", - "@sentry/types": "6.5.0", - "@sentry/utils": "6.5.0", + "@sentry/core": "6.5.1", + "@sentry/types": "6.5.1", + "@sentry/utils": "6.5.1", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.0.tgz", - "integrity": "sha512-yQpTCIYxBsYT0GenqHNNKeXV8CSkkYlAxB1IGV2eac4IKC5ph5GW6TfDGwvlzQSQ297RsRmOSA8o3I5gGPd2yA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", + "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" }, "@sentry/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-CcHuaQN6vRuAsIC+3sA23NmWLRmUN0x/HNQxk0DHJylvYQdEA0AUNoLXogykaXh6NrCx4DNq9yCQTNTSC3mFxg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", + "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", "requires": { - "@sentry/types": "6.5.0", + "@sentry/types": "6.5.1", "tslib": "^1.9.3" } } } }, "@sentry/core": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.5.0.tgz", - "integrity": "sha512-Hx/WvhM5bXcXqfIiz+505TjYYfPjQ8mrxby/EWl+L7dYUCyI/W6IZKTc/MoHlLuM+JPUW9c1bw/97TzbgTzaAA==", - "requires": { - "@sentry/hub": "6.5.0", - "@sentry/minimal": "6.5.0", - "@sentry/types": "6.5.0", - "@sentry/utils": "6.5.0", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.5.1.tgz", + "integrity": "sha512-Mh3sl/iUOT1myHmM6RlDy2ARzkUClx/g4DAt1rJ/IpQBOlDYQraplXSIW80i/hzRgQDfwhwgf4wUa5DicKBjKw==", + "requires": { + "@sentry/hub": "6.5.1", + "@sentry/minimal": "6.5.1", + "@sentry/types": "6.5.1", + "@sentry/utils": "6.5.1", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.0.tgz", - "integrity": "sha512-yQpTCIYxBsYT0GenqHNNKeXV8CSkkYlAxB1IGV2eac4IKC5ph5GW6TfDGwvlzQSQ297RsRmOSA8o3I5gGPd2yA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", + "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" }, "@sentry/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-CcHuaQN6vRuAsIC+3sA23NmWLRmUN0x/HNQxk0DHJylvYQdEA0AUNoLXogykaXh6NrCx4DNq9yCQTNTSC3mFxg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", + "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", "requires": { - "@sentry/types": "6.5.0", + "@sentry/types": "6.5.1", "tslib": "^1.9.3" } } } }, "@sentry/hub": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.5.0.tgz", - "integrity": "sha512-vEChnLoozOJzEJoTUvaAsK/n7IHoQFx8P1TzQmnR+8XGZJZmGHG6bBXUH0iS2a9hhR1WkoEBeiL+t96R9uyf0A==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.5.1.tgz", + "integrity": "sha512-lBRMBVMYP8B4PfRiM70murbtJAXiIAao/asDEMIRNGMP6pI2ArqXfJCBYDkStukhikYD0Kqb4trXq+JYF07Hbg==", "requires": { - "@sentry/types": "6.5.0", - "@sentry/utils": "6.5.0", + "@sentry/types": "6.5.1", + "@sentry/utils": "6.5.1", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.0.tgz", - "integrity": "sha512-yQpTCIYxBsYT0GenqHNNKeXV8CSkkYlAxB1IGV2eac4IKC5ph5GW6TfDGwvlzQSQ297RsRmOSA8o3I5gGPd2yA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", + "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" }, "@sentry/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-CcHuaQN6vRuAsIC+3sA23NmWLRmUN0x/HNQxk0DHJylvYQdEA0AUNoLXogykaXh6NrCx4DNq9yCQTNTSC3mFxg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", + "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", "requires": { - "@sentry/types": "6.5.0", + "@sentry/types": "6.5.1", "tslib": "^1.9.3" } } @@ -5019,19 +5019,19 @@ } }, "@sentry/minimal": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.5.0.tgz", - "integrity": "sha512-MT83ONaBhTCFUlDIQFpsG/lq3ZjGK7jwQ10qxGadSg1KW6EvtQRg+OBwULeQ7C+nNEAhseNrC/qomZMT8brncg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.5.1.tgz", + "integrity": "sha512-q9Do/oreu1RP695CXCLowVDuQyk7ilE6FGdz2QLpTXAfx8247qOwk6+zy9Kea/Djk93+BoSDVQUSneNiVwl0nQ==", "requires": { - "@sentry/hub": "6.5.0", - "@sentry/types": "6.5.0", + "@sentry/hub": "6.5.1", + "@sentry/types": "6.5.1", "tslib": "^1.9.3" }, "dependencies": { "@sentry/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.0.tgz", - "integrity": "sha512-yQpTCIYxBsYT0GenqHNNKeXV8CSkkYlAxB1IGV2eac4IKC5ph5GW6TfDGwvlzQSQ297RsRmOSA8o3I5gGPd2yA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", + "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" } } }, diff --git a/package.json b/package.json index 29c7cbe10e..cddfde8c18 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@opengovsg/myinfo-gov-client": "^4.0.0", "@opengovsg/ng-file-upload": "^12.2.15", "@opengovsg/spcp-auth-client": "^1.4.7", - "@sentry/browser": "^6.5.0", + "@sentry/browser": "^6.5.1", "@sentry/integrations": "^6.5.0", "@stablelib/base64": "^1.0.1", "JSONStream": "^1.3.5", From 517caa503699bc61034f7203a707cd08d6e9907e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 17:36:46 +0000 Subject: [PATCH 13/37] fix(deps): bump aws-sdk from 2.919.0 to 2.920.0 (#2070) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.919.0 to 2.920.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.919.0...v2.920.0) --- updated-dependencies: - dependency-name: aws-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 895af89dd1..f3d46eb944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7098,9 +7098,9 @@ "integrity": "sha512-24q5Rh3bno7ldoyCq99d6hpnLI+PAMocdeVaaGt/5BTQMprvDwQToHfNnruqN11odCHZZIQbRBw+nZo1lTCH9g==" }, "aws-sdk": { - "version": "2.919.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.919.0.tgz", - "integrity": "sha512-9hPKuw3w7iDeDByIeoMspKgmiljTtYGM3bDkICEIDeJuPAlEMu+z3jOTFu5gHzZ95noXRqhRL1eeXI7o1F/hpQ==", + "version": "2.920.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.920.0.tgz", + "integrity": "sha512-tbMZ/Y2rRo6R6TTBODJXTiil+MXaoT6Qzotws3yvI1IWGpYxKo7N/3L06XB8ul8tCG0TigxIOY70SMICM70Ppg==", "requires": { "buffer": "4.9.2", "events": "1.1.1", diff --git a/package.json b/package.json index cddfde8c18..955fb29ad7 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "angular-ui-bootstrap": "~2.5.6", "angular-ui-router": "~1.0.29", "aws-info": "^1.2.0", - "aws-sdk": "^2.919.0", + "aws-sdk": "^2.920.0", "axios": "^0.21.1", "bcrypt": "^5.0.1", "bluebird": "^3.5.2", From 3197069dc1d1a7406cc2d0b8eb2669a11e73c620 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 17:41:14 +0000 Subject: [PATCH 14/37] fix(deps): bump twilio from 3.63.0 to 3.63.1 (#2072) Bumps [twilio](https://github.com/twilio/twilio-node) from 3.63.0 to 3.63.1. - [Release notes](https://github.com/twilio/twilio-node/releases) - [Changelog](https://github.com/twilio/twilio-node/blob/main/CHANGES.md) - [Commits](https://github.com/twilio/twilio-node/compare/3.63.0...3.63.1) --- updated-dependencies: - dependency-name: twilio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3d46eb944..49549fb354 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10213,9 +10213,9 @@ } }, "dayjs": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", - "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==" + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.5.tgz", + "integrity": "sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==" }, "debug": { "version": "3.1.0", @@ -25150,9 +25150,9 @@ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" }, "twilio": { - "version": "3.63.0", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.63.0.tgz", - "integrity": "sha512-ftZckbTBjJ5dgzdII9j0sqYw9SYq3wqTC9r6NmV7CRU0EXXDil5/AbKb78xNPLtMPx3+mn2N+2oTkQlTtWs9TQ==", + "version": "3.63.1", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-3.63.1.tgz", + "integrity": "sha512-xwtOM78sO2jGxKg1AW+7XlJdrhTMW9dzr6665O+IB/VtNVQB7JQS48pLCZFnBaTvZOILVO0Q6t63wv24hIbr/A==", "requires": { "axios": "^0.21.1", "dayjs": "^1.8.29", diff --git a/package.json b/package.json index 955fb29ad7..989fa69830 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "toastr": "^2.1.4", "triple-beam": "^1.3.0", "tweetnacl": "^1.0.1", - "twilio": "^3.63.0", + "twilio": "^3.63.1", "ui-select": "^0.19.8", "uid-generator": "^2.0.0", "uuid": "^8.3.2", From c9872154483b7aa8911e6977955c715929efbabb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jun 2021 17:53:58 +0000 Subject: [PATCH 15/37] fix(deps): bump @sentry/integrations from 6.5.0 to 6.5.1 (#2071) Bumps [@sentry/integrations](https://github.com/getsentry/sentry-javascript) from 6.5.0 to 6.5.1. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/6.5.0...6.5.1) --- updated-dependencies: - dependency-name: "@sentry/integrations" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49549fb354..e49bd44ced 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5008,12 +5008,12 @@ } }, "@sentry/integrations": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.5.0.tgz", - "integrity": "sha512-pI8DESNTbsj60CDtLzIdLHex59NGzjTBR9Wpt7SG8NPhgEAuS3tUU4Thjyib7sGb7mxRw1sSQt/FsjDd7vMjLg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.5.1.tgz", + "integrity": "sha512-NYiW0rH7fwv7aRtrRnfCSIiwulfV2NoLjhmghCONsyo10DNtYmOpogLotCytZFWLDnTJW1+pmTomq8UW/OSTcQ==", "requires": { - "@sentry/types": "6.5.0", - "@sentry/utils": "6.5.0", + "@sentry/types": "6.5.1", + "@sentry/utils": "6.5.1", "localforage": "^1.8.1", "tslib": "^1.9.3" } @@ -5036,16 +5036,16 @@ } }, "@sentry/types": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.0.tgz", - "integrity": "sha512-yQpTCIYxBsYT0GenqHNNKeXV8CSkkYlAxB1IGV2eac4IKC5ph5GW6TfDGwvlzQSQ297RsRmOSA8o3I5gGPd2yA==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.5.1.tgz", + "integrity": "sha512-b/7a6CMoytaeFPx4IBjfxPw3nPvsQh7ui1C8Vw0LxNNDgBwVhPLzUOWeLWbo5YZCVbGEMIWwtCUQYWxneceZSA==" }, "@sentry/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-CcHuaQN6vRuAsIC+3sA23NmWLRmUN0x/HNQxk0DHJylvYQdEA0AUNoLXogykaXh6NrCx4DNq9yCQTNTSC3mFxg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.5.1.tgz", + "integrity": "sha512-Wv86JYGQH+ZJ5XGFQX7h6ijl32667ikenoL9EyXMn8UoOYX/MLwZoQZin1P60wmKkYR9ifTNVmpaI9OoTaH+UQ==", "requires": { - "@sentry/types": "6.5.0", + "@sentry/types": "6.5.1", "tslib": "^1.9.3" } }, diff --git a/package.json b/package.json index 989fa69830..9dd6d0f8eb 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@opengovsg/ng-file-upload": "^12.2.15", "@opengovsg/spcp-auth-client": "^1.4.7", "@sentry/browser": "^6.5.1", - "@sentry/integrations": "^6.5.0", + "@sentry/integrations": "^6.5.1", "@stablelib/base64": "^1.0.1", "JSONStream": "^1.3.5", "abortcontroller-polyfill": "^1.7.3", From 7305b833f5096bb3d1a5d4a8604706fe3deb0540 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 14:04:48 +0800 Subject: [PATCH 16/37] fix(deps): bump nocache from 2.1.0 to 3.0.0 (#2068) Bumps [nocache](https://github.com/helmetjs/nocache) from 2.1.0 to 3.0.0. - [Release notes](https://github.com/helmetjs/nocache/releases) - [Changelog](https://github.com/helmetjs/nocache/blob/main/CHANGELOG.md) - [Commits](https://github.com/helmetjs/nocache/compare/v2.1.0...v3.0.0) --- updated-dependencies: - dependency-name: nocache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e49bd44ced..dd7b1005cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18764,9 +18764,9 @@ } }, "nocache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz", - "integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.0.tgz", + "integrity": "sha512-fd31hZ7uOvBrRSvzTsX51A8nW1SLKfcK7dESRDkEyltsGmpYfeINND8HoFGPEGkQZuhCsB5csAzcrLPXTtcRKg==" }, "node-abi": { "version": "2.19.1", diff --git a/package.json b/package.json index 9dd6d0f8eb..681a44ba90 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "ng-infinite-scroll": "^1.3.0", "ng-table": "^3.0.1", "ngclipboard": "^2.0.0", - "nocache": "^2.1.0", + "nocache": "^3.0.0", "node-cache": "^5.1.2", "nodemailer": "^6.6.1", "opossum": "^6.1.0", From e809a50f05937d2b8d1ff8b9474588eb8a8c0b68 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Fri, 4 Jun 2021 16:25:40 +0800 Subject: [PATCH 17/37] docs(script): add scripts to privatize all sp/rp student forms (#2073) --- .../private-rp-stud-forms.js | 45 +++++++++++++++++++ .../private-sp-stud-forms.js | 45 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 scripts/20210604_private-rp-sp-student-forms/private-rp-stud-forms.js create mode 100644 scripts/20210604_private-rp-sp-student-forms/private-sp-stud-forms.js diff --git a/scripts/20210604_private-rp-sp-student-forms/private-rp-stud-forms.js b/scripts/20210604_private-rp-sp-student-forms/private-rp-stud-forms.js new file mode 100644 index 0000000000..eb28bd9a90 --- /dev/null +++ b/scripts/20210604_private-rp-sp-student-forms/private-rp-stud-forms.js @@ -0,0 +1,45 @@ +/* eslint-disable */ + +// BEFORE +// A: Count number of forms belonging to RP students that are still public. +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds }, status: 'PUBLIC' }).count() +} +// B: Count number of forms belonging to RP students that are private. +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds }, status: 'PRIVATE' }).count() +} + +// UPDATE +// Number updated should be A +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + let rpStudentFormIds = db.forms.find({ admin: { $in: rpStudentUserIds }, status: 'PUBLIC' }).map(b => b._id) + + db.forms.updateMany( + { + _id: { $in: rpStudentFormIds }, + }, + { + $set: { + status: 'PRIVATE', + }, + } + ) +} + +// AFTER +// Count number of forms belonging to RP students that are still public +// Should be 0 +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds }, status: 'PUBLIC' }).count() +} +// Count number of forms belonging to RP students that are private. +// Should be A + B +{ + let rpStudentUserIds = db.users.find({ email: /.+myrp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: rpStudentUserIds }, status: 'PRIVATE' }).count() +} \ No newline at end of file diff --git a/scripts/20210604_private-rp-sp-student-forms/private-sp-stud-forms.js b/scripts/20210604_private-rp-sp-student-forms/private-sp-stud-forms.js new file mode 100644 index 0000000000..ac3b2dac26 --- /dev/null +++ b/scripts/20210604_private-rp-sp-student-forms/private-sp-stud-forms.js @@ -0,0 +1,45 @@ +/* eslint-disable */ + +// BEFORE +// A: Count number of forms belonging to SP students that are still public. +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: spStudentUserIds }, status: 'PUBLIC' }).count() +} +// B: Count number of forms belonging to SP students that are private. +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: spStudentUserIds }, status: 'PRIVATE' }).count() +} + +// UPDATE +// Number updated should be A +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + let spStudentFormIds = db.forms.find({ admin: { $in: spStudentUserIds }, status: 'PUBLIC' }).map(b => b._id) + + db.forms.updateMany( + { + _id: { $in: spStudentFormIds }, + }, + { + $set: { + status: 'PRIVATE', + }, + } + ) +} + +// AFTER +// Count number of forms belonging to SP students that are still public +// Should be 0 +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: spStudentUserIds }, status: 'PUBLIC' }).count() +} +// Count number of forms belonging to SP students that are private. +// Should be A + B +{ + let spStudentUserIds = db.users.find({ email: /.+ichat\.sp\.edu\.sg$/i }).map(b => b._id) + db.forms.find({ admin: { $in: spStudentUserIds }, status: 'PRIVATE' }).count() +} From bdce983535df29020ed8f5669bb0d31f175f470f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Jun 2021 17:28:47 +0000 Subject: [PATCH 18/37] chore(deps-dev): bump core-js from 3.13.1 to 3.14.0 (#2076) Bumps [core-js](https://github.com/zloirock/core-js/tree/HEAD/packages/core-js) from 3.13.1 to 3.14.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/commits/v3.14.0/packages/core-js) --- updated-dependencies: - dependency-name: core-js dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd7b1005cf..df41511a62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9450,9 +9450,9 @@ } }, "core-js": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.1.tgz", - "integrity": "sha512-JqveUc4igkqwStL2RTRn/EPFGBOfEZHxJl/8ej1mXJR75V3go2mFF4bmUYkEIT1rveHKnkUlcJX/c+f1TyIovQ==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.14.0.tgz", + "integrity": "sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA==", "dev": true }, "core-js-compat": { diff --git a/package.json b/package.json index 681a44ba90..8453254c61 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "babel-loader": "^8.2.2", "concurrently": "^6.2.0", "copy-webpack-plugin": "^6.0.2", - "core-js": "^3.13.1", + "core-js": "^3.14.0", "coveralls": "^3.1.0", "css-loader": "^2.1.1", "csv-parse": "^4.15.4", From 1f331dc6068f240204d3b77c1af8ac91718fd151 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Jun 2021 17:32:26 +0000 Subject: [PATCH 19/37] chore(deps-dev): bump eslint from 7.27.0 to 7.28.0 (#2077) Bumps [eslint](https://github.com/eslint/eslint) from 7.27.0 to 7.28.0. - [Release notes](https://github.com/eslint/eslint/releases) - [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md) - [Commits](https://github.com/eslint/eslint/compare/v7.27.0...v7.28.0) --- updated-dependencies: - dependency-name: eslint dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 67 ++++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index df41511a62..967fcbed56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4073,15 +4073,15 @@ } }, "@eslint/eslintrc": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.1.tgz", - "integrity": "sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz", + "integrity": "sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.1.1", "espree": "^7.3.0", - "globals": "^12.1.0", + "globals": "^13.9.0", "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", @@ -4099,12 +4099,12 @@ } }, "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, "requires": { - "type-fest": "^0.8.1" + "type-fest": "^0.20.2" } }, "ignore": { @@ -4120,9 +4120,9 @@ "dev": true }, "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true } } @@ -11214,13 +11214,13 @@ } }, "eslint": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.27.0.tgz", - "integrity": "sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.28.0.tgz", + "integrity": "sha512-UMfH0VSjP0G4p3EWirscJEQ/cHqnT/iuH6oNZOB94nBjWbMnhGEPxsZm1eyIW0C/9jLI0Fow4W5DXLjEI7mn1g==", "dev": true, "requires": { "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.1", + "@eslint/eslintrc": "^0.4.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -11237,7 +11237,7 @@ "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", + "glob-parent": "^5.1.2", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", @@ -11415,21 +11415,22 @@ } } }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, "globals": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.8.0.tgz", - "integrity": "sha512-rHtdA6+PDBIjeEvA91rpqzEvk/k3/i7EeNQiryiWuJH0Hw9cpyJMAt2jtbAwUaRdhD+573X4vWw6IcjKPasi9Q==", + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.9.0.tgz", + "integrity": "sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==", "dev": true, "requires": { "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } } }, "has-flag": { @@ -11537,6 +11538,12 @@ "prelude-ls": "^1.2.1" } }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -23415,9 +23422,9 @@ }, "dependencies": { "ajv": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.5.0.tgz", - "integrity": "sha512-Y2l399Tt1AguU3BPRP9Fn4eN+Or+StUGWCUpbnFyXSo8NZ9S4uj+AG2pjs5apK+ZMOwYOz1+a+VKvKH7CudXgQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz", + "integrity": "sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", diff --git a/package.json b/package.json index 8453254c61..5fc69f3634 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "csv-parse": "^4.15.4", "date-fns": "^2.22.1", "env-cmd": "^10.1.0", - "eslint": "^7.27.0", + "eslint": "^7.28.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-angular": "^4.0.1", "eslint-plugin-import": "^2.23.4", From 6f03d5f5c952a2bd771b4104808dbcb19f2d685f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Jun 2021 17:43:17 +0000 Subject: [PATCH 20/37] fix(deps): bump aws-sdk from 2.920.0 to 2.922.0 (#2078) Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.920.0 to 2.922.0. - [Release notes](https://github.com/aws/aws-sdk-js/releases) - [Changelog](https://github.com/aws/aws-sdk-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js/compare/v2.920.0...v2.922.0) --- updated-dependencies: - dependency-name: aws-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 967fcbed56..591bfe49d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7098,9 +7098,9 @@ "integrity": "sha512-24q5Rh3bno7ldoyCq99d6hpnLI+PAMocdeVaaGt/5BTQMprvDwQToHfNnruqN11odCHZZIQbRBw+nZo1lTCH9g==" }, "aws-sdk": { - "version": "2.920.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.920.0.tgz", - "integrity": "sha512-tbMZ/Y2rRo6R6TTBODJXTiil+MXaoT6Qzotws3yvI1IWGpYxKo7N/3L06XB8ul8tCG0TigxIOY70SMICM70Ppg==", + "version": "2.922.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.922.0.tgz", + "integrity": "sha512-SufbR5TTCK94Zk/xIv4v/m0MM9z+KW999XnjXOyNWGFGHP9/FArjtHtq69+a3KpohYBR1dBj8wUhVjbClmQIBA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", diff --git a/package.json b/package.json index 5fc69f3634..e9596e2b01 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "angular-ui-bootstrap": "~2.5.6", "angular-ui-router": "~1.0.29", "aws-info": "^1.2.0", - "aws-sdk": "^2.920.0", + "aws-sdk": "^2.922.0", "axios": "^0.21.1", "bcrypt": "^5.0.1", "bluebird": "^3.5.2", From f8f235a2dccced982d0c35f6a4381026b83b8698 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 7 Jun 2021 09:45:09 +0800 Subject: [PATCH 21/37] fix: restore typings to some model static methods (#2067) --- src/app/models/form_feedback.server.model.ts | 6 ++++-- src/app/models/submission.server.model.ts | 12 ++++++++---- src/types/submission.ts | 8 ++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/app/models/form_feedback.server.model.ts b/src/app/models/form_feedback.server.model.ts index 2197c4f980..5315c81de8 100644 --- a/src/app/models/form_feedback.server.model.ts +++ b/src/app/models/form_feedback.server.model.ts @@ -1,4 +1,4 @@ -import { Mongoose, Schema } from 'mongoose' +import { Mongoose, QueryCursor, Schema } from 'mongoose' import { IFormFeedbackModel, IFormFeedbackSchema } from '../../types' @@ -39,7 +39,9 @@ const FormFeedbackSchema = new Schema( * @param formId the form id to return the submissions cursor for * @returns a cursor to the feedback retrieved */ -FormFeedbackSchema.statics.getFeedbackCursorByFormId = function (formId) { +FormFeedbackSchema.statics.getFeedbackCursorByFormId = function ( + formId: string, +): QueryCursor { return this.find({ formId }).batchSize(2000).read('secondary').lean().cursor() } diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index 64a6b072f1..9b3c92e9d8 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -15,6 +15,7 @@ import { IWebhookResponseSchema, MyInfoAttribute, SubmissionCursorData, + SubmissionData, SubmissionMetadata, SubmissionType, WebhookData, @@ -314,9 +315,12 @@ EncryptSubmissionSchema.statics.findAllMetadataByFormId = function ( } EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = function ( - formId, - dateRange = {}, -) { + formId: string, + dateRange: { + startDate?: string + endDate?: string + } = {}, +): QueryCursor { const streamQuery = { form: formId, ...createQueryWithDateParam(dateRange?.startDate, dateRange?.endDate), @@ -341,7 +345,7 @@ EncryptSubmissionSchema.statics.getSubmissionCursorByFormId = function ( EncryptSubmissionSchema.statics.findEncryptedSubmissionById = function ( formId: string, submissionId: string, -) { +): Promise { return this.findOne({ _id: submissionId, form: formId, diff --git a/src/types/submission.ts b/src/types/submission.ts index 0af21c0e26..d1940974d0 100644 --- a/src/types/submission.ts +++ b/src/types/submission.ts @@ -120,9 +120,13 @@ export type SubmissionCursorData = Pick< 'encryptedContent' | 'verifiedContent' | 'created' | 'id' > & { attachmentMetadata?: Record } & Document -export type SubmissionData = Omit< +export type SubmissionData = Pick< IEncryptedSubmissionSchema, - 'version' | 'webhookResponses' + | 'encryptedContent' + | 'verifiedContent' + | 'attachmentMetadata' + | 'created' + | '_id' > export type IEmailSubmissionModel = Model & From 93b46a6ca0dd6ed9c7e0ad0ae305e7d79ac41f74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jun 2021 04:07:54 +0000 Subject: [PATCH 22/37] chore(deps-dev): bump prettier from 2.2.1 to 2.3.1 (#2075) * chore(deps-dev): bump prettier from 2.2.1 to 2.3.1 Bumps [prettier](https://github.com/prettier/prettier) from 2.2.1 to 2.3.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.2.1...2.3.1) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * ref: run npm lint * ref: run npm run lint-html Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kar Rui Lau --- package-lock.json | 6 +- package.json | 2 +- .../feature-manager/aggregate-stats.config.ts | 22 +- .../google-analytics.config.ts | 21 +- .../config/feature-manager/intranet.config.ts | 3 +- .../feature-manager/spcp-myinfo.config.ts | 48 ++--- .../feature-manager/verified-fields.config.ts | 21 +- .../webhook-verified-content.config.ts | 22 +- src/app/config/logger.ts | 2 +- src/app/config/schema.ts | 27 +-- .../admin_verification.server.model.spec.ts | 10 +- .../encrypt-submission.server.model.spec.ts | 14 +- .../__tests__/form.server.model.spec.ts | 30 ++- ...form_statistics_total.server.model.spec.ts | 8 +- .../__tests__/login.server.model.spec.ts | 4 +- src/app/models/form.server.model.ts | 2 +- .../__tests__/analytics.service.spec.ts | 12 +- src/app/modules/auth/auth.service.ts | 26 +-- .../billing/__tests__/billing.factory.spec.ts | 17 +- .../billing/__tests__/billing.routes.spec.ts | 14 +- .../billing/__tests__/billing.service.spec.ts | 10 +- .../__tests__/bounce.controller.spec.ts | 4 +- .../bounce/__tests__/bounce.service.spec.ts | 24 +-- src/app/modules/bounce/bounce.controller.ts | 186 +++++++++-------- src/app/modules/core/core.types.ts | 2 +- .../__tests__/examples.factory.spec.ts | 18 +- src/app/modules/examples/examples.service.ts | 151 +++++++------- .../__tests__/feedback.service.spec.ts | 10 +- .../form/__tests__/form.service.spec.ts | 65 +++--- .../modules/form/__tests__/form.utils.spec.ts | 4 +- .../__tests__/admin-form.controller.spec.ts | 24 +-- .../__tests__/admin-form.service.spec.ts | 186 ++++++++--------- .../__tests__/admin-form.utils.spec.ts | 8 +- .../form/admin-form/admin-form.controller.ts | 14 +- .../__tests__/public-form.controller.spec.ts | 68 +++---- .../google-analytics.factory.spec.ts | 14 +- .../myinfo/__tests__/myinfo.adapter.spec.ts | 10 +- .../__tests__/myinfo.controller.spec.ts | 2 +- .../myinfo/__tests__/myinfo.factory.spec.ts | 22 +- .../myinfo/__tests__/myinfo.service.spec.ts | 10 +- .../myinfo/__tests__/myinfo.test.constants.ts | 4 +- src/app/modules/myinfo/myinfo.adapter.ts | 7 +- src/app/modules/myinfo/myinfo.service.ts | 5 +- .../spcp/__tests__/spcp.factory.spec.ts | 24 +-- .../spcp/__tests__/spcp.test.constants.ts | 12 +- .../submission/submission.service.spec.ts | 80 ++++---- .../email-submission.receiver.spec.ts | 39 ++-- .../email-submission.service.spec.ts | 12 +- .../email-submission.test.constants.ts | 10 +- .../__tests__/email-submission.util.spec.ts | 24 +-- .../email-submission.controller.ts | 5 +- .../email-submission.middleware.ts | 5 +- .../email-submission/email-submission.util.ts | 17 +- .../encrypt-submission.service.spec.ts | 8 +- .../encrypt-submission.controller.ts | 30 ++- .../user/__tests__/user.service.spec.ts | 24 +-- .../__tests__/verification.factory.spec.ts | 22 +- .../__tests__/verification.service.spec.ts | 4 +- .../verified-content.factory.spec.ts | 37 ++-- .../webhook/__tests__/webhook.service.spec.ts | 18 +- .../admin-forms.logic.routes.spec.ts | 24 +-- .../__tests__/billings.routes.spec.ts | 14 +- .../public-forms.routes.spec.constants.ts | 10 +- .../captcha/__tests__/captcha.factory.spec.ts | 8 +- .../captcha/__tests__/captcha.service.spec.ts | 20 +- src/app/services/captcha/captcha.service.ts | 76 +++---- .../mail/__tests__/mail.service.spec.ts | 11 +- .../sms/__tests__/sms.factory.spec.ts | 4 +- .../sms/__tests__/sms.service.spec.ts | 8 +- .../__tests__/sms_count.server.model.spec.ts | 5 +- src/app/services/sms/sms.factory.ts | 8 +- src/app/services/sms/sms.service.ts | 8 +- .../services/sms/sms_count.server.model.ts | 10 +- .../__tests__/dropdown-validation.spec.ts | 2 +- .../__tests__/email-validation.spec.ts | 16 +- .../__tests__/mobile-num-validation.spec.ts | 4 +- .../__tests__/table-validation.spec.ts | 2 +- .../validators/attachmentValidator.ts | 17 +- .../validators/checkboxValidator.ts | 191 +++++++++--------- .../field-validation/validators/common.ts | 68 +++---- .../validators/dateValidator.ts | 21 +- .../validators/decimalValidator.ts | 43 ++-- .../validators/dropdownValidator.ts | 27 ++- .../validators/emailValidator.ts | 30 ++- .../validators/numberValidator.ts | 51 +++-- .../validators/radioButtonValidator.ts | 23 +-- .../validators/ratingValidator.ts | 27 ++- .../validators/sectionValidator.ts | 16 +- .../validators/tableValidator.ts | 158 +++++++-------- .../validators/textValidator.ts | 73 ++++--- .../core/resources/landing-examples.js | 21 +- .../views/edit-contact-number-modal.view.html | 7 +- .../edit-logic.client.view.html | 5 +- .../form-emails-input.client.view.html | 5 +- .../modules/forms/admin/constants/covid19.js | 12 +- .../admin-form.client.controller.js | 15 +- .../create-form-modal.client.controller.js | 5 +- .../edit-fields-modal.client.controller.js | 12 +- .../admin/views/admin-form.client.view.html | 7 +- .../views/collaborator.client.modal.html | 10 +- .../views/edit-end-page.client.modal.html | 8 +- .../admin/views/edit-fields.client.modal.html | 8 +- .../admin/views/edit-logic.client.modal.html | 18 +- .../views/edit-myinfo-field.client.modal.html | 8 +- .../views/edit-start-page.client.modal.html | 8 +- .../admin/views/list-forms.client.view.html | 15 +- .../field-attachment.client.view.html | 7 +- .../form-logic.client.service.spec.ts | 4 +- .../controllers/billing.client.controller.js | 7 +- .../__tests__/AdminFormService.test.ts | 8 +- .../__tests__/PublicFormService.test.ts | 8 +- src/shared/util/__tests__/logic.spec.ts | 4 +- 112 files changed, 1325 insertions(+), 1382 deletions(-) diff --git a/package-lock.json b/package-lock.json index 591bfe49d6..412c853d31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20418,9 +20418,9 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, "prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", + "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", "dev": true }, "prettier-linter-helpers": { diff --git a/package.json b/package.json index e9596e2b01..551536530d 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "mongodb-memory-server-core": "^6.9.6", "ngrok": "^4.0.1", "optimize-css-assets-webpack-plugin": "^5.0.1", - "prettier": "^2.2.1", + "prettier": "^2.3.1", "proxyquire": "^2.1.3", "regenerator": "^0.14.4", "rimraf": "^3.0.2", diff --git a/src/app/config/feature-manager/aggregate-stats.config.ts b/src/app/config/feature-manager/aggregate-stats.config.ts index 21de03185f..c9793a833d 100644 --- a/src/app/config/feature-manager/aggregate-stats.config.ts +++ b/src/app/config/feature-manager/aggregate-stats.config.ts @@ -1,16 +1,16 @@ import { FeatureNames, RegisterableFeature } from './types' -const aggregateCollectionFeature: RegisterableFeature = { - name: FeatureNames.AggregateStats, - schema: { - aggregateCollection: { - doc: - 'Has to be defined (i.e. =true) if FormStats collection is to be used', - format: '*', - default: null, - env: 'AGGREGATE_COLLECTION', +const aggregateCollectionFeature: RegisterableFeature = + { + name: FeatureNames.AggregateStats, + schema: { + aggregateCollection: { + doc: 'Has to be defined (i.e. =true) if FormStats collection is to be used', + format: '*', + default: null, + env: 'AGGREGATE_COLLECTION', + }, }, - }, -} + } export default aggregateCollectionFeature diff --git a/src/app/config/feature-manager/google-analytics.config.ts b/src/app/config/feature-manager/google-analytics.config.ts index 166567bc81..35ef2e042c 100644 --- a/src/app/config/feature-manager/google-analytics.config.ts +++ b/src/app/config/feature-manager/google-analytics.config.ts @@ -1,15 +1,16 @@ import { FeatureNames, RegisterableFeature } from './types' -const googleAnalyticsFeature: RegisterableFeature = { - name: FeatureNames.GoogleAnalytics, - schema: { - GATrackingID: { - doc: 'Google Analytics tracking ID', - format: String, - default: null, - env: 'GA_TRACKING_ID', +const googleAnalyticsFeature: RegisterableFeature = + { + name: FeatureNames.GoogleAnalytics, + schema: { + GATrackingID: { + doc: 'Google Analytics tracking ID', + format: String, + default: null, + env: 'GA_TRACKING_ID', + }, }, - }, -} + } export default googleAnalyticsFeature diff --git a/src/app/config/feature-manager/intranet.config.ts b/src/app/config/feature-manager/intranet.config.ts index ff4bb94382..65a924df64 100644 --- a/src/app/config/feature-manager/intranet.config.ts +++ b/src/app/config/feature-manager/intranet.config.ts @@ -4,8 +4,7 @@ export const intranetFeature: RegisterableFeature = { name: FeatureNames.Intranet, schema: { intranetIpListPath: { - doc: - 'Path to file containing list of intranet IP addresses, separated by newlines', + doc: 'Path to file containing list of intranet IP addresses, separated by newlines', format: String, default: null, env: 'INTRANET_IP_LIST_PATH', diff --git a/src/app/config/feature-manager/spcp-myinfo.config.ts b/src/app/config/feature-manager/spcp-myinfo.config.ts index 21bfadf481..dcdf6157a6 100644 --- a/src/app/config/feature-manager/spcp-myinfo.config.ts +++ b/src/app/config/feature-manager/spcp-myinfo.config.ts @@ -9,8 +9,7 @@ const spcpMyInfoFeature: RegisterableFeature = { name: FeatureNames.SpcpMyInfo, schema: { isSPMaintenance: { - doc: - 'If set, displays a banner message on SingPass forms. Overrides IS_CP_MAINTENANCE', + doc: 'If set, displays a banner message on SingPass forms. Overrides IS_CP_MAINTENANCE', format: '*', default: null, env: 'IS_SP_MAINTENANCE', @@ -46,29 +45,25 @@ const spcpMyInfoFeature: RegisterableFeature = { env: 'CP_COOKIE_MAX_AGE', }, spIdpId: { - doc: - 'Partner ID of National Digital Identity Office for SingPass authentication', + doc: 'Partner ID of National Digital Identity Office for SingPass authentication', format: 'url', default: null, env: 'SINGPASS_IDP_ID', }, cpIdpId: { - doc: - 'Partner ID of National Digital Identity Office for CorpPass authentication', + doc: 'Partner ID of National Digital Identity Office for CorpPass authentication', format: 'url', default: null, env: 'CORPPASS_IDP_ID', }, spPartnerEntityId: { - doc: - 'Partner ID registered with National Digital Identity Office for SingPass authentication', + doc: 'Partner ID registered with National Digital Identity Office for SingPass authentication', format: 'url', default: null, env: 'SINGPASS_PARTNER_ENTITY_ID', }, cpPartnerEntityId: { - doc: - 'Partner ID registered with National Digital Identity Office for CorpPass authentication', + doc: 'Partner ID registered with National Digital Identity Office for CorpPass authentication', format: 'url', default: null, env: 'CORPPASS_PARTNER_ENTITY_ID', @@ -98,78 +93,67 @@ const spcpMyInfoFeature: RegisterableFeature = { env: 'CORPPASS_IDP_ENDPOINT', }, spEsrvcId: { - doc: - 'e-service ID registered with National Digital Identity office for SingPass authentication', + doc: 'e-service ID registered with National Digital Identity office for SingPass authentication', format: String, default: null, env: 'SINGPASS_ESRVC_ID', }, cpEsrvcId: { - doc: - 'e-service ID registered with National Digital Identity office for CorpPass authentication', + doc: 'e-service ID registered with National Digital Identity office for CorpPass authentication', format: String, default: null, env: 'CORPPASS_ESRVC_ID', }, spFormSgKeyPath: { - doc: - 'Path to X.509 key used for SingPass related communication with National Digital Identity office', + doc: 'Path to X.509 key used for SingPass related communication with National Digital Identity office', format: String, default: null, env: 'SP_FORMSG_KEY_PATH', }, cpFormSgKeyPath: { - doc: - 'Path to X.509 key used for CorpPass related communication with National Digital Identity office', + doc: 'Path to X.509 key used for CorpPass related communication with National Digital Identity office', format: String, default: null, env: 'CP_FORMSG_KEY_PATH', }, spFormSgCertPath: { - doc: - 'Path to X.509 cert used for SingPass related communication with National Digital Identity office', + doc: 'Path to X.509 cert used for SingPass related communication with National Digital Identity office', format: String, default: null, env: 'SP_FORMSG_CERT_PATH', }, cpFormSgCertPath: { - doc: - 'Path to X.509 cert used for CorpPass related communication with National Digital Identity office', + doc: 'Path to X.509 cert used for CorpPass related communication with National Digital Identity office', format: String, default: null, env: 'CP_FORMSG_CERT_PATH', }, spIdpCertPath: { - doc: - 'Path to National Digital Identity offices X.509 cert used for SingPass related communication', + doc: 'Path to National Digital Identity offices X.509 cert used for SingPass related communication', format: String, default: null, env: 'SP_IDP_CERT_PATH', }, cpIdpCertPath: { - doc: - 'Path to National Digital Identity offices X.509 cert used for CorpPass related communication', + doc: 'Path to National Digital Identity offices X.509 cert used for CorpPass related communication', format: String, default: null, env: 'CP_IDP_CERT_PATH', }, myInfoClientMode: { - doc: - 'Configures MyInfoGovClient. Set this to either `stg` or `prod` to fetch MyInfo data from the corresponding endpoints.', + doc: 'Configures MyInfoGovClient. Set this to either `stg` or `prod` to fetch MyInfo data from the corresponding endpoints.', format: Object.values(MyInfoMode), default: MyInfoMode.Production, env: 'MYINFO_CLIENT_CONFIG', }, myInfoKeyPath: { - doc: - 'Filepath to MyInfo private key, which is used to decrypt data and sign requests when communicating with MyInfo.', + doc: 'Filepath to MyInfo private key, which is used to decrypt data and sign requests when communicating with MyInfo.', format: String, default: null, env: 'MYINFO_FORMSG_KEY_PATH', }, myInfoCertPath: { - doc: - "Path to MyInfo's public certificate, which is used to verify their signature.", + doc: "Path to MyInfo's public certificate, which is used to verify their signature.", format: String, default: null, env: 'MYINFO_CERT_PATH', diff --git a/src/app/config/feature-manager/verified-fields.config.ts b/src/app/config/feature-manager/verified-fields.config.ts index 44bce07431..181b9bbf72 100644 --- a/src/app/config/feature-manager/verified-fields.config.ts +++ b/src/app/config/feature-manager/verified-fields.config.ts @@ -1,15 +1,16 @@ import { FeatureNames, RegisterableFeature } from './types' -const verifiedFieldsFeature: RegisterableFeature = { - name: FeatureNames.VerifiedFields, - schema: { - verificationSecretKey: { - doc: 'The secret key for signing verified responses (email, mobile)', - format: String, - default: null, - env: 'VERIFICATION_SECRET_KEY', +const verifiedFieldsFeature: RegisterableFeature = + { + name: FeatureNames.VerifiedFields, + schema: { + verificationSecretKey: { + doc: 'The secret key for signing verified responses (email, mobile)', + format: String, + default: null, + env: 'VERIFICATION_SECRET_KEY', + }, }, - }, -} + } export default verifiedFieldsFeature diff --git a/src/app/config/feature-manager/webhook-verified-content.config.ts b/src/app/config/feature-manager/webhook-verified-content.config.ts index 77999d3fcc..2a5fd6520e 100644 --- a/src/app/config/feature-manager/webhook-verified-content.config.ts +++ b/src/app/config/feature-manager/webhook-verified-content.config.ts @@ -1,16 +1,16 @@ import { FeatureNames, RegisterableFeature } from './types' -const webhookVerifiedContentFeature: RegisterableFeature = { - name: FeatureNames.WebhookVerifiedContent, - schema: { - signingSecretKey: { - doc: - 'The secret key for signing verified content passed into the database and for signing webhooks', - format: String, - default: null, - env: 'SIGNING_SECRET_KEY', +const webhookVerifiedContentFeature: RegisterableFeature = + { + name: FeatureNames.WebhookVerifiedContent, + schema: { + signingSecretKey: { + doc: 'The secret key for signing verified content passed into the database and for signing webhooks', + format: String, + default: null, + env: 'SIGNING_SECRET_KEY', + }, }, - }, -} + } export default webhookVerifiedContentFeature diff --git a/src/app/config/logger.ts b/src/app/config/logger.ts index fe7455aa66..6f007071a7 100644 --- a/src/app/config/logger.ts +++ b/src/app/config/logger.ts @@ -106,7 +106,7 @@ export const customFormat = format.printf((info) => { // e.g. logger.info('param1', 'param2') // The second parameter onwards will be passed into the `splat` key and // require formatting (because that is just how the library is written). - const splatSymbol = (Symbol.for('splat') as unknown) as string + const splatSymbol = Symbol.for('splat') as unknown as string const splatArgs = info[splatSymbol] || [] const rest = splatArgs.map((data: any) => formatWithInspect(data)).join(' ') const msg = formatWithInspect(info.message) diff --git a/src/app/config/schema.ts b/src/app/config/schema.ts index 68a57683f6..249d0dc5af 100644 --- a/src/app/config/schema.ts +++ b/src/app/config/schema.ts @@ -156,8 +156,7 @@ export const optionalVarsSchema: Schema = { env: 'IS_LOGIN_BANNER', }, siteBannerContent: { - doc: - 'The banner message to show on all pages. Allows for HTML. Will supersede all other banner content if it exists.', + doc: 'The banner message to show on all pages. Allows for HTML. Will supersede all other banner content if it exists.', format: String, default: '', env: 'SITE_BANNER_CONTENT', @@ -170,8 +169,7 @@ export const optionalVarsSchema: Schema = { }, }, formsgSdkMode: { - doc: - 'Inform SDK which public keys are to be used to sign, encrypt, or decrypt data that is passed to it', + doc: 'Inform SDK which public keys are to be used to sign, encrypt, or decrypt data that is passed to it', format: ['staging', 'production', 'development', 'test'], default: 'production' as PackageMode, env: 'FORMSG_SDK_MODE', @@ -190,8 +188,7 @@ export const optionalVarsSchema: Schema = { env: 'MAIL_LOGGER', }, debug: { - doc: - 'If set to true, then logs SMTP traffic, otherwise logs only transaction events.', + doc: 'If set to true, then logs SMTP traffic, otherwise logs only transaction events.', format: 'Boolean', default: false, env: 'MAIL_DEBUG', @@ -209,8 +206,7 @@ export const optionalVarsSchema: Schema = { env: 'CHROMIUM_BIN', }, maxMessages: { - doc: - 'Nodemailer config to help to keep the connection up-to-date for long-running messaging', + doc: 'Nodemailer config to help to keep the connection up-to-date for long-running messaging', format: 'int', default: 100, env: 'SES_MAX_MESSAGES', @@ -236,8 +232,7 @@ export const optionalVarsSchema: Schema = { env: 'AWS_REGION', }, customCloudWatchGroup: { - doc: - 'Name of CloudWatch log group to store short-term logs. Log streams are separated by date.', + doc: 'Name of CloudWatch log group to store short-term logs. Log streams are separated by date.', format: String, default: '', env: 'CUSTOM_CLOUDWATCH_LOG_GROUP', @@ -251,8 +246,7 @@ export const optionalVarsSchema: Schema = { env: 'PORT', }, otpLifeSpan: { - doc: - 'OTP Life Span for Login. (Should be in miliseconds, e.g. 1000 * 60 * 15 = 15 mins)', + doc: 'OTP Life Span for Login. (Should be in miliseconds, e.g. 1000 * 60 * 15 = 15 mins)', format: 'int', default: 900000, env: 'OTP_LIFE_SPAN', @@ -272,15 +266,13 @@ export const optionalVarsSchema: Schema = { }, rateLimit: { submissions: { - doc: - 'Per-minute, per-IP, per-instance request limit for submissions endpoints', + doc: 'Per-minute, per-IP, per-instance request limit for submissions endpoints', format: 'int', default: 80, env: 'SUBMISSIONS_RATE_LIMIT', }, sendAuthOtp: { - doc: - 'Per-minute, per-IP request limit for OTPs to log in to the admin console', + doc: 'Per-minute, per-IP request limit for OTPs to log in to the admin console', format: 'int', default: 60, env: 'SEND_AUTH_OTP_RATE_LIMIT', @@ -361,8 +353,7 @@ export const loadS3BucketUrlSchema = ({ env: 'AWS_ENDPOINT', }, attachmentBucketUrl: { - doc: - 'Url of attachment S3 bucket derived from S3 endpoint and bucket name', + doc: 'Url of attachment S3 bucket derived from S3 endpoint and bucket name', format: (val) => validateS3BucketUrl(val, { isDev, hasTrailingSlash: true, region }), default: null, diff --git a/src/app/models/__tests__/admin_verification.server.model.spec.ts b/src/app/models/__tests__/admin_verification.server.model.spec.ts index 250cc50e50..930f9cdd3d 100644 --- a/src/app/models/__tests__/admin_verification.server.model.spec.ts +++ b/src/app/models/__tests__/admin_verification.server.model.spec.ts @@ -234,9 +234,8 @@ describe('AdminVerification Model', () => { await expect(AdminVerification.countDocuments()).resolves.toEqual(1) // Act - const actualPromise = AdminVerification.incrementAttemptsByAdminId( - adminId, - ) + const actualPromise = + AdminVerification.incrementAttemptsByAdminId(adminId) // Assert // Exactly the same as initial params, but with numOtpAttempts @@ -256,9 +255,8 @@ describe('AdminVerification Model', () => { const freshAdminId = new ObjectID() // Act - const actualPromise = AdminVerification.incrementAttemptsByAdminId( - freshAdminId, - ) + const actualPromise = + AdminVerification.incrementAttemptsByAdminId(freshAdminId) // Assert await expect(actualPromise).resolves.toBeNull() diff --git a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts index e182bcfe1b..f48656cb2f 100644 --- a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts +++ b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts @@ -33,16 +33,15 @@ describe('Encrypt Submission Model', () => { const validFormId = new ObjectId().toHexString() const createdDate = new Date() // Add valid encrypt submission. - const validSubmission = await Submission.create( - { + const validSubmission = + await Submission.create({ form: validFormId, myInfoFields: [], submissionType: SubmissionType.Encrypt, encryptedContent: MOCK_ENCRYPTED_CONTENT, version: 1, created: createdDate, - }, - ) + }) // Act const result = await EncryptSubmission.findSingleMetadata( @@ -391,15 +390,14 @@ describe('Encrypt Submission Model', () => { it('should return null when type of submission with given id is not SubmissionType.Encrypt', async () => { // Arrange const validFormId = new ObjectId().toHexString() - const validEmailSubmission = await Submission.create( - { + const validEmailSubmission = + await Submission.create({ submissionType: SubmissionType.Email, form: validFormId, recipientEmails: ['any@example.com'], responseHash: 'any hash', responseSalt: 'any salt', - }, - ) + }) // Act const actual = await EncryptSubmission.findEncryptedSubmissionById( diff --git a/src/app/models/__tests__/form.server.model.spec.ts b/src/app/models/__tests__/form.server.model.spec.ts index a38e6d32ce..7ff3e8c133 100644 --- a/src/app/models/__tests__/form.server.model.spec.ts +++ b/src/app/models/__tests__/form.server.model.spec.ts @@ -392,9 +392,9 @@ describe('Form Model', () => { expect(actualSavedObject).toEqual(expectedObject) // Remove indeterministic id from actual permission list - const actualPermissionList = ((saved.toObject() as unknown) as IEncryptedForm).permissionList?.map( - (permission) => omit(permission, '_id'), - ) + const actualPermissionList = ( + saved.toObject() as unknown as IEncryptedForm + ).permissionList?.map((permission) => omit(permission, '_id')) expect(actualPermissionList).toEqual(permissionList) }) @@ -1074,9 +1074,9 @@ describe('Form Model', () => { ], } - const mockNewFormLogic = ({ + const mockNewFormLogic = { logicType: LogicType.PreventSubmit, - } as unknown) as ILogicSchema + } as unknown as ILogicSchema it('should return form upon successful create logic if form_logic is currently empty', async () => { // arrange @@ -1957,7 +1957,9 @@ describe('Form Model', () => { it('should return updated form when successfully updating form field', async () => { // Arrange - const originalFormFields = (form.form_fields as Types.DocumentArray).toObject() + const originalFormFields = ( + form.form_fields as Types.DocumentArray + ).toObject() const newField = { ...originalFormFields[1], @@ -1994,7 +1996,9 @@ describe('Form Model', () => { it('should return validation error if field type of new field does not match the field to update', async () => { // Arrange - const originalFormFields = (form.form_fields as Types.DocumentArray).toObject() + const originalFormFields = ( + form.form_fields as Types.DocumentArray + ).toObject() const newField: FormFieldWithId = { ...originalFormFields[1], @@ -2017,7 +2021,9 @@ describe('Form Model', () => { it('should return validation error if model validation fails whilst updating field', async () => { // Arrange - const originalFormFields = (form.form_fields as Types.DocumentArray).toObject() + const originalFormFields = ( + form.form_fields as Types.DocumentArray + ).toObject() const newField: FormFieldWithId = { ...originalFormFields[2], @@ -2149,7 +2155,9 @@ describe('Form Model', () => { // Assert expect(updatedForm).not.toBeNull() expect( - (updatedForm?.form_fields as Types.DocumentArray).toObject(), + ( + updatedForm?.form_fields as Types.DocumentArray + ).toObject(), ).toEqual([ // Should be rearranged to the 0th index position, and the previously // 0th index field should be pushed to 1st index. @@ -2174,7 +2182,9 @@ describe('Form Model', () => { // Assert expect(updatedForm).not.toBeNull() expect( - (updatedForm?.form_fields as Types.DocumentArray).toObject(), + ( + updatedForm?.form_fields as Types.DocumentArray + ).toObject(), ).toEqual([ originalFields[0], originalFields[2], diff --git a/src/app/models/__tests__/form_statistics_total.server.model.spec.ts b/src/app/models/__tests__/form_statistics_total.server.model.spec.ts index 7163dacca7..cc20af1f8d 100644 --- a/src/app/models/__tests__/form_statistics_total.server.model.spec.ts +++ b/src/app/models/__tests__/form_statistics_total.server.model.spec.ts @@ -22,11 +22,11 @@ describe('FormStatisticsTotal Model', () => { formCounts.forEach((count) => { submissionPromises.push( // Using mongodb native function to bypass collection presave hook. - (FormStatsModel.collection.insertOne({ + FormStatsModel.collection.insertOne({ formId: new ObjectId(), totalCount: count, lastSubmission: new Date(), - }) as unknown) as Promise, + }) as unknown as Promise, ) }) await Promise.all(submissionPromises) @@ -53,11 +53,11 @@ describe('FormStatisticsTotal Model', () => { formCounts.forEach((count) => { submissionPromises.push( // Using mongodb native function to bypass collection presave hook. - (FormStatsModel.collection.insertOne({ + FormStatsModel.collection.insertOne({ formId: new ObjectId(), totalCount: count, lastSubmission: new Date(), - }) as unknown) as Promise, + }) as unknown as Promise, ) }) await Promise.all(submissionPromises) diff --git a/src/app/models/__tests__/login.server.model.spec.ts b/src/app/models/__tests__/login.server.model.spec.ts index 64ce5e53cd..1c625b1d2f 100644 --- a/src/app/models/__tests__/login.server.model.spec.ts +++ b/src/app/models/__tests__/login.server.model.spec.ts @@ -126,7 +126,7 @@ describe('login.server.model', () => { const agencyId = new ObjectId() const mockEsrvcId = 'esrvcid' const mockAuthType = 'SP' - const fullForm = ({ + const fullForm = { _id: formId, admin: { _id: adminId, @@ -136,7 +136,7 @@ describe('login.server.model', () => { }, authType: mockAuthType, esrvcId: mockEsrvcId, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm it('should save the correct form data', async () => { const saved = await LoginModel.addLoginFromForm(fullForm) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index 709dd79b4a..1f9aff820a 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -499,7 +499,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { return this.save() } - const FormDocumentSchema = (FormSchema as unknown) as Schema + const FormDocumentSchema = FormSchema as unknown as Schema FormDocumentSchema.methods.getSettings = function (): FormSettings { return pick(this, FORM_SETTING_FIELDS) diff --git a/src/app/modules/analytics/__tests__/analytics.service.spec.ts b/src/app/modules/analytics/__tests__/analytics.service.spec.ts index 1b25fdf1ca..930d645489 100644 --- a/src/app/modules/analytics/__tests__/analytics.service.spec.ts +++ b/src/app/modules/analytics/__tests__/analytics.service.spec.ts @@ -85,9 +85,9 @@ describe('analytics.service', () => { it('should return DatabaseError when error occurs whilst retrieving form count', async () => { // Arrange const execSpy = jest.fn().mockRejectedValueOnce(new Error('boom')) - jest.spyOn(FormModel, 'estimatedDocumentCount').mockReturnValueOnce(({ + jest.spyOn(FormModel, 'estimatedDocumentCount').mockReturnValueOnce({ exec: execSpy, - } as unknown) as Query) + } as unknown as Query) // Act const actualTE = await getFormCount() @@ -146,9 +146,9 @@ describe('analytics.service', () => { it('should return DatabaseError when error occurs whilst retrieving user count', async () => { // Arrange const execSpy = jest.fn().mockRejectedValueOnce(new Error('boom')) - jest.spyOn(UserModel, 'estimatedDocumentCount').mockReturnValueOnce(({ + jest.spyOn(UserModel, 'estimatedDocumentCount').mockReturnValueOnce({ exec: execSpy, - } as unknown) as Query) + } as unknown as Query) // Act const actualTE = await getUserCount() @@ -205,9 +205,9 @@ describe('analytics.service', () => { const execSpy = jest.fn().mockRejectedValueOnce(new Error('boom')) jest .spyOn(SubmissionModel, 'estimatedDocumentCount') - .mockReturnValueOnce(({ + .mockReturnValueOnce({ exec: execSpy, - } as unknown) as Query) + } as unknown as Query) // Act const actualTE = await getSubmissionCount() diff --git a/src/app/modules/auth/auth.service.ts b/src/app/modules/auth/auth.service.ts index ccd8d1e38b..885d2b410f 100644 --- a/src/app/modules/auth/auth.service.ts +++ b/src/app/modules/auth/auth.service.ts @@ -300,18 +300,20 @@ export const getFormAfterPermissionChecks = ({ * @returns err(ForbiddenFormError if user does not have permission * @returns err(DatabaseError) if any database error occurs */ -export const checkFormForPermissions = (level: PermissionLevel) => ({ - user, - form, -}: { - user: IUserSchema - form: IPopulatedForm -}): Result => - // Step 1: Check whether form is available to be retrieved. - assertFormAvailable(form) - // Step 2: Check required permission levels. - .andThen(() => getAssertPermissionFn(level)(user, form)) - .map(() => form) +export const checkFormForPermissions = + (level: PermissionLevel) => + ({ + user, + form, + }: { + user: IUserSchema + form: IPopulatedForm + }): Result => + // Step 1: Check whether form is available to be retrieved. + assertFormAvailable(form) + // Step 2: Check required permission levels. + .andThen(() => getAssertPermissionFn(level)(user, form)) + .map(() => form) /** * Retrieves the form of given formId provided that the form is public. diff --git a/src/app/modules/billing/__tests__/billing.factory.spec.ts b/src/app/modules/billing/__tests__/billing.factory.spec.ts index 247ace448c..4657c62e57 100644 --- a/src/app/modules/billing/__tests__/billing.factory.spec.ts +++ b/src/app/modules/billing/__tests__/billing.factory.spec.ts @@ -50,7 +50,7 @@ describe('billing.factory', () => { it('should return MissingFeatureError when spcp-myinfo feature is disabled', async () => { // Argument here does not matter, as the function should always return a MissingFeatureError const result = await BillingFactory.recordLoginByForm( - ({} as unknown) as IPopulatedForm, + {} as unknown as IPopulatedForm, ) expect(MockBillingService.recordLoginByForm).not.toHaveBeenCalled() expect(result._unsafeUnwrapErr()).toEqual( @@ -80,9 +80,10 @@ describe('billing.factory', () => { total: 100, }, ] - const serviceGetStatsSpy = MockBillingService.getSpLoginStats.mockReturnValue( - okAsync(mockLoginStats), - ) + const serviceGetStatsSpy = + MockBillingService.getSpLoginStats.mockReturnValue( + okAsync(mockLoginStats), + ) // Act const actualResults = await BillingFactory.getSpLoginStats( @@ -100,12 +101,12 @@ describe('billing.factory', () => { describe('recordLoginByForm', () => { it('should call BillingService.recordLoginByForm', async () => { - const mockLoginDoc = ({ + const mockLoginDoc = { mockKey: 'mockValue', - } as unknown) as ILoginSchema - const mockForm = ({ + } as unknown as ILoginSchema + const mockForm = { mockFormKey: 'mockFormvalue', - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm MockBillingService.recordLoginByForm.mockResolvedValueOnce( okAsync(mockLoginDoc), ) diff --git a/src/app/modules/billing/__tests__/billing.routes.spec.ts b/src/app/modules/billing/__tests__/billing.routes.spec.ts index 5bc76cde85..0a076eea6a 100644 --- a/src/app/modules/billing/__tests__/billing.routes.spec.ts +++ b/src/app/modules/billing/__tests__/billing.routes.spec.ts @@ -51,14 +51,12 @@ describe('billing.routes', () => { // Log in user. const session = await createAuthedSession(defaultUser.email, request) // Generate login statistics. - const { - generatedLoginTimes, - generatedForms, - } = await generateLoginStatistics({ - user: defaultUser, - esrvcIdToCheck: VALID_ESRVCID_1, - altEsrvcId: VALID_ESRVCID_2, - }) + const { generatedLoginTimes, generatedForms } = + await generateLoginStatistics({ + user: defaultUser, + esrvcIdToCheck: VALID_ESRVCID_1, + altEsrvcId: VALID_ESRVCID_2, + }) // Act const response = await session.get('/billing').query({ diff --git a/src/app/modules/billing/__tests__/billing.service.spec.ts b/src/app/modules/billing/__tests__/billing.service.spec.ts index bf42ca1841..9f02cf29fb 100644 --- a/src/app/modules/billing/__tests__/billing.service.spec.ts +++ b/src/app/modules/billing/__tests__/billing.service.spec.ts @@ -19,8 +19,8 @@ describe('billing.service', () => { describe('recordLoginByForm', () => { beforeEach(() => jest.restoreAllMocks()) it('should call LoginModel.addLoginFromForm with the given form', async () => { - const mockForm = ({ authType: AuthType.SP } as unknown) as IPopulatedForm - const mockLogin = ({ esrvcId: 'esrvcId' } as unknown) as ILoginSchema + const mockForm = { authType: AuthType.SP } as unknown as IPopulatedForm + const mockLogin = { esrvcId: 'esrvcId' } as unknown as ILoginSchema const addLoginSpy = jest .spyOn(LoginModel, 'addLoginFromForm') .mockResolvedValueOnce(mockLogin) @@ -30,8 +30,8 @@ describe('billing.service', () => { }) it('should return FormHasNoAuthError when form has authType NIL', async () => { - const mockForm = ({ authType: AuthType.NIL } as unknown) as IPopulatedForm - const mockLogin = ({ esrvcId: 'esrvcId' } as unknown) as ILoginSchema + const mockForm = { authType: AuthType.NIL } as unknown as IPopulatedForm + const mockLogin = { esrvcId: 'esrvcId' } as unknown as ILoginSchema const addLoginSpy = jest .spyOn(LoginModel, 'addLoginFromForm') .mockResolvedValueOnce(mockLogin) @@ -41,7 +41,7 @@ describe('billing.service', () => { }) it('should return DatabaseError when adding login fails', async () => { - const mockForm = ({ authType: AuthType.SP } as unknown) as IPopulatedForm + const mockForm = { authType: AuthType.SP } as unknown as IPopulatedForm const addLoginSpy = jest .spyOn(LoginModel, 'addLoginFromForm') .mockRejectedValueOnce('') diff --git a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts index 85e91c8e29..e835515e2c 100644 --- a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts @@ -44,9 +44,9 @@ jest.doMock('mongoose', () => ({ const MOCK_NOTIFICATION = { notificationType: 'Bounce' } as IEmailNotification const MOCK_REQ = expressHandler.mockRequest({ - body: ({ + body: { Message: JSON.stringify(MOCK_NOTIFICATION), - } as unknown) as ISnsNotification, + } as unknown as ISnsNotification, }) const MOCK_RES = expressHandler.mockResponse() const MOCK_EMAIL_RECIPIENTS = ['a@email.com', 'b@email.com'] diff --git a/src/app/modules/bounce/__tests__/bounce.service.spec.ts b/src/app/modules/bounce/__tests__/bounce.service.spec.ts index b04642b9dc..d98175ebf6 100644 --- a/src/app/modules/bounce/__tests__/bounce.service.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.service.spec.ts @@ -298,10 +298,8 @@ describe('BounceService', () => { ], }) - const notifiedRecipients = await BounceService.sendEmailBounceNotification( - bounceDoc, - form, - ) + const notifiedRecipients = + await BounceService.sendEmailBounceNotification(bounceDoc, form) expect(MockMailService.sendBounceNotification).toHaveBeenCalledWith({ emailRecipients: [testUser.email], @@ -329,10 +327,8 @@ describe('BounceService', () => { ], }) - const notifiedRecipients = await BounceService.sendEmailBounceNotification( - bounceDoc, - form, - ) + const notifiedRecipients = + await BounceService.sendEmailBounceNotification(bounceDoc, form) expect(MockMailService.sendBounceNotification).toHaveBeenCalledWith({ emailRecipients: [collabEmail], @@ -358,10 +354,8 @@ describe('BounceService', () => { ], }) - const notifiedRecipients = await BounceService.sendEmailBounceNotification( - bounceDoc, - form, - ) + const notifiedRecipients = + await BounceService.sendEmailBounceNotification(bounceDoc, form) expect(MockMailService.sendBounceNotification).not.toHaveBeenCalled() expect(notifiedRecipients._unsafeUnwrap()).toEqual([]) @@ -384,10 +378,8 @@ describe('BounceService', () => { ], }) - const notifiedRecipients = await BounceService.sendEmailBounceNotification( - bounceDoc, - form, - ) + const notifiedRecipients = + await BounceService.sendEmailBounceNotification(bounceDoc, form) expect(MockMailService.sendBounceNotification).not.toHaveBeenCalled() expect(notifiedRecipients._unsafeUnwrap()).toEqual([]) diff --git a/src/app/modules/bounce/bounce.controller.ts b/src/app/modules/bounce/bounce.controller.ts index ece53f08f3..7b0e065500 100644 --- a/src/app/modules/bounce/bounce.controller.ts +++ b/src/app/modules/bounce/bounce.controller.ts @@ -18,106 +18,104 @@ const logger = createLoggerWithLabel(module) * @param req Express request object * @param res - Express response object */ -export const handleSns: ControllerHandler< - unknown, - never, - ISnsNotification -> = async (req, res) => { - const notificationResult = await BounceService.validateSnsRequest( - req.body, - ).andThen(() => BounceService.safeParseNotification(req.body.Message)) - if (notificationResult.isErr()) { - logger.warn({ - message: 'Unable to parse email notification request', - meta: { - action: 'handleSns', - }, - error: notificationResult.error, - }) - return res.sendStatus(StatusCodes.UNAUTHORIZED) - } - const notification = notificationResult.value - - BounceService.logEmailNotification(notification) - // If not admin response, no more action to be taken - if ( - BounceService.extractEmailType(notification) !== EmailType.AdminResponse - ) { - return res.sendStatus(StatusCodes.OK) - } +export const handleSns: ControllerHandler = + async (req, res) => { + const notificationResult = await BounceService.validateSnsRequest( + req.body, + ).andThen(() => BounceService.safeParseNotification(req.body.Message)) + if (notificationResult.isErr()) { + logger.warn({ + message: 'Unable to parse email notification request', + meta: { + action: 'handleSns', + }, + error: notificationResult.error, + }) + return res.sendStatus(StatusCodes.UNAUTHORIZED) + } + const notification = notificationResult.value - const bounceDocResult = await BounceService.getUpdatedBounceDoc(notification) - if (bounceDocResult.isErr()) { - logger.warn({ - message: 'Error while retrieving or creating new bounce doc', - meta: { - action: 'handleSns', - }, - error: bounceDocResult.error, - }) - return res.sendStatus(StatusCodes.OK) - } - const bounceDoc = bounceDocResult.value + BounceService.logEmailNotification(notification) + // If not admin response, no more action to be taken + if ( + BounceService.extractEmailType(notification) !== EmailType.AdminResponse + ) { + return res.sendStatus(StatusCodes.OK) + } - const formResult = await FormService.retrieveFullFormById(bounceDoc.formId) - if (formResult.isErr()) { - // Either database error occurred or the formId saved in the bounce collection - // doesn't exist, so something went wrong. - logger.error({ - message: 'Failed to retrieve form corresponding to bounced formId', - meta: { - action: 'handleSns', - formId: bounceDoc.formId, - }, - }) - return res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR) - } - const form = formResult.value + const bounceDocResult = await BounceService.getUpdatedBounceDoc( + notification, + ) + if (bounceDocResult.isErr()) { + logger.warn({ + message: 'Error while retrieving or creating new bounce doc', + meta: { + action: 'handleSns', + }, + error: bounceDocResult.error, + }) + return res.sendStatus(StatusCodes.OK) + } + const bounceDoc = bounceDocResult.value - if (bounceDoc.isCriticalBounce()) { - // Send notifications and deactivate form on best-effort basis, ignore errors - const possibleSmsRecipients = await BounceService.getEditorsWithContactNumbers( - form, - ).unwrapOr([]) - const emailRecipients = await BounceService.sendEmailBounceNotification( - bounceDoc, - form, - ).unwrapOr([]) - const smsRecipients = await BounceService.sendSmsBounceNotification( - bounceDoc, - form, - possibleSmsRecipients, - ).unwrapOr([]) - bounceDoc.setNotificationState(emailRecipients, smsRecipients) + const formResult = await FormService.retrieveFullFormById(bounceDoc.formId) + if (formResult.isErr()) { + // Either database error occurred or the formId saved in the bounce collection + // doesn't exist, so something went wrong. + logger.error({ + message: 'Failed to retrieve form corresponding to bounced formId', + meta: { + action: 'handleSns', + formId: bounceDoc.formId, + }, + }) + return res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR) + } + const form = formResult.value - const shouldDeactivate = bounceDoc.areAllPermanentBounces() - if (shouldDeactivate) { - await FormService.deactivateForm(bounceDoc.formId) - await BounceService.notifyAdminsOfDeactivation( + if (bounceDoc.isCriticalBounce()) { + // Send notifications and deactivate form on best-effort basis, ignore errors + const possibleSmsRecipients = + await BounceService.getEditorsWithContactNumbers(form).unwrapOr([]) + const emailRecipients = await BounceService.sendEmailBounceNotification( + bounceDoc, + form, + ).unwrapOr([]) + const smsRecipients = await BounceService.sendSmsBounceNotification( + bounceDoc, form, possibleSmsRecipients, - ) + ).unwrapOr([]) + bounceDoc.setNotificationState(emailRecipients, smsRecipients) + + const shouldDeactivate = bounceDoc.areAllPermanentBounces() + if (shouldDeactivate) { + await FormService.deactivateForm(bounceDoc.formId) + await BounceService.notifyAdminsOfDeactivation( + form, + possibleSmsRecipients, + ) + } + + // Important log message for user follow-ups + BounceService.logCriticalBounce({ + bounceDoc, + notification, + autoEmailRecipients: emailRecipients, + autoSmsRecipients: smsRecipients, + hasDeactivated: shouldDeactivate, + }) } - // Important log message for user follow-ups - BounceService.logCriticalBounce({ - bounceDoc, - notification, - autoEmailRecipients: emailRecipients, - autoSmsRecipients: smsRecipients, - hasDeactivated: shouldDeactivate, - }) + return BounceService.saveBounceDoc(bounceDoc) + .map(() => res.sendStatus(StatusCodes.OK)) + .mapErr((error) => { + // Accept the risk that there might be concurrency problems + // when multiple server instances try to access the same + // document, due to notifications arriving asynchronously. + if (error instanceof DatabaseConflictError) + return res.sendStatus(StatusCodes.OK) + // Otherwise internal database error + return res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR) + }) } - - return BounceService.saveBounceDoc(bounceDoc) - .map(() => res.sendStatus(StatusCodes.OK)) - .mapErr((error) => { - // Accept the risk that there might be concurrency problems - // when multiple server instances try to access the same - // document, due to notifications arriving asynchronously. - if (error instanceof DatabaseConflictError) - return res.sendStatus(StatusCodes.OK) - // Otherwise internal database error - return res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR) - }) -} diff --git a/src/app/modules/core/core.types.ts b/src/app/modules/core/core.types.ts index 896aa7618a..a8168d8572 100644 --- a/src/app/modules/core/core.types.ts +++ b/src/app/modules/core/core.types.ts @@ -11,5 +11,5 @@ export type ControllerHandler< ResBody = unknown, ReqBody = unknown, ReqQuery = unknown, - Locals = Record + Locals = Record, > = RequestHandler diff --git a/src/app/modules/examples/__tests__/examples.factory.spec.ts b/src/app/modules/examples/__tests__/examples.factory.spec.ts index 21089076a3..4df4721198 100644 --- a/src/app/modules/examples/__tests__/examples.factory.spec.ts +++ b/src/app/modules/examples/__tests__/examples.factory.spec.ts @@ -20,10 +20,11 @@ const MockExamplesService = mocked(ExamplesService) describe('examples.factory', () => { describe('aggregate-stats feature disabled', () => { - const MOCK_DISABLED_FEATURE: RegisteredFeature = { - isEnabled: false, - props: {} as IAggregateStats, - } + const MOCK_DISABLED_FEATURE: RegisteredFeature = + { + isEnabled: false, + props: {} as IAggregateStats, + } const ExamplesFactory = createExamplesFactory(MOCK_DISABLED_FEATURE) describe('getExampleForms', () => { @@ -52,10 +53,11 @@ describe('examples.factory', () => { }) describe('aggregate-stats feature enabled', () => { - const MOCK_ENABLED_FEATURE: RegisteredFeature = { - isEnabled: true, - props: {} as IAggregateStats, - } + const MOCK_ENABLED_FEATURE: RegisteredFeature = + { + isEnabled: true, + props: {} as IAggregateStats, + } const ExamplesFactory = createExamplesFactory(MOCK_ENABLED_FEATURE) describe('getExampleForms', () => { diff --git a/src/app/modules/examples/examples.service.ts b/src/app/modules/examples/examples.service.ts index 52fbc4c3a0..d6c7c907c2 100644 --- a/src/app/modules/examples/examples.service.ts +++ b/src/app/modules/examples/examples.service.ts @@ -239,29 +239,28 @@ const getFormInfo = ( * @returns ok(list of retrieved example forms) if `shouldGetTotalNumResults` is not of string `"true"` * @returns err(DatabaseError) if any errors occurs whilst running the pipeline on the database */ -export const getExampleForms = (type: RetrievalType) => ( - query: ExamplesQueryParams, -): ResultAsync => { - const { - lookUpMiddleware, - groupByMiddleware, - generalQueryModel, - } = RETRIEVAL_TO_QUERY_DATA_MAP[type] +export const getExampleForms = + (type: RetrievalType) => + ( + query: ExamplesQueryParams, + ): ResultAsync => { + const { lookUpMiddleware, groupByMiddleware, generalQueryModel } = + RETRIEVAL_TO_QUERY_DATA_MAP[type] - const queryBuilder = getExamplesQueryBuilder({ - query, - lookUpMiddleware, - groupByMiddleware, - generalQueryModel, - }) + const queryBuilder = getExamplesQueryBuilder({ + query, + lookUpMiddleware, + groupByMiddleware, + generalQueryModel, + }) - const { pageNo, shouldGetTotalNumResults } = query - const offset = pageNo * PAGE_SIZE || 0 + const { pageNo, shouldGetTotalNumResults } = query + const offset = pageNo * PAGE_SIZE || 0 - return shouldGetTotalNumResults - ? execExamplesQueryWithTotal(queryBuilder, offset) - : execExamplesQuery(queryBuilder, offset) -} + return shouldGetTotalNumResults + ? execExamplesQueryWithTotal(queryBuilder, offset) + : execExamplesQuery(queryBuilder, offset) + } /** * Retrieves a single form for examples from either the FormStatisticsTotal @@ -273,63 +272,63 @@ export const getExampleForms = (type: RetrievalType) => ( * @returns err(DatabaseError) if any errors occurs whilst running the pipeline on the database * @returns err(ResultsNotFoundError) if form info cannot be retrieved with the given form id */ -export const getSingleExampleForm = (type: RetrievalType) => ( - formId: string, -): ResultAsync => { - const { - singleSearchPipeline, - generalQueryModel, - } = RETRIEVAL_TO_QUERY_DATA_MAP[type] +export const getSingleExampleForm = + (type: RetrievalType) => + ( + formId: string, + ): ResultAsync => { + const { singleSearchPipeline, generalQueryModel } = + RETRIEVAL_TO_QUERY_DATA_MAP[type] - return ( - // Step 1: Retrieve base form info to augment. - getFormInfo(formId) - // Step 2a: Execute aggregate query with relevant single search pipeline. - .andThen((formInfo) => - ResultAsync.fromPromise( - generalQueryModel - .aggregate(singleSearchPipeline(formId)) - .read('secondary') - .exec() as Promise, - (error) => { - logger.error({ - message: 'Failed to retrieve a single example form', - meta: { - action: 'getSingleExampleForm', - }, - error, - }) + return ( + // Step 1: Retrieve base form info to augment. + getFormInfo(formId) + // Step 2a: Execute aggregate query with relevant single search pipeline. + .andThen((formInfo) => + ResultAsync.fromPromise( + generalQueryModel + .aggregate(singleSearchPipeline(formId)) + .read('secondary') + .exec() as Promise, + (error) => { + logger.error({ + message: 'Failed to retrieve a single example form', + meta: { + action: 'getSingleExampleForm', + }, + error, + }) - return new DatabaseError() - }, - // Step 2b: Augment the initial base form info with the retrieved - // statistics from the aggregate pipeline. - ).map((queryResult) => { - // Process result depending on whether search pipeline returned - // results. - // If the statistics cannot be found, add default "null" fields. - if (!queryResult || queryResult.length === 0) { - const emptyStatsExampleInfo: FormInfo = { - ...formInfo, - count: 0, - lastSubmission: null, - timeText: '-', - avgFeedback: null, + return new DatabaseError() + }, + // Step 2b: Augment the initial base form info with the retrieved + // statistics from the aggregate pipeline. + ).map((queryResult) => { + // Process result depending on whether search pipeline returned + // results. + // If the statistics cannot be found, add default "null" fields. + if (!queryResult || queryResult.length === 0) { + const emptyStatsExampleInfo: FormInfo = { + ...formInfo, + count: 0, + lastSubmission: null, + timeText: '-', + avgFeedback: null, + } + return { form: emptyStatsExampleInfo } } - return { form: emptyStatsExampleInfo } - } - // Statistics can be found. - const [statistics] = queryResult - const processedExampleInfo: FormInfo = { - ...formInfo, - count: statistics.count, - lastSubmission: statistics.lastSubmission, - avgFeedback: statistics.avgFeedback, - timeText: formatToRelativeString(statistics.lastSubmission), - } - return { form: processedExampleInfo } - }), - ) - ) -} + // Statistics can be found. + const [statistics] = queryResult + const processedExampleInfo: FormInfo = { + ...formInfo, + count: statistics.count, + lastSubmission: statistics.lastSubmission, + avgFeedback: statistics.avgFeedback, + timeText: formatToRelativeString(statistics.lastSubmission), + } + return { form: processedExampleInfo } + }), + ) + ) + } diff --git a/src/app/modules/feedback/__tests__/feedback.service.spec.ts b/src/app/modules/feedback/__tests__/feedback.service.spec.ts index 378f775030..68b5a54271 100644 --- a/src/app/modules/feedback/__tests__/feedback.service.spec.ts +++ b/src/app/modules/feedback/__tests__/feedback.service.spec.ts @@ -70,9 +70,9 @@ describe('feedback.service', () => { const validFormId = new ObjectId().toHexString() countSpy.mockImplementationOnce( () => - (({ + ({ exec: () => Promise.reject(new Error('boom')), - } as unknown) as mongoose.Query), + } as unknown as mongoose.Query), ) // Act @@ -93,7 +93,7 @@ describe('feedback.service', () => { it('should return stream successfully', async () => { // Arrange const mockFormId = 'some form id' - const mockCursor = ('some cursor' as unknown) as mongoose.QueryCursor + const mockCursor = 'some cursor' as unknown as mongoose.QueryCursor const streamSpy = jest .spyOn(FormFeedback, 'getFeedbackCursorByFormId') .mockReturnValue(mockCursor) @@ -211,10 +211,10 @@ describe('feedback.service', () => { const sortSpy = jest.fn().mockReturnThis() const findSpy = jest.spyOn(FormFeedback, 'find').mockImplementationOnce( () => - (({ + ({ sort: sortSpy, exec: () => Promise.reject(new Error('boom')), - } as unknown) as mongoose.Query), + } as unknown as mongoose.Query), ) // Act diff --git a/src/app/modules/form/__tests__/form.service.spec.ts b/src/app/modules/form/__tests__/form.service.spec.ts index 5bcd535d64..9ba77c679a 100644 --- a/src/app/modules/form/__tests__/form.service.spec.ts +++ b/src/app/modules/form/__tests__/form.service.spec.ts @@ -65,14 +65,14 @@ describe('FormService', () => { it('should return full populated form successfully', async () => { // Arrange const formId = new ObjectId().toHexString() - const expectedForm = ({ + const expectedForm = { _id: formId, title: 'mock title', admin: { _id: new ObjectId(), email: 'mockEmail@example.com', }, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const retrieveFormSpy = jest .spyOn(Form, 'getFullFormById') .mockResolvedValueOnce(expectedForm) @@ -106,11 +106,11 @@ describe('FormService', () => { it('should return FormNotFoundError when retrieved form does not contain admin', async () => { // Arrange const formId = new ObjectId().toHexString() - const expectedForm = ({ + const expectedForm = { _id: formId, title: 'mock title', // Note no admin key-value. - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const retrieveFormSpy = jest .spyOn(Form, 'getFullFormById') .mockResolvedValueOnce(expectedForm) @@ -146,14 +146,14 @@ describe('FormService', () => { it('should return form successfully', async () => { // Arrange const formId = new ObjectId().toHexString() - const expectedForm = ({ + const expectedForm = { _id: formId, title: 'mock title', admin: { _id: new ObjectId(), email: 'mockEmail@example.com', }, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const retrieveFormSpy = jest .spyOn(Form, 'getFullFormById') .mockResolvedValueOnce(expectedForm) @@ -193,11 +193,11 @@ describe('FormService', () => { it('should still return retrieved form even when it does not contain admin', async () => { // Arrange const formId = new ObjectId().toHexString() - const expectedForm = ({ + const expectedForm = { _id: formId, title: 'mock title', // Note no admin key-value. - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const retrieveFormSpy = jest .spyOn(Form, 'getFullFormById') .mockResolvedValueOnce(expectedForm) @@ -243,11 +243,9 @@ describe('FormService', () => { title: 'mock title', admin: new ObjectId(), } as IFormSchema - const retrieveFormSpy = jest - .spyOn(Form, 'findById') - .mockReturnValueOnce(({ - exec: jest.fn().mockResolvedValue(expectedForm), - } as unknown) as mongoose.Query) + const retrieveFormSpy = jest.spyOn(Form, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(expectedForm), + } as unknown as mongoose.Query) // Act const actualResult = await FormService.retrieveFormById(formId) @@ -262,11 +260,9 @@ describe('FormService', () => { // Arrange const formId = new ObjectId().toHexString() // Resolve query to null. - const retrieveFormSpy = jest - .spyOn(Form, 'findById') - .mockReturnValueOnce(({ - exec: jest.fn().mockResolvedValue(null), - } as unknown) as mongoose.Query) + const retrieveFormSpy = jest.spyOn(Form, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + } as unknown as mongoose.Query) // Act const actualResult = await FormService.retrieveFormById(formId) @@ -285,11 +281,9 @@ describe('FormService', () => { title: 'mock title', // Note no admin key-value. } as IFormSchema - const retrieveFormSpy = jest - .spyOn(Form, 'findById') - .mockReturnValueOnce(({ - exec: jest.fn().mockResolvedValue(expectedForm), - } as unknown) as mongoose.Query) + const retrieveFormSpy = jest.spyOn(Form, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(expectedForm), + } as unknown as mongoose.Query) // Act const actualResult = await FormService.retrieveFormById(formId) @@ -304,11 +298,9 @@ describe('FormService', () => { // Arrange const formId = new ObjectId().toHexString() // Mock rejection. - const retrieveFormSpy = jest - .spyOn(Form, 'findById') - .mockReturnValueOnce(({ - exec: jest.fn().mockRejectedValue(new Error('some error')), - } as unknown) as mongoose.Query) + const retrieveFormSpy = jest.spyOn(Form, 'findById').mockReturnValueOnce({ + exec: jest.fn().mockRejectedValue(new Error('some error')), + } as unknown as mongoose.Query) // Act const actualResult = await FormService.retrieveFormById(formId) @@ -329,9 +321,8 @@ describe('FormService', () => { } as IPopulatedForm // Act - const actual = await FormService.checkFormSubmissionLimitAndDeactivateForm( - form, - ) + const actual = + await FormService.checkFormSubmissionLimitAndDeactivateForm(form) // Assert expect(actual._unsafeUnwrap()).toEqual(form) @@ -359,9 +350,10 @@ describe('FormService', () => { await Promise.all(submissionPromises) // Act - const actual = await FormService.checkFormSubmissionLimitAndDeactivateForm( - form as IPopulatedForm, - ) + const actual = + await FormService.checkFormSubmissionLimitAndDeactivateForm( + form as IPopulatedForm, + ) // Assert expect(actual._unsafeUnwrap()).toEqual(validForm) @@ -389,9 +381,8 @@ describe('FormService', () => { await Promise.all(submissionPromises) // Act - const actual = await FormService.checkFormSubmissionLimitAndDeactivateForm( - form, - ) + const actual = + await FormService.checkFormSubmissionLimitAndDeactivateForm(form) // Assert expect(actual._unsafeUnwrapErr()).toEqual( diff --git a/src/app/modules/form/__tests__/form.utils.spec.ts b/src/app/modules/form/__tests__/form.utils.spec.ts index 0f63b8504c..7012d93ee5 100644 --- a/src/app/modules/form/__tests__/form.utils.spec.ts +++ b/src/app/modules/form/__tests__/form.utils.spec.ts @@ -65,12 +65,12 @@ describe('form.utils', () => { // Arrange const fieldToFind = generateDefaultField(BasicField.Number) // Should not turn this unit test into an integration test, so mocking return and leaving responsibility to mongoose. - const mockDocArray = ({ + const mockDocArray = { 0: generateDefaultField(BasicField.LongText), 1: fieldToFind, isMongooseDocumentArray: true, id: jest.fn().mockReturnValue(fieldToFind), - } as unknown) as Types.DocumentArray + } as unknown as Types.DocumentArray // Act const result = getFormFieldById(mockDocArray, fieldToFind._id) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts index 5867d3b566..e719669a1e 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.controller.spec.ts @@ -623,18 +623,18 @@ describe('admin-form.controller', () => { email: 'randomrandomtest@example.com', } as IPopulatedUser - const MOCK_SCRUBBED_FORM = ({ + const MOCK_SCRUBBED_FORM = { _id: MOCK_FORM_ID, title: 'mock preview title', admin: { _id: MOCK_USER_ID }, - } as unknown) as PublicForm + } as unknown as PublicForm - const MOCK_FORM = (mocked({ + const MOCK_FORM = mocked({ admin: MOCK_USER, _id: MOCK_FORM_ID, title: MOCK_SCRUBBED_FORM.title, getPublicView: jest.fn().mockResolvedValue(MOCK_SCRUBBED_FORM), - }) as unknown) as MockedObject + }) as unknown as MockedObject const MOCK_REQ = expressHandler.mockRequest({ params: { @@ -3266,18 +3266,18 @@ describe('admin-form.controller', () => { email: 'alwaystesting@example.com', } as IPopulatedUser - const MOCK_SCRUBBED_FORM = ({ + const MOCK_SCRUBBED_FORM = { _id: MOCK_FORM_ID, title: "guess what it's another mock title", admin: { _id: MOCK_USER_ID }, - } as unknown) as PublicForm + } as unknown as PublicForm - const MOCK_FORM = (mocked({ + const MOCK_FORM = mocked({ admin: MOCK_USER, _id: MOCK_FORM_ID, title: MOCK_SCRUBBED_FORM.title, getPublicView: jest.fn().mockResolvedValue(MOCK_SCRUBBED_FORM), - }) as unknown) as MockedObject + }) as unknown as MockedObject const MOCK_REQ = expressHandler.mockRequest({ params: { @@ -5133,11 +5133,11 @@ describe('admin-form.controller', () => { MockSubmissionService.sendEmailConfirmations.mockReturnValue( okAsync(true), ) - jest.spyOn(EmailSubmissionUtil, 'SubmissionEmailObj').mockReturnValue(({ + jest.spyOn(EmailSubmissionUtil, 'SubmissionEmailObj').mockReturnValue({ dataCollationData: MOCK_DATA_COLLATION_DATA, formData: MOCK_FORM_DATA, autoReplyData: MOCK_AUTOREPLY_DATA, - } as unknown) as EmailSubmissionUtil.SubmissionEmailObj) + } as unknown as EmailSubmissionUtil.SubmissionEmailObj) }) it('should call all services correctly when submission is valid', async () => { @@ -8798,14 +8798,14 @@ describe('admin-form.controller', () => { email: 'somerandom@example.com', } as IPopulatedUser - const MOCK_FORM = ({ + const MOCK_FORM = { admin: MOCK_USER, _id: MOCK_FORM_ID, startPage: { paragraph: 'old end page', }, title: 'mock start page title', - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const MOCK_UPDATED_FORM = { ...MOCK_FORM, diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts index 1caf142f5a..5c0ac4f00a 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts @@ -368,9 +368,9 @@ describe('admin-form.service', () => { status: Status.Archived, } as IEmailFormSchema const mockArchiveFn = jest.fn().mockResolvedValue(mockArchivedForm) - const mockInitialForm = ({ + const mockInitialForm = { archive: mockArchiveFn, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await archiveForm(mockInitialForm) @@ -386,9 +386,9 @@ describe('admin-form.service', () => { const mockArchiveFn = jest .fn() .mockRejectedValue(new Error(mockErrorString)) - const mockInitialForm = ({ + const mockInitialForm = { archive: mockArchiveFn, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await archiveForm(mockInitialForm) @@ -403,7 +403,7 @@ describe('admin-form.service', () => { describe('duplicateForm', () => { const MOCK_NEW_ADMIN_ID = new ObjectId().toHexString() - const MOCK_VALID_FORM = ({ + const MOCK_VALID_FORM = { _id: new ObjectId(), admin: new ObjectId(), endPage: { @@ -417,7 +417,7 @@ describe('admin-form.service', () => { fileSizeInBytes: 10000, } as ICustomFormLogo, }, - } as unknown) as IFormDocument + } as unknown as IFormDocument const MOCK_EMAIL_OVERRIDE_PARAMS: DuplicateFormBody = { responseMode: ResponseMode.Email, title: 'mock new title', @@ -585,7 +585,7 @@ describe('admin-form.service', () => { title: 'mock populated form', } as IPopulatedForm - const mockUpdatedForm = ({ + const mockUpdatedForm = { _id: new ObjectId(), admin: MOCK_CURRENT_OWNER, emails: [MOCK_NEW_OWNER_EMAIL], @@ -594,13 +594,13 @@ describe('admin-form.service', () => { populate: jest.fn().mockReturnValue({ execPopulate: jest.fn().mockResolvedValue(expectedPopulateResult), }), - } as unknown) as IFormSchema + } as unknown as IFormSchema - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm MockUserService.findUserById.mockReturnValueOnce( okAsync(MOCK_CURRENT_OWNER), @@ -634,11 +634,11 @@ describe('admin-form.service', () => { MockUserService.findUserByEmail.mockReturnValueOnce( errAsync(new MissingUserError()), ) - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actualResult = await transferFormOwnership( @@ -659,11 +659,11 @@ describe('admin-form.service', () => { it('should return MissingUserError when current form owner cannot be found in the database', async () => { // Arrange - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm MockUserService.findUserById.mockReturnValueOnce( errAsync(new MissingUserError()), ) @@ -683,11 +683,11 @@ describe('admin-form.service', () => { it('should return DatabaseError when database error occurs whilst retrieving current form owner', async () => { // Arrange - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm MockUserService.findUserById.mockReturnValueOnce( errAsync(new DatabaseError()), ) @@ -713,11 +713,11 @@ describe('admin-form.service', () => { MockUserService.findUserByEmail.mockReturnValueOnce( errAsync(new DatabaseError()), ) - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actualResult = await transferFormOwnership( @@ -737,11 +737,11 @@ describe('admin-form.service', () => { MockUserService.findUserById.mockReturnValueOnce( okAsync(MOCK_CURRENT_OWNER), ) - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actualResult = await transferFormOwnership( @@ -762,7 +762,7 @@ describe('admin-form.service', () => { it('should return DatabaseError when database error occurs during populating the updated form', async () => { // Arrange const mockPopulateErrorStr = 'population failed!' - const mockUpdatedForm = ({ + const mockUpdatedForm = { _id: new ObjectId(), admin: MOCK_CURRENT_OWNER, emails: [MOCK_NEW_OWNER_EMAIL], @@ -774,13 +774,13 @@ describe('admin-form.service', () => { .fn() .mockRejectedValue(new Error(mockPopulateErrorStr)), }), - } as unknown) as IFormSchema + } as unknown as IFormSchema - const mockValidForm = ({ + const mockValidForm = { title: 'some mock form', admin: MOCK_CURRENT_OWNER, transferOwner: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm MockUserService.findUserById.mockReturnValueOnce( okAsync(MOCK_CURRENT_OWNER), @@ -933,7 +933,7 @@ describe('admin-form.service', () => { }) describe('editFormFields', () => { - const MOCK_UPDATED_FORM = ({ + const MOCK_UPDATED_FORM = { _id: new ObjectId(), admin: new ObjectId(), form_fields: [ @@ -941,9 +941,9 @@ describe('admin-form.service', () => { generateDefaultField(BasicField.Mobile), generateDefaultField(BasicField.Dropdown), ], - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm - const MOCK_INTIAL_FORM = mocked(({ + const MOCK_INTIAL_FORM = mocked({ _id: MOCK_UPDATED_FORM._id, admin: MOCK_UPDATED_FORM.admin, form_fields: [ @@ -951,7 +951,7 @@ describe('admin-form.service', () => { generateDefaultField(BasicField.Mobile), ], save: jest.fn().mockResolvedValue(MOCK_UPDATED_FORM), - } as unknown) as IPopulatedForm) + } as unknown as IPopulatedForm) it('should return updated form', async () => { // Arrange @@ -1027,7 +1027,7 @@ describe('admin-form.service', () => { }) describe('updateForm', () => { - const MOCK_UPDATED_FORM = ({ + const MOCK_UPDATED_FORM = { _id: new ObjectId(), admin: new ObjectId(), status: Status.Private, @@ -1035,15 +1035,15 @@ describe('admin-form.service', () => { generateDefaultField(BasicField.Mobile), generateDefaultField(BasicField.Dropdown), ], - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm - const MOCK_INITIAL_FORM = mocked(({ + const MOCK_INITIAL_FORM = mocked({ _id: MOCK_UPDATED_FORM._id, admin: MOCK_UPDATED_FORM.admin, status: Status.Public, form_fields: MOCK_UPDATED_FORM.form_fields, save: jest.fn().mockResolvedValue(MOCK_UPDATED_FORM), - } as unknown) as IPopulatedForm) + } as unknown as IPopulatedForm) it('should successfully update given form keys', async () => { // Arrange @@ -1099,23 +1099,23 @@ describe('admin-form.service', () => { }, } - const MOCK_UPDATED_FORM = ({ + const MOCK_UPDATED_FORM = { ...MOCK_UPDATED_SETTINGS, responseMode: ResponseMode.Encrypt, publicKey: 'some public key', getSettings: jest.fn().mockReturnValue(MOCK_UPDATED_SETTINGS), - } as unknown) as IFormDocument + } as unknown as IFormDocument - const MOCK_EMAIL_FORM = mocked(({ + const MOCK_EMAIL_FORM = mocked({ _id: new ObjectId(), status: Status.Public, responseMode: ResponseMode.Email, - } as unknown) as IPopulatedForm) - const MOCK_ENCRYPT_FORM = mocked(({ + } as unknown as IPopulatedForm) + const MOCK_ENCRYPT_FORM = mocked({ _id: new ObjectId(), status: Status.Public, responseMode: ResponseMode.Encrypt, - } as unknown) as IPopulatedForm) + } as unknown as IPopulatedForm) const EMAIL_UPDATE_SPY = jest .spyOn(EmailFormModel, 'findByIdAndUpdate') @@ -1229,11 +1229,11 @@ describe('admin-form.service', () => { title: 'some mock form', form_fields: [mockNewField], } - const mockForm = ({ + const mockForm = { ...mockUpdatedForm, form_fields: [fieldToUpdate], updateFormFieldById: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await updateFormField( @@ -1252,11 +1252,11 @@ describe('admin-form.service', () => { it('should return FieldNotFoundError when field update returns null', async () => { // Arrange - const mockForm = ({ + const mockForm = { title: 'another mock form', form_fields: [], updateFormFieldById: jest.fn().mockResolvedValue(null), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const invalidFieldId = new ObjectId().toHexString() const mockNewField = generateDefaultField( @@ -1276,14 +1276,14 @@ describe('admin-form.service', () => { it('should return DatabaseValidationError when field model update throws a validation error', async () => { // Arrange - const mockForm = ({ + const mockForm = { title: 'another another mock form', form_fields: [], updateFormFieldById: jest.fn().mockRejectedValue( // @ts-ignore new mongoose.Error.ValidationError(), ), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const invalidFieldId = new ObjectId().toHexString() const mockNewField = generateDefaultField( @@ -1317,11 +1317,11 @@ describe('admin-form.service', () => { // Append created field to end of form_fields. form_fields: [...initialFields, expectedCreatedField], } - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: initialFields, insertFormField: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const formCreateParams = pick(expectedCreatedField, [ 'title', 'fieldType', @@ -1341,14 +1341,14 @@ describe('admin-form.service', () => { generateDefaultField(BasicField.Mobile), generateDefaultField(BasicField.Image), ] - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: initialFields, insertFormField: jest.fn().mockRejectedValue( // @ts-ignore new mongoose.Error.ValidationError({ errors: 'does not matter' }), ), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const formCreateParams = { fieldType: BasicField.ShortText, title: 'some title', @@ -1378,18 +1378,18 @@ describe('admin-form.service', () => { let mockEmailForm: IPopulatedForm, mockEncryptForm: IPopulatedForm beforeEach(() => { - mockEmailForm = ({ + mockEmailForm = { _id: new ObjectId(), status: Status.Public, responseMode: ResponseMode.Email, ...mockFormLogic, - } as unknown) as IPopulatedForm - mockEncryptForm = ({ + } as unknown as IPopulatedForm + mockEncryptForm = { _id: new ObjectId(), status: Status.Public, responseMode: ResponseMode.Encrypt, ...mockFormLogic, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm }) it('should return ok(form) on successful form logic delete for email mode form', async () => { @@ -1482,12 +1482,12 @@ describe('admin-form.service', () => { // Append duplicated field to end of form_fields. form_fields: [fieldToDuplicate, duplicatedField], } as IFormSchema - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: [fieldToDuplicate], _id: new ObjectId(), duplicateFormFieldById: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await duplicateFormField( @@ -1512,12 +1512,12 @@ describe('admin-form.service', () => { it('should return FormNotFoundError when field duplication returns null', async () => { // Arrange const fieldToDuplicate = generateDefaultField(BasicField.Mobile) - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: [fieldToDuplicate], _id: new ObjectId(), duplicateFormFieldById: jest.fn().mockResolvedValue(null), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await duplicateFormField(mockForm, fieldToDuplicate._id) @@ -1529,14 +1529,14 @@ describe('admin-form.service', () => { it('should return DatabaseValidationError when field model update throws a validation error', async () => { // Arrange const initialFields = [generateDefaultField(BasicField.Mobile)] - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: initialFields, duplicateFormFieldById: jest.fn().mockRejectedValue( // @ts-ignore new mongoose.Error.ValidationError({ errors: 'does not matter' }), ), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await duplicateFormField(mockForm, initialFields[0]._id) @@ -1556,10 +1556,10 @@ describe('admin-form.service', () => { const mockUpdatedForm = { form_fields: [mockFormFields[1], mockFormFields[0]], } - const mockForm = ({ + const mockForm = { form_fields: mockFormFields, reorderFormFieldById: jest.fn().mockResolvedValue(mockUpdatedForm), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const fieldToReorder = String(mockFormFields[0]._id) const newPosition = 1 @@ -1580,10 +1580,10 @@ describe('admin-form.service', () => { it('should return FieldNotFoundError when null is returned from the model instance method', async () => { // Arrange - const mockForm = ({ + const mockForm = { form_fields: [generateDefaultField(BasicField.YesNo)], reorderFormFieldById: jest.fn().mockResolvedValue(null), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const fieldToReorder = new ObjectId().toHexString() const newPosition = 2 @@ -1604,13 +1604,13 @@ describe('admin-form.service', () => { it('should return database error when error occurs whilst reordering fields', async () => { // Arrange - const mockForm = ({ + const mockForm = { form_fields: [generateDefaultField(BasicField.YesNo)], // Rejection reorderFormFieldById: jest .fn() .mockRejectedValue(new Error('some error')), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const fieldToReorder = new ObjectId().toHexString() const newPosition = 2 @@ -1641,12 +1641,12 @@ describe('admin-form.service', () => { write: false, }, ] - const mockForm = ({ + const mockForm = { title: 'some mock form', updateFormCollaborators: jest .fn() .mockResolvedValue({ permissionList: newCollaborators }), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await updateFormCollaborators(mockForm, newCollaborators) @@ -1666,12 +1666,12 @@ describe('admin-form.service', () => { write: false, }, ] - const mockForm = ({ + const mockForm = { title: 'some mock form', updateFormCollaborators: jest .fn() .mockRejectedValue(new DatabaseError()), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await updateFormCollaborators(mockForm, newCollaborators) @@ -1733,26 +1733,26 @@ describe('admin-form.service', () => { mockEncryptFormUpdated: IPopulatedForm beforeEach(() => { - mockEmailForm = ({ + mockEmailForm = { _id: mockEmailFormId, status: Status.Public, responseMode: ResponseMode.Email, ...mockFormLogicOld, - } as unknown) as IPopulatedForm - mockEncryptForm = ({ + } as unknown as IPopulatedForm + mockEncryptForm = { _id: mockEncryptFormId, status: Status.Public, responseMode: ResponseMode.Encrypt, ...mockFormLogicOld, - } as unknown) as IPopulatedForm - mockEmailFormUpdated = ({ + } as unknown as IPopulatedForm + mockEmailFormUpdated = { ...mockEmailForm, ...mockFormLogicUpdated, - } as unknown) as IPopulatedForm - mockEncryptFormUpdated = ({ + } as unknown as IPopulatedForm + mockEncryptFormUpdated = { ...mockEncryptForm, ...mockFormLogicUpdated, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm }) it('should return ok(created logic) on successful form logic create for email mode form', async () => { @@ -1798,7 +1798,7 @@ describe('admin-form.service', () => { it('should return err(FormNotFoundError) if db does not return form object', async () => { // Arrange - CREATE_SPY.mockResolvedValue((undefined as unknown) as IFormSchema) + CREATE_SPY.mockResolvedValue(undefined as unknown as IFormSchema) // Act const actualResult = await createFormLogic( @@ -1883,11 +1883,11 @@ describe('admin-form.service', () => { // Append created field to end of form_fields. form_fields: [initialFields[1]], } as IFormSchema - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: initialFields, _id: new ObjectId(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm deleteSpy.mockResolvedValueOnce(mockUpdatedForm) // Act @@ -1903,11 +1903,11 @@ describe('admin-form.service', () => { it("should return FieldNotFoundError when the fieldId does not exist in the form's fields", async () => { // Arrange - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: [generateDefaultField(BasicField.Nric)], _id: new ObjectId(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Act const actual = await deleteFormField( @@ -1923,11 +1923,11 @@ describe('admin-form.service', () => { it('should return FormNotFoundError when field deletion returns null', async () => { // Arrange const fieldToDelete = generateDefaultField(BasicField.Mobile) - const mockForm = ({ + const mockForm = { title: 'some mock form', form_fields: [fieldToDelete], _id: new ObjectId(), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm deleteSpy.mockResolvedValueOnce(null) // Act @@ -2091,26 +2091,26 @@ describe('admin-form.service', () => { mockEncryptFormUpdated: IPopulatedForm beforeEach(() => { - mockEmailForm = ({ + mockEmailForm = { _id: mockEmailFormId, status: Status.Public, responseMode: ResponseMode.Email, ...mockFormLogicOld, - } as unknown) as IPopulatedForm - mockEncryptForm = ({ + } as unknown as IPopulatedForm + mockEncryptForm = { _id: mockEncryptFormId, status: Status.Public, responseMode: ResponseMode.Encrypt, ...mockFormLogicOld, - } as unknown) as IPopulatedForm - mockEmailFormUpdated = ({ + } as unknown as IPopulatedForm + mockEmailFormUpdated = { ...mockEmailForm, ...mockFormLogicUpdated, - } as unknown) as IPopulatedForm - mockEncryptFormUpdated = ({ + } as unknown as IPopulatedForm + mockEncryptFormUpdated = { ...mockEncryptForm, ...mockFormLogicUpdated, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm }) it('should return ok(updated logic) on successful form logic update for email mode form', async () => { @@ -2194,12 +2194,12 @@ describe('admin-form.service', () => { it("should return FieldNotFoundError when the fieldId does not exist in the form's fields", async () => { // Arrange const MOCK_ID = new ObjectId().toHexString() - const MOCK_FORM = ({ + const MOCK_FORM = { title: 'some mock form', // Append created field to end of form_fields. form_fields: [], _id: new ObjectId(), - } as unknown) as IFormSchema + } as unknown as IFormSchema const expectedError = new FieldNotFoundError( `Attempted to retrieve field ${MOCK_ID} from ${MOCK_FORM._id} but field was not present`, ) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts index 6842b1a9ae..b4a01b62e5 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.utils.spec.ts @@ -446,13 +446,13 @@ describe('admin-form.utils', () => { const initialField = generateDefaultField(BasicField.Email, { title: 'some old title', }) - const desyncedEmailField = ({ + const desyncedEmailField = { ...initialField, title: 'new title', hasAllowedEmailDomains: true, // true but empty array allowedEmailDomains: [], - } as unknown) as IEmailFieldSchema + } as unknown as IEmailFieldSchema const updateFieldParams: EditFormFieldParams = { action: { @@ -481,12 +481,12 @@ describe('admin-form.utils', () => { it('should return synced email field when creating with desynced email field', async () => { // Arrange - const desyncedEmailField = ({ + const desyncedEmailField = { ...generateDefaultField(BasicField.Email), hasAllowedEmailDomains: false, // False but contains domains. allowedEmailDomains: ['@example.com'], - } as unknown) as IEmailFieldSchema + } as unknown as IEmailFieldSchema const createFieldParams: EditFormFieldParams = { action: { diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts index ee1f95aadc..d8c0c4032b 100644 --- a/src/app/modules/form/admin-form/admin-form.controller.ts +++ b/src/app/modules/form/admin-form/admin-form.controller.ts @@ -1422,15 +1422,14 @@ export const submitEncryptPreview: ControllerHandler< }), ) .map(({ parsedResponses, form }) => { - const submission = EncryptSubmissionService.createEncryptSubmissionWithoutSave( - { + const submission = + EncryptSubmissionService.createEncryptSubmissionWithoutSave({ form, encryptedContent, // Don't bother encrypting and signing mock variables for previews verifiedContent: '', version, - }, - ) + }) void SubmissionService.sendEmailConfirmations({ form, @@ -1505,9 +1504,10 @@ export const submitEmailPreview: ControllerHandler< } const form = formResult.value - const parsedResponsesResult = await EmailSubmissionService.validateAttachments( - responses, - ).andThen(() => SubmissionService.getProcessedResponses(form, responses)) + const parsedResponsesResult = + await EmailSubmissionService.validateAttachments(responses).andThen(() => + SubmissionService.getProcessedResponses(form, responses), + ) if (parsedResponsesResult.isErr()) { logger.error({ message: 'Error while parsing responses for preview submission', diff --git a/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts b/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts index 8ac521272d..8f5eab18ed 100644 --- a/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts +++ b/src/app/modules/form/public-form/__tests__/public-form.controller.spec.ts @@ -439,11 +439,11 @@ describe('public-form.controller', () => { email: 'randomrandomtest@example.com', } as IPopulatedUser - const MOCK_SCRUBBED_FORM = ({ + const MOCK_SCRUBBED_FORM = { _id: MOCK_FORM_ID, title: 'mock title', admin: { _id: MOCK_USER_ID }, - } as unknown) as PublicForm + } as unknown as PublicForm const BASE_FORM = { admin: MOCK_USER, @@ -489,10 +489,10 @@ describe('public-form.controller', () => { it('should return 200 when there is no AuthType on the request', async () => { // Arrange - const MOCK_NIL_AUTH_FORM = ({ + const MOCK_NIL_AUTH_FORM = { ...BASE_FORM, authType: AuthType.NIL, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() MockAuthService.getFormIfPublic.mockReturnValueOnce( @@ -519,10 +519,10 @@ describe('public-form.controller', () => { it('should return 200 when client authenticates using SP', async () => { // Arrange const MOCK_SPCP_SESSION = { userName: MOCK_JWT_PAYLOAD.userName } - const MOCK_SP_AUTH_FORM = ({ + const MOCK_SP_AUTH_FORM = { ...BASE_FORM, authType: AuthType.SP, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() MockAuthService.getFormIfPublic.mockReturnValueOnce( @@ -553,10 +553,10 @@ describe('public-form.controller', () => { it('should return 200 when client authenticates using CP', async () => { // Arrange const MOCK_SPCP_SESSION = { userName: MOCK_JWT_PAYLOAD.userName } - const MOCK_CP_AUTH_FORM = ({ + const MOCK_CP_AUTH_FORM = { ...BASE_FORM, authType: AuthType.CP, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() MockAuthService.getFormIfPublic.mockReturnValueOnce( @@ -585,12 +585,12 @@ describe('public-form.controller', () => { it('should return 200 when client authenticates using MyInfo', async () => { // Arrange - const MOCK_MYINFO_AUTH_FORM = ({ + const MOCK_MYINFO_AUTH_FORM = { ...BASE_FORM, esrvcId: 'thing', authType: AuthType.MyInfo, toJSON: jest.fn().mockReturnValue(BASE_FORM), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const MOCK_MYINFO_DATA = new MyInfoData({ uinFin: 'i am a fish', } as IPersonResponse) @@ -632,11 +632,11 @@ describe('public-form.controller', () => { // Errors describe('errors in myInfo', () => { - const MOCK_MYINFO_FORM = ({ + const MOCK_MYINFO_FORM = { ...BASE_FORM, toJSON: jest.fn().mockReturnThis(), authType: AuthType.MyInfo, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm // Setup because this gets invoked at the start of the controller to decide which branch to take beforeAll(() => { @@ -852,10 +852,10 @@ describe('public-form.controller', () => { }) describe('errors in spcp', () => { - const MOCK_SPCP_FORM = ({ + const MOCK_SPCP_FORM = { ...BASE_FORM, authType: AuthType.SP, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm it('should return 200 with the form but without a spcpSession when the JWT token could not be found', async () => { // Arrange // 1. Mock the response and calls @@ -1000,10 +1000,10 @@ describe('public-form.controller', () => { it('should return 200 with isIntranetUser set to false when a user accesses a form from outside intranet', async () => { // Arrange - const MOCK_NIL_AUTH_FORM = ({ + const MOCK_NIL_AUTH_FORM = { ...BASE_FORM, authType: AuthType.NIL, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() MockAuthService.getFormIfPublic.mockReturnValueOnce( @@ -1030,10 +1030,10 @@ describe('public-form.controller', () => { it('should return 200 with isIntranetUser set to true when a intranet user accesses an AuthType.SP form', async () => { // Arrange - const MOCK_SP_AUTH_FORM = ({ + const MOCK_SP_AUTH_FORM = { ...BASE_FORM, authType: AuthType.SP, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() @@ -1065,10 +1065,10 @@ describe('public-form.controller', () => { it('should return 200 with isIntranetUser set to true when a intranet user accesses an AuthType.CP form', async () => { // Arrange - const MOCK_CP_AUTH_FORM = ({ + const MOCK_CP_AUTH_FORM = { ...BASE_FORM, authType: AuthType.CP, - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse() @@ -1100,12 +1100,12 @@ describe('public-form.controller', () => { it('should return 200 with isIntranetUser set to true when a intranet user accesses an AuthType.MyInfo form', async () => { // Arrange - const MOCK_MYINFO_AUTH_FORM = ({ + const MOCK_MYINFO_AUTH_FORM = { ...BASE_FORM, esrvcId: 'thing', authType: AuthType.MyInfo, toJSON: jest.fn().mockReturnValue(BASE_FORM), - } as unknown) as IPopulatedForm + } as unknown as IPopulatedForm const mockRes = expressHandler.mockResponse({ clearCookie: jest.fn().mockReturnThis(), cookie: jest.fn().mockReturnThis(), @@ -1279,11 +1279,11 @@ describe('public-form.controller', () => { it('should return 200 with the redirect url when the request is valid and the form has authType MyInfo', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { authType: AuthType.MyInfo, esrvcId: '12345', getUniqueMyInfoAttrs: jest.fn().mockReturnValue([]), - } as unknown) as MyInfoForm + } as unknown as MyInfoForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( @@ -1334,9 +1334,9 @@ describe('public-form.controller', () => { it('should return 400 when the form has authType MyInfo and is missing esrvcId', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { authType: AuthType.MyInfo, - } as unknown) as MyInfoForm + } as unknown as MyInfoForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( @@ -1360,9 +1360,9 @@ describe('public-form.controller', () => { it('should return 400 when the form has authType SP and is missing esrvcId', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { authType: AuthType.SP, - } as unknown) as SpcpForm + } as unknown as SpcpForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( @@ -1386,9 +1386,9 @@ describe('public-form.controller', () => { it('should return 400 when the form has authType CP and is missing esrvcId', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { authType: AuthType.CP, - } as unknown) as SpcpForm + } as unknown as SpcpForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( @@ -1434,10 +1434,10 @@ describe('public-form.controller', () => { it('should return 500 when the redirectURL could not be created', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { esrvcId: '234', authType: AuthType.CP, - } as unknown) as SpcpForm + } as unknown as SpcpForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( @@ -1463,11 +1463,11 @@ describe('public-form.controller', () => { it('should return 500 when the redirectURL feature is not implemented', async () => { // Arrange - const MOCK_FORM = ({ + const MOCK_FORM = { esrvcId: '234', authType: AuthType.MyInfo, getUniqueMyInfoAttrs: jest.fn().mockReturnValue([]), - } as unknown) as SpcpForm + } as unknown as SpcpForm const mockRes = expressHandler.mockResponse() MockFormService.retrieveFullFormById.mockReturnValueOnce( okAsync(MOCK_FORM), diff --git a/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts b/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts index a25e3d1ec1..37c17b3122 100644 --- a/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts +++ b/src/app/modules/frontend/__tests__/google-analytics.factory.spec.ts @@ -22,9 +22,10 @@ describe('google-analytics.factory', () => { const mockRes = expressHandler.mockResponse() it('should call res correctly if google-analytics feature is disabled', () => { - const MOCK_DISABLED_GA_FEATURE: RegisteredFeature = { - isEnabled: false, - } + const MOCK_DISABLED_GA_FEATURE: RegisteredFeature = + { + isEnabled: false, + } const GoogleAnalyticsFactory = createGoogleAnalyticsFactory( MOCK_DISABLED_GA_FEATURE, @@ -38,9 +39,10 @@ describe('google-analytics.factory', () => { }) it('should call res correctly if google-analytics feature is enabled', () => { - const MOCK_ENABLED_GA_FEATURE: RegisteredFeature = { - isEnabled: true, - } + const MOCK_ENABLED_GA_FEATURE: RegisteredFeature = + { + isEnabled: true, + } const GoogleAnalyticsFactory = createGoogleAnalyticsFactory( MOCK_ENABLED_GA_FEATURE, diff --git a/src/app/modules/myinfo/__tests__/myinfo.adapter.spec.ts b/src/app/modules/myinfo/__tests__/myinfo.adapter.spec.ts index fcba314c2e..d0d5d21f02 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.adapter.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.adapter.spec.ts @@ -206,10 +206,12 @@ describe('myinfo.adapter', () => { it('should correctly return single vehicle numbers', () => { // Grab first vehicle number - const expected = (MYINFO_VEHNO_AVAILABLE.vehicles![0] as SetRequired< - MyInfoVehicleFull, - 'vehicleno' - >).vehicleno.value + const expected = ( + MYINFO_VEHNO_AVAILABLE.vehicles![0] as SetRequired< + MyInfoVehicleFull, + 'vehicleno' + > + ).vehicleno.value const response: IPersonResponse = { uinFin: MOCK_UINFIN, data: { diff --git a/src/app/modules/myinfo/__tests__/myinfo.controller.spec.ts b/src/app/modules/myinfo/__tests__/myinfo.controller.spec.ts index 3b27cb5137..231c643d3c 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.controller.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.controller.spec.ts @@ -294,7 +294,7 @@ describe('MyInfoController', () => { ) // Return value is ignored MockBillingFactory.recordLoginByForm.mockReturnValue( - okAsync(({} as unknown) as ILoginSchema), + okAsync({} as unknown as ILoginSchema), ) }) diff --git a/src/app/modules/myinfo/__tests__/myinfo.factory.spec.ts b/src/app/modules/myinfo/__tests__/myinfo.factory.spec.ts index 0c87e6b5c9..7f63539a37 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.factory.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.factory.spec.ts @@ -46,14 +46,11 @@ describe('myinfo.factory', () => { const parseMyInfoRelayStateResult = MyInfoFactory.parseMyInfoRelayState('') const extractUinFinResult = MyInfoFactory.extractUinFin('') const getMyInfoDataForFormResult = await MyInfoFactory.getMyInfoDataForForm( - ({} as unknown) as IPopulatedForm, + {} as unknown as IPopulatedForm, {}, ) - const prefillAndSaveMyInfoFieldsResult = await MyInfoFactory.prefillAndSaveMyInfoFields( - '', - {} as MyInfoData, - [], - ) + const prefillAndSaveMyInfoFieldsResult = + await MyInfoFactory.prefillAndSaveMyInfoFields('', {} as MyInfoData, []) const saveMyInfoHashesResult = await MyInfoFactory.saveMyInfoHashes( '', '', @@ -95,14 +92,11 @@ describe('myinfo.factory', () => { const parseMyInfoRelayStateResult = MyInfoFactory.parseMyInfoRelayState('') const extractUinFinResult = MyInfoFactory.extractUinFin('') const getMyInfoDataForFormResult = await MyInfoFactory.getMyInfoDataForForm( - ({} as unknown) as IPopulatedForm, + {} as unknown as IPopulatedForm, {}, ) - const prefillAndSaveMyInfoFieldsResult = await MyInfoFactory.prefillAndSaveMyInfoFields( - '', - {} as MyInfoData, - [], - ) + const prefillAndSaveMyInfoFieldsResult = + await MyInfoFactory.prefillAndSaveMyInfoFields('', {} as MyInfoData, []) const saveMyInfoHashesResult = await MyInfoFactory.saveMyInfoHashes( '', '', @@ -128,12 +122,12 @@ describe('myinfo.factory', () => { }) it('should call the MyInfoService constructor when isEnabled is true and props is truthy', () => { - const mockProps = ({ + const mockProps = { myInfoClientMode: 'mock1', myInfoKeyPath: 'mock2', spCookieMaxAge: 200, spEsrvcId: 'mock3', - } as unknown) as ISpcpMyInfo + } as unknown as ISpcpMyInfo createMyInfoFactory({ isEnabled: true, props: mockProps, diff --git a/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts b/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts index e2d8c08a83..59d08aab77 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.service.spec.ts @@ -210,9 +210,7 @@ describe('MyInfoService', () => { const mockReturnValue = { mock: 'value' } const mockUpdateHashes = jest .spyOn(MyInfoHash, 'updateHashes') - .mockResolvedValueOnce( - (mockReturnValue as unknown) as IMyInfoHashSchema, - ) + .mockResolvedValueOnce(mockReturnValue as unknown as IMyInfoHashSchema) MockBcrypt.hash.mockImplementation((v) => Promise.resolve(v)) const expectedHashes = {} as Record MOCK_POPULATED_FORM_FIELDS.forEach((field) => { @@ -318,7 +316,7 @@ describe('MyInfoService', () => { MockBcrypt.compare.mockResolvedValue(true) const result = await myInfoService.checkMyInfoHashes( - (MOCK_RESPONSES as unknown) as ProcessedFieldResponse[], + MOCK_RESPONSES as unknown as ProcessedFieldResponse[], MOCK_HASHES as IHashes, ) @@ -329,7 +327,7 @@ describe('MyInfoService', () => { MockBcrypt.compare.mockRejectedValue('') const result = await myInfoService.checkMyInfoHashes( - (MOCK_RESPONSES as unknown) as ProcessedFieldResponse[], + MOCK_RESPONSES as unknown as ProcessedFieldResponse[], MOCK_HASHES as IHashes, ) @@ -348,7 +346,7 @@ describe('MyInfoService', () => { }) const result = await myInfoService.checkMyInfoHashes( - (MOCK_RESPONSES as unknown) as ProcessedFieldResponse[], + MOCK_RESPONSES as unknown as ProcessedFieldResponse[], MOCK_HASHES as IHashes, ) diff --git a/src/app/modules/myinfo/__tests__/myinfo.test.constants.ts b/src/app/modules/myinfo/__tests__/myinfo.test.constants.ts index 2056029180..7f0e680335 100644 --- a/src/app/modules/myinfo/__tests__/myinfo.test.constants.ts +++ b/src/app/modules/myinfo/__tests__/myinfo.test.constants.ts @@ -145,7 +145,7 @@ export const MOCK_SERVICE_PARAMS: IMyInfoServiceConfig = { } as ISpcpMyInfo, } -export const MOCK_MYINFO_FORM = ({ +export const MOCK_MYINFO_FORM = { _id: MOCK_FORM_ID, esrvcId: MOCK_ESRVC_ID, authType: AuthType.MyInfo, @@ -161,7 +161,7 @@ export const MOCK_MYINFO_FORM = ({ return this }, form_fields: [], -} as unknown) as IFormSchema +} as unknown as IFormSchema export const MOCK_SUCCESSFUL_COOKIE: MyInfoSuccessfulCookiePayload = { accessToken: MOCK_ACCESS_TOKEN, diff --git a/src/app/modules/myinfo/myinfo.adapter.ts b/src/app/modules/myinfo/myinfo.adapter.ts index 89408a1dce..d202462eb3 100644 --- a/src/app/modules/myinfo/myinfo.adapter.ts +++ b/src/app/modules/myinfo/myinfo.adapter.ts @@ -291,9 +291,10 @@ export class MyInfoData { * Retrieves the fieldValue for the given internal MyInfo attribute. * @param attr Internal FormSG MyInfo attribute */ - getFieldValueForAttr( - attr: InternalAttr, - ): { fieldValue: string | undefined; isReadOnly: boolean } { + getFieldValueForAttr(attr: InternalAttr): { + fieldValue: string | undefined + isReadOnly: boolean + } { const externalAttr = internalAttrToExternal(attr) const fieldValue = this._formatFieldValue(externalAttr) return { diff --git a/src/app/modules/myinfo/myinfo.service.ts b/src/app/modules/myinfo/myinfo.service.ts index a0107861e1..06d753aa95 100644 --- a/src/app/modules/myinfo/myinfo.service.ts +++ b/src/app/modules/myinfo/myinfo.service.ts @@ -302,9 +302,8 @@ export class MyInfoService { if (!field.myInfo?.attr) return field const myInfoAttr = field.myInfo.attr - const { fieldValue, isReadOnly } = myInfoData.getFieldValueForAttr( - myInfoAttr, - ) + const { fieldValue, isReadOnly } = + myInfoData.getFieldValueForAttr(myInfoAttr) const prefilledField = cloneDeep(field) as IPossiblyPrefilledField prefilledField.fieldValue = fieldValue diff --git a/src/app/modules/spcp/__tests__/spcp.factory.spec.ts b/src/app/modules/spcp/__tests__/spcp.factory.spec.ts index b7fb6db682..0999570c5e 100644 --- a/src/app/modules/spcp/__tests__/spcp.factory.spec.ts +++ b/src/app/modules/spcp/__tests__/spcp.factory.spec.ts @@ -27,12 +27,10 @@ describe('spcp.factory', () => { ) const fetchLoginPageResult = await SpcpFactory.fetchLoginPage('') const validateLoginPageResult = SpcpFactory.validateLoginPage('') - const extractSingpassJwtPayloadResult = await SpcpFactory.extractSingpassJwtPayload( - '', - ) - const extractCorppassJwtPayloadResult = await SpcpFactory.extractCorppassJwtPayload( - '', - ) + const extractSingpassJwtPayloadResult = + await SpcpFactory.extractSingpassJwtPayload('') + const extractCorppassJwtPayloadResult = + await SpcpFactory.extractCorppassJwtPayload('') const parseOOBParamsResult = SpcpFactory.parseOOBParams('', '', AuthType.SP) const getSpcpAttributesResult = await SpcpFactory.getSpcpAttributes( '', @@ -40,7 +38,7 @@ describe('spcp.factory', () => { AuthType.SP, ) const createJWTResult = SpcpFactory.createJWT( - ({} as unknown) as JwtPayload, + {} as unknown as JwtPayload, 0, AuthType.SP, ) @@ -77,12 +75,10 @@ describe('spcp.factory', () => { ) const fetchLoginPageResult = await SpcpFactory.fetchLoginPage('') const validateLoginPageResult = SpcpFactory.validateLoginPage('') - const extractSingpassJwtPayloadResult = await SpcpFactory.extractSingpassJwtPayload( - '', - ) - const extractCorppassJwtPayloadResult = await SpcpFactory.extractCorppassJwtPayload( - '', - ) + const extractSingpassJwtPayloadResult = + await SpcpFactory.extractSingpassJwtPayload('') + const extractCorppassJwtPayloadResult = + await SpcpFactory.extractCorppassJwtPayload('') const parseOOBParamsResult = SpcpFactory.parseOOBParams('', '', AuthType.SP) const getSpcpAttributesResult = await SpcpFactory.getSpcpAttributes( '', @@ -90,7 +86,7 @@ describe('spcp.factory', () => { AuthType.SP, ) const createJWTResult = SpcpFactory.createJWT( - ({} as unknown) as JwtPayload, + {} as unknown as JwtPayload, 0, AuthType.SP, ) diff --git a/src/app/modules/spcp/__tests__/spcp.test.constants.ts b/src/app/modules/spcp/__tests__/spcp.test.constants.ts index bb9234c51f..fce05097de 100644 --- a/src/app/modules/spcp/__tests__/spcp.test.constants.ts +++ b/src/app/modules/spcp/__tests__/spcp.test.constants.ts @@ -107,7 +107,7 @@ export const MOCK_JWT_PAYLOAD = { rememberMe: true, } -export const MOCK_SP_FORM = ({ +export const MOCK_SP_FORM = { authType: 'SP', title: 'Mock SP form', _id: new ObjectId().toHexString(), @@ -116,9 +116,9 @@ export const MOCK_SP_FORM = ({ agency: new ObjectId().toHexString(), }, getPublicView: () => _.omit(this, 'admin'), -} as unknown) as IPopulatedForm +} as unknown as IPopulatedForm -export const MOCK_CP_FORM = ({ +export const MOCK_CP_FORM = { authType: 'CP', title: 'Mock CP form', _id: new ObjectId().toHexString(), @@ -127,9 +127,9 @@ export const MOCK_CP_FORM = ({ agency: new ObjectId().toHexString(), }, getPublicView: () => _.omit(this, 'admin'), -} as unknown) as IPopulatedForm +} as unknown as IPopulatedForm -export const MOCK_MYINFO_FORM = ({ +export const MOCK_MYINFO_FORM = { authType: 'MyInfo', title: 'Mock MyInfo form', _id: new ObjectId().toHexString(), @@ -137,7 +137,7 @@ export const MOCK_MYINFO_FORM = ({ _id: new ObjectId().toHexString(), agency: new ObjectId().toHexString(), }, -} as unknown) as IPopulatedForm +} as unknown as IPopulatedForm export const MOCK_LOGIN_DOC = { _id: new ObjectId().toHexString(), diff --git a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts index 357f777847..4a04c1180b 100644 --- a/src/app/modules/submission/__tests__/submission/submission.service.spec.ts +++ b/src/app/modules/submission/__tests__/submission/submission.service.spec.ts @@ -136,10 +136,10 @@ describe('submission.service', () => { // Act const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Encrypt, form_fields: [mobileField, emailField], - } as unknown) as IFormSchema, + } as unknown as IFormSchema, [mobileResponse, emailResponse], ) @@ -180,10 +180,10 @@ describe('submission.service', () => { // Act const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Email, form_fields: [shortTextField, decimalField], - } as unknown) as IFormSchema, + } as unknown as IFormSchema, [shortTextResponse, decimalResponse], ) @@ -204,10 +204,10 @@ describe('submission.service', () => { // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Email, form_fields: [extraField], - } as unknown) as IEmailFormSchema, + } as unknown as IEmailFormSchema, [], ) @@ -224,10 +224,10 @@ describe('submission.service', () => { // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Encrypt, form_fields: [extraField], - } as unknown) as IEncryptedFormSchema, + } as unknown as IEncryptedFormSchema, [], ) @@ -267,10 +267,10 @@ describe('submission.service', () => { // Act const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Encrypt, form_fields: [mobileField, emailField], - } as unknown) as IFormSchema, + } as unknown as IFormSchema, [mobileResponse, emailResponse], ) @@ -296,10 +296,10 @@ describe('submission.service', () => { // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Encrypt, form_fields: [mobileField], - } as unknown) as IEncryptedFormSchema, + } as unknown as IEncryptedFormSchema, [mobileResponse], ) @@ -317,10 +317,10 @@ describe('submission.service', () => { // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Email, form_fields: [nricField], - } as unknown) as IEmailFormSchema, + } as unknown as IEmailFormSchema, [nricResponse], ) @@ -335,19 +335,19 @@ describe('submission.service', () => { // Mock logic util to return non-empty to check if error is thrown jest .spyOn(LogicUtil, 'getLogicUnitPreventingSubmit') - .mockReturnValueOnce(({ + .mockReturnValueOnce({ preventSubmitMessage: 'mock prevent submit', conditions: [], logicType: LogicType.PreventSubmit, _id: 'some id', - } as unknown) as IPreventSubmitLogicSchema) + } as unknown as IPreventSubmitLogicSchema) // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Encrypt, form_fields: [], - } as unknown) as IEncryptedFormSchema, + } as unknown as IEncryptedFormSchema, [], ) @@ -360,12 +360,12 @@ describe('submission.service', () => { it('should return error when email form submission is prevented by logic', async () => { // Arrange // Mock logic util to return non-empty to check if error is thrown. - const mockReturnLogicUnit = ({ + const mockReturnLogicUnit = { preventSubmitMessage: 'mock prevent submit', conditions: [], logicType: LogicType.PreventSubmit, _id: 'some id', - } as unknown) as IPreventSubmitLogicSchema + } as unknown as IPreventSubmitLogicSchema jest .spyOn(LogicUtil, 'getLogicUnitPreventingSubmit') @@ -373,10 +373,10 @@ describe('submission.service', () => { // Act + Assert const actualResult = SubmissionService.getProcessedResponses( - ({ + { responseMode: ResponseMode.Email, form_fields: [], - } as unknown) as IEmailFormSchema, + } as unknown as IEmailFormSchema, [], ) @@ -529,9 +529,9 @@ describe('submission.service', () => { // Arrange countSpy.mockImplementationOnce( () => - (({ + ({ exec: () => Promise.reject(new Error('boom')), - } as unknown) as mongoose.Query), + } as unknown as mongoose.Query), ) // Act @@ -550,7 +550,7 @@ describe('submission.service', () => { describe('sendEmailConfirmations', () => { it('should call mail service and return true when email confirmations are sent successfully', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -562,7 +562,7 @@ describe('submission.service', () => { autoReplyOptions: AUTOREPLY_OPTIONS_2, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema MockMailService.sendAutoReplyEmails.mockResolvedValueOnce([ { status: 'fulfilled', @@ -612,10 +612,10 @@ describe('submission.service', () => { }) it('should not call mail service when there are no email fields', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [generateDefaultField(BasicField.Number)], - } as unknown) as IFormSchema + } as unknown as IFormSchema const responses = [ { @@ -638,7 +638,7 @@ describe('submission.service', () => { }) it('should not call mail service when there are email fields but all without email confirmation', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -650,7 +650,7 @@ describe('submission.service', () => { autoReplyOptions: { hasAutoReply: false }, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema const responses = [ { @@ -679,7 +679,7 @@ describe('submission.service', () => { }) it('should call mail service when there is a mix of email fields with and without confirmation', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -691,7 +691,7 @@ describe('submission.service', () => { autoReplyOptions: { hasAutoReply: false }, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema MockMailService.sendAutoReplyEmails.mockResolvedValueOnce([ { status: 'fulfilled', @@ -734,7 +734,7 @@ describe('submission.service', () => { }) it('should call mail service with responsesData empty when autoReplyData is undefined', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -746,7 +746,7 @@ describe('submission.service', () => { autoReplyOptions: AUTOREPLY_OPTIONS_2, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema MockMailService.sendAutoReplyEmails.mockResolvedValueOnce([ { status: 'fulfilled', @@ -796,7 +796,7 @@ describe('submission.service', () => { }) it('should call mail service with attachments undefined when there are no attachments', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -808,7 +808,7 @@ describe('submission.service', () => { autoReplyOptions: AUTOREPLY_OPTIONS_2, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema MockMailService.sendAutoReplyEmails.mockResolvedValueOnce([ { status: 'fulfilled', @@ -858,7 +858,7 @@ describe('submission.service', () => { }) it('should return SendEmailConfirmationError when mail service errors', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -870,7 +870,7 @@ describe('submission.service', () => { autoReplyOptions: AUTOREPLY_OPTIONS_2, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema MockMailService.sendAutoReplyEmails.mockImplementationOnce(() => Promise.reject('rejected'), ) @@ -915,7 +915,7 @@ describe('submission.service', () => { }) it('should return SendEmailConfirmationError when any email confirmations fail', async () => { - const mockForm = ({ + const mockForm = { _id: MOCK_FORM_ID, form_fields: [ { @@ -927,7 +927,7 @@ describe('submission.service', () => { autoReplyOptions: AUTOREPLY_OPTIONS_2, }, ], - } as unknown) as IFormSchema + } as unknown as IFormSchema const mockReason = 'reason' MockMailService.sendAutoReplyEmails.mockResolvedValueOnce([ { diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.receiver.spec.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.receiver.spec.ts index 6a795dc426..06895b026e 100644 --- a/src/app/modules/submission/email-submission/__tests__/email-submission.receiver.spec.ts +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.receiver.spec.ts @@ -26,9 +26,9 @@ const RealBusboy = jest.requireActual('busboy') as typeof Busboy const MOCK_HEADERS = { key: 'value' } const MOCK_BUSBOY_ON = jest.fn().mockReturnThis() -const MOCK_BUSBOY = ({ +const MOCK_BUSBOY = { on: MOCK_BUSBOY_ON, -} as unknown) as busboy.Busboy +} as unknown as busboy.Busboy const VALID_FILE_PATH = 'tests/unit/backend/resources/' const VALID_FILENAME_1 = 'valid.txt' @@ -102,9 +102,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) fileStream.emit('data', VALID_FILE_CONTENT_1) @@ -150,9 +149,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) fileStream.emit('data', VALID_FILE_CONTENT_1) @@ -212,9 +210,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) fileStream1.emit('data', VALID_FILE_CONTENT_1) @@ -278,9 +275,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) fileStream1.emit('data', VALID_FILE_CONTENT_1) @@ -336,9 +332,8 @@ describe('email-submission.receiver', () => { fileSize: 7 * MB, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) fileStream.emit('data', Buffer.alloc(7 * MB + 1)) @@ -357,9 +352,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) realBusboy.emit('error', new Error()) @@ -376,9 +370,8 @@ describe('email-submission.receiver', () => { 'content-type': `multipart/form-data; boundary=${form.getBoundary()}`, }, }) - const resultPromise = EmailSubmissionReceiver.configureMultipartReceiver( - realBusboy, - ) + const resultPromise = + EmailSubmissionReceiver.configureMultipartReceiver(realBusboy) form.pipe(realBusboy) form.emit('end') diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts index ab8042a986..32e576b257 100644 --- a/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts @@ -51,14 +51,14 @@ describe('email-submission.service', () => { const MOCK_EMAIL = 'a@abc.com' const MOCK_RESPONSE_HASH = 'mockHash' const MOCK_RESPONSE_SALT = 'mockSalt' - const MOCK_FORM = ({ + const MOCK_FORM = { admin: new ObjectId(), _id: new ObjectId(), title: 'mock title', getUniqueMyInfoAttrs: () => [], authType: 'NIL', emails: [MOCK_EMAIL], - } as unknown) as IPopulatedEmailForm + } as unknown as IPopulatedEmailForm it('should create a new submission without saving it to the database', async () => { const result = EmailSubmissionService.createEmailSubmissionWithoutSave( @@ -227,7 +227,7 @@ describe('email-submission.service', () => { ) const response = generateNewAttachmentResponse() const responseAsEmailField = generateSingleAnswerFormData( - (response as unknown) as ProcessedSingleAnswerResponse, + response as unknown as ProcessedSingleAnswerResponse, ) const expectedBaseString = `${response.question} ${response.answer}; ${response.content}` @@ -273,7 +273,7 @@ describe('email-submission.service', () => { content: Buffer.from('content1'), }) const responseAsEmailField1 = generateSingleAnswerFormData( - (response1 as unknown) as ProcessedSingleAnswerResponse, + response1 as unknown as ProcessedSingleAnswerResponse, ) const response2 = generateNewAttachmentResponse({ @@ -283,7 +283,7 @@ describe('email-submission.service', () => { }) const expectedBaseString = `${response1.question} ${response1.answer}; ${response2.question} ${response2.answer}; ${response1.content}${response2.content}` const responseAsEmailField2 = generateSingleAnswerFormData( - (response2 as unknown) as ProcessedSingleAnswerResponse, + response2 as unknown as ProcessedSingleAnswerResponse, ) const result = await EmailSubmissionService.hashSubmission( @@ -333,7 +333,7 @@ describe('email-submission.service', () => { const createEmailSubmissionSpy = jest .spyOn(EmailSubmissionModel, 'create') .mockResolvedValueOnce( - (mockSubmission as unknown) as ResolvedValue, + mockSubmission as unknown as ResolvedValue, ) const result = await EmailSubmissionService.saveSubmissionMetadata( MOCK_EMAIL_FORM as IPopulatedEmailForm, diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.test.constants.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.test.constants.ts index fabc049246..3e7c402147 100644 --- a/src/app/modules/submission/email-submission/__tests__/email-submission.test.constants.ts +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.test.constants.ts @@ -19,9 +19,8 @@ export const MOCK_NO_RESPONSES_BODY = { } export const MOCK_TEXT_FIELD = generateDefaultField(BasicField.ShortText) -export const MOCK_TEXTFIELD_RESPONSE = generateSingleAnswerResponse( - MOCK_TEXT_FIELD, -) +export const MOCK_TEXTFIELD_RESPONSE = + generateSingleAnswerResponse(MOCK_TEXT_FIELD) export const MOCK_ATTACHMENT_FIELD = generateDefaultField(BasicField.Attachment) export const MOCK_ATTACHMENT_RESPONSE = generateAttachmentResponse( @@ -31,9 +30,8 @@ export const MOCK_ATTACHMENT_RESPONSE = generateAttachmentResponse( ) export const MOCK_SECTION_FIELD = generateDefaultField(BasicField.Section) -export const MOCK_SECTION_RESPONSE = generateSingleAnswerResponse( - MOCK_SECTION_FIELD, -) +export const MOCK_SECTION_RESPONSE = + generateSingleAnswerResponse(MOCK_SECTION_FIELD) export const MOCK_CHECKBOX_FIELD = generateDefaultField(BasicField.Checkbox) export const MOCK_CHECKBOX_RESPONSE = generateCheckboxResponse( diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.util.spec.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.util.spec.ts index 309db8751c..25533986b7 100644 --- a/src/app/modules/submission/email-submission/__tests__/email-submission.util.spec.ts +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.util.spec.ts @@ -96,12 +96,12 @@ const getResponse = ( _id: string, answer: string, ): WithQuestion => - (({ + ({ _id, fieldType: BasicField.Attachment, question: 'mockQuestion', answer, - } as unknown) as WithQuestion) + } as unknown as WithQuestion) const ALL_SINGLE_SUBMITTED_RESPONSES = basicTypes // Attachments are special cases, requiring filename and content @@ -216,18 +216,18 @@ describe('email-submission.util', () => { [firstAttachment, secondAttachment], ) expect(firstResponse.answer).toBe(firstAttachment.filename) - expect(((firstResponse as unknown) as IAttachmentResponse).filename).toBe( + expect((firstResponse as unknown as IAttachmentResponse).filename).toBe( firstAttachment.filename, ) - expect( - ((firstResponse as unknown) as IAttachmentResponse).content, - ).toEqual(firstAttachment.content) + expect((firstResponse as unknown as IAttachmentResponse).content).toEqual( + firstAttachment.content, + ) expect(secondResponse.answer).toBe(secondAttachment.filename) + expect((secondResponse as unknown as IAttachmentResponse).filename).toBe( + secondAttachment.filename, + ) expect( - ((secondResponse as unknown) as IAttachmentResponse).filename, - ).toBe(secondAttachment.filename) - expect( - ((secondResponse as unknown) as IAttachmentResponse).content, + (secondResponse as unknown as IAttachmentResponse).content, ).toEqual(secondAttachment.content) }) @@ -236,10 +236,10 @@ describe('email-submission.util', () => { const response = getResponse(attachment.fieldId, MOCK_ANSWER) addAttachmentToResponses([response], [attachment]) expect(response.answer).toBe(attachment.filename) - expect(((response as unknown) as IAttachmentResponse).filename).toBe( + expect((response as unknown as IAttachmentResponse).filename).toBe( attachment.filename, ) - expect(((response as unknown) as IAttachmentResponse).content).toEqual( + expect((response as unknown as IAttachmentResponse).content).toEqual( attachment.content, ) }) diff --git a/src/app/modules/submission/email-submission/email-submission.controller.ts b/src/app/modules/submission/email-submission/email-submission.controller.ts index d55f3523eb..c6ebd63aa7 100644 --- a/src/app/modules/submission/email-submission/email-submission.controller.ts +++ b/src/app/modules/submission/email-submission/email-submission.controller.ts @@ -272,9 +272,8 @@ const submitEmailModeForm: ControllerHandler< // NOTE: This should short circuit in the event of an error. // This is why sendSubmissionToAdmin is separated from sendEmailConfirmations in 2 blocks return MailService.sendSubmissionToAdmin({ - replyToEmails: EmailSubmissionService.extractEmailAnswers( - parsedResponses, - ), + replyToEmails: + EmailSubmissionService.extractEmailAnswers(parsedResponses), form, submission, attachments, diff --git a/src/app/modules/submission/email-submission/email-submission.middleware.ts b/src/app/modules/submission/email-submission/email-submission.middleware.ts index 1bf2709c6f..755b55b55f 100644 --- a/src/app/modules/submission/email-submission/email-submission.middleware.ts +++ b/src/app/modules/submission/email-submission/email-submission.middleware.ts @@ -30,9 +30,8 @@ export const receiveEmailSubmission: ControllerHandler< } return EmailSubmissionReceiver.createMultipartReceiver(req.headers) .asyncAndThen((receiver) => { - const result = EmailSubmissionReceiver.configureMultipartReceiver( - receiver, - ) + const result = + EmailSubmissionReceiver.configureMultipartReceiver(receiver) req.pipe(receiver) return result }) diff --git a/src/app/modules/submission/email-submission/email-submission.util.ts b/src/app/modules/submission/email-submission/email-submission.util.ts index 5f3c838500..93fc5f8fc5 100644 --- a/src/app/modules/submission/email-submission/email-submission.util.ts +++ b/src/app/modules/submission/email-submission/email-submission.util.ts @@ -267,9 +267,8 @@ export const getInvalidFileExtensions = ( ): Promise => { // Turn it into an array of promises that each resolve // to an array of file extensions that are invalid (if any) - const getInvalidFileExtensionsInZip = FileValidation.getInvalidFileExtensionsInZip( - FilePlatforms.Server, - ) + const getInvalidFileExtensionsInZip = + FileValidation.getInvalidFileExtensionsInZip(FilePlatforms.Server) const promises = attachments.map((attachment) => { const extension = FileValidation.getFileExtension(attachment.filename) if (FileValidation.isInvalidFileExtension(extension)) { @@ -492,13 +491,11 @@ export const addAttachmentToResponses = ( attachments: IAttachmentInfo[], ): void => { // Create a map of the attachments with fieldId as keys - const attachmentMap: Record< - IAttachmentInfo['fieldId'], - IAttachmentInfo - > = attachments.reduce>((acc, attachment) => { - acc[attachment.fieldId] = attachment - return acc - }, {}) + const attachmentMap: Record = + attachments.reduce>((acc, attachment) => { + acc[attachment.fieldId] = attachment + return acc + }, {}) if (responses) { // matches responses to attachments using id, adding filename and content to response diff --git a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts index 17dd3b4bf7..b5b668c076 100644 --- a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts +++ b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts @@ -43,13 +43,13 @@ describe('encrypt-submission.service', () => { afterAll(async () => await dbHandler.closeDatabase()) describe('createEncryptSubmissionWithoutSave', () => { - const MOCK_FORM = ({ + const MOCK_FORM = { admin: new ObjectId(), _id: new ObjectId(), title: 'mock title', getUniqueMyInfoAttrs: () => [], authType: 'NIL', - } as unknown) as IPopulatedEncryptedForm + } as unknown as IPopulatedEncryptedForm const MOCK_ENCRYPTED_CONTENT = 'mockEncryptedContent' const MOCK_VERIFIED_CONTENT = 'mockVerifiedContent' const MOCK_VERSION = 1 @@ -81,7 +81,7 @@ describe('encrypt-submission.service', () => { describe('getSubmissionCursor', () => { it('should return cursor successfully when date range is not provided', async () => { // Arrange - const mockCursor = (jest.fn() as unknown) as mongoose.QueryCursor + const mockCursor = jest.fn() as unknown as mongoose.QueryCursor const getSubmissionSpy = jest .spyOn(EncryptSubmission, 'getSubmissionCursorByFormId') .mockReturnValueOnce(mockCursor) @@ -99,7 +99,7 @@ describe('encrypt-submission.service', () => { it('should return cursor successfully when date range is provided', async () => { // Arrange - const mockCursor = (jest.fn() as unknown) as mongoose.QueryCursor + const mockCursor = jest.fn() as unknown as mongoose.QueryCursor const getSubmissionSpy = jest .spyOn(EncryptSubmission, 'getSubmissionCursorByFormId') .mockReturnValueOnce(mockCursor) diff --git a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts index a8538da65e..06fbac9f9f 100644 --- a/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts +++ b/src/app/modules/submission/encrypt-submission/encrypt-submission.controller.ts @@ -149,9 +149,8 @@ const submitEncryptModeForm: ControllerHandler< } // Check that the form has not reached submission limits - const formSubmissionLimitResult = await FormService.checkFormSubmissionLimitAndDeactivateForm( - form, - ) + const formSubmissionLimitResult = + await FormService.checkFormSubmissionLimitAndDeactivateForm(form) if (formSubmissionLimitResult.isErr()) { logger.warn({ message: @@ -270,14 +269,16 @@ const submitEncryptModeForm: ControllerHandler< // Encrypt Verified SPCP Fields let verified if (form.authType === AuthType.SP || form.authType === AuthType.CP) { - const encryptVerifiedContentResult = VerifiedContentFactory.getVerifiedContent( - { type: form.authType, data: { uinFin, userInfo } }, - ).andThen((verifiedContent) => - VerifiedContentFactory.encryptVerifiedContent({ - verifiedContent, - formPublicKey: form.publicKey, - }), - ) + const encryptVerifiedContentResult = + VerifiedContentFactory.getVerifiedContent({ + type: form.authType, + data: { uinFin, userInfo }, + }).andThen((verifiedContent) => + VerifiedContentFactory.encryptVerifiedContent({ + verifiedContent, + formPublicKey: form.publicKey, + }), + ) if (encryptVerifiedContentResult.isErr()) { const { error } = encryptVerifiedContentResult @@ -363,11 +364,8 @@ const submitEncryptModeForm: ControllerHandler< // As such, we should not await on these post requests const webhookUrl = form.webhook?.url if (webhookUrl) { - void WebhookFactory.sendWebhook( - submission, - webhookUrl, - ).andThen((response) => - WebhookFactory.saveWebhookRecord(submission._id, response), + void WebhookFactory.sendWebhook(submission, webhookUrl).andThen( + (response) => WebhookFactory.saveWebhookRecord(submission._id, response), ) } diff --git a/src/app/modules/user/__tests__/user.service.spec.ts b/src/app/modules/user/__tests__/user.service.spec.ts index 91b86e802c..eb5eb8f5bc 100644 --- a/src/app/modules/user/__tests__/user.service.spec.ts +++ b/src/app/modules/user/__tests__/user.service.spec.ts @@ -413,9 +413,9 @@ describe('user.service', () => { it('should return admin successfully', async () => { // Arrange const mockUserId = new ObjectID().toHexString() - const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(defaultUser), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -429,9 +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').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockRejectedValue(new Error('database bad')), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -445,9 +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').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findById').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserById(mockUserId) @@ -463,9 +463,9 @@ describe('user.service', () => { it('should return admin successfully', async () => { // Arrange const mockEmail = 'someemail@example.com' - const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(defaultUser), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) @@ -479,9 +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').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce({ exec: jest.fn().mockRejectedValue(new Error('database bad!')), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) @@ -495,9 +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').mockReturnValueOnce(({ + const findSpy = jest.spyOn(UserModel, 'findOne').mockReturnValueOnce({ exec: jest.fn().mockResolvedValue(null), - } as unknown) as Query) + } as unknown as Query) // Act const actualResult = await UserService.findUserByEmail(mockEmail) diff --git a/src/app/modules/verification/__tests__/verification.factory.spec.ts b/src/app/modules/verification/__tests__/verification.factory.spec.ts index 40b39c6fb6..da87e62650 100644 --- a/src/app/modules/verification/__tests__/verification.factory.spec.ts +++ b/src/app/modules/verification/__tests__/verification.factory.spec.ts @@ -23,13 +23,10 @@ describe('verification.factory', () => { const createTransactionResult = await verificationFactory.createTransaction( '', ) - const getTransactionMetadataResult = await verificationFactory.getTransactionMetadata( - '', - ) - const resetFieldForTransactionResult = await verificationFactory.resetFieldForTransaction( - '', - '', - ) + const getTransactionMetadataResult = + await verificationFactory.getTransactionMetadata('') + const resetFieldForTransactionResult = + await verificationFactory.resetFieldForTransaction('', '') const sendNewOtpResult = await verificationFactory.sendNewOtp({ transactionId: '', fieldId: '', @@ -57,13 +54,10 @@ describe('verification.factory', () => { const createTransactionResult = await verificationFactory.createTransaction( '', ) - const getTransactionMetadataResult = await verificationFactory.getTransactionMetadata( - '', - ) - const resetFieldForTransactionResult = await verificationFactory.resetFieldForTransaction( - '', - '', - ) + const getTransactionMetadataResult = + await verificationFactory.getTransactionMetadata('') + const resetFieldForTransactionResult = + await verificationFactory.resetFieldForTransaction('', '') const sendNewOtpResult = await verificationFactory.sendNewOtp({ transactionId: '', fieldId: '', diff --git a/src/app/modules/verification/__tests__/verification.service.spec.ts b/src/app/modules/verification/__tests__/verification.service.spec.ts index d511c1f83b..2d8adaa53e 100644 --- a/src/app/modules/verification/__tests__/verification.service.spec.ts +++ b/src/app/modules/verification/__tests__/verification.service.spec.ts @@ -83,11 +83,11 @@ describe('Verification service', () => { afterAll(async () => await dbHandler.closeDatabase()) describe('createTransaction', () => { - const mockForm = ({ + const mockForm = { _id: new ObjectId(), title: 'mockForm', form_fields: [], - } as unknown) as IFormSchema + } as unknown as IFormSchema let createTransactionFromFormSpy: jest.SpyInstance< Promise, [form: IFormSchema] diff --git a/src/app/modules/verified-content/__tests__/verified-content.factory.spec.ts b/src/app/modules/verified-content/__tests__/verified-content.factory.spec.ts index 188bd48b2a..cbf8f0f834 100644 --- a/src/app/modules/verified-content/__tests__/verified-content.factory.spec.ts +++ b/src/app/modules/verified-content/__tests__/verified-content.factory.spec.ts @@ -17,13 +17,13 @@ const MockVerifiedContentService = mocked(VerifiedContentService) describe('verified-content.factory', () => { describe('verified content feature disabled', () => { - const MOCK_DISABLED_FEAT: RegisteredFeature = { - isEnabled: false, - } + const MOCK_DISABLED_FEAT: RegisteredFeature = + { + isEnabled: false, + } - const VerifiedContentFactory = createVerifiedContentFactory( - MOCK_DISABLED_FEAT, - ) + const VerifiedContentFactory = + createVerifiedContentFactory(MOCK_DISABLED_FEAT) it('should return MissingFeatureError when invoking getVerifiedContent', async () => { // Act @@ -51,9 +51,10 @@ describe('verified-content.factory', () => { }) describe('verified content feature enabled but missing signing secret key', () => { - const MOCK_ENABLED_FEAT_WO_KEY: RegisteredFeature = { - isEnabled: true, - } + const MOCK_ENABLED_FEAT_WO_KEY: RegisteredFeature = + { + isEnabled: true, + } const VerifiedContentFactory = createVerifiedContentFactory( MOCK_ENABLED_FEAT_WO_KEY, @@ -85,16 +86,16 @@ describe('verified-content.factory', () => { }) describe('verified content feature enabled with signing secret key', () => { - const MOCK_ENABLED_FEAT: RegisteredFeature = { - isEnabled: true, - props: { - signingSecretKey: 'some secret key', - }, - } + const MOCK_ENABLED_FEAT: RegisteredFeature = + { + isEnabled: true, + props: { + signingSecretKey: 'some secret key', + }, + } - const VerifiedContentFactory = createVerifiedContentFactory( - MOCK_ENABLED_FEAT, - ) + const VerifiedContentFactory = + createVerifiedContentFactory(MOCK_ENABLED_FEAT) it('should invoke VerifiedContentService.getVerifiedContent', async () => { // Arrange diff --git a/src/app/modules/webhook/__tests__/webhook.service.spec.ts b/src/app/modules/webhook/__tests__/webhook.service.spec.ts index e0e5485dda..a42645c3eb 100644 --- a/src/app/modules/webhook/__tests__/webhook.service.spec.ts +++ b/src/app/modules/webhook/__tests__/webhook.service.spec.ts @@ -68,16 +68,14 @@ const MOCK_WEBHOOK_FAILURE_RESPONSE: Pick = { headers: '{}', }, } -const MOCK_WEBHOOK_DEFAULT_FORMAT_RESPONSE: Pick< - IWebhookResponse, - 'response' -> = { - response: { - data: '', - status: 0, - headers: '', - }, -} +const MOCK_WEBHOOK_DEFAULT_FORMAT_RESPONSE: Pick = + { + response: { + data: '', + status: 0, + headers: '', + }, + } describe('webhook.service', () => { beforeAll(async () => await dbHandler.connect()) diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.logic.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.logic.routes.spec.ts index f0797fdd6a..c3d1858f34 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.logic.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.logic.routes.spec.ts @@ -212,12 +212,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: formLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const session = await createAuthedSession(user.email, request) @@ -244,12 +244,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: formLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const session = await createAuthedSession(user.email, request) @@ -275,12 +275,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: formLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const diffUser = await dbHandler.insertUser({ mailName: 'newUser', @@ -317,12 +317,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: wrongLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const session = await createAuthedSession(user.email, request) @@ -352,12 +352,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: formLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const session = await createAuthedSession(user.email, request) @@ -388,12 +388,12 @@ describe('admin-form.logic.routes', () => { }, }) - const updatedLogic = ({ + const updatedLogic = { _id: formLogicId, logicType: LogicType.PreventSubmit, conditions: [], preventSubmitMessage: 'Some message', - } as unknown) as ILogicSchema + } as unknown as ILogicSchema const session = await createAuthedSession(user.email, request) diff --git a/src/app/routes/api/v3/billings/__tests__/billings.routes.spec.ts b/src/app/routes/api/v3/billings/__tests__/billings.routes.spec.ts index 0fd696d66f..7628bc38a4 100644 --- a/src/app/routes/api/v3/billings/__tests__/billings.routes.spec.ts +++ b/src/app/routes/api/v3/billings/__tests__/billings.routes.spec.ts @@ -51,14 +51,12 @@ describe('billings.routes', () => { // Log in user. const session = await createAuthedSession(defaultUser.email, request) // Generate login statistics. - const { - generatedLoginTimes, - generatedForms, - } = await generateLoginStatistics({ - user: defaultUser, - esrvcIdToCheck: VALID_ESRVCID_1, - altEsrvcId: VALID_ESRVCID_2, - }) + const { generatedLoginTimes, generatedForms } = + await generateLoginStatistics({ + user: defaultUser, + esrvcIdToCheck: VALID_ESRVCID_1, + altEsrvcId: VALID_ESRVCID_2, + }) // Act const response = await session.get('/billings').query({ diff --git a/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts b/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts index 89164a5423..62de12cb67 100644 --- a/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts +++ b/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts @@ -19,9 +19,8 @@ export const MOCK_NO_RESPONSES_BODY = { } export const MOCK_TEXT_FIELD = generateDefaultField(BasicField.ShortText) -export const MOCK_TEXTFIELD_RESPONSE = generateSingleAnswerResponse( - MOCK_TEXT_FIELD, -) +export const MOCK_TEXTFIELD_RESPONSE = + generateSingleAnswerResponse(MOCK_TEXT_FIELD) export const MOCK_ATTACHMENT_FIELD = generateDefaultField(BasicField.Attachment) export const MOCK_ATTACHMENT_RESPONSE = generateAttachmentResponse( @@ -31,9 +30,8 @@ export const MOCK_ATTACHMENT_RESPONSE = generateAttachmentResponse( ) export const MOCK_SECTION_FIELD = generateDefaultField(BasicField.Section) -export const MOCK_SECTION_RESPONSE = generateSingleAnswerResponse( - MOCK_SECTION_FIELD, -) +export const MOCK_SECTION_RESPONSE = + generateSingleAnswerResponse(MOCK_SECTION_FIELD) export const MOCK_CHECKBOX_FIELD = generateDefaultField(BasicField.Checkbox) export const MOCK_CHECKBOX_RESPONSE = generateCheckboxResponse( diff --git a/src/app/services/captcha/__tests__/captcha.factory.spec.ts b/src/app/services/captcha/__tests__/captcha.factory.spec.ts index 6f49b5f257..ad13cbaa88 100644 --- a/src/app/services/captcha/__tests__/captcha.factory.spec.ts +++ b/src/app/services/captcha/__tests__/captcha.factory.spec.ts @@ -28,8 +28,8 @@ describe('captcha.factory', () => { undefined, ) captchaFactory.validateCaptchaParams( - ({} as unknown) as Request, - ({} as unknown) as Response, + {} as unknown as Request, + {} as unknown as Response, nextSpy, ) expect(verifyResult._unsafeUnwrap()).toBe(true) @@ -48,8 +48,8 @@ describe('captcha.factory', () => { undefined, ) captchaFactory.validateCaptchaParams( - ({} as unknown) as Request, - ({} as unknown) as Response, + {} as unknown as Request, + {} as unknown as Response, nextSpy, ) expect(verifyResult._unsafeUnwrap()).toBe(true) diff --git a/src/app/services/captcha/__tests__/captcha.service.spec.ts b/src/app/services/captcha/__tests__/captcha.service.spec.ts index 8492e5d4b9..32eebd6b85 100644 --- a/src/app/services/captcha/__tests__/captcha.service.spec.ts +++ b/src/app/services/captcha/__tests__/captcha.service.spec.ts @@ -20,9 +20,8 @@ describe('captcha.service', () => { beforeEach(() => jest.clearAllMocks()) it('should return MissingCaptchaError when response is falsy', async () => { - const verifyCaptchaResponse = makeCaptchaResponseVerifier( - MOCK_PRIVATE_KEY, - ) + const verifyCaptchaResponse = + makeCaptchaResponseVerifier(MOCK_PRIVATE_KEY) const result = await verifyCaptchaResponse(null, undefined) expect(result._unsafeUnwrapErr()).toEqual(new MissingCaptchaError()) @@ -31,9 +30,8 @@ describe('captcha.service', () => { it('should return VerifyCaptchaError when captcha response is incorrect', async () => { MockAxios.get.mockResolvedValueOnce({ data: { success: false } }) - const verifyCaptchaResponse = makeCaptchaResponseVerifier( - MOCK_PRIVATE_KEY, - ) + const verifyCaptchaResponse = + makeCaptchaResponseVerifier(MOCK_PRIVATE_KEY) const result = await verifyCaptchaResponse(MOCK_RESPONSE, MOCK_REMOTE_IP) expect(MockAxios.get).toHaveBeenCalledWith(GOOGLE_RECAPTCHA_URL, { @@ -49,9 +47,8 @@ describe('captcha.service', () => { it('should return true when captcha response is correct', async () => { MockAxios.get.mockResolvedValueOnce({ data: { success: true } }) - const verifyCaptchaResponse = makeCaptchaResponseVerifier( - MOCK_PRIVATE_KEY, - ) + const verifyCaptchaResponse = + makeCaptchaResponseVerifier(MOCK_PRIVATE_KEY) const result = await verifyCaptchaResponse(MOCK_RESPONSE, MOCK_REMOTE_IP) expect(MockAxios.get).toHaveBeenCalledWith(GOOGLE_RECAPTCHA_URL, { @@ -67,9 +64,8 @@ describe('captcha.service', () => { it('should return CaptchaConnectionError when connection with captcha server fails', async () => { MockAxios.get.mockRejectedValueOnce(false) - const verifyCaptchaResponse = makeCaptchaResponseVerifier( - MOCK_PRIVATE_KEY, - ) + const verifyCaptchaResponse = + makeCaptchaResponseVerifier(MOCK_PRIVATE_KEY) const result = await verifyCaptchaResponse(MOCK_RESPONSE, MOCK_REMOTE_IP) expect(MockAxios.get).toHaveBeenCalledWith(GOOGLE_RECAPTCHA_URL, { diff --git a/src/app/services/captcha/captcha.service.ts b/src/app/services/captcha/captcha.service.ts index a29c4865d9..dbc1e6a846 100644 --- a/src/app/services/captcha/captcha.service.ts +++ b/src/app/services/captcha/captcha.service.ts @@ -12,45 +12,47 @@ import { const logger = createLoggerWithLabel(module) -export const makeCaptchaResponseVerifier = (captchaPrivateKey: string) => ( - response?: unknown, - remoteip?: string, -): ResultAsync< - true, - CaptchaConnectionError | VerifyCaptchaError | MissingCaptchaError -> => { - if (!response || typeof response !== 'string') { - return errAsync(new MissingCaptchaError()) - } - const verifyCaptchaPromise = axios.get<{ success: boolean }>( - GOOGLE_RECAPTCHA_URL, - { - params: { - secret: captchaPrivateKey, - response, - remoteip, - }, - }, - ) - return ResultAsync.fromPromise(verifyCaptchaPromise, (error) => { - logger.error({ - message: 'Error verifying captcha', - meta: { - action: 'verifyCaptchaResponse', +export const makeCaptchaResponseVerifier = + (captchaPrivateKey: string) => + ( + response?: unknown, + remoteip?: string, + ): ResultAsync< + true, + CaptchaConnectionError | VerifyCaptchaError | MissingCaptchaError + > => { + if (!response || typeof response !== 'string') { + return errAsync(new MissingCaptchaError()) + } + const verifyCaptchaPromise = axios.get<{ success: boolean }>( + GOOGLE_RECAPTCHA_URL, + { + params: { + secret: captchaPrivateKey, + response, + remoteip, + }, }, - error, - }) - return new CaptchaConnectionError() - }).andThen(({ data }) => { - if (!data.success) { - logger.warn({ - message: 'Incorrect captcha response', + ) + return ResultAsync.fromPromise(verifyCaptchaPromise, (error) => { + logger.error({ + message: 'Error verifying captcha', meta: { action: 'verifyCaptchaResponse', }, + error, }) - return errAsync(new VerifyCaptchaError()) - } - return okAsync(true) - }) -} + return new CaptchaConnectionError() + }).andThen(({ data }) => { + if (!data.success) { + logger.warn({ + message: 'Incorrect captcha response', + meta: { + action: 'verifyCaptchaResponse', + }, + }) + return errAsync(new VerifyCaptchaError()) + } + return okAsync(true) + }) + } diff --git a/src/app/services/mail/__tests__/mail.service.spec.ts b/src/app/services/mail/__tests__/mail.service.spec.ts index 37611565f2..dc2c8be9bf 100644 --- a/src/app/services/mail/__tests__/mail.service.spec.ts +++ b/src/app/services/mail/__tests__/mail.service.spec.ts @@ -28,9 +28,9 @@ const MOCK_RETRY_COUNT = 10 describe('mail.service', () => { const sendMailSpy = jest.fn() - const mockTransporter = ({ + const mockTransporter = { sendMail: sendMailSpy, - } as unknown) as Mail + } as unknown as Mail // Set up mocks for MailUtils beforeAll(() => { @@ -725,9 +725,10 @@ describe('mail.service', () => { }, ], } - const DEFAULT_AUTO_REPLY_BODY = `Dear Sir or Madam,\n\nThank you for submitting this form.\n\nRegards,\n${MOCK_AUTOREPLY_PARAMS.form.admin.agency.fullName}`.split( - '\n', - ) + const DEFAULT_AUTO_REPLY_BODY = + `Dear Sir or Madam,\n\nThank you for submitting this form.\n\nRegards,\n${MOCK_AUTOREPLY_PARAMS.form.admin.agency.fullName}`.split( + '\n', + ) beforeAll(async () => { defaultHtml = ( diff --git a/src/app/services/sms/__tests__/sms.factory.spec.ts b/src/app/services/sms/__tests__/sms.factory.spec.ts index ff9945ef15..7927f985b2 100644 --- a/src/app/services/sms/__tests__/sms.factory.spec.ts +++ b/src/app/services/sms/__tests__/sms.factory.spec.ts @@ -24,9 +24,9 @@ jest.mock('twilio', () => jest.mock('../sms.service') const MockSmsService = mocked(SmsService, true) -const MOCKED_TWILIO = ({ +const MOCKED_TWILIO = { mocked: 'this is mocked', -} as unknown) as Twilio.Twilio +} as unknown as Twilio.Twilio const MOCK_BOUNCE_SMS_PARAMS: BounceNotificationSmsParams = { adminEmail: 'admin@email.com', diff --git a/src/app/services/sms/__tests__/sms.service.spec.ts b/src/app/services/sms/__tests__/sms.service.spec.ts index 8452e600d0..4579a0344c 100644 --- a/src/app/services/sms/__tests__/sms.service.spec.ts +++ b/src/app/services/sms/__tests__/sms.service.spec.ts @@ -35,14 +35,14 @@ const twilioSuccessSpy = jest.fn().mockResolvedValue({ sid: 'testSid', }) -const MOCK_VALID_CONFIG = ({ +const MOCK_VALID_CONFIG = { msgSrvcSid: MOCK_MSG_SRVC_SID, client: { messages: { create: twilioSuccessSpy, }, }, -} as unknown) as TwilioConfig +} as unknown as TwilioConfig const twilioFailureSpy = jest.fn().mockResolvedValue({ status: 'testStatus', @@ -50,14 +50,14 @@ const twilioFailureSpy = jest.fn().mockResolvedValue({ errorCode: 21211, }) -const MOCK_INVALID_CONFIG = ({ +const MOCK_INVALID_CONFIG = { msgSrvcSid: MOCK_MSG_SRVC_SID, client: { messages: { create: twilioFailureSpy, }, }, -} as unknown) as TwilioConfig +} as unknown as TwilioConfig const smsCountSpy = jest.spyOn(SmsCountModel, 'logSms') diff --git a/src/app/services/sms/__tests__/sms_count.server.model.spec.ts b/src/app/services/sms/__tests__/sms_count.server.model.spec.ts index 7a022c6f86..ad0d66b67f 100644 --- a/src/app/services/sms/__tests__/sms_count.server.model.spec.ts +++ b/src/app/services/sms/__tests__/sms_count.server.model.spec.ts @@ -561,9 +561,8 @@ const createVerificationSmsCountParams = ({ logType?: LogType smsType?: SmsType } = {}) => { - const smsCountParams: Partial = cloneDeep( - MOCK_SMSCOUNT_PARAMS, - ) + const smsCountParams: Partial = + cloneDeep(MOCK_SMSCOUNT_PARAMS) smsCountParams.logType = logType smsCountParams.smsType = smsType smsCountParams.msgSrvcSid = MOCK_MSG_SRVC_SID diff --git a/src/app/services/sms/sms.factory.ts b/src/app/services/sms/sms.factory.ts index 737f430ada..0af5748e39 100644 --- a/src/app/services/sms/sms.factory.ts +++ b/src/app/services/sms/sms.factory.ts @@ -73,12 +73,8 @@ export const createSmsFactory = ( } } - const { - twilioAccountSid, - twilioApiKey, - twilioApiSecret, - twilioMsgSrvcSid, - } = smsFeature.props + const { twilioAccountSid, twilioApiKey, twilioApiSecret, twilioMsgSrvcSid } = + smsFeature.props const twilioClient = Twilio(twilioApiKey, twilioApiSecret, { accountSid: twilioAccountSid, diff --git a/src/app/services/sms/sms.service.ts b/src/app/services/sms/sms.service.ts index 95fd568f8b..489ad34d99 100644 --- a/src/app/services/sms/sms.service.ts +++ b/src/app/services/sms/sms.service.ts @@ -102,12 +102,8 @@ const getTwilio = async ( try { const credentials = await getCredentials(msgSrvcName) if (credentials !== null) { - const { - accountSid, - apiKey, - apiSecret, - messagingServiceSid, - } = credentials + const { accountSid, apiKey, apiSecret, messagingServiceSid } = + credentials // Create twilioClient const result: TwilioConfig = { client: Twilio(apiKey, apiSecret, { accountSid }), diff --git a/src/app/services/sms/sms_count.server.model.ts b/src/app/services/sms/sms_count.server.model.ts index 9073ca0df2..66d8176f15 100644 --- a/src/app/services/sms/sms_count.server.model.ts +++ b/src/app/services/sms/sms_count.server.model.ts @@ -74,13 +74,11 @@ const bounceSmsCountSchema = { }, } -const FormDeactivatedSmsCountSchema = new Schema( - bounceSmsCountSchema, -) +const FormDeactivatedSmsCountSchema = + new Schema(bounceSmsCountSchema) -const BouncedSubmissionSmsCountSchema = new Schema( - bounceSmsCountSchema, -) +const BouncedSubmissionSmsCountSchema = + new Schema(bounceSmsCountSchema) const compileSmsCountModel = (db: Mongoose) => { const SmsCountSchema = new Schema( diff --git a/src/app/utils/field-validation/validators/__tests__/dropdown-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/dropdown-validation.spec.ts index 5c0ff431f1..d9049d034d 100644 --- a/src/app/utils/field-validation/validators/__tests__/dropdown-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/dropdown-validation.spec.ts @@ -94,7 +94,7 @@ describe('Dropdown validation', () => { fieldOptions: ['KISS', 'DRY', 'YAGNI'], }) const response = generateNewSingleAnswerResponse(BasicField.Dropdown, { - answer: (['KISS', 'DRY'] as unknown) as string, + answer: ['KISS', 'DRY'] as unknown as string, }) const validateResult = validateField('formId', formField, response) expect(validateResult.isErr()).toBe(true) diff --git a/src/app/utils/field-validation/validators/__tests__/email-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/email-validation.spec.ts index 1a29fb2a9d..ec83b72acf 100644 --- a/src/app/utils/field-validation/validators/__tests__/email-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/email-validation.spec.ts @@ -14,7 +14,7 @@ describe('Email field validation', () => { beforeEach(() => { jest .spyOn( - (formsgSdk.verification as unknown) as VerificationMock, + formsgSdk.verification as unknown as VerificationMock, 'authenticate', ) .mockImplementation(() => true) @@ -175,7 +175,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isOk()).toBe(true) @@ -205,7 +205,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isErr()).toBe(true) @@ -237,7 +237,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isOk()).toBe(true) @@ -267,7 +267,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isOk()).toBe(true) @@ -296,7 +296,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isOk()).toBe(true) @@ -325,7 +325,7 @@ describe('Email field validation', () => { } as ISingleAnswerResponse const validateResult = validateField( 'formId', - (formField as unknown) as IFieldSchema, + formField as unknown as IFieldSchema, response as ProcessedFieldResponse, ) expect(validateResult.isErr()).toBe(true) @@ -366,7 +366,7 @@ describe('Email field validation', () => { it('should reject email addresses if isVerifiable is true but signature is invalid', () => { jest .spyOn( - (formsgSdk.verification as unknown) as VerificationMock, + formsgSdk.verification as unknown as VerificationMock, 'authenticate', ) .mockImplementation(() => false) diff --git a/src/app/utils/field-validation/validators/__tests__/mobile-num-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/mobile-num-validation.spec.ts index ccb9fdd978..caff55f504 100644 --- a/src/app/utils/field-validation/validators/__tests__/mobile-num-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/mobile-num-validation.spec.ts @@ -17,7 +17,7 @@ describe('Mobile number validation tests', () => { beforeEach(() => { jest .spyOn( - (formsgSdk.verification as unknown) as VerificationMock, + formsgSdk.verification as unknown as VerificationMock, 'authenticate', ) .mockImplementation(() => true) @@ -178,7 +178,7 @@ describe('Mobile number validation tests', () => { it('should reject mobile numbers if isVerifiable is true and signature is present but invalid', () => { jest .spyOn( - (formsgSdk.verification as unknown) as VerificationMock, + formsgSdk.verification as unknown as VerificationMock, 'authenticate', ) .mockImplementation(() => false) diff --git a/src/app/utils/field-validation/validators/__tests__/table-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/table-validation.spec.ts index 576a20600b..ec14515f03 100644 --- a/src/app/utils/field-validation/validators/__tests__/table-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/table-validation.spec.ts @@ -268,7 +268,7 @@ describe('Table validation', () => { columns: [generateTableShortTextColumn()], }) const response = generateNewTableResponse({ - answerArray: [[(null as unknown) as string]], + answerArray: [[null as unknown as string]], }) const validateResult = validateField(formId, formField, response) expect(validateResult.isErr()).toBe(true) diff --git a/src/app/utils/field-validation/validators/attachmentValidator.ts b/src/app/utils/field-validation/validators/attachmentValidator.ts index f786e22373..6aed2bd41c 100644 --- a/src/app/utils/field-validation/validators/attachmentValidator.ts +++ b/src/app/utils/field-validation/validators/attachmentValidator.ts @@ -38,15 +38,14 @@ const attachmentContentValidator: AttachmentValidator = (response) => { * Returns a validation function to check if * attachment size is within the specified limit. */ -const makeAttachmentSizeValidator: AttachmentValidatorConstructor = ( - attachmentField, -) => (response) => { - const { attachmentSize } = attachmentField - const byteSizeLimit = parseInt(attachmentSize) * MILLION - return response.content.byteLength < byteSizeLimit - ? right(response) - : left(`AttachmentValidator:\t File size more than limit`) -} +const makeAttachmentSizeValidator: AttachmentValidatorConstructor = + (attachmentField) => (response) => { + const { attachmentSize } = attachmentField + const byteSizeLimit = parseInt(attachmentSize) * MILLION + return response.content.byteLength < byteSizeLimit + ? right(response) + : left(`AttachmentValidator:\t File size more than limit`) + } /** * Returns a validation function for an attachment field when called. diff --git a/src/app/utils/field-validation/validators/checkboxValidator.ts b/src/app/utils/field-validation/validators/checkboxValidator.ts index 689eef03be..d69e0b8e6c 100644 --- a/src/app/utils/field-validation/validators/checkboxValidator.ts +++ b/src/app/utils/field-validation/validators/checkboxValidator.ts @@ -27,41 +27,39 @@ const checkboxAnswerValidator: CheckboxValidator = (response) => { * Returns a validation function to check if number of * selected checkbox options is less than the minimum number specified. */ -const minOptionsValidator: CheckboxValidatorConstructor = (checkboxField) => ( - response, -) => { - const { validateByValue } = checkboxField - const { customMin } = checkboxField.ValidationOptions - const { answerArray } = response - - if (!validateByValue || !customMin) return right(response) - - return answerArray.length >= customMin - ? right(response) - : left( - `CheckboxValidator:\t answer has less options selected than minimum specified`, - ) -} +const minOptionsValidator: CheckboxValidatorConstructor = + (checkboxField) => (response) => { + const { validateByValue } = checkboxField + const { customMin } = checkboxField.ValidationOptions + const { answerArray } = response + + if (!validateByValue || !customMin) return right(response) + + return answerArray.length >= customMin + ? right(response) + : left( + `CheckboxValidator:\t answer has less options selected than minimum specified`, + ) + } /** * Returns a validation function to check if number of * selected checkbox options is more than the maximum number specified. */ -const maxOptionsValidator: CheckboxValidatorConstructor = (checkboxField) => ( - response, -) => { - const { validateByValue } = checkboxField - const { customMax } = checkboxField.ValidationOptions - const { answerArray } = response - - if (!validateByValue || !customMax) return right(response) - - return answerArray.length <= customMax - ? right(response) - : left( - `CheckboxValidator:\t answer has more options selected than maximum specified`, - ) -} +const maxOptionsValidator: CheckboxValidatorConstructor = + (checkboxField) => (response) => { + const { validateByValue } = checkboxField + const { customMax } = checkboxField.ValidationOptions + const { answerArray } = response + + if (!validateByValue || !customMax) return right(response) + + return answerArray.length <= customMax + ? right(response) + : left( + `CheckboxValidator:\t answer has more options selected than maximum specified`, + ) + } // The overall logic for the following three validators is as follows: // We split the answers into: @@ -79,19 +77,19 @@ const maxOptionsValidator: CheckboxValidatorConstructor = (checkboxField) => ( * For those which do not start with "Others: ", they must be one of the fieldOptions since they cannot possibly be an "Others" option. * For those which start with "Others: ", they must also be one of the fieldOptions unless othersRadioButton is enabled. */ -const validOptionsValidator: CheckboxValidatorConstructor = (checkboxField) => ( - response, -) => { - const { fieldOptions, othersRadioButton } = checkboxField - const { answerArray } = response - - return answerArray.every( - (answer) => - fieldOptions.includes(answer) || isOtherOption(othersRadioButton, answer), - ) - ? right(response) - : left(`CheckboxValidator:\t answer is not valid`) -} +const validOptionsValidator: CheckboxValidatorConstructor = + (checkboxField) => (response) => { + const { fieldOptions, othersRadioButton } = checkboxField + const { answerArray } = response + + return answerArray.every( + (answer) => + fieldOptions.includes(answer) || + isOtherOption(othersRadioButton, answer), + ) + ? right(response) + : left(`CheckboxValidator:\t answer is not valid`) + } /** * Returns a validation function to check if there are any @@ -101,20 +99,19 @@ const validOptionsValidator: CheckboxValidatorConstructor = (checkboxField) => ( * We had already checked if all of them are one of the fieldOptions. Since fieldOptions are distinct, * there should be no duplicates amongst the non-others answers. */ -const duplicateNonOtherOptionsValidator: CheckboxValidatorConstructor = ( - checkboxField, -) => (response) => { - const { othersRadioButton } = checkboxField - const { answerArray } = response - - const nonOtherAnswers = answerArray.filter( - (answer) => !isOtherOption(othersRadioButton, answer), - ) - - return nonOtherAnswers.length === new Set(nonOtherAnswers).size - ? right(response) - : left(`CheckboxValidator:\t duplicate non-other answers in response`) -} +const duplicateNonOtherOptionsValidator: CheckboxValidatorConstructor = + (checkboxField) => (response) => { + const { othersRadioButton } = checkboxField + const { answerArray } = response + + const nonOtherAnswers = answerArray.filter( + (answer) => !isOtherOption(othersRadioButton, answer), + ) + + return nonOtherAnswers.length === new Set(nonOtherAnswers).size + ? right(response) + : left(`CheckboxValidator:\t duplicate non-other answers in response`) + } /** * Returns a validation function to check if there are any @@ -124,48 +121,48 @@ const duplicateNonOtherOptionsValidator: CheckboxValidatorConstructor = ( * Note that it is possible for Admins to create fieldOptions that * look like ['Option 1', 'Others: please elaborate']. */ -const duplicateOtherOptionsValidator: CheckboxValidatorConstructor = ( - checkboxField, -) => (response) => { - const { fieldOptions, othersRadioButton } = checkboxField - const { answerArray } = response - - const otherAnswers = answerArray.filter((answer) => - isOtherOption(othersRadioButton, answer), - ) - - // First check the answers which do not appear in fieldOptions. - // There should be at most one. - - const otherAnswersNotInFieldOptions = otherAnswers.filter( - (answer) => !fieldOptions.includes(answer), - ) - - if (otherAnswersNotInFieldOptions.length > 1) { - return left(`CheckboxValidator:\t duplicate other answers in response`) +const duplicateOtherOptionsValidator: CheckboxValidatorConstructor = + (checkboxField) => (response) => { + const { fieldOptions, othersRadioButton } = checkboxField + const { answerArray } = response + + const otherAnswers = answerArray.filter((answer) => + isOtherOption(othersRadioButton, answer), + ) + + // First check the answers which do not appear in fieldOptions. + // There should be at most one. + + const otherAnswersNotInFieldOptions = otherAnswers.filter( + (answer) => !fieldOptions.includes(answer), + ) + + if (otherAnswersNotInFieldOptions.length > 1) { + return left(`CheckboxValidator:\t duplicate other answers in response`) + } + + // Next check that for the remaining answers which do appear in fieldOptions, + // Either there should no duplicates, OR + // There should be at most 1 duplicate and otherAnswersNotInFieldOptions.length === 0 + // i.e. the 'Others' field is used for the duplicate response. + + const otherAnswersInFieldOptions = otherAnswers.filter((answer) => + fieldOptions.includes(answer), + ) + + const numDuplicates = + otherAnswersInFieldOptions.length - + new Set(otherAnswersInFieldOptions).size + + if (numDuplicates > 1) { + return left(`CheckboxValidator:\t duplicate other answers in response`) + } else if (numDuplicates === 1 && otherAnswersInFieldOptions.length !== 0) { + return left(`CheckboxValidator:\t duplicate other answers in response`) + } else { + return right(response) + } } - // Next check that for the remaining answers which do appear in fieldOptions, - // Either there should no duplicates, OR - // There should be at most 1 duplicate and otherAnswersNotInFieldOptions.length === 0 - // i.e. the 'Others' field is used for the duplicate response. - - const otherAnswersInFieldOptions = otherAnswers.filter((answer) => - fieldOptions.includes(answer), - ) - - const numDuplicates = - otherAnswersInFieldOptions.length - new Set(otherAnswersInFieldOptions).size - - if (numDuplicates > 1) { - return left(`CheckboxValidator:\t duplicate other answers in response`) - } else if (numDuplicates === 1 && otherAnswersInFieldOptions.length !== 0) { - return left(`CheckboxValidator:\t duplicate other answers in response`) - } else { - return right(response) - } -} - /** * Returns a validation function for a checkbox field when called. */ diff --git a/src/app/utils/field-validation/validators/common.ts b/src/app/utils/field-validation/validators/common.ts index 9b130b512f..5c50a9ebba 100644 --- a/src/app/utils/field-validation/validators/common.ts +++ b/src/app/utils/field-validation/validators/common.ts @@ -9,15 +9,14 @@ import formsgSdk from '../../../config/formsg-sdk' /** * A function which returns a validator to check if single answer has a non-empty response */ -export const notEmptySingleAnswerResponse: ResponseValidator = ( - response, -) => { - if (response.answer.trim().length === 0) - return left( - 'CommonValidator.notEmptySingleAnswerResponse:\tanswer is an empty string', - ) - return right(response) -} +export const notEmptySingleAnswerResponse: ResponseValidator = + (response) => { + if (response.answer.trim().length === 0) + return left( + 'CommonValidator.notEmptySingleAnswerResponse:\tanswer is an empty string', + ) + return right(response) + } /** * A function which returns a signature validator constructor for mobile and email verified field. @@ -25,31 +24,30 @@ export const notEmptySingleAnswerResponse: ResponseValidator ResponseValidator = (formField) => ( - response, -) => { - const { isVerifiable, _id } = formField - if (!isVerifiable) { - return right(response) // no validation occurred - } - const { signature, answer } = response - if (!signature) { - return left( - `CommonValidator.makeSignatureValidator:\t answer does not have valid signature`, - ) - } - const isSigned = - formsgSdk.verification.authenticate && - formsgSdk.verification.authenticate({ - signatureString: signature, - submissionCreatedAt: Date.now(), - fieldId: _id, - answer, - }) - - return isSigned - ? right(response) - : left( +) => ResponseValidator = + (formField) => (response) => { + const { isVerifiable, _id } = formField + if (!isVerifiable) { + return right(response) // no validation occurred + } + const { signature, answer } = response + if (!signature) { + return left( `CommonValidator.makeSignatureValidator:\t answer does not have valid signature`, ) -} + } + const isSigned = + formsgSdk.verification.authenticate && + formsgSdk.verification.authenticate({ + signatureString: signature, + submissionCreatedAt: Date.now(), + fieldId: _id, + answer, + }) + + return isSigned + ? right(response) + : left( + `CommonValidator.makeSignatureValidator:\t answer does not have valid signature`, + ) + } diff --git a/src/app/utils/field-validation/validators/dateValidator.ts b/src/app/utils/field-validation/validators/dateValidator.ts index 073bc48629..32e4288654 100644 --- a/src/app/utils/field-validation/validators/dateValidator.ts +++ b/src/app/utils/field-validation/validators/dateValidator.ts @@ -71,19 +71,18 @@ const futureOnlyValidator: DateValidator = (response) => { * Returns a validator to check if date is within the * specified custom date range. */ -const makeCustomDateValidator: DateValidatorConstructor = (dateField) => ( - response, -) => { - const { answer } = response - const answerDate = createMomentFromDateString(answer) +const makeCustomDateValidator: DateValidatorConstructor = + (dateField) => (response) => { + const { answer } = response + const answerDate = createMomentFromDateString(answer) - const { customMinDate, customMaxDate } = dateField.dateValidation || {} + const { customMinDate, customMaxDate } = dateField.dateValidation || {} - return (customMinDate && answerDate.isBefore(customMinDate)) || - (customMaxDate && answerDate.isAfter(customMaxDate)) - ? left(`DateValidator:\t answer does not pass date logic validation`) - : right(response) -} + return (customMinDate && answerDate.isBefore(customMinDate)) || + (customMaxDate && answerDate.isAfter(customMaxDate)) + ? left(`DateValidator:\t answer does not pass date logic validation`) + : right(response) + } /** * Returns the appropriate validator diff --git a/src/app/utils/field-validation/validators/decimalValidator.ts b/src/app/utils/field-validation/validators/decimalValidator.ts index 1ebb427c33..7de5cee0a0 100644 --- a/src/app/utils/field-validation/validators/decimalValidator.ts +++ b/src/app/utils/field-validation/validators/decimalValidator.ts @@ -22,31 +22,30 @@ interface IIsFloatOptions { * Returns a validation function * to check if decimal is within the specified custom range. */ -const makeDecimalFloatRangeValidator: DecimalValidatorConstructor = ( - decimalField, -) => (response) => { - const { customMin, customMax } = decimalField.ValidationOptions // defaults to customMin: null, customMax: null - const { answer } = response +const makeDecimalFloatRangeValidator: DecimalValidatorConstructor = + (decimalField) => (response) => { + const { customMin, customMax } = decimalField.ValidationOptions // defaults to customMin: null, customMax: null + const { answer } = response - const isFloatOptions: IIsFloatOptions = {} - // Necessary to add 'min' and 'max' property manually as - // isFloatOptions tests for presence of property - // See https://github.com/validatorjs/validator.js/blob/302d2957c924b515cb22f7e87b5e84fee8636d6e/src/lib/isFloat.js#L13 + const isFloatOptions: IIsFloatOptions = {} + // Necessary to add 'min' and 'max' property manually as + // isFloatOptions tests for presence of property + // See https://github.com/validatorjs/validator.js/blob/302d2957c924b515cb22f7e87b5e84fee8636d6e/src/lib/isFloat.js#L13 - if (customMin || customMin === 0) { - isFloatOptions['min'] = customMin - } - if (customMax || customMax === 0) { - isFloatOptions['max'] = customMax - } + if (customMin || customMin === 0) { + isFloatOptions['min'] = customMin + } + if (customMax || customMax === 0) { + isFloatOptions['max'] = customMax + } - // isFloat validates range correctly for floats up to 15 decimal places - // (1.999999999999999 >= 2) is False - // (1.9999999999999999 >= 2) is True - return isFloat(answer, isFloatOptions) - ? right(response) - : left(`DecimalValidator:\t answer is not a valid float`) -} + // isFloat validates range correctly for floats up to 15 decimal places + // (1.999999999999999 >= 2) is False + // (1.9999999999999999 >= 2) is True + return isFloat(answer, isFloatOptions) + ? right(response) + : left(`DecimalValidator:\t answer is not a valid float`) + } /** * Returns a validator to check if diff --git a/src/app/utils/field-validation/validators/dropdownValidator.ts b/src/app/utils/field-validation/validators/dropdownValidator.ts index 1e434f47af..065c9063f4 100644 --- a/src/app/utils/field-validation/validators/dropdownValidator.ts +++ b/src/app/utils/field-validation/validators/dropdownValidator.ts @@ -19,20 +19,19 @@ type DropdownValidatorConstructor = ( * Returns a validation function * to check if dropdown selection is one of the options. */ -const makeDropdownValidator: DropdownValidatorConstructor = (dropdownField) => ( - response, -) => { - const { myInfo, fieldOptions } = dropdownField - // Inject fieldOptions for MyInfo. This is necessary because the - // client strips out MyInfo data to keep each form submission lightweight - const validOptions = myInfo?.attr - ? getMyInfoFieldOptions(myInfo.attr) - : fieldOptions - const { answer } = response - return isOneOfOptions(validOptions, answer) - ? right(response) - : left(`DropdownValidator:\t answer is not a valid dropdown option`) -} +const makeDropdownValidator: DropdownValidatorConstructor = + (dropdownField) => (response) => { + const { myInfo, fieldOptions } = dropdownField + // Inject fieldOptions for MyInfo. This is necessary because the + // client strips out MyInfo data to keep each form submission lightweight + const validOptions = myInfo?.attr + ? getMyInfoFieldOptions(myInfo.attr) + : fieldOptions + const { answer } = response + return isOneOfOptions(validOptions, answer) + ? right(response) + : left(`DropdownValidator:\t answer is not a valid dropdown option`) + } /** * Returns a validation function for a dropdown field when called. diff --git a/src/app/utils/field-validation/validators/emailValidator.ts b/src/app/utils/field-validation/validators/emailValidator.ts index 1a5f72dd8c..e7394552bc 100644 --- a/src/app/utils/field-validation/validators/emailValidator.ts +++ b/src/app/utils/field-validation/validators/emailValidator.ts @@ -27,24 +27,20 @@ const emailFormatValidator: EmailValidator = (response) => { * Returns a validation function * to check if email domain is valid. */ -const makeEmailDomainValidator: EmailValidatorConstructor = (emailField) => ( - response, -) => { - const { - isVerifiable, - hasAllowedEmailDomains, - allowedEmailDomains, - } = emailField - const { answer } = response - const emailAddress = String(answer) - if (!(isVerifiable && hasAllowedEmailDomains && allowedEmailDomains.length)) - return right(response) - const emailDomain = '@' + emailAddress.split('@').pop() +const makeEmailDomainValidator: EmailValidatorConstructor = + (emailField) => (response) => { + const { isVerifiable, hasAllowedEmailDomains, allowedEmailDomains } = + emailField + const { answer } = response + const emailAddress = String(answer) + if (!(isVerifiable && hasAllowedEmailDomains && allowedEmailDomains.length)) + return right(response) + const emailDomain = '@' + emailAddress.split('@').pop() - return allowedEmailDomains.includes(emailDomain) - ? right(response) - : left(`EmailValidator:\t answer is not a valid email domain`) -} + return allowedEmailDomains.includes(emailDomain) + ? right(response) + : left(`EmailValidator:\t answer is not a valid email domain`) + } /** * Returns a validation function for a email field when called. diff --git a/src/app/utils/field-validation/validators/numberValidator.ts b/src/app/utils/field-validation/validators/numberValidator.ts index a504219aa8..23307d9fa1 100644 --- a/src/app/utils/field-validation/validators/numberValidator.ts +++ b/src/app/utils/field-validation/validators/numberValidator.ts @@ -26,43 +26,40 @@ const numberFormatValidator: NumberValidator = (response) => { * Returns a validation function to check if number length is * less than the minimum length specified. */ -const minLengthValidator: NumberValidatorConstructor = (numberField) => ( - response, -) => { - const { answer } = response - const { customMin } = numberField.ValidationOptions - return !customMin || answer.length >= customMin - ? right(response) - : left(`NumberValidator:\t answer is shorter than custom minimum length`) -} +const minLengthValidator: NumberValidatorConstructor = + (numberField) => (response) => { + const { answer } = response + const { customMin } = numberField.ValidationOptions + return !customMin || answer.length >= customMin + ? right(response) + : left(`NumberValidator:\t answer is shorter than custom minimum length`) + } /** * Returns a validation function to check if number length is * more than the maximum length specified. */ -const maxLengthValidator: NumberValidatorConstructor = (numberField) => ( - response, -) => { - const { answer } = response - const { customMax } = numberField.ValidationOptions - return !customMax || answer.length <= customMax - ? right(response) - : left(`NumberValidator:\t answer is longer than custom maximum length`) -} +const maxLengthValidator: NumberValidatorConstructor = + (numberField) => (response) => { + const { answer } = response + const { customMax } = numberField.ValidationOptions + return !customMax || answer.length <= customMax + ? right(response) + : left(`NumberValidator:\t answer is longer than custom maximum length`) + } /** * Returns a validation function to check if number length is * equal to the exact length specified. */ -const exactLengthValidator: NumberValidatorConstructor = (numberField) => ( - response, -) => { - const { answer } = response - const { customVal } = numberField.ValidationOptions - return !customVal || answer.length === customVal - ? right(response) - : left(`NumberValidator:\t answer does not match custom exact length`) -} +const exactLengthValidator: NumberValidatorConstructor = + (numberField) => (response) => { + const { answer } = response + const { customVal } = numberField.ValidationOptions + return !customVal || answer.length === customVal + ? right(response) + : left(`NumberValidator:\t answer does not match custom exact length`) + } /** * Returns the appropriate validation function diff --git a/src/app/utils/field-validation/validators/radioButtonValidator.ts b/src/app/utils/field-validation/validators/radioButtonValidator.ts index 2e71cf8cda..87020a4211 100644 --- a/src/app/utils/field-validation/validators/radioButtonValidator.ts +++ b/src/app/utils/field-validation/validators/radioButtonValidator.ts @@ -17,19 +17,18 @@ type RadioButtonValidatorConstructor = ( * Returns a validation function to check if the * selected radio option is one of the specified options. */ -const makeRadioOptionsValidator: RadioButtonValidatorConstructor = ( - radioButtonField, -) => (response) => { - const { answer } = response - const { fieldOptions, othersRadioButton } = radioButtonField - const isValid = - isOneOfOptions(fieldOptions, answer) || - isOtherOption(othersRadioButton, answer) +const makeRadioOptionsValidator: RadioButtonValidatorConstructor = + (radioButtonField) => (response) => { + const { answer } = response + const { fieldOptions, othersRadioButton } = radioButtonField + const isValid = + isOneOfOptions(fieldOptions, answer) || + isOtherOption(othersRadioButton, answer) - return isValid - ? right(response) - : left(`RadioButtonValidator:\tanswer is not a valid radio button option`) -} + return isValid + ? right(response) + : left(`RadioButtonValidator:\tanswer is not a valid radio button option`) + } /** * Returns a validation function for a radio button field when called. diff --git a/src/app/utils/field-validation/validators/ratingValidator.ts b/src/app/utils/field-validation/validators/ratingValidator.ts index 6c06b5d61e..14c95e179c 100644 --- a/src/app/utils/field-validation/validators/ratingValidator.ts +++ b/src/app/utils/field-validation/validators/ratingValidator.ts @@ -15,22 +15,21 @@ type RatingValidatorConstructor = (ratingField: IRatingField) => RatingValidator * Returns a validation function to check if the * selected rating option is a valid option. */ -const makeRatingLimitsValidator: RatingValidatorConstructor = (ratingField) => ( - response, -) => { - const { answer } = response - const { steps } = ratingField.ratingOptions +const makeRatingLimitsValidator: RatingValidatorConstructor = + (ratingField) => (response) => { + const { answer } = response + const { steps } = ratingField.ratingOptions - const isValid = isInt(answer, { - min: 1, - max: steps, - allow_leading_zeroes: false, - }) + const isValid = isInt(answer, { + min: 1, + max: steps, + allow_leading_zeroes: false, + }) - return isValid - ? right(response) - : left(`RatingValidator:\t answer is not a valid rating`) -} + return isValid + ? right(response) + : left(`RatingValidator:\t answer is not a valid rating`) + } /** * Returns a validation function for a rating field when called. diff --git a/src/app/utils/field-validation/validators/sectionValidator.ts b/src/app/utils/field-validation/validators/sectionValidator.ts index f28bce5bca..c60d83bfcd 100644 --- a/src/app/utils/field-validation/validators/sectionValidator.ts +++ b/src/app/utils/field-validation/validators/sectionValidator.ts @@ -3,15 +3,15 @@ import { left, right } from 'fp-ts/lib/Either' import { ProcessedSingleAnswerResponse } from 'src/app/modules/submission/submission.types' import { ResponseValidator } from 'src/types/field/utils/validation' -type SectionValidatorConstructor = () => ResponseValidator +type SectionValidatorConstructor = + () => ResponseValidator /** * Returns a validation function for a section field when called. */ -export const constructSectionValidator: SectionValidatorConstructor = () => ( - response, -) => { - return response.answer === '' - ? right(response) - : left(`SectionValidator.emptyAnswer:\tanswer is not an empty string`) -} +export const constructSectionValidator: SectionValidatorConstructor = + () => (response) => { + return response.answer === '' + ? right(response) + : left(`SectionValidator.emptyAnswer:\tanswer is not an empty string`) + } diff --git a/src/app/utils/field-validation/validators/tableValidator.ts b/src/app/utils/field-validation/validators/tableValidator.ts index c0659d164f..d0bdf04fe4 100644 --- a/src/app/utils/field-validation/validators/tableValidator.ts +++ b/src/app/utils/field-validation/validators/tableValidator.ts @@ -21,117 +21,113 @@ type TableValidatorConstructor = ( * Returns a validation function to check if the * response has less than the minimum number of rows specified. */ -const makeMinimumRowsValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { answerArray } = response - const { minimumRows } = tableField +const makeMinimumRowsValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { answerArray } = response + const { minimumRows } = tableField - return answerArray.length >= minimumRows - ? right(response) - : left(`TableValidator:\tanswer has less than the minimum number of rows`) -} + return answerArray.length >= minimumRows + ? right(response) + : left(`TableValidator:\tanswer has less than the minimum number of rows`) + } /** * Returns a validation function to check if addMoreRows is not set * and if so, whether the response has more than minimum number of rows. */ -const makeAddMoreRowsValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { answerArray } = response - const { minimumRows, addMoreRows } = tableField - - if (addMoreRows) return right(response) - return answerArray.length === minimumRows - ? right(response) - : left( - `TableValidator:\tanswer has extra rows even though addMoreRows is false`, - ) -} +const makeAddMoreRowsValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { answerArray } = response + const { minimumRows, addMoreRows } = tableField + + if (addMoreRows) return right(response) + return answerArray.length === minimumRows + ? right(response) + : left( + `TableValidator:\tanswer has extra rows even though addMoreRows is false`, + ) + } /** * Returns a validation function to check if the * response has more than the maximum number of rows specified. */ -const makeMaximumRowsValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { answerArray } = response - const { maximumRows } = tableField +const makeMaximumRowsValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { answerArray } = response + const { maximumRows } = tableField - if (!maximumRows) return right(response) + if (!maximumRows) return right(response) - return answerArray.length <= maximumRows - ? right(response) - : left(`TableValidator:\tanswer has more than the maximum number of rows`) -} + return answerArray.length <= maximumRows + ? right(response) + : left(`TableValidator:\tanswer has more than the maximum number of rows`) + } /** * Returns a validation function to check if the * response has the correct number of answers for each row. */ -const makeRowLengthValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { answerArray } = response - const { columns } = tableField - - return answerArray.every((row) => row.length === columns.length) - ? right(response) - : left(`TableValidator:\tanswer has rows with incorrect number of answers`) -} +const makeRowLengthValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { answerArray } = response + const { columns } = tableField + + return answerArray.every((row) => row.length === columns.length) + ? right(response) + : left( + `TableValidator:\tanswer has rows with incorrect number of answers`, + ) + } /** * Returns a validation function that checks that the columns only have allowed types */ -const makeColumnTypeValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { columns } = tableField - return columns.every((column) => - ALLOWED_COLUMN_TYPES.includes(column.columnType), - ) - ? right(response) - : left(`TableValidator:\tanswer has columns with non-allowed types`) -} +const makeColumnTypeValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { columns } = tableField + return columns.every((column) => + ALLOWED_COLUMN_TYPES.includes(column.columnType), + ) + ? right(response) + : left(`TableValidator:\tanswer has columns with non-allowed types`) + } /** * Returns a validation function that applies * the correct validator for each table cell. */ -const makeTableCellValidator: TableValidatorConstructor = (tableField) => ( - response, -) => { - const { answerArray, isVisible, _id } = response - const { columns } = tableField - const answerFieldColumns = columns.map((column) => - createAnswerFieldFromColumn(column), - ) - - return answerArray.every((row) => { - return row.every((answer, i) => { - const answerField = answerFieldColumns[i] - const answerResponse: ProcessedSingleAnswerResponse = { - answer, - isVisible, - fieldType: answerField.fieldType, - _id, - question: answerField.title, - } - - return validateField( - answerField._id || 'Table field validation', - answerField, - answerResponse, - ).isOk() +const makeTableCellValidator: TableValidatorConstructor = + (tableField) => (response) => { + const { answerArray, isVisible, _id } = response + const { columns } = tableField + const answerFieldColumns = columns.map((column) => + createAnswerFieldFromColumn(column), + ) + + return answerArray.every((row) => { + return row.every((answer, i) => { + const answerField = answerFieldColumns[i] + const answerResponse: ProcessedSingleAnswerResponse = { + answer, + isVisible, + fieldType: answerField.fieldType, + _id, + question: answerField.title, + } + + return validateField( + answerField._id || 'Table field validation', + answerField, + answerResponse, + ).isOk() + }) }) - }) - ? right(response) - : left(`TableValidator:\tanswer failed field validation`) -} + ? right(response) + : left(`TableValidator:\tanswer failed field validation`) + } /** * Returns a validation function for a table field when called. diff --git a/src/app/utils/field-validation/validators/textValidator.ts b/src/app/utils/field-validation/validators/textValidator.ts index 65be85fadd..b58aea807c 100644 --- a/src/app/utils/field-validation/validators/textValidator.ts +++ b/src/app/utils/field-validation/validators/textValidator.ts @@ -17,53 +17,52 @@ type TextFieldValidatorConstructor = ( * Returns a validator to check if * text length is less than the min length specified. */ -const minLengthValidator: TextFieldValidatorConstructor = (textField) => ( - response, -) => { - const { customMin } = textField.ValidationOptions - const min = customMin !== null ? Number(customMin) : null - if (min === null) return right(response) - return response.answer.length >= min - ? right(response) - : left(`TextValidator.minLength:\tanswer is less than minimum of ${min}`) -} +const minLengthValidator: TextFieldValidatorConstructor = + (textField) => (response) => { + const { customMin } = textField.ValidationOptions + const min = customMin !== null ? Number(customMin) : null + if (min === null) return right(response) + return response.answer.length >= min + ? right(response) + : left(`TextValidator.minLength:\tanswer is less than minimum of ${min}`) + } /** * Returns a validator to check if * text length is more than the max length specified. */ -const maxLengthValidator: TextFieldValidatorConstructor = (textField) => ( - response, -) => { - const { customMax } = textField.ValidationOptions - const max = customMax !== null ? Number(customMax) : null - if (max === null) return right(response) - return response.answer.length <= max - ? right(response) - : left(`TextValidator.maxLength:\tanswer is greater than maximum of ${max}`) -} +const maxLengthValidator: TextFieldValidatorConstructor = + (textField) => (response) => { + const { customMax } = textField.ValidationOptions + const max = customMax !== null ? Number(customMax) : null + if (max === null) return right(response) + return response.answer.length <= max + ? right(response) + : left( + `TextValidator.maxLength:\tanswer is greater than maximum of ${max}`, + ) + } /** * Returns a validator to check if * text length is the exact length specified. */ -const exactLengthValidator: TextFieldValidatorConstructor = (textField) => ( - response, -) => { - const { customMin, customMax } = textField.ValidationOptions - const exact = - customMin !== null - ? Number(customMin) - : customMax !== null - ? Number(customMax) - : null - if (exact === null) return right(response) - return response.answer.length === exact - ? right(response) - : left( - `TextValidator.exactLength:\tanswer is not exactly equal to ${exact}`, - ) -} +const exactLengthValidator: TextFieldValidatorConstructor = + (textField) => (response) => { + const { customMin, customMax } = textField.ValidationOptions + const exact = + customMin !== null + ? Number(customMin) + : customMax !== null + ? Number(customMax) + : null + if (exact === null) return right(response) + return response.answer.length === exact + ? right(response) + : left( + `TextValidator.exactLength:\tanswer is not exactly equal to ${exact}`, + ) + } /** * Returns the appropriate validator diff --git a/src/public/modules/core/resources/landing-examples.js b/src/public/modules/core/resources/landing-examples.js index f1413df722..931df4e758 100644 --- a/src/public/modules/core/resources/landing-examples.js +++ b/src/public/modules/core/resources/landing-examples.js @@ -41,44 +41,37 @@ const testims = [ { name: 'Lua Lee Hui', agency: 'Sport Singapore (SportSG)', - text: - 'Form.sg has helped us to overcome data collection limitations and with the user friendly interface on mobile devices, we have progressively moved away from paper forms and that has significantly reduced administrative efforts spent on transcribing hardcopy forms. We also love the logic feature which has gotten us creative in designing forms with customised view for our Team Nila volunteers. KUDOS to Form.sg team for the continuous effort to improve the product for us!', + text: 'Form.sg has helped us to overcome data collection limitations and with the user friendly interface on mobile devices, we have progressively moved away from paper forms and that has significantly reduced administrative efforts spent on transcribing hardcopy forms. We also love the logic feature which has gotten us creative in designing forms with customised view for our Team Nila volunteers. KUDOS to Form.sg team for the continuous effort to improve the product for us!', }, { name: 'Ang Mui Kim', agency: 'Ministry of Manpower (MOM)', - text: - 'Thank you for a good product that is easy to use and deploy. FYI - we had used it for a workshop last week to collate questions from participants and presented some scattered plots on the spot. It is easy to create and we had a few iterations including some last minute changes at the workshop itself. Cheers!', + text: 'Thank you for a good product that is easy to use and deploy. FYI - we had used it for a workshop last week to collate questions from participants and presented some scattered plots on the spot. It is easy to create and we had a few iterations including some last minute changes at the workshop itself. Cheers!', }, { name: 'Pang Sze Kiang', agency: 'Ministry of Education (MOE)', - text: - 'It is really easy to create forms using Form.sg. There is a good variety of form elements, ranging from ‘Short Text’ to ‘Dropdown’ fields. My colleagues and I are able to build customized forms that are able to meet the various needs of my branch. One big plus point is that the form responses are sent in the form of emails to only intended recipients – thus making the data collected very secure. Thank you, Form.sg!', + text: 'It is really easy to create forms using Form.sg. There is a good variety of form elements, ranging from ‘Short Text’ to ‘Dropdown’ fields. My colleagues and I are able to build customized forms that are able to meet the various needs of my branch. One big plus point is that the form responses are sent in the form of emails to only intended recipients – thus making the data collected very secure. Thank you, Form.sg!', }, { name: 'Elton Kang', agency: "People's Association (PA)", - text: - 'With Form.sg, users can easily access the form with the comfort and convenience of their mobile phones. Although the product is not all-encompassing compared to other digital form platforms out in the market, Form.sg edges out its competitors in one main and critical aspect: security. Having to interface with multiple internal and external stakeholders, data security is of paramount importance and thus far, Form.sg is most effective when we used it for workshops, surveys, registration for programmes, and collation of personal particulars.', + text: 'With Form.sg, users can easily access the form with the comfort and convenience of their mobile phones. Although the product is not all-encompassing compared to other digital form platforms out in the market, Form.sg edges out its competitors in one main and critical aspect: security. Having to interface with multiple internal and external stakeholders, data security is of paramount importance and thus far, Form.sg is most effective when we used it for workshops, surveys, registration for programmes, and collation of personal particulars.', }, { name: 'Regina Wang', agency: 'National Environment Authority (NEA)', - text: - 'We regularly hold industry briefing and will require collating industrial partners’ and external parties’ attendance. This takes a lot of effort using emails and excel. Using formsg, we cut down manpower to collate these registration details. The user experience feedback was positive and pleasant as well.', + text: 'We regularly hold industry briefing and will require collating industrial partners’ and external parties’ attendance. This takes a lot of effort using emails and excel. Using formsg, we cut down manpower to collate these registration details. The user experience feedback was positive and pleasant as well.', }, { name: 'Ji Min Sheng', agency: 'Municipal Services Office (MSO)', - text: - 'Thank you GovTech for coming up with FormSG! FormSG provides a quick and structured way to consolidate information across agencies. It has improved the productivity of our officers by allowing them to highlight cases to other agencies on-the-go, and eliminated the need for manual collation of data. The clear form structure has also minimised clarification emails.', + text: 'Thank you GovTech for coming up with FormSG! FormSG provides a quick and structured way to consolidate information across agencies. It has improved the productivity of our officers by allowing them to highlight cases to other agencies on-the-go, and eliminated the need for manual collation of data. The clear form structure has also minimised clarification emails.', }, { name: 'Jeremy Ang', agency: 'Nanyang Junior College (NYJC)', - text: - 'Our sincere thanks to GovTech for creating FormSG along with the macro script, data collation tool and SingPass verification. This is very useful on so many levels. We use it to collect data from the public, from students and from colleagues for a variety of applications, while still meeting IM8 requirements on personal data protection. The scripts and templates are easy to use and customisable and the option to integrate this with a Shared CES Inbox streamlines processes for our School Admin Team and teachers. With one click, we can aggregate the data into the format in that we need. It’s all done in less than a minute. We’ve also shared this with several other schools and they are very keen to use it too. Great job, GovTech!', + text: 'Our sincere thanks to GovTech for creating FormSG along with the macro script, data collation tool and SingPass verification. This is very useful on so many levels. We use it to collect data from the public, from students and from colleagues for a variety of applications, while still meeting IM8 requirements on personal data protection. The scripts and templates are easy to use and customisable and the option to integrate this with a Shared CES Inbox streamlines processes for our School Admin Team and teachers. With one click, we can aggregate the data into the format in that we need. It’s all done in less than a minute. We’ve also shared this with several other schools and they are very keen to use it too. Great job, GovTech!', }, ] diff --git a/src/public/modules/core/views/edit-contact-number-modal.view.html b/src/public/modules/core/views/edit-contact-number-modal.view.html index af7dd4795d..3219b041e9 100644 --- a/src/public/modules/core/views/edit-contact-number-modal.view.html +++ b/src/public/modules/core/views/edit-contact-number-modal.view.html @@ -65,7 +65,12 @@ diff --git a/src/public/modules/forms/admin/views/collaborator.client.modal.html b/src/public/modules/forms/admin/views/collaborator.client.modal.html index a5c366a479..eb7a5cb873 100644 --- a/src/public/modules/forms/admin/views/collaborator.client.modal.html +++ b/src/public/modules/forms/admin/views/collaborator.client.modal.html @@ -202,7 +202,10 @@ {{ ROLES.ADMIN }}
@@ -284,7 +287,10 @@