diff --git a/.travis.yml b/.travis.yml
index 1188c390ba..36623c1df1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ script:
- npm run lint-ci
- npm run build
- npm run test-ci
- - npm run test-e2e-ci
+ # - npm run test-e2e-ci
before_deploy:
# Workaround to run before_deploy only once
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000000..7af702c5b0
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,211 @@
+### Changelog
+
+All notable changes to this project will be documented in this file. Dates are displayed in UTC.
+
+Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
+
+#### [v4.35.1](https://github.com/opengovsg/formsg/compare/v4.34.0...v4.35.1)
+
+> 17 September 2020
+
+- feat: update copy for email fields, intranet, privacy [`#321`](https://github.com/opengovsg/formsg/pull/321)
+- refactor: turn on strict mode in Typescript configuration [`#262`](https://github.com/opengovsg/formsg/pull/262)
+- fix: allow inline styles from angular-sanitize [`#316`](https://github.com/opengovsg/formsg/pull/316)
+- feat(changelog): autogenerate CHANGELOG.md from conventional commits [`#306`](https://github.com/opengovsg/formsg/pull/306)
+- chore: reduce number of e2e tests and other fixes [`#305`](https://github.com/opengovsg/formsg/pull/305)
+- feat: merge release 4.34.1 into develop [`#312`](https://github.com/opengovsg/formsg/pull/312)
+- feat: log all critical bounces [`#288`](https://github.com/opengovsg/formsg/pull/288)
+- refactor(proxy): do not override X-Forwarded-Proto headers [`#304`](https://github.com/opengovsg/formsg/pull/304)
+- fix(deps): bump angular-translate-loader-partial from 2.18.2 to 2.18.3 [`#298`](https://github.com/opengovsg/formsg/pull/298)
+- chore(deps-dev): bump eslint-plugin-jest from 23.20.0 to 24.0.0 [`#299`](https://github.com/opengovsg/formsg/pull/299)
+- fix(deps): bump ejs from 2.7.4 to 3.1.5 [`#282`](https://github.com/opengovsg/formsg/pull/282)
+- chore: document env vars needed for EFS [`#303`](https://github.com/opengovsg/formsg/pull/303)
+- Greater clarity for available features, project roadmap and deployment instructions; disable E2E tests [`#301`](https://github.com/opengovsg/formsg/pull/301)
+- build: bump version to v4.35.0 [`fc7f9c9`](https://github.com/opengovsg/formsg/commit/fc7f9c92a5bcf65728bf0436e732f561b016be37)
+- build: bump version to 4.34.1 [`d5bfd5a`](https://github.com/opengovsg/formsg/commit/d5bfd5a60a9b006ad2329eb6ec95979b38522358)
+- Removed unused variable [`588b24d`](https://github.com/opengovsg/formsg/commit/588b24da28be5cb8a80847f6ee6c8f31cdc1c0b0)
+
+#### [v4.34.0](https://github.com/opengovsg/formsg/compare/v4.33.0...v4.34.0)
+
+> 8 September 2020
+
+- refactor: migrate /auth endpoint handling to Typescript, Domain Driven Design [`#215`](https://github.com/opengovsg/formsg/pull/215)
+- chore(deps-dev): bump stylelint-config-prettier from 8.0.1 to 8.0.2 [`#280`](https://github.com/opengovsg/formsg/pull/280)
+- fix: upgrade mongoose from 5.9.19 to 5.10.0 [`#289`](https://github.com/opengovsg/formsg/pull/289)
+- revert: reintroduce convict [`#287`](https://github.com/opengovsg/formsg/pull/287)
+- revert(convict): "refactor: use convict for configuration (#190)" [`#285`](https://github.com/opengovsg/formsg/pull/285)
+- chore(deps-dev): bump @typescript-eslint/eslint-plugin and @typescript-eslint/parser [`#246`](https://github.com/opengovsg/formsg/pull/246)
+- feat: verified sms modal [`#274`](https://github.com/opengovsg/formsg/pull/274)
+- feat: add try-catch block to custom logger for js files [`#267`](https://github.com/opengovsg/formsg/pull/267)
+- fix: fix invocations of logger that does not adhere to expected shape [`#273`](https://github.com/opengovsg/formsg/pull/273)
+- chore(deps-dev): bump @babel/core from 7.10.2 to 7.11.5 [`#270`](https://github.com/opengovsg/formsg/pull/270)
+- feat: upgrade localstack version [`#275`](https://github.com/opengovsg/formsg/pull/275)
+- chore(deps-dev): bump testcafe from 1.8.6 to 1.9.1 [`#271`](https://github.com/opengovsg/formsg/pull/271)
+- chore(deps-dev): bump @babel/preset-env from 7.11.0 to 7.11.5 [`#268`](https://github.com/opengovsg/formsg/pull/268)
+- refactor: use convict for configuration [`#190`](https://github.com/opengovsg/formsg/pull/190)
+- fix: revert changes to configureAws [`#266`](https://github.com/opengovsg/formsg/pull/266)
+- refactor: remove redundant feature factory [`#261`](https://github.com/opengovsg/formsg/pull/261)
+- chore: remove form_field.isFutureOnly key [`#235`](https://github.com/opengovsg/formsg/pull/235)
+- refactor: remove unused Nodemailer env vars [`#253`](https://github.com/opengovsg/formsg/pull/253)
+- feat: upgrade Sentry SDK [`#254`](https://github.com/opengovsg/formsg/pull/254)
+- fix(deps): bump lodash from 4.17.19 to 4.17.20 [`#259`](https://github.com/opengovsg/formsg/pull/259)
+- chore(deps-dev): bump eslint from 7.7.0 to 7.8.1 [`#258`](https://github.com/opengovsg/formsg/pull/258)
+- chore(deps-dev): bump jest from 26.2.2 to 26.4.2 [`#257`](https://github.com/opengovsg/formsg/pull/257)
+- refactor: typify webhook and migrate from middleware pattern [`#251`](https://github.com/opengovsg/formsg/pull/251)
+- fix(dev): fix Localstack yet again [`#252`](https://github.com/opengovsg/formsg/pull/252)
+- chore(deps-dev): bump prettier from 2.0.5 to 2.1.1 [`#249`](https://github.com/opengovsg/formsg/pull/249)
+- fix: prevent discriminated models from being created before their base model [`#244`](https://github.com/opengovsg/formsg/pull/244)
+- fix(deps): remove ajv as dependency [`#248`](https://github.com/opengovsg/formsg/pull/248)
+- chore(deps-dev): bump @typescript-eslint/parser from 3.3.0 to 3.10.1 [`#247`](https://github.com/opengovsg/formsg/pull/247)
+- feat: merge Release 4.33.0 into develop [`#245`](https://github.com/opengovsg/formsg/pull/245)
+- Bump version [`830211a`](https://github.com/opengovsg/formsg/commit/830211a541ff55ccd2fa1c74dc2ec4bfc29839ef)
+
+#### [v4.33.0](https://github.com/opengovsg/formsg/compare/v4.32.1...v4.33.0)
+
+> 1 September 2020
+
+- fix: use original questionCount [`#242`](https://github.com/opengovsg/formsg/pull/242)
+- fix: correct left margin in acknowledgment error when activating storage mode form [`#240`](https://github.com/opengovsg/formsg/pull/240)
+- feat: log more info about critical bounces [`#237`](https://github.com/opengovsg/formsg/pull/237)
+- fix: remove filetype from permission levels imports [`#236`](https://github.com/opengovsg/formsg/pull/236)
+- fix(deps): bump http-status-codes from 1.4.0 to 2.1.2 [`#229`](https://github.com/opengovsg/formsg/pull/229)
+- refactor: use express router for modules [`#204`](https://github.com/opengovsg/formsg/pull/204)
+- chore(deps-dev): bump @types/helmet from 0.0.47 to 0.0.48 [`#232`](https://github.com/opengovsg/formsg/pull/232)
+- refactor(utils/attachment): typescriptify [`#166`](https://github.com/opengovsg/formsg/pull/166)
+- fix(deps): bump validator from 11.1.0 to 13.1.1 [`#209`](https://github.com/opengovsg/formsg/pull/209)
+- refactor: typify utils [`#171`](https://github.com/opengovsg/formsg/pull/171)
+- feat: mailto option after form activation [`#213`](https://github.com/opengovsg/formsg/pull/213)
+- fix(deps): bump axios from 0.19.2 to 0.20.0 [`#218`](https://github.com/opengovsg/formsg/pull/218)
+- chore(deps-dev): bump @types/mongoose from 5.7.25 to 5.7.36 [`#230`](https://github.com/opengovsg/formsg/pull/230)
+- feat: Bulk download of storage mode attachments in a zip file [`#141`](https://github.com/opengovsg/formsg/pull/141)
+- feat: merge release v4.32.1 back into develop branch [`#226`](https://github.com/opengovsg/formsg/pull/226)
+- fix(deps): bump opossum from 5.0.0 to 5.0.1 [`#221`](https://github.com/opengovsg/formsg/pull/221)
+- chore(deps-dev): bump eslint from 6.8.0 to 7.7.0 [`#220`](https://github.com/opengovsg/formsg/pull/220)
+- feat: standardize logger format and output [`#211`](https://github.com/opengovsg/formsg/pull/211)
+- fix: fix linting not working on frontend code [`#217`](https://github.com/opengovsg/formsg/pull/217)
+- fix: pass missing $state param into EditContactNumberModalController [`#216`](https://github.com/opengovsg/formsg/pull/216)
+- feat: add Emergency Contact feature frontend [`#142`](https://github.com/opengovsg/formsg/pull/142)
+- refactor: convert webhook service to Typescript [`#83`](https://github.com/opengovsg/formsg/pull/83)
+- chore(deps-dev): bump sinon from 6.3.5 to 9.0.3 [`#207`](https://github.com/opengovsg/formsg/pull/207)
+- feat: Share form secret keys across browser tabs using BroadcastChannel [`#203`](https://github.com/opengovsg/formsg/pull/203)
+- chore: merge Release v4.32.0 into develop branch [`#205`](https://github.com/opengovsg/formsg/pull/205)
+- Introduce minimum test coverage thresholds, coveralls.io for threshold reporting and repo badge [`#185`](https://github.com/opengovsg/formsg/pull/185)
+- feat: MailService#sendNodeMail invocations to retry on 4xx errors(#227) [`61d5103`](https://github.com/opengovsg/formsg/commit/61d510312affdea6e971147dd547a6f5449b270b)
+- build: bump version to 4.33.0 [`6c0951e`](https://github.com/opengovsg/formsg/commit/6c0951e877e498751c94a539ce93b03eb0ff9d53)
+
+#### [v4.32.1](https://github.com/opengovsg/formsg/compare/v4.32.0...v4.32.1)
+
+> 27 August 2020
+
+- chore: bump version to v4.32.1 [`0bf07cf`](https://github.com/opengovsg/formsg/commit/0bf07cfc9b804a2e602a096032065d73805acfea)
+- fix: split mail by semicolon in addition to comma when validating [`824380e`](https://github.com/opengovsg/formsg/commit/824380ef2a015674b5931cc3f9516036eb80a917)
+
+#### [v4.32.0](https://github.com/opengovsg/formsg/compare/v4.30.4...v4.32.0)
+
+> 25 August 2020
+
+- fix: shift userEmail retrieval to GA service [`#192`](https://github.com/opengovsg/formsg/pull/192)
+- chore(deps-dev): bump @opengovsg/mockpass from 2.2.0 to 2.4.6 [`#198`](https://github.com/opengovsg/formsg/pull/198)
+- feat: remove beta field validations [`#194`](https://github.com/opengovsg/formsg/pull/194)
+- fix(deps): bump uid-generator from 1.0.0 to 2.0.0 [`#187`](https://github.com/opengovsg/formsg/pull/187)
+- fix(deps): bump puppeteer-core from 4.0.0 to 5.2.1 [`#188`](https://github.com/opengovsg/formsg/pull/188)
+- chore(deps-dev): bump @typescript-eslint/eslint-plugin [`#197`](https://github.com/opengovsg/formsg/pull/197)
+- feat: add core ApplicationError for express app [`#195`](https://github.com/opengovsg/formsg/pull/195)
+- chore(deps-dev): bump typescript to 4.0.2 [`#196`](https://github.com/opengovsg/formsg/pull/196)
+- fix(deps): bump font-awesome from 4.6.1 to 4.7.0 [`#186`](https://github.com/opengovsg/formsg/pull/186)
+- feat: migrate `util/response` to new Submission module (service, utils, etc) [`#176`](https://github.com/opengovsg/formsg/pull/176)
+- feat: log form ID in GA event labels [`#154`](https://github.com/opengovsg/formsg/pull/154)
+- refactor(verification): convert to module and typescriptify [`#172`](https://github.com/opengovsg/formsg/pull/172)
+- feat: support &`;'" in form title [`#156`](https://github.com/opengovsg/formsg/pull/156)
+- fix: run npm audit fix to resolve security issues with minimist dependency in the selectize package [`#181`](https://github.com/opengovsg/formsg/pull/181)
+- chore(deps-dev): bump jasmine from 3.5.0 to 3.6.1 [`#158`](https://github.com/opengovsg/formsg/pull/158)
+- chore(deps-dev): bump env-cmd from 9.0.3 to 10.1.0 [`#133`](https://github.com/opengovsg/formsg/pull/133)
+- feat: mailto link for secret key [`#150`](https://github.com/opengovsg/formsg/pull/150)
+- fix: enable forceDelivery on twilio message sending [`#178`](https://github.com/opengovsg/formsg/pull/178)
+- feat: increase breaker window time and add minimum volume threshold [`#165`](https://github.com/opengovsg/formsg/pull/165)
+- refactor: migrate encryption util to typescript [`#167`](https://github.com/opengovsg/formsg/pull/167)
+- refactor: delete render promise util [`#168`](https://github.com/opengovsg/formsg/pull/168)
+- refactor: convert date util to typescript [`#161`](https://github.com/opengovsg/formsg/pull/161)
+- feat: create bounce collection and alarms [`#131`](https://github.com/opengovsg/formsg/pull/131)
+- [develop] Release v4.31.0 [`#155`](https://github.com/opengovsg/formsg/pull/155)
+- refactor: convert MailService to a class based Typescript implementation [`#76`](https://github.com/opengovsg/formsg/pull/76)
+- fix(deps): bump aws-sdk from 2.699.0 to 2.734.0 [`#146`](https://github.com/opengovsg/formsg/pull/146)
+- fix(deps): bump node-cache from 5.1.1 to 5.1.2 [`#145`](https://github.com/opengovsg/formsg/pull/145)
+- feat: include user ip address when sending otp [`#147`](https://github.com/opengovsg/formsg/pull/147)
+- chore(deps-dev): bump htmlhint from 0.11.0 to 0.14.1 [`#116`](https://github.com/opengovsg/formsg/pull/116)
+- fix(deps): bump angular-* dependency packages from 1.7.9 to 1.8.0 [`#108`](https://github.com/opengovsg/formsg/pull/108)
+- feat: log IP, submissionId and formId together [`#130`](https://github.com/opengovsg/formsg/pull/130)
+- fix(deps): bump crypto-js from 3.3.0 to 4.0.0 [`#110`](https://github.com/opengovsg/formsg/pull/110)
+- [develop] Release 4.30.4 [`#138`](https://github.com/opengovsg/formsg/pull/138)
+- fix(deps): bump express-winston from 4.0.3 to 4.0.5 [`#109`](https://github.com/opengovsg/formsg/pull/109)
+- chore: add --watch flag back to build-frontend-dev script [`#128`](https://github.com/opengovsg/formsg/pull/128)
+- docs: create trouble shooting guide [`#119`](https://github.com/opengovsg/formsg/pull/119)
+- chore: Merge release 4.30.3 into develop [`#127`](https://github.com/opengovsg/formsg/pull/127)
+- fix(deps): bump bcrypt from 3.0.8 to 5.0.0 [`#88`](https://github.com/opengovsg/formsg/pull/88)
+- fix(deps): bump nodemailer from 6.4.10 to 6.4.11 [`#117`](https://github.com/opengovsg/formsg/pull/117)
+- tests: fix flakiness and migrate remaining mongoose model tests to Typescript [`#122`](https://github.com/opengovsg/formsg/pull/122)
+- chore: bump version to v4.32.0 [`aa34114`](https://github.com/opengovsg/formsg/commit/aa341141d47a806ece786fcccbe0faef0945ccfc)
+
+#### [v4.30.4](https://github.com/opengovsg/formsg/compare/v4.30.3...v4.30.4)
+
+> 14 August 2020
+
+- Revert "feat: Filter Storage Mode Responses by Submission Id (#71)" [`ffe4218`](https://github.com/opengovsg/formsg/commit/ffe42187130d1d4147f22b37687992062af7d7c6)
+- chore: bump version to 4.30.4 [`35d68de`](https://github.com/opengovsg/formsg/commit/35d68debce62bd86f001724608bbc59bce483aa3)
+
+#### [v4.30.3](https://github.com/opengovsg/formsg/compare/v4.30.2...v4.30.3)
+
+> 12 August 2020
+
+- fix: Revert url loader [`#125`](https://github.com/opengovsg/formsg/pull/125)
+- feat: show error upon FileReader failure [`#121`](https://github.com/opengovsg/formsg/pull/121)
+- [develop] Release 4.30.2 [`#114`](https://github.com/opengovsg/formsg/pull/114)
+- refactor: remove unused mongoTimestamp plugin [`#120`](https://github.com/opengovsg/formsg/pull/120)
+- docs: updating contributing, readme, license for open source [`#86`](https://github.com/opengovsg/formsg/pull/86)
+- chore: setup jest for use with Typescript tests [`#106`](https://github.com/opengovsg/formsg/pull/106)
+- fix: fix myInfoError typo [`#115`](https://github.com/opengovsg/formsg/pull/115)
+- docs(readme): point build status image to new repo [`#112`](https://github.com/opengovsg/formsg/pull/112)
+- feat: add getQuestion instance method to form field schema [`#103`](https://github.com/opengovsg/formsg/pull/103)
+- chore(deps-dev): bump webpack-cli from 3.3.11 to 3.3.12 [`#105`](https://github.com/opengovsg/formsg/pull/105)
+- feat: Filter Storage Mode Responses by Submission Id [`#71`](https://github.com/opengovsg/formsg/pull/71)
+- fix(deps): bump angular-cookies from 1.7.9 to 1.8.0 [`#104`](https://github.com/opengovsg/formsg/pull/104)
+- chore(deps-dev): bump angular from 1.7.9 to 1.8.0 [`#10`](https://github.com/opengovsg/formsg/pull/10)
+- docs: updated docs for open source [`#95`](https://github.com/opengovsg/formsg/pull/95)
+- test: add tests for verification model [`#99`](https://github.com/opengovsg/formsg/pull/99)
+- chore(deps-dev): bump url-loader from 1.1.2 to 4.1.0 [`#90`](https://github.com/opengovsg/formsg/pull/90)
+- refactor: migrate `utils/request` to Typescript [`#98`](https://github.com/opengovsg/formsg/pull/98)
+- fix: phone validation now only accepts 8 digit #s starting with 8 or 9 [`#101`](https://github.com/opengovsg/formsg/pull/101)
+- [develop] Release 4.30.1 [`#80`](https://github.com/opengovsg/formsg/pull/80)
+- [develop] Release 4.30.0 [`#79`](https://github.com/opengovsg/formsg/pull/79)
+- fix(deps): bump uuid from 8.2.0 to 8.3.0 [`#96`](https://github.com/opengovsg/formsg/pull/96)
+- chore(deps-dev): bump jasmine-spec-reporter from 4.2.1 to 5.0.2 [`#89`](https://github.com/opengovsg/formsg/pull/89)
+- chore(deps-dev): bump @babel/preset-env from 7.10.2 to 7.11.0 [`#87`](https://github.com/opengovsg/formsg/pull/87)
+- fix(deps): bump lodash from 4.17.15 to 4.17.19 [`#91`](https://github.com/opengovsg/formsg/pull/91)
+- refactor(logic): typescriptify [`#81`](https://github.com/opengovsg/formsg/pull/81)
+- fix: update dependabot config to use v2 syntax [`#85`](https://github.com/opengovsg/formsg/pull/85)
+- chore: add dependabot.yml [`#82`](https://github.com/opengovsg/formsg/pull/82)
+- feat: remove allowSms beta flag [`#73`](https://github.com/opengovsg/formsg/pull/73)
+- feat(FormSchema): Document new indexes for form dashboard [`#77`](https://github.com/opengovsg/formsg/pull/77)
+- refactor: add _id to all model interfaces [`#75`](https://github.com/opengovsg/formsg/pull/75)
+- Bump version to 4.30.3 [`4e97a48`](https://github.com/opengovsg/formsg/commit/4e97a48e52eefa621b1ef84f1d991d90e96a57b4)
+
+#### [v4.30.2](https://github.com/opengovsg/formsg/compare/v4.30.1...v4.30.2)
+
+> 5 August 2020
+
+- fix: get env vars directly, not from config [`5397c06`](https://github.com/opengovsg/formsg/commit/5397c06107f52d7d3d5032ce6507daeb9d0604cf)
+- fix: add trailing / only for attachments [`1b9d1c0`](https://github.com/opengovsg/formsg/commit/1b9d1c045c4f250533452f8a0448b37ebd6346f7)
+- chore: bump version to 4.30.2 [`f623c1a`](https://github.com/opengovsg/formsg/commit/f623c1abb8bd85dd06d36994c783ed0409cd8a5d)
+
+#### v4.30.1
+
+> 4 August 2020
+
+- fix: change enum to uppercase [`#72`](https://github.com/opengovsg/formsg/pull/72)
+- fix: activation modal width change when activation succeeds [`#69`](https://github.com/opengovsg/formsg/pull/69)
+- Extend e2e [`#65`](https://github.com/opengovsg/formsg/pull/65)
+- chore: update documentation for banner environment variables [`#3`](https://github.com/opengovsg/formsg/pull/3)
+- fix: add fake aws credentials [`#64`](https://github.com/opengovsg/formsg/pull/64)
+- Initial commit [`203e62d`](https://github.com/opengovsg/formsg/commit/203e62dfc346cef9fb893c7b84c481b762216dea)
+- chore: bump version to 4.30.1 [`9ea64ac`](https://github.com/opengovsg/formsg/commit/9ea64ac0c19df843839357223abda0d449603704)
+- chore: bump version to 4.30.0 [`bf0cca8`](https://github.com/opengovsg/formsg/commit/bf0cca862f9c25b0984986aae295b42938c81884)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2ca5cda003..1b86e1e19d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,23 +2,30 @@
Welcome to FormSG! The following are guidelines for contribution. Use your best judgment, and feel free to propose changes to this document in an issue.
-## How can I contribute?
+## Getting started
-When contributing to this repository, **please first discuss the change you wish to make via issue**, email, or any other method with the repository owners before making the change.
+To contribute, you can start by taking a look at our open issues marked 'contribute' under GitHub's 'Issues' tab. Feel free to assign yourself to any 'contribute' issue that interests you, and comment with questions or clarifications.
-## Bug reports and feature requests
+Otherwise, please **first discuss the change you wish to make via GitHub issue**, [email](mailto:formsg@tech.gov.sg), or any other method with the repository owners beforehand.
+
+## Security reports
+
+Please do not file an open issue for ongoing security bugs. Instead, email us directly with your findings at [formsg@tech.gov.sg](mailto:formsg@tech.gov.sg).
+
+## Bug reports and feature requests
The following guidelines help maintainers and the community understand your report, reproduce the behavior, and find related reports.
Before submitting bug reports or feature request, please check [existing or past issues](https://go.gov.sg/formsg-issues) and [existing or past pull requests](https://go.gov.sg/formsg-pulls).
-You might find out that you don't need to create one.
+You might find out that you don't need to create one.
When **submitting a bug report**, please include as many details as possible, such as the steps to reproduce this bug, expected and actual behaviour.
When **submitting a feature request**, please include the motivation, alternatives that you've considered and any additional contexts that could help us better understand your goal.
Here are some tips to writing good issues:
-- **Use clear and descriptive title** to identify the problem
+
+- **Use a clear and descriptive title** to identify the problem
- **Describe the exact steps to reproduce the problem** and **explain how you did it**
- **Provide specific examples to demonstrate the steps**
- **Include screenshots or animated GIFs if you can**
@@ -31,18 +38,17 @@ Issues available to be picked up by the community are labeled with `contribute`.
If you're submitting a pull request, some points to note:
1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. Refer to [README.md](https://go.gov.sg/formsg-readme) for more details
-2. Update the [README.md](https://go.gov.sg/formsg-readme) with details of changes to the interface, this includes new environment variables, exposed ports, useful file locations and container parameters.
+2. Update the [README.md](https://go.gov.sg/formsg-readme) with details of changes to the interface, including new environment variables, exposed ports, useful file locations and container parameters.
3. You may merge the Pull Request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
## Contributor License Agreement
-Code contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.
-Head over [here](https://go.gov.sg/ogp-cla) to submit one.
+Code contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.
+Head over [here](https://go.gov.sg/ogp-cla) to submit one.
You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project owned by [GovTech](https://www.tech.gov.sg)), you probably don't need to do it again.
## Contact us
-Have questions? Feel free out to us [here](formsg@tech.gov.sg).
-
+Have questions? Feel free to reach out to us at [formsg@tech.gov.sg](mailto:formsg@tech.gov.sg).
diff --git a/README.md b/README.md
index c67584ab15..f00489325e 100755
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@
- [FormSG](#formsg)
- [Table of Contents](#table-of-contents)
+ - [Features](#features)
- [Local Development (Docker)](#local-development-docker)
- [Prerequisites](#prerequisites)
- [Running Locally](#running-locally)
@@ -23,6 +24,29 @@
- [Contributing](#contributing)
- [Support](#support)
+## Features
+
+FormSG is a form builder application built, open sourced and maintained by the [Open Government Products](https://open.gov.sg) team of the Singapore [Government Technology Agency](https://tech.gov.sg) to digitise paper processes.
+
+Notable features include:
+
+- 19 different form field types, including attachments, tables, email and mobile
+- Verified email and mobile phone fields via integrations with Twilio and AWS SES
+- Automatic emailing of submissions for forms built with Email Mode
+- End-to-end encryption for forms built with Storage Mode
+- (Singapore government agencies only) Citizen authentication with [SingPass](https://www.singpass.gov.sg/singpass/common/aboutus)
+- (Singapore government agencies only) Corporate authentication with [CorpPass](https://www.corppass.gov.sg/corppass/common/aboutus)
+- (Singapore government agencies only) Automatic prefill of verified data with [MyInfo](https://www.singpass.gov.sg/myinfo/common/aboutus)
+- (beta) Webhooks functionality via the [FormSG JavaScript SDK](https://github.com/opengovsg/formsg-sdk).
+
+The current product roadmap includes:
+
+- (in progress) Migrating backend code from JavaScript to [TypeScript](https://www.typescriptlang.org/)
+- (in progress) Refactoring backend code to use [Domain-driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)
+- (in progress) Migrating backend tests from [Jasmine](https://jasmine.github.io/) to [Jest](https://jestjs.io/) and expanding unit vs integration tests
+- (yet to start) Support for webhooks attachments
+- (yet to start) Frontend rewrite from [AngularJS](https://angularjs.org/) to [React](https://reactjs.org/)
+
## Local Development (Docker)
### Prerequisites
@@ -31,16 +55,14 @@ Install [docker and docker-compose](https://docs.docker.com/get-docker/).
### Running Locally
-Run
+Run the following shell command to build the Docker image from scratch. This will usually take 10 or so minutes.
```bash
npm run dev
```
-to build the Docker image from scratch. This will usually take 10 or so minutes.
-
If there have been no dependency changes in `package.json` or changes in the
-root `server.js` file, you can run
+`src/server.ts` file, you can run
```bash
docker-compose up
@@ -165,7 +187,7 @@ forms and configured using the environment variables below.
## Contributing
-We welcome contributions to code open sourced by the Government Technology Agency of Singapore. All contributors will be asked to sign a Contributor License Agreement (CLA) in order to ensure that everybody is free to use their contributions.
+We welcome all contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas to code open sourced by the Government Technology Agency of Singapore. Contributors should read [CONTRIBUTING.md](CONTRIBUTING.md) and will also be asked to sign a Contributor License Agreement (CLA) in order to ensure that everybody is free to use their contributions.
## Support
diff --git a/docs/DEPLOYMENT_SETUP.md b/docs/DEPLOYMENT_SETUP.md
index db64c833dd..384faa974c 100644
--- a/docs/DEPLOYMENT_SETUP.md
+++ b/docs/DEPLOYMENT_SETUP.md
@@ -12,39 +12,49 @@ $ npm start
## Deploying to AWS Elastic Beanstalk
-As a prerequisite for EB deployment, make sure you have already created your AWS environment. Some of the services used are listed below:
+Make sure you have created an AWS cloud environment as a prerequisite. Some of the services used are listed below:
-- Elastic Beanstalk / EC2 for hosting and deployment
-- VPC with peering to MongoDB Atlas
-- NAT Gateway (for static IP whitelisting with SingPass)
-- S3 for image and logo hosting
-- Elastic Container Registry for built Docker images
-- SES for sending emails
-- EFS for mounting files e.g. SingPass/MyInfo private keys into the `/certs` directory
-- Secrets Manager
+Infrastructure
-### Dockerrun.aws.json
+- AWS Elastic Beanstalk / EC2 for hosting and deployment
+- AWS Elastic File System for mounting files (i.e. SingPass/MyInfo private keys into the `/certs` directory)
+- AWS S3 for image and logo hosting, attachments for Storage Mode forms
-```json
-{
- "AWSEBDockerrunVersion": "1",
- "Image": {
- "Name": "",
- "Update": "true"
- },
- "Ports": [
- {
- "ContainerPort": "4545"
- }
- ],
- "Volumes": [
- {
- "HostDirectory": "/certs",
- "ContainerDirectory": "/certs"
- }
- ]
-}
-```
+DevOps
+
+- TravisCI for running tests and builds
+- AWS Elastic Container Registry to host built Docker images
+
+Network
+
+- AWS VPC (with peering preferred) for managed database hosted by MongoDB Atlas
+- AWS NAT Gateway (for static IP whitelisting with SingPass)
+
+Database
+
+- MongoDB instance (we use Mongo Atlas)
+
+Emails
+
+- AWS Simple Email Service with SMTP integration for sending emails to login/send OTPs/form submissions/submission autoreplies
+
+SMS
+
+- Twilio for sending OTPs
+- AWS Secrets Manager (to manage user-provided or hosted Twilio credentials)
+
+Analytics and Monitoring
+
+- Sentry.io
+- Google Analytics
+
+Spam protection
+
+- Google reCAPTCHA
+
+### Mounting Elastic File System into Docker container on Elastic Beanstalk
+
+Please see [Dockerrun.aws.json](../Dockerrun.aws.json). This file is required for SingPass/MyInfo/CorpPass functionality to be enabled.
### Secrets Manager (Optional)
@@ -54,10 +64,10 @@ Firstly, name the secret with a unique secret name and store the secret value in
```json
{
- "accountSid": "",
- "apiKey": "",
- "apiSecret": "",
- "messagingServiceSid": ""
+ "accountSid": "",
+ "apiKey": "redacted>",
+ "apiSecret": "redacted>",
+ "messagingServiceSid": "redacted>"
}
```
@@ -73,7 +83,7 @@ For more information about the various environment variables, please refer to
The following env variables are set in Travis:
| Variable | Description|
|:---------|------------|
-|`REPO`|The repository of the AWS ECR|
+|`REPO`|The repository of the AWS Elastic Container Registry|
|`STAGING_BRANCH`|Name of staging branch, usually `master`.|
|`STAGING_ALT_BRANCH`|Name of staging-alt (if any) branch, usually `release`. An alternate staging branch is used to host diverging feature sets, useful for A/B testing.|
|`PROD_BRANCH`|Name of production branch, usually `release`.|
@@ -98,34 +108,34 @@ The following env variables are set in Travis:
#### App Config
-| Variable | Description |
-| :----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `APP_NAME` | Application name in window title; also used as an identifier for MyInfo. Defaults to `'FormSG'`. |
-| `APP_DESC` | Defaults to `'Form Manager for Government'`. |
-| `APP_URL` | Defaults to `'https://form.gov.sg'`. |
-| `APP_KEYWORDS` | Defaults to `'forms, formbuilder, nodejs'`. |
-| `APP_IMAGES` | Defaults to `'/public/modules/core/img/og/img_metatag.png,/public/modules/core/img/og/logo-vertical-color.png'`. |
-| `APP_TWITTER_IMAGE` | Path to Twitter image. Defaults to `'/public/modules/core/img/og/logo-vertical-color.png'`. |
+| Variable | Description |
+| :------------------ | ---------------------------------------------------------------------------------------------------------------- |
+| `APP_NAME` | Application name in window title; also used as an identifier for MyInfo. Defaults to `'FormSG'`. |
+| `APP_DESC` | Defaults to `'Form Manager for Government'`. |
+| `APP_URL` | Defaults to `'https://form.gov.sg'`. |
+| `APP_KEYWORDS` | Defaults to `'forms, formbuilder, nodejs'`. |
+| `APP_IMAGES` | Defaults to `'/public/modules/core/img/og/img_metatag.png,/public/modules/core/img/og/logo-vertical-color.png'`. |
+| `APP_TWITTER_IMAGE` | Path to Twitter image. Defaults to `'/public/modules/core/img/og/logo-vertical-color.png'`. |
#### App and Database
-| Variable | Description |
-| :----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `DB_HOST` | A MongoDB URI. |
-| `OTP_LIFE_SPAN` | Time in milliseconds that admin login OTP is valid for. Defaults to 900000ms or 15 minutes. |
-| `PORT` | Server port. Defaults to `5000`. |
-| `NODE_ENV` | [Express environment mode](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production). Defaults to `'development'`. This should always be set to a production environment |
-| `SESSION_SECRET` | Secret for `express-session`. Defaults to `'sandcrawler-138577'`. This should always be set in a production environment. |
-| `SUBMISSIONS_TOP_UP` | Use this to inflate the number of submissions displayed on the landing page. Defaults to `0`. |
+| Variable | Description |
+| :------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `DB_HOST` | A MongoDB URI. |
+| `OTP_LIFE_SPAN` | Time in milliseconds that admin login OTP is valid for. Defaults to 900000ms or 15 minutes. |
+| `PORT` | Server port. Defaults to `5000`. |
+| `NODE_ENV` | [Express environment mode](https://expressjs.com/en/advanced/best-practice-performance.html#set-node_env-to-production). Defaults to `'production'`. This should always be set to a production environment |
+| `SESSION_SECRET` | Secret for `express-session` for session management. This should always be set to a secret and random value in a production environment. |
+| `SUBMISSIONS_TOP_UP` | Use this to inflate the number of submissions displayed on the landing page. Defaults to `0`. |
#### Banners
-| Variable | Description |
-| :----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `SITE_BANNER_CONTENT` | If set, displays a banner message on both private routes that `ADMIN_BANNER_CONTENT` covers **and** public form routes that `IS_GENERAL_MAINTENANCE` covers. Overrides all other banner environment variables |
-| `ADMIN_BANNER_CONTENT` | If set, displays a banner message on private admin routes such as the form list page as well as form builder pages. |
-| `IS_LOGIN_BANNER` | If set, displays a banner message on the login page |
-| `IS_GENERAL_MAINTENANCE` | If set, displays a banner message on all forms. Overrides `IS_SP_MAINTENANCE` and `IS_CP_MAINTENANCE`. |
+| Variable | Description |
+| :----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `SITE_BANNER_CONTENT` | If set, displays a banner message on both private routes that `ADMIN_BANNER_CONTENT` covers **and** public form routes that `IS_GENERAL_MAINTENANCE` covers. Overrides all other banner environment variables |
+| `ADMIN_BANNER_CONTENT` | If set, displays a banner message on private admin routes such as the form list page as well as form builder pages. |
+| `IS_LOGIN_BANNER` | If set, displays a banner message on the login page |
+| `IS_GENERAL_MAINTENANCE` | If set, displays a banner message on all forms. Overrides `IS_SP_MAINTENANCE` and `IS_CP_MAINTENANCE`. |
#### AWS services
@@ -134,10 +144,10 @@ The following env variables are set in Travis:
| `AWS_REGION` | AWS region. |
| `AWS_ACCESS_KEY_ID` | AWS IAM access key ID used to access S3. |
| `AWS_SECRET_ACCESS_KEY` | AWS IAM access secret used to access S3. |
-| `AWS_ENDPOINT` | AWS S3 bucket endpoint. |
+| `AWS_ENDPOINT` | AWS S3 bucket endpoint. |
| `IMAGE_S3_BUCKET` | Name of S3 bucket for image field uploads. |
| `LOGO_S3_BUCKET` | Name of S3 bucket for form logo uploads. |
-| `LOGO_S3_BUCKET` | Name of S3 bucket for form logo uploads. |
+| `ATTACHMENT_S3_BUCKET` | Name of S3 bucket for attachment uploads on Storage Mode. |
| `CUSTOM_CLOUDWATCH_LOG_GROUP` | Name of CloudWatch log group to send custom logs. Use this if you want some logs to have custom settings, e.g. shorter expiry time. |
#### [FormSG JavaScript SDK](https://www.npmjs.com/package/@opengovsg/formsg-sdk)
@@ -148,20 +158,20 @@ The following env variables are set in Travis:
#### Email and Nodemailer
-| Variable | Description |
-| :-------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `SES_HOST` | SMTP hostname. |
-| `SES_PORT` | SMTP port number. |
-| `SES_USER` | SMTP username. |
-| `SES_PASS` | SMTP password. |
-| `SES_MAX_MESSAGES` | Nodemailer configuration. Connection removed and new one created when this limit is reached. This helps to keep the connection up-to-date for long-running email messaging. Defaults to `100`. |
-| `SES_POOL` | Connection pool to send email in parallel to the SMTP server. Defaults to `38`. |
-| `MAIL_FROM` | Sender email address. Defaults to `'donotreply@mail.form.gov.sg'`. | |
-| `MAIL_SOCKET_TIMEOUT` | Milliseconds of inactivity to allow before killing a connection. This helps to keep the connection up-to-date for long-running email messaging. Defaults to `600000`. |
-| `MAIL_LOGGER` | If set to true then logs to console. If value is not set or is false then nothing is logged. |
-| `MAIL_DEBUG` | If set to `true`, then logs SMTP traffic, otherwise logs only transaction events. |
-| `CHROMIUM_BIN` | Filepath to chromium binary. Required for email autoreply PDF generation with Puppeteer. |
-| `BOUNCE_LIFE_SPAN` | Time in milliseconds that bounces are tracked for each form. Defaults to 10800000ms or 3 hours. Only relevant if you have set up AWS to send bounce and delivery notifications to the /emailnotifications endpoint. |
+| Variable | Description |
+| :-------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `SES_HOST` | SMTP hostname. |
+| `SES_PORT` | SMTP port number. |
+| `SES_USER` | SMTP username. |
+| `SES_PASS` | SMTP password. |
+| `SES_MAX_MESSAGES` | Nodemailer configuration. Connection removed and new one created when this limit is reached. This helps to keep the connection up-to-date for long-running email messaging. Defaults to `100`. |
+| `SES_POOL` | Connection pool to send email in parallel to the SMTP server. Defaults to `38`. |
+| `MAIL_FROM` | Sender email address. Defaults to `'donotreply@mail.form.gov.sg'`. |
+| `MAIL_SOCKET_TIMEOUT` | Milliseconds of inactivity to allow before killing a connection. This helps to keep the connection up-to-date for long-running email messaging. Defaults to `600000`. |
+| `MAIL_LOGGER` | If set to true then logs to console. If value is not set or is false then nothing is logged. |
+| `MAIL_DEBUG` | If set to `true`, then logs SMTP traffic, otherwise logs only transaction events. |
+| `CHROMIUM_BIN` | Filepath to chromium binary. Required for email autoreply PDF generation with Puppeteer. |
+| `BOUNCE_LIFE_SPAN` | Time in milliseconds that bounces are tracked for each form. Defaults to 10800000ms or 3 hours. Only relevant if you have set up AWS to send bounce and delivery notifications to the /emailnotifications endpoint. |
### Additional Features
@@ -246,8 +256,10 @@ Note that MyInfo is currently not supported for storage mode forms and enabling
| `MYINFO_CLIENT_CONFIG` | Configures [MyInfoGovClient](https://github.com/opengovsg/myinfo-gov-client). Set this to either`stg` or `prod` to fetch MyInfo data from the corresponding endpoints. |
| `MYINFO_FORMSG_KEY_PATH` | Filepath to MyInfo private key, which is used to decrypt returned responses. |
| `MYINFO_APP_KEY` | (deprecated) Directly specify contents of the MyInfo FormSG private key. Only works if `NODE_ENV` is set to `development`. |
-| `IS_SP_MAINTENANCE` | If set, displays a banner message on SingPass forms. Overrides `IS_CP_MAINTENANCE`. |
-| `IS_CP_MAINTENANCE` | If set, displays a banner message on CorpPass forms. |
+| `IS_SP_MAINTENANCE` | If set, displays a banner message on SingPass forms. Overrides `IS_CP_MAINTENANCE`. |
+| `IS_CP_MAINTENANCE` | If set, displays a banner message on CorpPass forms. |
+| `FILE_SYSTEM_ID` | The id of the AWS Elastic File System (EFS) file system to mount onto the instances. |
+| `CERT_PATH` | The specific directory within the network file system that is to be mounted. This directory is expected to contain the public certs and private keys relevant to SingPass, CorpPass and MyInfo. |
#### Verified Emails/SMSes
@@ -271,9 +283,9 @@ If this feature is enabled, storage mode forms will also support authentication
### Tests
-| Variable | Description |
-| :--------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
-| `MONGO_BINARY_VERSION` | Version of the Mongo binary used. Defaults to `'latest'` according to [MongoMemoryServer](https://github.com/nodkz/mongodb-memory-server) docs. |
-| `PWD` | Path of working directory. |
-| `MOCK_WEBHOOK_CONFIG_FILE` | Path of configuration file for mock webhook server |
-| `MOCK_WEBHOOK_PORT` | Port of mock webhook server |
+| Variable | Description |
+| :------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
+| `MONGO_BINARY_VERSION` | Version of the Mongo binary used. Defaults to `'latest'` according to [MongoMemoryServer](https://github.com/nodkz/mongodb-memory-server) docs. |
+| `PWD` | Path of working directory. |
+| `MOCK_WEBHOOK_CONFIG_FILE` | Path of configuration file for mock webhook server |
+| `MOCK_WEBHOOK_PORT` | Port of mock webhook server |
diff --git a/package-lock.json b/package-lock.json
index 8bcc000ecd..889ce93bc5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "FormSG",
- "version": "4.34.1",
+ "version": "4.35.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4160,13 +4160,13 @@
}
},
"@shelf/jest-mongodb": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@shelf/jest-mongodb/-/jest-mongodb-1.2.2.tgz",
- "integrity": "sha512-FgNmvYmfXuOFyffziRheSwCBXrFwBZ0zTcNny04YyBD5VO1ukr7jnR+rKkKSMZ8E9LOVFU8Go8Ex24E2J917kw==",
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@shelf/jest-mongodb/-/jest-mongodb-1.2.3.tgz",
+ "integrity": "sha512-RGECov7b9anpHqrEoegYeZFWN3WEOw/3hPu3fQUi4gnNIGH0jyMVCQd4DgB37n2aoEWFfe7Kq59aQUrgIQRITA==",
"dev": true,
"requires": {
"debug": "4.1.1",
- "mongodb-memory-server": "6.6.3",
+ "mongodb-memory-server": "6.6.7",
"uuid": "8.3.0"
},
"dependencies": {
@@ -4545,6 +4545,12 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ=="
},
+ "@types/json-stringify-safe": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz",
+ "integrity": "sha512-UUA1sH0RSRROdInuDOA1yoRzbi5xVFD1RHCoOvNRPTNwR8zBkJ/84PZ6NhKVDtKp0FTeIccJCdQz1X2aJPr4uw==",
+ "dev": true
+ },
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -4605,6 +4611,12 @@
"@types/node": "*"
}
},
+ "@types/mongodb-uri": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@types/mongodb-uri/-/mongodb-uri-0.9.0.tgz",
+ "integrity": "sha512-N52kUCiYyH4H2xOMHV7lIDjv4ZLRcRgEiN0xut/BNKHD/dLox10Q6WVl1vjnfA+rvdL9rFZGUxs9EQunQosAlA==",
+ "dev": true
+ },
"@types/mongoose": {
"version": "5.7.36",
"resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.7.36.tgz",
@@ -4795,6 +4807,12 @@
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"dev": true
},
+ "@types/semver": {
+ "version": "7.3.3",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.3.tgz",
+ "integrity": "sha512-jQxClWFzv9IXdLdhSaTf16XI3NYe6zrEbckSpb5xhKfPbWgIyAY0AFyWWWfaiDcBuj3UHmMkCIwSRqpKMTZL2Q==",
+ "dev": true
+ },
"@types/serve-static": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.4.tgz",
@@ -5428,11 +5446,21 @@
}
},
"angular-translate-loader-partial": {
- "version": "2.18.2",
- "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.2.tgz",
- "integrity": "sha512-rtLOUU8ImyWyERugDtuFck8G6fFJ9KsF3It4tgYd91JS9t8tGnarDORmUhMPF8M2wMjdgRKFDcp4nAZLHso5vA==",
+ "version": "2.18.3",
+ "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.3.tgz",
+ "integrity": "sha512-Wi8x45aScHYcxLd2Pi761F0rDjPV3cKeAjuxUadYtOqov0pWWH18nBELUFMhaELt6qUi4fFxQ9o5rfqDNXqVoQ==",
"requires": {
- "angular-translate": "~2.18.2"
+ "angular-translate": "~2.18.3"
+ },
+ "dependencies": {
+ "angular-translate": {
+ "version": "2.18.3",
+ "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.3.tgz",
+ "integrity": "sha512-ziEi1nTaNIsdn3iKpcALaSFjM9CjwvoIWE/EKepajB/6qT5oB3K1IU5VcaH7Bd0scNRfaJpW+qUD6nsHWDEZ6A==",
+ "requires": {
+ "angular": "^1.8.0"
+ }
+ }
}
},
"angular-ui-bootstrap": {
@@ -5758,6 +5786,34 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
},
+ "auto-changelog": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.2.0.tgz",
+ "integrity": "sha512-RBY0hhVNXstggOQL0SyUaCfSiVD11CVXEHvDwB+mEt9UnhXPqhdpQ7nIVGDEog7JopTdYbydULLLt6v//qrWjw==",
+ "dev": true,
+ "requires": {
+ "commander": "^5.0.0",
+ "handlebars": "^4.7.3",
+ "lodash.uniqby": "^4.7.0",
+ "node-fetch": "^2.6.0",
+ "parse-github-url": "^1.0.2",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "dev": true
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
"autoprefixer": {
"version": "9.8.0",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.0.tgz",
@@ -10342,9 +10398,12 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"ejs": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
- "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz",
+ "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==",
+ "requires": {
+ "jake": "^10.6.1"
+ }
},
"electron-to-chromium": {
"version": "1.3.475",
@@ -11171,56 +11230,12 @@
}
},
"eslint-plugin-jest": {
- "version": "23.20.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.20.0.tgz",
- "integrity": "sha512-+6BGQt85OREevBDWCvhqj1yYA4+BFK4XnRZSGJionuEYmcglMZYLNNBBemwzbqUAckURaHdJSBcjHPyrtypZOw==",
+ "version": "24.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.0.0.tgz",
+ "integrity": "sha512-a0G7hSDbuBCW4PNT6MVpAyfnGbUDOqxzOyhR6wT2BIBnR7MhvfAqd6KKfsTjX+Z3gxzIHiEsihzdClU4cSc6qQ==",
"dev": true,
"requires": {
- "@typescript-eslint/experimental-utils": "^2.5.0"
- },
- "dependencies": {
- "@typescript-eslint/experimental-utils": {
- "version": "2.34.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz",
- "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.3",
- "@typescript-eslint/typescript-estree": "2.34.0",
- "eslint-scope": "^5.0.0",
- "eslint-utils": "^2.0.0"
- }
- },
- "@typescript-eslint/typescript-estree": {
- "version": "2.34.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz",
- "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==",
- "dev": true,
- "requires": {
- "debug": "^4.1.1",
- "eslint-visitor-keys": "^1.1.0",
- "glob": "^7.1.6",
- "is-glob": "^4.0.1",
- "lodash": "^4.17.15",
- "semver": "^7.3.2",
- "tsutils": "^3.17.1"
- }
- },
- "debug": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
- "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "semver": {
- "version": "7.3.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
- "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
- "dev": true
- }
+ "@typescript-eslint/experimental-utils": "^4.0.1"
}
},
"eslint-plugin-prettier": {
@@ -11264,9 +11279,9 @@
"dev": true
},
"esotope-hammerhead": {
- "version": "0.5.5",
- "resolved": "https://registry.npmjs.org/esotope-hammerhead/-/esotope-hammerhead-0.5.5.tgz",
- "integrity": "sha512-EuSYJDtF8gLMB24lzjHw2KotauPsVJybFrtGfQyMm48oC7sTkspA26DqcqcbnRl4GC6sPVKWEx+ex72eqopX9Q==",
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/esotope-hammerhead/-/esotope-hammerhead-0.5.3.tgz",
+ "integrity": "sha512-EMZvx+2MXsAZxqa+bOJZp+5qWzKZ6jx/tYung2dOalujGWW5WKb52UhXR8rb60XyW/WbmoVBjOB1WMPkaSjEzw==",
"dev": true,
"requires": {
"@types/estree": "^0.0.39"
@@ -11950,6 +11965,14 @@
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
+ "filelist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz",
+ "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==",
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
"fill-keys": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz",
@@ -12779,6 +12802,33 @@
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=",
"dev": true
},
+ "handlebars": {
+ "version": "4.7.6",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
+ "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.5",
+ "neo-async": "^2.6.0",
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4",
+ "wordwrap": "^1.0.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ }
+ }
+ },
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -14268,6 +14318,24 @@
"istanbul-lib-report": "^3.0.0"
}
},
+ "jake": {
+ "version": "10.8.2",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
+ "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
+ "requires": {
+ "async": "0.9.x",
+ "chalk": "^2.4.2",
+ "filelist": "^1.0.1",
+ "minimatch": "^3.0.4"
+ },
+ "dependencies": {
+ "async": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
+ "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
+ }
+ }
+ },
"jasmine": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.6.1.tgz",
@@ -16544,12 +16612,12 @@
}
},
"jest-util": {
- "version": "26.2.0",
- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.2.0.tgz",
- "integrity": "sha512-YmDwJxLZ1kFxpxPfhSJ0rIkiZOM0PQbRcfH0TzJOhqCisCAsI1WcmoQqO83My9xeVA2k4n+rzg2UuexVKzPpig==",
+ "version": "26.3.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz",
+ "integrity": "sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw==",
"dev": true,
"requires": {
- "@jest/types": "^26.2.0",
+ "@jest/types": "^26.3.0",
"@types/node": "*",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.4",
@@ -16558,18 +16626,27 @@
},
"dependencies": {
"@jest/types": {
- "version": "26.2.0",
- "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.2.0.tgz",
- "integrity": "sha512-lvm3rJvctxd7+wxKSxxbzpDbr4FXDLaC57WEKdUIZ2cjTYuxYSc0zlyD7Z4Uqr5VdKxRUrtwIkiqBuvgf8uKJA==",
+ "version": "26.3.0",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz",
+ "integrity": "sha512-BDPG23U0qDeAvU4f99haztXwdAg3hz4El95LkAM+tHAqqhiVzRpEGHHU8EDxT/AnxOrA65YjLBwDahdJ9pTLJQ==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
- "@types/istanbul-reports": "^1.1.1",
+ "@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^15.0.0",
"chalk": "^4.0.0"
}
},
+ "@types/istanbul-reports": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz",
+ "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==",
+ "dev": true,
+ "requires": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
@@ -16612,9 +16689,9 @@
"dev": true
},
"supports-color": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
- "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
@@ -17582,6 +17659,12 @@
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=",
"dev": true
},
+ "lodash.uniqby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz",
+ "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=",
+ "dev": true
+ },
"log-driver": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz",
@@ -18441,12 +18524,12 @@
}
},
"mongodb-memory-server": {
- "version": "6.6.3",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-6.6.3.tgz",
- "integrity": "sha512-zx91SQQUBafVfBX8IJjfZa0lIMzdDYs/UB1vnr33e5bSPBwSai+mVV6gW3osF4paLFxOkcvOwx758G9F9HytgA==",
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-6.6.7.tgz",
+ "integrity": "sha512-azRGr5csTAl0MCLR/amPCJrmV5TFwRcVtal56dHrPy1o2T8wZRc3AaJyukob8a/JP38JYa/pQnw1AQH7lFA2Cg==",
"dev": true,
"requires": {
- "mongodb-memory-server-core": "6.6.3"
+ "mongodb-memory-server-core": "6.6.7"
},
"dependencies": {
"agent-base": {
@@ -18458,17 +18541,6 @@
"debug": "4"
}
},
- "bl": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
- "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
- "dev": true,
- "requires": {
- "buffer": "^5.5.0",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
- }
- },
"camelcase": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
@@ -18542,6 +18614,14 @@
"dev": true,
"requires": {
"semver": "^6.0.0"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
}
},
"md5-file": {
@@ -18557,9 +18637,9 @@
"dev": true
},
"mongodb-memory-server-core": {
- "version": "6.6.3",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-6.6.3.tgz",
- "integrity": "sha512-MTs2qCb+5JG4qPCenqo+L0cxQiO09EqTZNk4I5+dz8nRUiVPFLL64tO/oJ1WDuSJ6vcyk8dC+QsNAD1wjZxx8g==",
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-6.6.7.tgz",
+ "integrity": "sha512-21g2FpQdgqN3sFsj5lbGje1BhrSRGNHgz6gMAl8bvmdpRpoZErclkImVtjBXNHCNmCc1Dxr+EBvH11KaVE+9iQ==",
"dev": true,
"requires": {
"@types/cross-spawn": "^6.0.2",
@@ -18570,12 +18650,12 @@
"@types/lockfile": "^1.0.1",
"@types/md5-file": "^4.0.2",
"@types/mkdirp": "^1.0.1",
+ "@types/semver": "^7.3.3",
"@types/tmp": "^0.2.0",
"@types/uuid": "^8.0.0",
"camelcase": "^6.0.0",
"cross-spawn": "^7.0.3",
"debug": "^4.1.1",
- "dedent": "^0.7.0",
"find-cache-dir": "^3.3.1",
"find-package-json": "^1.2.0",
"get-port": "^5.1.1",
@@ -18584,6 +18664,7 @@
"md5-file": "^5.0.0",
"mkdirp": "^1.0.4",
"mongodb": "^3.5.9",
+ "semver": "^7.3.2",
"tar-stream": "^2.1.3",
"tmp": "^0.2.1",
"uuid": "^8.2.0",
@@ -18635,21 +18716,10 @@
"find-up": "^4.0.0"
}
},
- "readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
- "dev": true,
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- },
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
+ "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
},
"shebang-command": {
@@ -18667,19 +18737,6 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
- "tar-stream": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
- "integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
- "dev": true,
- "requires": {
- "bl": "^4.0.1",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- }
- },
"tmp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
@@ -19235,6 +19292,12 @@
"clone": "2.x"
}
},
+ "node-fetch": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
+ "dev": true
+ },
"node-forge": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz",
@@ -20025,6 +20088,12 @@
"is-hexadecimal": "^1.0.0"
}
},
+ "parse-github-url": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-github-url/-/parse-github-url-1.0.2.tgz",
+ "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==",
+ "dev": true
+ },
"parse-glob": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
@@ -23917,9 +23986,9 @@
}
},
"testcafe": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/testcafe/-/testcafe-1.9.2.tgz",
- "integrity": "sha512-85du0zDvzFWleVqRTTsAr8Lo+3gn4yETs9qFZBIEufk6oN1fLzgv6Q14GeaH3/IaKi+/smv55umTce/OXX0mbA==",
+ "version": "1.8.6",
+ "resolved": "https://registry.npmjs.org/testcafe/-/testcafe-1.8.6.tgz",
+ "integrity": "sha512-h4cpvyBZqBXAxCqjaf3iswWFWJ4ZCtNP64+BniWj+bU0MZbTD64DOsyF92zQg09x+odQhU8Hm2zyCxKgAMCbtg==",
"dev": true,
"requires": {
"@types/node": "^10.12.19",
@@ -23946,12 +24015,10 @@
"dedent": "^0.4.0",
"del": "^3.0.0",
"device-specs": "^1.0.0",
- "diff": "^4.0.2",
"elegant-spinner": "^1.0.1",
"emittery": "^0.4.1",
"endpoint-utils": "^1.0.2",
"error-stack-parser": "^1.3.6",
- "execa": "^4.0.3",
"globby": "^9.2.0",
"graceful-fs": "^4.1.11",
"graphlib": "^2.1.5",
@@ -23986,8 +24053,8 @@
"sanitize-filename": "^1.6.0",
"source-map-support": "^0.5.16",
"strip-bom": "^2.0.0",
- "testcafe-browser-tools": "2.0.13",
- "testcafe-hammerhead": "17.1.15",
+ "testcafe-browser-tools": "2.0.12",
+ "testcafe-hammerhead": "17.1.2",
"testcafe-legacy-api": "4.0.0",
"testcafe-reporter-json": "^2.1.0",
"testcafe-reporter-list": "^2.1.0",
@@ -24007,9 +24074,9 @@
"dev": true
},
"@types/node": {
- "version": "10.17.29",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.29.tgz",
- "integrity": "sha512-zLo9rjUeQ5+QVhOufDwrb3XKyso31fJBJnk9wUUQIBDExF/O4LryvpOfozfUaxgqifTnlt7FyqsAPXUq5yFZSA==",
+ "version": "10.17.30",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.30.tgz",
+ "integrity": "sha512-euU8QLX0ipj+5mOYa4ZqZoTv+53BY7yTg9I2ZIhDXgiI3M+0n4mdAt9TQCuvxVAgU179g8OsRLaBt0qEi0T6xA==",
"dev": true
},
"array-union": {
@@ -24062,17 +24129,6 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true
},
- "cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dev": true,
- "requires": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- }
- },
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -24097,31 +24153,6 @@
"path-type": "^3.0.0"
}
},
- "execa": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz",
- "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==",
- "dev": true,
- "requires": {
- "cross-spawn": "^7.0.0",
- "get-stream": "^5.0.0",
- "human-signals": "^1.1.1",
- "is-stream": "^2.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^4.0.0",
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2",
- "strip-final-newline": "^2.0.0"
- },
- "dependencies": {
- "is-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
- "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
- "dev": true
- }
- }
- },
"fast-glob": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz",
@@ -24347,27 +24378,12 @@
"integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ==",
"dev": true
},
- "npm-run-path": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
- "dev": true,
- "requires": {
- "path-key": "^3.0.0"
- }
- },
"parse5": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz",
"integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=",
"dev": true
},
- "path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true
- },
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@@ -24412,21 +24428,6 @@
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
- "shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "requires": {
- "shebang-regex": "^3.0.0"
- }
- },
- "shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true
- },
"slash": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
@@ -24466,22 +24467,13 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true
- },
- "which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
}
}
},
"testcafe-browser-tools": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/testcafe-browser-tools/-/testcafe-browser-tools-2.0.13.tgz",
- "integrity": "sha512-r0AfCNsOJWXHAR+KADumfCffsH3LYoEbJXfmGOG47uEt1FBEw8cWTSWRBp5As+DaAmh9pi75P+ZF6K09WudM/g==",
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/testcafe-browser-tools/-/testcafe-browser-tools-2.0.12.tgz",
+ "integrity": "sha512-5oNNYlcZiDspqJB6L8CfI4vxjzkvARSZv3pa+JrFAoqYmEA3VPiAvzrn+P0zi0D5jEPkKngW2KTpq6r3GfdDNw==",
"dev": true,
"requires": {
"array-find": "^1.0.0",
@@ -24655,9 +24647,9 @@
}
},
"testcafe-hammerhead": {
- "version": "17.1.15",
- "resolved": "https://registry.npmjs.org/testcafe-hammerhead/-/testcafe-hammerhead-17.1.15.tgz",
- "integrity": "sha512-FvuaODbFUkoqyzH0OapiMO3/ISCB/v+0kKi0+5DhcIz1tRBishzyfLbHFyKXUf6rm0IhGJlME93uKFOM9NNOMw==",
+ "version": "17.1.2",
+ "resolved": "https://registry.npmjs.org/testcafe-hammerhead/-/testcafe-hammerhead-17.1.2.tgz",
+ "integrity": "sha512-WkTRrZoMYIZkB0NGiT+xrlD71hcMDX/a34iHNdrUBwcw9o/xi7ym4YzyZO0LXrG5b1pzuLS3ll/o5OqkhfRZnw==",
"dev": true,
"requires": {
"acorn-hammerhead": "^0.3.0",
@@ -24667,18 +24659,18 @@
"crypto-md5": "^1.0.0",
"css": "2.2.3",
"debug": "4.1.1",
- "esotope-hammerhead": "0.5.5",
+ "esotope-hammerhead": "0.5.3",
"iconv-lite": "0.5.1",
- "lodash": "^4.17.19",
+ "lodash": "^4.17.13",
"lru-cache": "2.6.3",
"match-url-wildcard": "0.0.4",
"merge-stream": "^1.0.1",
"mime": "~1.4.1",
"mustache": "^2.1.1",
- "nanoid": "^3.1.12",
+ "nanoid": "^0.2.2",
"os-family": "^1.0.0",
"parse5": "2.2.3",
- "pinkie": "2.0.4",
+ "pinkie": "1.0.0",
"read-file-relative": "^1.2.0",
"semver": "5.5.0",
"tough-cookie": "2.3.3",
@@ -24738,9 +24730,9 @@
"dev": true
},
"nanoid": {
- "version": "3.1.12",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
- "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==",
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-0.2.2.tgz",
+ "integrity": "sha512-GHoRrvNEKiwdkwQ/enKL8AhQkkrBC/2KxMZkDvQzp8OtkpX8ZAmoYJWFVl7l8F2+HcEJUfdg21Ab2wXXfrvACQ==",
"dev": true
},
"parse5": {
@@ -24749,6 +24741,12 @@
"integrity": "sha1-DE/EHBAAxea5PUiwP4CDg3g06fY=",
"dev": true
},
+ "pinkie": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz",
+ "integrity": "sha1-Wkfyi6EBXQIBvae/DzWOR77Ix+Q=",
+ "dev": true
+ },
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@@ -25170,11 +25168,12 @@
}
},
"ts-jest": {
- "version": "26.1.4",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.4.tgz",
- "integrity": "sha512-Nd7diUX6NZWfWq6FYyvcIPR/c7GbEF75fH1R6coOp3fbNzbRJBZZAn0ueVS0r8r9ral1VcrpneAFAwB3TsVS1Q==",
+ "version": "26.3.0",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.3.0.tgz",
+ "integrity": "sha512-Jq2uKfx6bPd9+JDpZNMBJMdMQUC3sJ08acISj8NXlVgR2d5OqslEHOR2KHMgwymu8h50+lKIm0m0xj/ioYdW2Q==",
"dev": true,
"requires": {
+ "@types/jest": "26.x",
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
@@ -27094,6 +27093,11 @@
"xpath": "0.0.27"
},
"dependencies": {
+ "ejs": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz",
+ "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
+ },
"node-forge": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz",
diff --git a/package.json b/package.json
index aa0303e22d..fc71dcde4c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "FormSG",
"description": "Form Manager for Government",
- "version": "4.34.1",
+ "version": "4.35.1",
"homepage": "https://form.gov.sg",
"authors": [
"FormSG "
@@ -33,8 +33,8 @@
"test": "npm run build-backend && npm run test-backend && npm run test-frontend",
"test-e2e-build": "npm run build-backend && npm run build-frontend-dev",
"test-run": "concurrently --success last --kill-others \"mockpass\" \"maildev\" \"node dist/backend/server.js\" \"localstack start --host\" \"node ./tests/mock-webhook-server.js\"",
- "testcafe-full-env": "testcafe -c 3 chrome:headless ./tests/end-to-end --test-meta full-env=true --app \"npm run test-run\" --app-init-delay 10000",
- "testcafe-basic-env": "testcafe -c 3 chrome:headless ./tests/end-to-end --test-meta basic-env=true --app \"npm run test-run\" --app-init-delay 10000",
+ "testcafe-full-env": "testcafe --skip-js-errors -c 3 chrome:headless ./tests/end-to-end --test-meta full-env=true --app \"npm run test-run\" --app-init-delay 10000",
+ "testcafe-basic-env": "testcafe --skip-js-errors -c 3 chrome:headless ./tests/end-to-end --test-meta basic-env=true --app \"npm run test-run\" --app-init-delay 10000",
"download-binary": "node tests/end-to-end/helpers/get-mongo-binary.js",
"test-e2e-full": "env-cmd -f tests/.test-full-env --use-shell \"npm run download-binary && npm run testcafe-full-env\"",
"test-e2e-basic": "env-cmd -f tests/.test-basic-env --use-shell \"npm run download-binary && npm run testcafe-basic-env\"",
@@ -44,7 +44,8 @@
"lint-style": "stylelint '*/**/*.css' --quiet --fix",
"lint-html": "htmlhint && prettier --write './src/public/**/*.html' --ignore-path './dist/**' --loglevel silent",
"lint": "npm run lint-code && npm run lint-style && npm run lint-html",
- "lint-ci": "eslint src/ --quiet && stylelint '*/**/*.css' --quiet && htmlhint && prettier --c './src/public/**/*.html' --ignore-path './dist/**'"
+ "lint-ci": "eslint src/ --quiet && stylelint '*/**/*.css' --quiet && htmlhint && prettier --c './src/public/**/*.html' --ignore-path './dist/**'",
+ "version": "auto-changelog -p && git add CHANGELOG.md"
},
"husky": {
"hooks": {
@@ -82,7 +83,7 @@
"angular-resource": "^1.8.0",
"angular-sanitize": "^1.8.0",
"angular-translate": "^2.18.2",
- "angular-translate-loader-partial": "^2.18.2",
+ "angular-translate-loader-partial": "^2.18.3",
"angular-ui-bootstrap": "~2.5.6",
"angular-ui-router": "~1.0.22",
"async": "~1.5.2",
@@ -110,7 +111,7 @@
"csv-string": "^3.1.5",
"dedent-js": "^1.0.1",
"deep-diff": "^1.0.1",
- "ejs": "^2.6.1",
+ "ejs": "^3.1.5",
"express": "^4.16.4",
"express-device": "~0.4.2",
"express-session": "^1.15.6",
@@ -168,7 +169,7 @@
"@babel/core": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@opengovsg/mockpass": "^2.4.6",
- "@shelf/jest-mongodb": "^1.2.2",
+ "@shelf/jest-mongodb": "^1.2.3",
"@types/bcrypt": "^3.0.0",
"@types/compression": "^1.7.0",
"@types/convict": "^5.2.1",
@@ -180,6 +181,8 @@
"@types/helmet": "0.0.48",
"@types/ip": "^1.1.0",
"@types/jest": "^26.0.9",
+ "@types/json-stringify-safe": "^5.0.0",
+ "@types/mongodb-uri": "^0.9.0",
"@types/mongoose": "^5.7.36",
"@types/node": "^14.0.13",
"@types/nodemailer": "^6.4.0",
@@ -193,6 +196,7 @@
"@types/validator": "^13.0.0",
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.0",
+ "auto-changelog": "^2.2.0",
"axios-mock-adapter": "^1.18.1",
"babel-loader": "^8.0.5",
"concurrently": "^3.6.1",
@@ -206,7 +210,7 @@
"eslint-plugin-angular": "^4.0.1",
"eslint-plugin-html": "^6.0.2",
"eslint-plugin-import": "^2.21.2",
- "eslint-plugin-jest": "^23.20.0",
+ "eslint-plugin-jest": "^24.0.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-simple-import-sort": "^5.0.3",
"google-fonts-plugin": "4.1.0",
@@ -233,8 +237,8 @@
"stylelint-prettier": "^1.1.2",
"supertest": "^3.3.0",
"terser-webpack-plugin": "^1.2.3",
- "testcafe": "^1.9.1",
- "ts-jest": "^26.1.4",
+ "testcafe": "^1.8.6",
+ "ts-jest": "^26.3.0",
"ts-loader": "^7.0.5",
"ts-mock-imports": "^1.3.0",
"ts-node": "^8.10.2",
diff --git a/src/app/models/field/attachmentField.ts b/src/app/models/field/attachmentField.ts
index 0fb57f9f55..3b9639d7e3 100644
--- a/src/app/models/field/attachmentField.ts
+++ b/src/app/models/field/attachmentField.ts
@@ -30,7 +30,7 @@ const createAttachmentFieldSchema = () => {
) {
const { webhook, responseMode } = this.parent()
- if (responseMode === ResponseMode.Encrypt && webhook.url) {
+ if (responseMode === ResponseMode.Encrypt && webhook?.url) {
return next(
Error('Attachments are not allowed when a form has a webhook url'),
)
diff --git a/src/app/models/field/dateField.ts b/src/app/models/field/dateField.ts
index b8a87b7eb8..e7c3716093 100644
--- a/src/app/models/field/dateField.ts
+++ b/src/app/models/field/dateField.ts
@@ -18,7 +18,7 @@ const createDateFieldSchema = () => {
},
selectedDateValidation: {
type: String,
- enum: Object.values(DateSelectedValidation).concat([null]),
+ enum: [...Object.values(DateSelectedValidation), null],
default: null,
},
},
diff --git a/src/app/models/field/longTextField.ts b/src/app/models/field/longTextField.ts
index e7e20f4caa..984ecfdc92 100644
--- a/src/app/models/field/longTextField.ts
+++ b/src/app/models/field/longTextField.ts
@@ -22,7 +22,7 @@ const createLongTextFieldSchema = () => {
},
selectedValidation: {
type: String,
- enum: Object.values(LongTextSelectedValidation).concat([null]),
+ enum: [...Object.values(LongTextSelectedValidation), null],
default: null,
},
},
diff --git a/src/app/models/field/numberField.ts b/src/app/models/field/numberField.ts
index 648a272b85..759fb45ebe 100644
--- a/src/app/models/field/numberField.ts
+++ b/src/app/models/field/numberField.ts
@@ -22,7 +22,7 @@ const createNumberFieldSchema = () => {
},
selectedValidation: {
type: String,
- enum: Object.values(NumberSelectedValidation).concat([null]),
+ enum: [...Object.values(NumberSelectedValidation), null],
default: null,
},
},
diff --git a/src/app/models/field/shortTextField.ts b/src/app/models/field/shortTextField.ts
index 3e41accdae..fcfa32ff7b 100644
--- a/src/app/models/field/shortTextField.ts
+++ b/src/app/models/field/shortTextField.ts
@@ -25,7 +25,7 @@ const createShortTextFieldSchema = () => {
},
selectedValidation: {
type: String,
- enum: Object.values(ShortTextSelectedValidation).concat([null]),
+ enum: [...Object.values(ShortTextSelectedValidation), null],
default: null,
},
},
diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts
index 41d0459105..b16f42f966 100644
--- a/src/app/models/form.server.model.ts
+++ b/src/app/models/form.server.model.ts
@@ -392,7 +392,7 @@ const compileFormModel = (db: Mongoose): IFormModel => {
}
// Compact is used to remove undefined from array
- return compact(uniq(this.form_fields.map((field) => field.myInfo?.attr)))
+ return compact(uniq(this.form_fields?.map((field) => field.myInfo?.attr)))
}
// Return a duplicate form object with the given properties
@@ -416,15 +416,16 @@ const compileFormModel = (db: Mongoose): IFormModel => {
path: 'admin',
select: 'email',
})
- const otpData: FormOtpData = {
- form: data._id,
- formAdmin: {
- email: data.admin.email,
- userId: data.admin._id,
- },
- msgSrvcName: data.msgSrvcName,
- }
- return otpData
+ return data
+ ? ({
+ form: data._id,
+ formAdmin: {
+ email: data.admin.email,
+ userId: data.admin._id,
+ },
+ msgSrvcName: data.msgSrvcName,
+ } as FormOtpData)
+ : null
} catch {
return null
}
@@ -434,8 +435,8 @@ const compileFormModel = (db: Mongoose): IFormModel => {
FormSchema.statics.getFullFormById = async function (
this: IFormModel,
formId: string,
- ): Promise {
- const data: IPopulatedForm = await this.findById(formId).populate({
+ ): Promise {
+ const data: IPopulatedForm | null = await this.findById(formId).populate({
path: 'admin',
populate: {
path: 'agency',
diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts
index ffe0a2b50e..1ce4759c8c 100644
--- a/src/app/models/submission.server.model.ts
+++ b/src/app/models/submission.server.model.ts
@@ -136,7 +136,7 @@ const encryptSubmissionSchema = new Schema({
* which will be posted to the webhook URL.
*/
encryptSubmissionSchema.methods.getWebhookView = function (
- this: ISubmissionSchema,
+ this: IEncryptedSubmissionSchema,
): WebhookView {
const webhookData: WebhookData = {
formId: String(this.form),
diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts
index 047e6634eb..32aaad4d37 100644
--- a/src/app/models/user.server.model.ts
+++ b/src/app/models/user.server.model.ts
@@ -69,7 +69,11 @@ const compileUserModel = (db: Mongoose) => {
*
* See: https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key.
*/
- UserSchema.post('save', function (err, doc, next) {
+ UserSchema.post('save', function (
+ err: any,
+ _doc: IUserSchema,
+ next: any,
+ ) {
if (err.name === 'MongoError' && err.code === 11000) {
next(new Error('Account already exists with this email'))
} else {
diff --git a/src/app/models/verification.server.model.ts b/src/app/models/verification.server.model.ts
index 9313edca4a..063cb81165 100644
--- a/src/app/models/verification.server.model.ts
+++ b/src/app/models/verification.server.model.ts
@@ -28,7 +28,7 @@ const VerificationFieldSchema = new Schema({
hashRetries: { type: Number, default: 0 },
})
-const compileVerificationModel = (db: Mongoose) => {
+const compileVerificationModel = (db: Mongoose): IVerificationModel => {
const VerificationSchema = new Schema({
formId: {
type: Schema.Types.ObjectId,
diff --git a/src/app/modules/auth/__tests__/auth.routes.spec.ts b/src/app/modules/auth/__tests__/auth.routes.spec.ts
index b6606de02e..fa3e69b9f6 100644
--- a/src/app/modules/auth/__tests__/auth.routes.spec.ts
+++ b/src/app/modules/auth/__tests__/auth.routes.spec.ts
@@ -32,7 +32,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('"email" is required')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.email is invalid', async () => {
@@ -46,7 +46,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('Please enter a valid email')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 401 when domain of body.email does not exist in Agency collection', async () => {
@@ -122,7 +122,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('"email" is required')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.email is invalid', async () => {
@@ -136,7 +136,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('Please enter a valid email')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 401 when domain of body.email does not exist in Agency collection', async () => {
@@ -254,7 +254,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('"email" is required')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.otp is not provided as a param', async () => {
@@ -265,7 +265,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('"otp" is required')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.email is invalid', async () => {
@@ -279,7 +279,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('Please enter a valid email')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.otp is less than 6 digits', async () => {
@@ -291,7 +291,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('Please enter a valid otp')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 400 when body.otp is 6 characters but does not consist purely of digits', async () => {
@@ -303,7 +303,7 @@ describe('auth.routes', () => {
// Assert
expect(response.status).toEqual(400)
- expect(response.text).toEqual('Please enter a valid otp')
+ expect(response.text).toEqual('Some required parameters are missing')
})
it('should return 401 when domain of body.email does not exist in Agency collection', async () => {
diff --git a/src/app/modules/auth/auth.controller.ts b/src/app/modules/auth/auth.controller.ts
index 6bee99769a..4fa51633a4 100644
--- a/src/app/modules/auth/auth.controller.ts
+++ b/src/app/modules/auth/auth.controller.ts
@@ -46,11 +46,11 @@ export const handleLoginSendOtp: RequestHandler = async (
// Create OTP.
const [otpErr, otp] = await to(AuthService.createLoginOtp(email))
- if (otpErr) {
+ if (otpErr || !otp) {
logger.error({
message: 'Error generating OTP',
meta: logMeta,
- error: otpErr,
+ error: otpErr ?? undefined,
})
return res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
@@ -138,7 +138,8 @@ export const handleLoginVerifyOtp: RequestHandler = async (
// OTP is valid, proceed to login user.
try {
- const user = await UserService.retrieveUser(email, agency)
+ // TODO (#317): remove usage of non-null assertion
+ const user = await UserService.retrieveUser(email, agency!)
// Create user object to return to frontend.
const userObj = { ...user.toObject(), agency }
@@ -181,7 +182,7 @@ export const handleSignout: RequestHandler = async (req, res) => {
return res.sendStatus(StatusCodes.BAD_REQUEST)
}
- req.session.destroy((error) => {
+ req.session!.destroy((error) => {
if (error) {
logger.error({
message: 'Failed to destroy session',
diff --git a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts
index ac73b3cdb2..9b2df51dbe 100644
--- a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts
+++ b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts
@@ -4,11 +4,14 @@ import { mocked } from 'ts-jest/utils'
import { handleSns } from 'src/app/modules/bounce/bounce.controller'
import * as BounceService from 'src/app/modules/bounce/bounce.service'
+import { ISnsNotification } from 'src/types'
jest.mock('src/app/modules/bounce/bounce.service')
const MockBounceService = mocked(BounceService, true)
-const MOCK_REQ = expressHandler.mockRequest({ body: { someKey: 'someValue' } })
+const MOCK_REQ = expressHandler.mockRequest({
+ body: ({ someKey: 'someValue' } as unknown) as ISnsNotification,
+})
const MOCK_RES = expressHandler.mockResponse()
describe('handleSns', () => {
diff --git a/src/app/modules/bounce/__tests__/bounce.model.spec.ts b/src/app/modules/bounce/__tests__/bounce.model.spec.ts
index 0475c12b0d..d90a425095 100644
--- a/src/app/modules/bounce/__tests__/bounce.model.spec.ts
+++ b/src/app/modules/bounce/__tests__/bounce.model.spec.ts
@@ -146,12 +146,12 @@ describe('Bounce Model', () => {
).Message,
)
const actual = Bounce.fromSnsNotification(notification)
- expect(omit(extractBounceObject(actual), 'expireAt')).toEqual({
+ expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({
formId,
bounces: [{ email: MOCK_EMAIL, hasBounced: false }],
hasAlarmed: false,
})
- expect(actual.expireAt).toBeInstanceOf(Date)
+ expect(actual!.expireAt).toBeInstanceOf(Date)
})
it('should create documents correctly when bounce notification is valid', () => {
@@ -166,12 +166,12 @@ describe('Bounce Model', () => {
).Message,
)
const actual = Bounce.fromSnsNotification(notification)
- expect(omit(extractBounceObject(actual), 'expireAt')).toEqual({
+ expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({
formId,
bounces: [{ email: MOCK_EMAIL, hasBounced: true }],
hasAlarmed: false,
})
- expect(actual.expireAt).toBeInstanceOf(Date)
+ expect(actual!.expireAt).toBeInstanceOf(Date)
})
})
})
diff --git a/src/app/modules/bounce/__tests__/bounce.service.spec.ts b/src/app/modules/bounce/__tests__/bounce.service.spec.ts
index 90df58a7a2..c8804005d9 100644
--- a/src/app/modules/bounce/__tests__/bounce.service.spec.ts
+++ b/src/app/modules/bounce/__tests__/bounce.service.spec.ts
@@ -41,22 +41,20 @@ import {
const Bounce = getBounceModel(mongoose)
describe('isValidSnsRequest', () => {
- let keys, body: ISnsNotification
-
- beforeAll(() => {
- keys = crypto.generateKeyPairSync('rsa', {
- modulusLength: 2048,
- publicKeyEncoding: {
- type: 'pkcs1',
- format: 'pem',
- },
- privateKeyEncoding: {
- type: 'pkcs8',
- format: 'pem',
- },
- })
+ const keys = crypto.generateKeyPairSync('rsa', {
+ modulusLength: 2048,
+ publicKeyEncoding: {
+ type: 'pkcs1',
+ format: 'pem',
+ },
+ privateKeyEncoding: {
+ type: 'pkcs8',
+ format: 'pem',
+ },
})
+ let body: ISnsNotification
+
beforeEach(() => {
body = cloneDeep(MOCK_SNS_BODY)
mockAxios.get.mockResolvedValue({
@@ -65,12 +63,12 @@ describe('isValidSnsRequest', () => {
})
it('should gracefully reject when input is empty', () => {
- return expect(isValidSnsRequest(undefined)).resolves.toBe(false)
+ return expect(isValidSnsRequest(undefined!)).resolves.toBe(false)
})
it('should reject requests when their structure is invalid', () => {
- delete body.Type
- return expect(isValidSnsRequest(body)).resolves.toBe(false)
+ const invalidBody = omit(cloneDeep(body), 'Type') as ISnsNotification
+ return expect(isValidSnsRequest(invalidBody)).resolves.toBe(false)
})
it('should reject requests when their certificate URL is invalid', () => {
@@ -140,7 +138,7 @@ describe('updateBounces', () => {
)
await updateBounces(notification)
const actualBounceDoc = await Bounce.findOne({ formId })
- const actualBounce = extractBounceObject(actualBounceDoc)
+ const actualBounce = extractBounceObject(actualBounceDoc!)
const expectedBounces = recipientList.map((email) => ({
email,
hasBounced: false,
@@ -175,7 +173,7 @@ describe('updateBounces', () => {
)
await updateBounces(notification)
const actualBounceDoc = await Bounce.findOne({ formId })
- const actualBounce = extractBounceObject(actualBounceDoc)
+ const actualBounce = extractBounceObject(actualBounceDoc!)
const expectedBounces = recipientList.map((email) => ({
email,
hasBounced: bounces[email],
@@ -208,7 +206,7 @@ describe('updateBounces', () => {
)
await updateBounces(notification)
const actualBounceDoc = await Bounce.findOne({ formId })
- const actualBounce = extractBounceObject(actualBounceDoc)
+ const actualBounce = extractBounceObject(actualBounceDoc!)
const expectedBounces = recipientList.map((email) => ({
email,
hasBounced: true,
diff --git a/src/app/modules/bounce/bounce.model.ts b/src/app/modules/bounce/bounce.model.ts
index affb7c9f45..e3735c96ce 100644
--- a/src/app/modules/bounce/bounce.model.ts
+++ b/src/app/modules/bounce/bounce.model.ts
@@ -1,4 +1,3 @@
-import { get } from 'lodash'
import { Model, Mongoose, Schema } from 'mongoose'
import validator from 'validator'
@@ -62,21 +61,25 @@ const BounceSchema = new Schema({
})
BounceSchema.index({ expireAt: 1 }, { expireAfterSeconds: 0 })
-// Create a new Bounce document from an SNS notification.
-// More info on format of SNS notifications:
-// https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
+/**
+ * Create a new Bounce document from an SNS notification.
+ * More info on format of SNS notifications:
+ * https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html.
+ * @param snsInfo the SNS notification to create a document from
+ * @returns the created Bounce document
+ */
BounceSchema.statics.fromSnsNotification = function (
this: IBounceModel,
snsInfo: IEmailNotification,
): IBounceSchema | null {
const emailType = extractHeader(snsInfo, EMAIL_HEADERS.emailType)
const formId = extractHeader(snsInfo, EMAIL_HEADERS.formId)
- // We only care about admin emails
+ // Only care about admin emails
if (emailType !== EmailType.AdminResponse || !formId) {
return null
}
const isBounce = isBounceNotification(snsInfo)
- const bounces: ISingleBounce[] = get(snsInfo, 'mail.commonHeaders.to').map(
+ const bounces: ISingleBounce[] = snsInfo.mail.commonHeaders.to.map(
(email) => {
if (isBounce && hasEmailBounced(snsInfo as IBounceNotification, email)) {
return { email, hasBounced: true }
@@ -88,14 +91,18 @@ BounceSchema.statics.fromSnsNotification = function (
return new this({ formId, bounces })
}
-// Updates an old bounce document with info from a new bounce document as well
-// as an SNS notification. This function does 3 things:
-// 1) If the old bounce document indicates that an email bounced, set hasBounced
-// to true for that email.
-// 2) If the new delivery notification indicates that an email was delivered
-// successfully, set hasBounced to false for that email, even if the old bounce
-// document indicates that that email previously bounced.
-// 3) Update the old recipient list according to the newest bounce notification.
+/**
+ * Updates an old bounce document with info from a new bounce document as well
+ * as an SNS notification. This function does 3 things:
+ * 1) If the old bounce document indicates that an email bounced, set
+ * `hasBounced` to `true` for that email.
+ * 2) If the new delivery notification indicates that an email was delivered
+ * successfully, set `hasBounced` to `false` for that email, even if the old
+ * bounce document indicates that that email previously bounced.
+ * 3) Update the old recipient list according to the newest bounce notification.
+ * @param latestBounces the newer bounce document to merge into the current document
+ * @param snsInfo the notification information to merge
+ */
BounceSchema.methods.merge = function (
this: IBounceSchema,
latestBounces: IBounceSchema,
diff --git a/src/app/modules/bounce/bounce.service.ts b/src/app/modules/bounce/bounce.service.ts
index 288884b504..aec827fa67 100644
--- a/src/app/modules/bounce/bounce.service.ts
+++ b/src/app/modules/bounce/bounce.service.ts
@@ -108,7 +108,7 @@ export const isValidSnsRequest = async (
// Writes a log message if all recipients have bounced
const logCriticalBounce = (
bounceDoc: IBounceSchema,
- submissionId: string,
+ submissionId: string | undefined,
bounceInfo: IBounceNotification['bounce'] | undefined,
): void => {
if (bounceDoc.isCriticalBounce()) {
@@ -118,7 +118,7 @@ const logCriticalBounce = (
action: 'updateBounces',
hasAlarmed: bounceDoc.hasAlarmed,
formId: String(bounceDoc.formId),
- submissionId,
+ submissionId: submissionId,
recipients: bounceDoc.bounces.map((emailInfo) => emailInfo.email),
// We know for sure that critical bounces can only happen because of bounce
// notifications, so we don't expect this to be undefined
@@ -169,7 +169,9 @@ export const updateBounces = async (body: ISnsNotification): Promise => {
if (!latestBounces) return
const formId = latestBounces.formId
const submissionId = extractHeader(notification, EMAIL_HEADERS.submissionId)
- const bounceInfo = isBounceNotification(notification) && notification.bounce
+ const bounceInfo = isBounceNotification(notification)
+ ? notification.bounce
+ : undefined
const oldBounces = await Bounce.findOne({ formId })
if (oldBounces) {
oldBounces.merge(latestBounces, notification)
diff --git a/src/app/modules/bounce/bounce.util.ts b/src/app/modules/bounce/bounce.util.ts
index 36e2eb3cf7..8d9d27e751 100644
--- a/src/app/modules/bounce/bounce.util.ts
+++ b/src/app/modules/bounce/bounce.util.ts
@@ -8,11 +8,12 @@ import {
* and email type (admin response, email confirmation OTP etc).
* @param body Body of SNS notification
* @param header Key of header to extract
+ * @returns the header from the body, if any.
*/
export const extractHeader = (
body: IEmailNotification,
header: string,
-): string => {
+): string | undefined => {
return body.mail.headers.find(
(mailHeader) => mailHeader.name.toLowerCase() === header.toLowerCase(),
)?.value
@@ -22,6 +23,7 @@ export const extractHeader = (
* Whether a bounce notification says a given email has bounced.
* @param bounceInfo Bounce notification from SNS
* @param email Email address to check
+ * @returns true if the email as bounced, false otherwise
*/
export const hasEmailBounced = (
bounceInfo: IBounceNotification,
diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts
index 63ce51ee1e..f99f2f4e67 100644
--- a/src/app/modules/submission/submission.service.ts
+++ b/src/app/modules/submission/submission.service.ts
@@ -27,7 +27,8 @@ const getFilteredResponses = (
const modeFilter = getModeFilter(form.responseMode)
// _id must be transformed to string as form response is jsonified.
- const fieldIds = modeFilter(form.form_fields).map((field) => ({
+ // TODO (#317): remove usage of non-null assertion
+ const fieldIds = modeFilter(form.form_fields!).map((field) => ({
_id: String(field._id),
}))
const uniqueResponses = _.uniqBy(modeFilter(responses), '_id')
@@ -69,7 +70,7 @@ export const getProcessedResponses = (
}
// Create a map keyed by field._id for easier access
- const fieldMap = form.form_fields.reduce<{
+ const fieldMap = form.form_fields!.reduce<{
[fieldId: string]: IFieldSchema
}>((acc, field) => {
acc[field._id] = field
diff --git a/src/app/modules/user/user.controller.ts b/src/app/modules/user/user.controller.ts
index 8a941ad0c3..c35b35cf30 100644
--- a/src/app/modules/user/user.controller.ts
+++ b/src/app/modules/user/user.controller.ts
@@ -128,7 +128,7 @@ export const handleFetchUser: RequestHandler = async (req, res) => {
action: 'handleFetchUser',
userId: sessionUserId,
},
- error: dbErr,
+ error: dbErr ?? undefined,
})
return res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
diff --git a/src/app/modules/user/user.service.ts b/src/app/modules/user/user.service.ts
index 7bad460194..358953227a 100644
--- a/src/app/modules/user/user.service.ts
+++ b/src/app/modules/user/user.service.ts
@@ -98,7 +98,7 @@ export const verifyContactOtp = async (
)
if (contactHashErr || otpHashError) {
- throw new MalformedOtpError(null, `bcrypt error for ${userId}`)
+ throw new MalformedOtpError(undefined, `bcrypt error for ${userId}`)
}
if (!isOtpMatch) {
@@ -149,7 +149,7 @@ export const updateUserContact = async (
export const getPopulatedUserById = async (
userId: IUserSchema['_id'],
-): Promise => {
+): Promise => {
return UserModel.findById(userId).populate({
path: 'agency',
model: AGENCY_SCHEMA_ID,
diff --git a/src/app/modules/verification/verification.factory.ts b/src/app/modules/verification/verification.factory.ts
index 2327aedeb1..3914e633b9 100644
--- a/src/app/modules/verification/verification.factory.ts
+++ b/src/app/modules/verification/verification.factory.ts
@@ -8,10 +8,10 @@ import * as verification from './verification.controller'
interface IVerifiedFieldsFactory {
createTransaction: RequestHandler
- getTransactionMetadata: RequestHandler
- resetFieldInTransaction: RequestHandler
- getNewOtp: RequestHandler
- verifyOtp: RequestHandler
+ getTransactionMetadata: RequestHandler<{ transactionId: string }>
+ resetFieldInTransaction: RequestHandler<{ transactionId: string }>
+ getNewOtp: RequestHandler<{ transactionId: string }>
+ verifyOtp: RequestHandler<{ transactionId: string }>
}
const verifiedFieldsFactory = ({
diff --git a/src/app/modules/verification/verification.service.ts b/src/app/modules/verification/verification.service.ts
index 0e3d456ffc..90272a8662 100644
--- a/src/app/modules/verification/verification.service.ts
+++ b/src/app/modules/verification/verification.service.ts
@@ -40,6 +40,11 @@ export const createTransaction = async (
formId: string,
): Promise => {
const form = await Form.findById(formId)
+
+ if (!form) {
+ return null
+ }
+
const fields = initializeVerifiableFields(form)
if (!_.isEmpty(fields)) {
const verification = new Verification({ formId, fields })
@@ -74,8 +79,8 @@ export const getTransaction = async (
transactionId: string,
): Promise => {
const transaction = await Verification.findById(transactionId)
- if (transaction === null) {
- throwError(VfnErrors.TransactionNotFound)
+ if (!transaction) {
+ return throwError(VfnErrors.TransactionNotFound)
}
return transaction
}
@@ -117,17 +122,19 @@ export const getNewOtp = async (
fieldId: string,
answer: string,
): Promise => {
- if (isTransactionExpired(transaction.expireAt)) {
+ // TODO (#317): remove usage of non-null assertion
+ if (isTransactionExpired(transaction.expireAt!)) {
throwError(VfnErrors.TransactionNotFound)
}
const field = getFieldFromTransaction(transaction, fieldId)
- if (field === undefined) {
- throwError('Field not found in transaction', VfnErrors.FieldNotFound)
+ if (!field) {
+ return throwError('Field not found in transaction', VfnErrors.FieldNotFound)
}
const { _id: transactionId, formId } = transaction
- const waitForSeconds = waitToResendOtpSeconds(field.hashCreatedAt)
+ // TODO (#317): remove usage of non-null assertion
+ const waitForSeconds = waitToResendOtpSeconds(field.hashCreatedAt!)
if (waitForSeconds > 0) {
- throwError(
+ return throwError(
`Wait for ${waitForSeconds} seconds before requesting for a new otp`,
VfnErrors.WaitForOtp,
)
@@ -135,7 +142,8 @@ export const getNewOtp = async (
const hashCreatedAt = new Date()
const otp = generateOtp()
const hashedOtp = await bcrypt.hash(otp, SALT_ROUNDS)
- const signedData = formsgSdk.verification.generateSignature({
+
+ const signedData = formsgSdk.verification.generateSignature!({
transactionId,
formId,
fieldId,
@@ -171,32 +179,33 @@ export const verifyOtp = async (
fieldId: string,
inputOtp: string,
): Promise => {
- if (isTransactionExpired(transaction.expireAt)) {
+ // TODO (#317): remove usage of non-null assertion
+ if (isTransactionExpired(transaction.expireAt!)) {
throwError(VfnErrors.TransactionNotFound)
}
const field = getFieldFromTransaction(transaction, fieldId)
- if (field === undefined) {
- throwError('Field not found in transaction', VfnErrors.FieldNotFound)
+ if (!field) {
+ return throwError('Field not found in transaction', VfnErrors.FieldNotFound)
}
const { hashedOtp, hashCreatedAt, signedData, hashRetries } = field
if (
hashedOtp &&
hashCreatedAt &&
!isHashedOtpExpired(hashCreatedAt) &&
- hashRetries < NUM_OTP_RETRIES
+ NUM_OTP_RETRIES > hashRetries!
) {
await Verification.updateOne(
{ _id: transaction._id, 'fields._id': fieldId },
{
$set: {
- 'fields.$.hashRetries': hashRetries + 1,
+ 'fields.$.hashRetries': hashRetries! + 1,
},
},
)
const validOtp = await bcrypt.compare(inputOtp, hashedOtp)
- return validOtp ? signedData : throwError(VfnErrors.InvalidOtp)
+ return validOtp ? signedData! : throwError(VfnErrors.InvalidOtp)
}
- throwError(VfnErrors.ResendOtp)
+ return throwError(VfnErrors.ResendOtp)
}
/**
diff --git a/src/app/modules/webhook/webhook.controller.ts b/src/app/modules/webhook/webhook.controller.ts
index d8b199c65f..10a2aa7b4e 100644
--- a/src/app/modules/webhook/webhook.controller.ts
+++ b/src/app/modules/webhook/webhook.controller.ts
@@ -22,7 +22,7 @@ export const post = (
// There should only be a webhook service, which is called within the submission controller
// This will also remove the need for retrieval of form/submission from req.
const { form, submission } = req
- const webhookUrl = form.webhook.url
+ const webhookUrl = form.webhook?.url
const submissionWebhookView = submission.getWebhookView()
if (webhookUrl) {
// Note that we push data to webhook endpoints on a best effort basis
diff --git a/src/app/modules/webhook/webhook.service.ts b/src/app/modules/webhook/webhook.service.ts
index 636a282566..947e394e76 100644
--- a/src/app/modules/webhook/webhook.service.ts
+++ b/src/app/modules/webhook/webhook.service.ts
@@ -273,7 +273,7 @@ const getFormattedResponse = (
*/
export const pushData = async (
webhookUrl: WebhookParams['webhookUrl'],
- submissionWebhookView: WebhookView,
+ submissionWebhookView: WebhookView | null,
): Promise => {
const now = Date.now()
// Log and return, this should not happen.
@@ -282,7 +282,6 @@ export const pushData = async (
new WebhookValidationError('submissionWebhookView was null'),
{
webhookUrl,
- submissionWebhookView,
now,
},
)
diff --git a/src/app/services/mail.service.ts b/src/app/services/mail.service.ts
index c319649eea..4803b4853b 100644
--- a/src/app/services/mail.service.ts
+++ b/src/app/services/mail.service.ts
@@ -90,20 +90,20 @@ export class MailService {
* The application name to be shown in some sent emails' fields such as mail
* subject or mail body.
*/
- #appName: Required
+ #appName: Required['appName']
/**
* The application URL to be shown in some sent emails' fields such as mail
* subject or mail body.
*/
- #appUrl: Required
+ #appUrl: Required['appUrl']
/**
* The transporter to be used to send mail.
*/
- #transporter: Required
+ #transporter: Required['transporter']
/**
* The email string to denote the "from" field of the email.
*/
- #senderMail: Required
+ #senderMail: Required['senderMail']
/**
* The full string that can be shown in the mail's "from" field created from
* the given `appName` and `senderMail` arguments.
diff --git a/src/app/services/sms.service.ts b/src/app/services/sms.service.ts
index ff2bc93d2b..316a4d31c8 100644
--- a/src/app/services/sms.service.ts
+++ b/src/app/services/sms.service.ts
@@ -82,7 +82,7 @@ type TwilioConfig = {
* @returns A TwilioConfig containing the client and the sid linked to the msgSrvcName if defined, or the defaultConfig if not.
*/
const getTwilio = async (
- msgSrvcName: string,
+ msgSrvcName: string | undefined,
defaultConfig: TwilioConfig,
): Promise => {
if (msgSrvcName) {
diff --git a/src/app/utils/attachment.ts b/src/app/utils/attachment.ts
index a7821450af..dc48bc94fc 100644
--- a/src/app/utils/attachment.ts
+++ b/src/app/utils/attachment.ts
@@ -69,7 +69,7 @@ export const addAttachmentToResponses = (
const attachmentMap: Record<
IAttachmentInfo['fieldId'],
IAttachmentInfo
- > = attachments.reduce((acc, attachment) => {
+ > = attachments.reduce>((acc, attachment) => {
acc[attachment.fieldId] = attachment
return acc
}, {})
diff --git a/src/config/config.ts b/src/config/config.ts
index c498e0d166..0158ebf57e 100644
--- a/src/config/config.ts
+++ b/src/config/config.ts
@@ -99,7 +99,8 @@ if (isDev) {
}
const dbConfig: DbConfig = {
- uri: dbUri,
+ // TODO (#317): remove usage of non-null assertion
+ uri: dbUri!,
options: {
user: '',
pass: '',
@@ -196,10 +197,10 @@ const configureAws = async () => {
})
}
await getCredentials()
- if (!aws.config.credentials.accessKeyId) {
+ if (!aws.config.credentials?.accessKeyId) {
throw new Error(`AWS Access Key Id is missing`)
}
- if (!aws.config.credentials.secretAccessKey) {
+ if (!aws.config.credentials?.secretAccessKey) {
throw new Error(`AWS Secret Access Key is missing`)
}
}
diff --git a/src/config/feature-manager/util/FeatureManager.class.ts b/src/config/feature-manager/util/FeatureManager.class.ts
index bb7ff7bd35..09e5aaa84b 100644
--- a/src/config/feature-manager/util/FeatureManager.class.ts
+++ b/src/config/feature-manager/util/FeatureManager.class.ts
@@ -78,38 +78,42 @@ export default class FeatureManager {
}
/**
- * Return whether requested feature is enabled
- * @param name
+ * Return true if requested feature is enabled. Else, throw error.
+ * @param name the feature name to check enabledness
+ * @returns true if the feature is enabled, false if disabled
+ * @throws {Error} if name given is not a registered feature
*/
isEnabled(name: FeatureNames): boolean {
if (this.states[name] !== undefined) {
- return this.states[name]
- } else {
- throw new Error(`A feature called ${name} does not exist`)
+ return !!this.states[name]
}
+
+ // Only throw error if state is undefined.
+ throw new Error(`A feature called ${name} does not exist`)
}
/**
* Return props registered for requested feature
- * @param name
+ * @param name the feature to return properties for
*/
props(name: K) {
if (this.states[name] !== undefined) {
return this.properties[name]
- } else {
- throw new Error(`A feature called ${name} does not exist`)
}
+ // Not enabled or not in state.
+ throw new Error(`A feature called ${name} does not exist`)
}
/**
* Return properties registered for requested feature
* and whether requested feature is enabled
- * @param name
+ * @param name the name of the feature to return
*/
get(name: FeatureNames): RegisteredFeature {
return {
isEnabled: this.isEnabled(name),
- props: this.props(name),
+ // TODO (#317): remove usage of non-null assertion
+ props: this.props(name)!,
}
}
}
diff --git a/src/config/formsg-sdk.ts b/src/config/formsg-sdk.ts
index ab76498d5a..84aa64ea8e 100644
--- a/src/config/formsg-sdk.ts
+++ b/src/config/formsg-sdk.ts
@@ -11,14 +11,14 @@ const formsgSdk = formsgSdkPackage({
webhookSecretKey: get(
featureManager.props(FeatureNames.WebhookVerifiedContent),
'signingSecretKey',
- null,
+ undefined,
),
mode: formsgSdkMode,
verificationOptions: {
secretKey: get(
featureManager.props(FeatureNames.VerifiedFields),
'verificationSecretKey',
- null,
+ undefined,
),
transactionExpiry: vfnConstants.TRANSACTION_EXPIRE_AFTER_SECONDS,
},
diff --git a/src/config/logger.ts b/src/config/logger.ts
index c1753217f0..bf4ef0e4b3 100644
--- a/src/config/logger.ts
+++ b/src/config/logger.ts
@@ -122,21 +122,22 @@ export const customFormat = format.printf((info) => {
* Function courtesy of
* https://github.com/winstonjs/winston/issues/1243#issuecomment-463548194.
*/
-const jsonErrorReplacer = (_key: never, value: any) => {
+function jsonErrorReplacer(this: any, key: string, value: any) {
if (value instanceof Error) {
return Object.getOwnPropertyNames(value).reduce((all, valKey) => {
if (valKey === 'stack') {
+ const errStack = value.stack ?? ''
return {
...all,
- at: value[valKey]
+ at: errStack
.split('\n')
- .filter((va) => va.trim().slice(0, 5) != 'Error')
- .map((va, i) => `stack ${i} ${va.trim().slice(3).trim()}`),
+ .filter((va) => va.trim().slice(0, 5) !== 'Error')
+ .map((va, i) => `stack ${i} ${va.trim()}`),
}
} else {
return {
...all,
- [valKey]: value[valKey],
+ [valKey]: value[valKey as keyof Error],
}
}
}, {})
@@ -180,7 +181,7 @@ const getModuleLabel = (callingModule: NodeModule) => {
// Remove the file extension from the filename and split with path separator.
const parts = callingModule.filename.replace(/\.[^/.]+$/, '').split(path.sep)
// Join the last two parts of the file path together.
- return path.join(parts[parts.length - 2], parts.pop())
+ return path.join(parts[parts.length - 2], parts.pop() ?? '')
}
/**
diff --git a/src/loaders/express/error-handler.ts b/src/loaders/express/error-handler.ts
index 5f7630d524..ef3fd38a9f 100644
--- a/src/loaders/express/error-handler.ts
+++ b/src/loaders/express/error-handler.ts
@@ -28,11 +28,6 @@ const errorHandlerMiddlewares = () => {
'Apologies, something odd happened. Please try again later!'
// Error page
if (isCelebrate(err)) {
- const errorMessage = get(
- err,
- 'joi.details[0].message',
- genericErrorMessage,
- )
// formId is only present for Joi validated routes that require it
const formId = get(req, 'form._id', null)
logger.error({
@@ -43,7 +38,9 @@ const errorHandlerMiddlewares = () => {
},
error: err,
})
- return res.status(StatusCodes.BAD_REQUEST).send(errorMessage)
+ return res
+ .status(StatusCodes.BAD_REQUEST)
+ .send('Some required parameters are missing')
}
logger.error({
diff --git a/src/loaders/express/helmet.ts b/src/loaders/express/helmet.ts
index 32a20d05e2..46a0122584 100644
--- a/src/loaders/express/helmet.ts
+++ b/src/loaders/express/helmet.ts
@@ -73,6 +73,8 @@ const helmetMiddlewares = () => {
'https://www.recaptcha.net/recaptcha/',
'https://www.gstatic.com/recaptcha/',
'https://www.gstatic.cn/',
+ // For inline styles in angular-sanitize.js
+ "'sha256-b3IrgBVvuKx/Q3tmAi79fnf6AFClibrz/0S5x1ghdGU='",
],
formAction: ["'self'"],
upgradeInsecureRequests: !config.isDev,
diff --git a/src/loaders/express/index.ts b/src/loaders/express/index.ts
index 38b517e1c3..8a2ffe5238 100644
--- a/src/loaders/express/index.ts
+++ b/src/loaders/express/index.ts
@@ -23,30 +23,31 @@ import sentryMiddlewares from './sentry'
import sessionMiddlewares from './session'
const loadExpressApp = async (connection: Connection) => {
- // Initialize express app
+ // Initialize express app.
let app = express()
app.locals = appLocals
- const environmentConfigs = {
- production(app: Express) {
- // Add x-forwarded-proto headers to handle https cookie,
- // and trust the proxy that is in front of you
- app.use(function (req, res, next) {
- req.headers['x-forwarded-proto'] = 'https'
- return next()
- })
- app.set('trust proxy', 1)
- return app
- },
+ const getConfigFunctionFor = (environment: string) => {
+ switch (environment) {
+ case 'production': {
+ return function (app: Express) {
+ // Trust the load balancer that is in front of the server
+ app.set('trust proxy', true)
+ return app
+ }
+ }
+ default:
+ return null
+ }
}
- const configureEnvironmentFor = environmentConfigs[config.nodeEnv]
- if (typeof configureEnvironmentFor === 'function') {
+ const configureEnvironmentFor = getConfigFunctionFor(config.nodeEnv)
+ if (configureEnvironmentFor) {
app = configureEnvironmentFor(app)
}
app.use(function (req, res, next) {
- const urlPath = url.parse(req.url).path.split('/')
+ const urlPath = url.parse(req.url).path?.split('/') ?? []
if (
urlPath.indexOf('static') > -1 &&
urlPath.indexOf('view') === urlPath.indexOf('static') - 1
diff --git a/src/loaders/mongoose.ts b/src/loaders/mongoose.ts
index 53190820e2..2b14368727 100644
--- a/src/loaders/mongoose.ts
+++ b/src/loaders/mongoose.ts
@@ -7,7 +7,7 @@ import { createLoggerWithLabel } from '../config/logger'
const logger = createLoggerWithLabel(module)
export default async (): Promise => {
- const usingMockedDb = config.db.uri === undefined
+ const usingMockedDb = !config.db.uri
// Mock out the database if we're developing locally
if (usingMockedDb) {
logger.warn({
@@ -43,7 +43,8 @@ export default async (): Promise => {
// Actually connect to the database
function connect() {
- return mongoose.connect(config.db.uri, config.db.options)
+ // TODO (#317): remove usage of non-null assertion
+ return mongoose.connect(config.db.uri!, config.db.options)
}
// Only required for initial connection errors, reconnect on error.
diff --git a/src/public/modules/forms/admin/directiveViews/edit-captcha.client.view.html b/src/public/modules/forms/admin/directiveViews/edit-captcha.client.view.html
index 658f281383..aeaa8c1b24 100644
--- a/src/public/modules/forms/admin/directiveViews/edit-captcha.client.view.html
+++ b/src/public/modules/forms/admin/directiveViews/edit-captcha.client.view.html
@@ -3,7 +3,7 @@
Enable Captcha
diff --git a/src/public/modules/forms/admin/directives/verify-secret-key.client.directive.js b/src/public/modules/forms/admin/directives/verify-secret-key.client.directive.js
index 0eb2a17b28..bdc7865490 100644
--- a/src/public/modules/forms/admin/directives/verify-secret-key.client.directive.js
+++ b/src/public/modules/forms/admin/directives/verify-secret-key.client.directive.js
@@ -54,7 +54,7 @@ function verifySecretKeyDirective(FormSgSdk) {
case 'reader-abort':
return 'File could not be read. Please try again.'
case 'invalid-key':
- return 'Form activation unsuccessful. Secret Key error, please try again.'
+ return 'Secret Key error. Please try again.'
default:
return 'An error occurred. Please try again.'
}
diff --git a/src/public/modules/forms/admin/views/edit-fields.client.modal.html b/src/public/modules/forms/admin/views/edit-fields.client.modal.html
index ed1ebe7e34..8d518d7997 100644
--- a/src/public/modules/forms/admin/views/edit-fields.client.modal.html
+++ b/src/public/modules/forms/admin/views/edit-fields.client.modal.html
@@ -593,11 +593,20 @@
- Email confirmation
+ Email confirmation
+
+
+
diff --git a/src/public/modules/forms/services/captcha.client.service.js b/src/public/modules/forms/services/captcha.client.service.js
index c8551a3ac6..e7e50e2ea2 100644
--- a/src/public/modules/forms/services/captcha.client.service.js
+++ b/src/public/modules/forms/services/captcha.client.service.js
@@ -93,7 +93,7 @@ function captchaService($window, vcRecaptchaService, Toastr) {
vcRecaptchaService.execute(this.widgetId)
} catch (err) {
// If https://www.google.com/recaptcha/api.js?onload=vcRecaptchaApiLoaded&render=explicit
- // could not loaded (eg, while on intranet). vcRecaptchaService throws an error.
+ // could not loaded. vcRecaptchaService throws an error.
Toastr.error(
'Please ensure you have internet connectivity for CAPTCHA to load on this form.',
)
diff --git a/src/public/modules/users/views/static/privacy.client.view.html b/src/public/modules/users/views/static/privacy.client.view.html
index 3335290f28..a8b2c6eda9 100644
--- a/src/public/modules/users/views/static/privacy.client.view.html
+++ b/src/public/modules/users/views/static/privacy.client.view.html
@@ -137,8 +137,9 @@
- 4. safeguard your personal data, all electronic storage and transmission
- of personal data is secured with appropriate security technologies.
+ 4. To safeguard your personal data, all electronic storage and
+ transmission of personal data is secured with appropriate security
+ technologies.
@@ -156,18 +157,40 @@
7. Please contact formsg@tech.gov.sg if you:
+ 7. Please see the Annex for additional terms/notices.
+
+ 8. Please contact formsg@tech.gov.sg if you:
-
- 7.1. have any enquires or feedback on our data protection policies and
+ 8.1. have any enquires or feedback on our data protection policies and
procedures; or
-
- 7.2. need more information on or access to data which you have provided
+ 8.2. need more information on or access to data which you have provided
to us in the past.
- This Privacy Policy is dated 1 Jun 2020.
+ This Privacy Policy is dated 15 Sep 2020.
+
+
+ Name of Service: form.gov.sg
+
+ 1. If you are a form creator, please note that GovTech will collect your
+ email address and other contact details. As GovTech may collect, store
+ and/or process data requested by you from the form respondents, please
+ ensure that your privacy policy adequately provides the respondents with
+ notice of the same and that your privacy policy complies with applicable
+ rules/laws.
+
+
+ 2. If you are a form respondent, please note that GovTech may collect,
+ store and/or process data that you submit in accordance with this Privacy
+ Policy (which applies in addition to the privacy policy of the form
+ creator) and disclose the data to the form creator, or process the data
+ for the form creator. If you have questions on the use of your data,
+ please consult the privacy policy of the form creator or contact the form
+ creator directly.
+
diff --git a/src/shared/util/file-validation.ts b/src/shared/util/file-validation.ts
index 1426d7df0c..1b17a33e30 100644
--- a/src/shared/util/file-validation.ts
+++ b/src/shared/util/file-validation.ts
@@ -109,9 +109,11 @@ export const getInvalidFileExtensionsInZip = (
// We wrap this checker into a closure because the data format
// needs to be different for frontend vs backend.
- const checkZipForInvalidFiles = async function (file: File | Buffer) {
+ const checkZipForInvalidFiles = async (
+ file: Blob | Buffer,
+ ): Promise => {
const zip = await JSZip.loadAsync(file)
- const invalidFileExtensions = []
+ const invalidFileExtensions: (string | string[] | Promise)[] = []
zip.forEach((relativePath, fileEntry) => {
if (fileEntry.dir) return
const fileExt = getFileExtension(fileEntry.name)
@@ -124,6 +126,7 @@ export const getInvalidFileExtensionsInZip = (
)
}
})
+
const results = await Promise.all(invalidFileExtensions)
return uniq(flattenDeep(results))
}
diff --git a/src/shared/util/logic.ts b/src/shared/util/logic.ts
index bd0b1387d9..860fd68934 100644
--- a/src/shared/util/logic.ts
+++ b/src/shared/util/logic.ts
@@ -6,10 +6,11 @@ import {
ILogicSchema,
IPreventSubmitLogicSchema,
IShowFieldsLogicSchema,
+ LogicConditionState,
LogicType,
} from '../../types'
-type GroupedLogic = Record
+type GroupedLogic = Record
type FieldIdSet = Set
// This module handles logic on both the client side (IFieldSchema[])
// and server side (FieldResponse[])
@@ -31,11 +32,13 @@ const isPreventSubmitLogic = (
}
/**
- * Parse logic into a map of fields that are shown/hidden depending on the values of other fields
- * Discards invalid logic, where the id in show or conditions do not exist in form_field
+ * Parse logic into a map of fields that are shown/hidden depending on the
+ * values of other fields.
+ * Discards invalid logic, where the id in show or conditions do not exist in
+ * the form_field.
*
- * Example:
- Show Email (_id: 1001) and Number (_id: 1002) if Dropdown (_id: 1003) is "Option 1" and Yes_No (_id: 1004) is "Yes"
+ * @example
+ * Show Email (_id: 1001) and Number (_id: 1002) if Dropdown (_id: 1003) is "Option 1" and Yes_No (_id: 1004) is "Yes"
Then,
form_logics: [
{
@@ -54,30 +57,24 @@ const isPreventSubmitLogic = (
"1002": [ [{field: "1003", ifValueType: "single-select", state: "is equals to", value: "Option 1"},
{field: "1004", ifValueType: "single-select", state: "is equals to", value: "Yes"}] ]
}
-
- If "1001" is deleted, "1002" will still be rendered since we just won't add "1001" into logicUnitsGroupedByField
+ * @caption If "1001" is deleted, "1002" will still be rendered since we just won't add "1001" into logicUnitsGroupedByField
*
- * @param {Object} form
- * @param {Mongoose.ObjectId} form._id : id of form
- * @param {Array} form.form_logics : An array of objects containing the conditions and ids of fields to be displayed. See FormLogicSchema
- * @param {Array} form.form_fields : An array of form fields containing the ids of the fields
- * @returns {Object} Object containing fields to be displayed and their corresponding conditions, keyed by id of the displayable field
+ * @param form the form object to group its logic by field for
+ * @returns an object containing fields to be displayed and their corresponding conditions, keyed by id of the displayable field
*/
export const groupLogicUnitsByField = (form: IForm): GroupedLogic => {
const formId = form._id
- const formLogics = form.form_logics.filter(isShowFieldsLogic)
+ const formLogics = form.form_logics?.filter(isShowFieldsLogic) ?? []
const formFieldIds = new Set(
- form.form_fields.map((field) => String(field._id)),
+ form.form_fields?.map((field) => String(field._id)),
)
- /**
- * @type {Object.>>} An index of logic units keyed by the field id to be shown. See FormLogicSchema
- */
+ /** An index of logic units keyed by the field id to be shown. */
const logicUnitsGroupedByField: GroupedLogic = {}
let hasInvalidLogic = false
formLogics.forEach(function (logicUnit) {
- // Only add fields with valid logic conditions to the returned map
+ // Only add fields with valid logic conditions to the returned map.
if (allConditionsExist(logicUnit.conditions, formFieldIds)) {
logicUnit.show.forEach(function (fieldId) {
fieldId = String(fieldId)
@@ -98,54 +95,55 @@ export const groupLogicUnitsByField = (form: IForm): GroupedLogic => {
}
/**
- * Parse logic to get a list of conditions where, if any condition in this list is
- * fulfilled, form submission is prevented.
- * @param {Object} form Form object
- * @returns {Array} Array of conditions to prevent submission
+ * Parse logic to get a list of conditions where, if any condition in this list
+ * is fulfilled, form submission is prevented.
+ * @param form the form document to check
+ * @returns array of conditions that prevent submission, can be empty
*/
const getPreventSubmitConditions = (
form: IForm,
): IPreventSubmitLogicSchema[] => {
const formFieldIds = new Set(
- form.form_fields.map((field) => String(field._id)),
+ form.form_fields?.map((field) => String(field._id)),
)
- return form.form_logics.filter((formLogic) => {
- return (
- isPreventSubmitLogic(formLogic) &&
- allConditionsExist(formLogic.conditions, formFieldIds)
- )
- })
+ const preventFormLogics =
+ form.form_logics?.filter(
+ (formLogic) =>
+ isPreventSubmitLogic(formLogic) &&
+ allConditionsExist(formLogic.conditions, formFieldIds),
+ ) ?? []
+
+ return preventFormLogics
}
/**
* Determines whether the submission should be prevented by form logic. If so,
* return the condition preventing the submission. If not, return undefined.
- * @param {Array} submission - form_fields (on client), or req.body.responses (on server)
- * @param {Object} form Form object
- * @param {Set} [visibleFieldIds] Optional set of currently visible fields. If this is not
- * provided, the function recomputes it.
- * @returns {Object} Condition if submission is to prevented, otherwise undefined
+ * @param submission the submission responses to retrieve logic units for. Can be `form_fields` (on client), or `req.body.responses` (on server)
+ * @param form the form document for the submission
+ * @param optionalVisibleFieldIds the optional set of currently visible fields. If this is not provided, it will be recomputed using the given form parameter.
+ * @returns a condition if submission is to prevented, otherwise `undefined`
*/
export const getLogicUnitPreventingSubmit = (
submission: LogicFieldArray,
form: IForm,
visibleFieldIds?: FieldIdSet,
-): IPreventSubmitLogicSchema => {
+): IPreventSubmitLogicSchema | undefined => {
if (!visibleFieldIds) {
visibleFieldIds = getVisibleFieldIds(submission, form)
}
const preventSubmitConditions = getPreventSubmitConditions(form)
return preventSubmitConditions.find((logicUnit) =>
- isLogicUnitSatisfied(submission, logicUnit.conditions, visibleFieldIds),
+ // TODO (#317): remove usage of non-null assertion
+ isLogicUnitSatisfied(submission, logicUnit.conditions, visibleFieldIds!),
)
}
/**
- * Checks if the field ids in logic's conditions all exist in the fieldIds
- *
- * @param {Array} conditions
- * @param {Set} formFieldIds
- * @returns {Boolean}
+ * Checks if the field ids in logic's conditions all exist in the fieldIds.
+ * @param conditions the list of conditions to check
+ * @param formFieldIds the set of form field ids to check
+ * @returns true if every condition's related form field id exists in the set of formFieldIds, false otherwise.
*/
const allConditionsExist = (
conditions: IConditionSchema[],
@@ -158,13 +156,12 @@ const allConditionsExist = (
/**
* Gets the IDs of visible fields in a form according to its responses.
- * This function loops through all the form fields until the set of visible fields no longer
- * changes. The first loop adds all the fields with no conditions attached, the second adds
- * fields which are made visible due to fields added in the previous loop, and so on.
- * @param {Array} submission - form_fields (on client), or req.body.responses (on server)
- * @param {Object} form - Form object
- * @var {Array} logicUnits - Array of logic units
- * @returns {Set} Set of IDs of visible fields
+ * This function loops through all the form fields until the set of visible
+ * fields no longer changes. The first loop adds all the fields with no
+ * conditions attached, the second adds fields which are made visible due to fields added in the previous loop, and so on.
+ * @param submission the submission responses to retrieve logic units for. Can be `form_fields` (on client), or `req.body.responses` (on server)
+ * @param form the form document for the submission
+ * @returns a set of IDs of visible fields in the submission
*/
export const getVisibleFieldIds = (
submission: LogicFieldArray,
@@ -176,10 +173,12 @@ export const getVisibleFieldIds = (
let changesMade = true
while (changesMade) {
changesMade = false
- form.form_fields.forEach((field) => {
+ form.form_fields?.forEach((field) => {
const logicUnits = logicUnitsGroupedByField[String(field._id)]
- // If a field's visibility does not have any conditions, it is always visible.
- // Otherwise, a field's visibility can be toggled by a combination of conditions.
+ // If a field's visibility does not have any conditions, it is always
+ // visible.
+ // Otherwise, a field's visibility can be toggled by a combination of
+ // conditions.
// Eg. the following are logicUnits - just one of them has to be satisfied
// 1) Show X if Y=yes and Z=yes
// Or
@@ -201,9 +200,10 @@ export const getVisibleFieldIds = (
/**
* Checks if an array of conditions is satisfied.
- * @param {Object} submission - form_fields (on client), or req.body.responses (on server)
- * @param {Object} logicUnit - Object containing the conditions specified in a single modal of `add new logic` on the form logic tab
- * @param {Set} visibleFieldIds - Set of field IDs that are visible, which is used to ensure that conditions are visible
+ * @param submission the submission responses to retrieve logic units for. Can be `form_fields` (on client), or `req.body.responses` (on server)
+ * @param logicUnit an object containing the conditions specified in a single modal of `add new logic` on the form logic tab
+ * @param visibleFieldIds the set of field IDs that are visible, which is used to ensure that conditions are visible
+ * @returns true if all the conditions are satisfied, false otherwise
*/
const isLogicUnitSatisfied = (
submission: LogicFieldArray,
@@ -220,7 +220,9 @@ const isLogicUnitSatisfied = (
})
}
-const getCurrentValue = (field: LogicField): string => {
+const getCurrentValue = (
+ field: LogicField,
+): string | null | undefined | string[] => {
if ('fieldValue' in field) {
// client
return field.fieldValue
@@ -250,11 +252,16 @@ const isConditionFulfilled = (
currentValue.length === 0
) {
return false
- } else if (
- condition.state === 'is equals to' ||
- condition.state === 'is either'
+ }
+
+ if (
+ condition.state === LogicConditionState.Equal ||
+ condition.state === LogicConditionState.Either
) {
- const conditionValues = [].concat(condition.value).map(String) // condition.value can be a string (is equals to), or an array (is either)
+ // condition.value can be a string (is equals to), or an array (is either)
+ const conditionValues = ([] as unknown[])
+ .concat(condition.value)
+ .map(String)
currentValue = String(currentValue)
/*
Handling 'Others' for radiobutton
@@ -293,16 +300,16 @@ const isConditionFulfilled = (
}
/**
- * Find the field in the current submission corresponding to the condition to be checked
- *
- * @param {Array} submission - form_fields (on client), or req.body.responses (on server)
- * @param {String} fieldId - id of condition field
- * @returns
+ * Find the field in the current submission corresponding to the condition to be
+ * checked.
+ * @param submission the submission responses to retrieve logic units for. Can be `form_fields` (on client), or `req.body.responses` (on server)
+ * @param fieldId the id of condition field to find
+ * @returns the condition field if it exists, `undefined` otherwise
*/
const findConditionField = (
submission: LogicFieldArray,
fieldId: IConditionSchema['field'],
-): LogicField => {
+): LogicField | undefined => {
return submission.find(
(submittedField) => String(submittedField._id) === String(fieldId),
)
diff --git a/src/shared/util/phone-num-validation.ts b/src/shared/util/phone-num-validation.ts
index 7bd51f93b3..ec80919053 100644
--- a/src/shared/util/phone-num-validation.ts
+++ b/src/shared/util/phone-num-validation.ts
@@ -39,12 +39,16 @@ export const isMobilePhoneNumber = (mobileNumber: string): boolean => {
)
}
+ // Not Singapore number, check type and early return if undefined.
+ const parsedType = parsedNumber.getType()
+ if (!parsedType) return false
+
// All other countries uses number type to check for validity.
return (
isPhoneNumber(mobileNumber) &&
// Have to include both MOBILE, FIXED_LINE_OR_MOBILE as some countries lump
// the types together.
- ['FIXED_LINE_OR_MOBILE', 'MOBILE'].includes(parsedNumber.getType())
+ ['FIXED_LINE_OR_MOBILE', 'MOBILE'].includes(parsedType)
)
}
diff --git a/src/shared/util/stringify-safe.ts b/src/shared/util/stringify-safe.ts
index 8e86d84413..131c7a1163 100644
--- a/src/shared/util/stringify-safe.ts
+++ b/src/shared/util/stringify-safe.ts
@@ -1,18 +1,21 @@
import stringify from 'json-stringify-safe'
/**
- * JSON.stringify docs:
+ * `JSON.stringify` docs:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
*
- * JSON.stringify throws an exception in two cases: if circular references are found,
- * and if a value is of type 'bigint'. This function wraps JSON.stringify in checks
- * to make sure circular references are dealt with (by the json-stringify-safe
- * library) and BigInts are converted to Numbers, so JSON.stringify does not throw
- * any errors. It is useful for stringifying objects which are passed to us by
- * external systems, e.g. HTTP responses.
+ * `JSON.stringify` throws an exception in two cases: if circular references are
+ * found, and if a value is of type `bigint`.
*
- * Currently does not support the replacer or spaces arguments to JSON.stringify.
- * @param obj Object to be stringified
+ * This function wraps JSON.stringify
+ * in checks to make sure circular references are dealt with (by the
+ * `json-stringify-safe` library) and `BigInts` are converted to `Numbers`, so
+ * `JSON.stringify` does not throw any errors. It is useful for stringifying
+ * objects which are passed to us by external systems, e.g. HTTP responses.
+ *
+ * Currently does not support the replacer or spaces arguments to
+ * JSON.stringify.
+ * @param obj the object to be stringified
*/
export const stringifySafe = (obj: any): string => {
const bigIntReplacer = (_key: any, value: any): any => {
diff --git a/src/shared/util/verified-content.ts b/src/shared/util/verified-content.ts
index f29cb14483..e276b8d4d9 100644
--- a/src/shared/util/verified-content.ts
+++ b/src/shared/util/verified-content.ts
@@ -35,7 +35,7 @@ const CpVerifiedKeys: ICpVerifiedKeys = {
// Array determines the order to process and display the verified fields in both
// the detailed responses page and the csv file.
-export const CURRENT_VERIFIED_FIELDS: VerifiedKeys[] = []
+export const CURRENT_VERIFIED_FIELDS: VerifiedKeys[] = ([] as VerifiedKeys[])
.concat(values(SpVerifiedKeys))
.concat(values(CpVerifiedKeys))
@@ -68,10 +68,7 @@ const getVerifiedKeyMap = (type?: 'SP' | 'CP'): VerifiedKeyMap => {
* @param newKeys The oldKey-newKey mapping
* @returns A new object with the renamed keys
*/
-const renameKeys = (
- obj: Record,
- newKeys: VerifiedKeyMap,
-): Record => {
+const renameKeys = (obj: Record, newKeys: Record) => {
const keyValues = Object.keys(obj).map((key) => {
const newKey = newKeys[key] || key
return { [newKey]: obj[key] }
@@ -99,10 +96,7 @@ export const mapDataToKey = ({
data,
}: IMappableData): Record => {
const fieldMap = getVerifiedKeyMap(type)
- const subsetKeys = pick(data, Object.keys(fieldMap)) as Record<
- VerifiedKeys,
- unknown
- >
+ const subsetKeys = pick(data, Object.keys(fieldMap))
return renameKeys(subsetKeys, fieldMap)
}
diff --git a/src/types/admin_verification.ts b/src/types/admin_verification.ts
index a432ee041b..17c9b350af 100644
--- a/src/types/admin_verification.ts
+++ b/src/types/admin_verification.ts
@@ -9,15 +9,21 @@ export interface IAdminVerification {
expireAt: Date
numOtpAttempts?: number
numOtpSent?: number
- _id?: Document['_id']
}
export interface IAdminVerificationSchema extends IAdminVerification, Document {
- _id: Document['_id']
createdAt?: Date
updatedAt?: Date
}
+// Fully created document with defaults populated.
+export interface IAdminVerificationDoc extends IAdminVerificationSchema {
+ createdAt: Date
+ updatedAt: Date
+ numOtpAttempts: number
+ numOtpSent: number
+}
+
export type UpsertOtpParams = Pick<
IAdminVerificationSchema,
'hashedOtp' | 'hashedContact' | 'admin' | 'expireAt'
@@ -27,5 +33,5 @@ export interface IAdminVerificationModel
upsertOtp: (params: UpsertOtpParams) => Promise
incrementAttemptsByAdminId: (
adminId: IUserSchema['_id'],
- ) => Promise
+ ) => Promise
}
diff --git a/src/types/form.ts b/src/types/form.ts
index 0e83e0639a..8c1de9af5f 100644
--- a/src/types/form.ts
+++ b/src/types/form.ts
@@ -109,7 +109,7 @@ export interface IFormSchema extends IForm, Document {
}
export interface IPopulatedForm extends IFormSchema {
- admin: IPopulatedUser | null
+ admin: IPopulatedUser
}
export interface IEncryptedForm extends IForm {
diff --git a/src/types/submission.ts b/src/types/submission.ts
index ff38a680ad..70cf5bd3a7 100644
--- a/src/types/submission.ts
+++ b/src/types/submission.ts
@@ -31,10 +31,10 @@ export interface ISubmission {
export interface WebhookData {
formId: string
submissionId: string
- encryptedContent: string
- verifiedContent: string
- version: number
- created: Date
+ encryptedContent: IEncryptedSubmissionSchema['encryptedContent']
+ verifiedContent: IEncryptedSubmissionSchema['verifiedContent']
+ version: IEncryptedSubmissionSchema['version']
+ created: IEncryptedSubmissionSchema['created']
}
export interface WebhookView {
diff --git a/src/types/vendor/aws-info.d.ts b/src/types/vendor/aws-info.d.ts
new file mode 100644
index 0000000000..4657e95c7f
--- /dev/null
+++ b/src/types/vendor/aws-info.d.ts
@@ -0,0 +1,12 @@
+declare module 'aws-info' {
+ type Data = {
+ regions: Record
+ services: Record
+ }
+
+ export const data: Data
+
+ export function endpoint(serviceName: string, regionName: string): string
+
+ export function regionName(regionNameShort: string): string
+}
diff --git a/src/types/vendor/bson-ext.d.ts b/src/types/vendor/bson-ext.d.ts
new file mode 100644
index 0000000000..e80b6acf20
--- /dev/null
+++ b/src/types/vendor/bson-ext.d.ts
@@ -0,0 +1,508 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+/* eslint-disable @typescript-eslint/ban-types */
+// Internal type definitions for bson-ext 2.0.3
+// !!! Typings are not verified !!!
+// Definitions retrieved from https://www.npmjs.com/package/@types/bson and
+// typed to follow bson-ext's exports.
+///
+
+declare module 'bson-ext' {
+ interface CommonSerializeOptions {
+ /** {default:false}, the serializer will check if keys are valid. */
+ checkKeys?: boolean
+ /** {default:false}, serialize the javascript functions. */
+ serializeFunctions?: boolean
+ /** {default:true}, ignore undefined fields. */
+ ignoreUndefined?: boolean
+ }
+
+ export interface SerializeOptions extends CommonSerializeOptions {
+ /** {default:1024*1024*17}, minimum size of the internal temporary serialization buffer. */
+ minInternalBufferSize?: number
+ }
+
+ export interface SerializeWithBufferAndIndexOptions
+ extends CommonSerializeOptions {
+ /** {default:0}, the index in the buffer where we wish to start serializing into. */
+ index?: number
+ }
+
+ export interface DeserializeOptions {
+ /** {default:false}, evaluate functions in the BSON document scoped to the object deserialized. */
+ evalFunctions?: boolean
+ /** {default:false}, cache evaluated functions for reuse. */
+ cacheFunctions?: boolean
+ /** {default:false}, use a crc32 code for caching, otherwise use the string of the function. */
+ cacheFunctionsCrc32?: boolean
+ /** {default:true}, when deserializing a Long will fit it into a Number if it's smaller than 53 bits. */
+ promoteLongs?: boolean
+ /** {default:false}, deserialize Binary data directly into node.js Buffer object. */
+ promoteBuffers?: boolean
+ /** {default:false}, when deserializing will promote BSON values to their Node.js closest equivalent types. */
+ promoteValues?: boolean
+ /** {default:null}, allow to specify if there what fields we wish to return as unserialized raw buffer. */
+ fieldsAsRaw?: { readonly [fieldName: string]: boolean }
+ /** {default:false}, return BSON regular expressions as BSONRegExp instances. */
+ bsonRegExp?: boolean
+ /** {default:false}, allows the buffer to be larger than the parsed BSON object. */
+ allowObjectSmallerThanBufferSize?: boolean
+ }
+
+ export interface CalculateObjectSizeOptions {
+ /** {default:false}, serialize the javascript functions */
+ serializeFunctions?: boolean
+ /** {default:true}, ignore undefined fields. */
+ ignoreUndefined?: boolean
+ }
+
+ /**
+ * Base class for Long and Timestamp.
+ * In original js-node@1.0.x code 'Timestamp' is a 100% copy-paste of 'Long'
+ * with 'Long' replaced by 'Timestamp' (changed to inheritance in js-node@2.0.0)
+ */
+ class LongLike {
+ /**
+ * @param low The low (signed) 32 bits.
+ * @param high The high (signed) 32 bits.
+ */
+ constructor(low: number, high: number)
+
+ /** Returns the sum of `this` and the `other`. */
+ add(other: T): T
+ /** Returns the bitwise-AND of `this` and the `other`. */
+ and(other: T): T
+ /**
+ * Compares `this` with the given `other`.
+ * @returns 0 if they are the same, 1 if the this is greater, and -1 if the given one is greater.
+ */
+ compare(other: T): number
+ /** Returns `this` divided by the given `other`. */
+ div(other: T): T
+ /** Return whether `this` equals the `other` */
+ equals(other: T): boolean
+ /** Return the high 32-bits value. */
+ getHighBits(): number
+ /** Return the low 32-bits value. */
+ getLowBits(): number
+ /** Return the low unsigned 32-bits value. */
+ getLowBitsUnsigned(): number
+ /** Returns the number of bits needed to represent the absolute value of `this`. */
+ getNumBitsAbs(): number
+ /** Return whether `this` is greater than the `other`. */
+ greaterThan(other: T): boolean
+ /** Return whether `this` is greater than or equal to the `other`. */
+ greaterThanOrEqual(other: T): boolean
+ /** Return whether `this` value is negative. */
+ isNegative(): boolean
+ /** Return whether `this` value is odd. */
+ isOdd(): boolean
+ /** Return whether `this` value is zero. */
+ isZero(): boolean
+ /** Return whether `this` is less than the `other`. */
+ lessThan(other: T): boolean
+ /** Return whether `this` is less than or equal to the `other`. */
+ lessThanOrEqual(other: T): boolean
+ /** Returns `this` modulo the given `other`. */
+ modulo(other: T): T
+ /** Returns the product of `this` and the given `other`. */
+ multiply(other: T): T
+ /** The negation of this value. */
+ negate(): T
+ /** The bitwise-NOT of this value. */
+ not(): T
+ /** Return whether `this` does not equal to the `other`. */
+ notEquals(other: T): boolean
+ /** Returns the bitwise-OR of `this` and the given `other`. */
+ or(other: T): T
+ /**
+ * Returns `this` with bits shifted to the left by the given amount.
+ * @param numBits The number of bits by which to shift.
+ */
+ shiftLeft(numBits: number): T
+ /**
+ * Returns `this` with bits shifted to the right by the given amount.
+ * @param numBits The number of bits by which to shift.
+ */
+ shiftRight(numBits: number): T
+ /**
+ * Returns `this` with bits shifted to the right by the given amount, with the new top bits matching the current sign bit.
+ * @param numBits The number of bits by which to shift.
+ */
+ shiftRightUnsigned(numBits: number): T
+ /** Returns the difference of `this` and the given `other`. */
+ subtract(other: T): T
+ /** Return the int value (low 32 bits). */
+ toInt(): number
+ /** Return the JSON value. */
+ toJSON(): string
+ /** Returns closest floating-point representation to `this` value */
+ toNumber(): number
+ /**
+ * Return as a string
+ * @param radix the radix in which the text should be written. {default:10}
+ */
+ toString(radix?: number): string
+ /** Returns the bitwise-XOR of `this` and the given `other`. */
+ xor(other: T): T
+ }
+
+ /** A class representation of the BSON Binary type. */
+ export class Binary {
+ static readonly SUBTYPE_DEFAULT: number
+ static readonly SUBTYPE_FUNCTION: number
+ static readonly SUBTYPE_BYTE_ARRAY: number
+ static readonly SUBTYPE_UUID_OLD: number
+ static readonly SUBTYPE_UUID: number
+ static readonly SUBTYPE_MD5: number
+ static readonly SUBTYPE_USER_DEFINED: number
+
+ /**
+ * @param buffer A buffer object containing the binary data
+ * @param subType Binary data subtype
+ */
+ constructor(buffer: Buffer, subType?: number)
+
+ /** The underlying Buffer which stores the binary data. */
+ readonly buffer: Buffer
+ /** Binary data subtype */
+ readonly sub_type?: number
+
+ /** The length of the binary. */
+ length(): number
+ /** Updates this binary with byte_value */
+ put(byte_value: number | string): void
+ /** Reads length bytes starting at position. */
+ read(position: number, length: number): Buffer
+ /** Returns the value of this binary as a string. */
+ value(): string
+ /** Writes a buffer or string to the binary */
+ write(buffer: Buffer | string, offset: number): void
+ }
+
+ /** A class representation of the BSON Code type. */
+ export class Code {
+ /**
+ * @param code A string or function.
+ * @param scope An optional scope for the function.
+ */
+ constructor(code: string | Function, scope?: any)
+
+ readonly code: string | Function
+ readonly scope?: any
+ }
+
+ /**
+ * A class representation of the BSON DBRef type.
+ */
+ export class DBRef {
+ /**
+ * @param namespace The collection name.
+ * @param oid The reference ObjectId.
+ * @param db Optional db name, if omitted the reference is local to the current db
+ */
+ constructor(namespace: string, oid: ObjectId, db?: string)
+ namespace: string
+ oid: ObjectId
+ db?: string
+ }
+
+ /** A class representation of the BSON Double type. */
+ export class Double {
+ /**
+ * @param value The number we want to represent as a double.
+ */
+ constructor(value: number)
+
+ /**
+ * https://github.com/mongodb/js-bson/blob/master/lib/double.js#L17
+ */
+ value: number
+
+ valueOf(): number
+ }
+
+ /** A class representation of the BSON Int32 type. */
+ export class Int32 {
+ /**
+ * @param value The number we want to represent as an int32.
+ */
+ constructor(value: number)
+
+ valueOf(): number
+ }
+
+ /** A class representation of the BSON Decimal128 type. */
+ export class Decimal128 {
+ /** Create a Decimal128 instance from a string representation. */
+ static fromString(s: string): Decimal128
+
+ /**
+ * @param bytes A buffer containing the raw Decimal128 bytes.
+ */
+ constructor(bytes: Buffer)
+
+ /** A buffer containing the raw Decimal128 bytes. */
+ readonly bytes: Buffer
+
+ toJSON(): string
+ toString(): string
+ }
+
+ /**
+ * A class representation of the BSON Long type, a 64-bit two's-complement
+ * integer value, which faithfully simulates the behavior of a Java "Long". This
+ * implementation is derived from LongLib in GWT.
+ */
+ export class Long extends LongLike {
+ static readonly MAX_VALUE: Long
+ static readonly MIN_VALUE: Long
+ static readonly NEG_ONE: Long
+ static readonly ONE: Long
+ static readonly ZERO: Long
+
+ /** Returns a Long representing the given (32-bit) integer value. */
+ static fromInt(i: number): Long
+ /** Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. */
+ static fromNumber(n: number): Long
+ /**
+ * Returns a Long representing the 64-bit integer that comes by concatenating the given high and low bits. Each is assumed to use 32 bits.
+ * @param lowBits The low 32-bits.
+ * @param highBits The high 32-bits.
+ */
+ static fromBits(lowBits: number, highBits: number): Long
+ /**
+ * Returns a Long representation of the given string
+ * @param opt_radix The radix in which the text is written. {default:10}
+ */
+ static fromString(s: string, opt_radix?: number): Long
+ }
+
+ /** A class representation of the BSON MaxKey type. */
+ export class MaxKey {
+ constructor()
+ }
+
+ /** A class representation of the BSON MinKey type. */
+ export class MinKey {
+ constructor()
+ }
+
+ /** A class representation of the BSON ObjectId type. */
+ export class ObjectId {
+ /**
+ * Create a new ObjectId instance
+ * @param {(string|number|ObjectId)} id Can be a 24 byte hex string, 12 byte binary string or a Number.
+ */
+ constructor(id?: string | number | ObjectId)
+ /** The generation time of this ObjectId instance */
+ generationTime: number
+ /** If true cache the hex string representation of ObjectId */
+ static cacheHexString?: boolean
+ /**
+ * Creates an ObjectId from a hex string representation of an ObjectId.
+ * @param {string} hexString create a ObjectId from a passed in 24 byte hexstring.
+ * @return {ObjectId} return the created ObjectId
+ */
+ static createFromHexString(hexString: string): ObjectId
+ /**
+ * Creates an ObjectId from a second based number, with the rest of the ObjectId zeroed out. Used for comparisons or sorting the ObjectId.
+ * @param {number} time an integer number representing a number of seconds.
+ * @return {ObjectId} return the created ObjectId
+ */
+ static createFromTime(time: number): ObjectId
+ /**
+ * Checks if a value is a valid bson ObjectId
+ *
+ * @return {boolean} return true if the value is a valid bson ObjectId, return false otherwise.
+ */
+ static isValid(id: string | number | ObjectId): boolean
+ /**
+ * Compares the equality of this ObjectId with `otherID`.
+ * @param {ObjectId|string} otherID ObjectId instance to compare against.
+ * @return {boolean} the result of comparing two ObjectId's
+ */
+ equals(otherID: ObjectId | string): boolean
+ /**
+ * Generate a 12 byte id string used in ObjectId's
+ * @param {number} time optional parameter allowing to pass in a second based timestamp.
+ * @return {string} return the 12 byte id binary string.
+ */
+ static generate(time?: number): Buffer
+ /**
+ * Returns the generation date (accurate up to the second) that this ID was generated.
+ * @return {Date} the generation date
+ */
+ getTimestamp(): Date
+ /**
+ * Return the ObjectId id as a 24 byte hex string representation
+ * @return {string} return the 24 byte hex string representation.
+ */
+ toHexString(): string
+ }
+
+ /** A class representation of the BSON RegExp type. */
+ export class BSONRegExp {
+ constructor(pattern: string, options: string)
+
+ readonly pattern: string
+ readonly options: string
+ }
+
+ /**
+ * A class representation of the BSON Symbol type.
+ * @deprecated
+ */
+ export class Symbol {
+ constructor(value: string)
+
+ /** Access the wrapped string value. */
+ valueOf(): string
+ }
+
+ /** A class representation of the BSON Timestamp type. */
+ export class Timestamp extends LongLike {
+ static readonly MAX_VALUE: Timestamp
+ static readonly MIN_VALUE: Timestamp
+ static readonly NEG_ONE: Timestamp
+ static readonly ONE: Timestamp
+ static readonly ZERO: Timestamp
+
+ /** Returns a Timestamp represented by the given (32-bit) integer value */
+ static fromInt(value: number): Timestamp
+ /** Returns a Timestamp representing the given number value, provided that it is a finite number. */
+ static fromNumber(value: number): Timestamp
+ /**
+ * Returns a Timestamp for the given high and low bits. Each is assumed to use 32 bits.
+ * @param lowBits The low 32-bits.
+ * @param highBits The high 32-bits.
+ */
+ static fromBits(lowBits: number, highBits: number): Timestamp
+ /**
+ * Returns a Timestamp from the given string.
+ * @param opt_radix The radix in which the text is written. {default:10}
+ */
+ static fromString(str: string, opt_radix?: number): Timestamp
+ }
+
+ /**
+ * A class representation of the BSON Map type.
+ * @deprecated
+ */
+ export class Map {
+ constructor()
+ }
+
+ export default class BSON {
+ constructor(types: any[]) {}
+
+ // BSON MAX VALUES
+ static readonly BSON_INT32_MAX = 0x7fffffff
+ static readonly BSON_INT32_MIN = -0x80000000
+
+ static readonly BSON_INT64_MAX = Math.pow(2, 63) - 1
+ static readonly BSON_INT64_MIN = -Math.pow(2, 63)
+
+ // JS MAX PRECISE VALUES
+ static readonly JS_INT_MAX = 0x20000000000000 // Any integer up to 2^53 can be precisely represented by a double.
+ static readonly JS_INT_MIN = -0x20000000000000 // Any integer down to -2^53 can be precisely represented by a double.
+
+ static Binary = Binary
+ static Code = Code
+ static DBRef = DBRef
+ static Decimal128 = Decimal128
+ static Double = Double
+ static Int32 = Int32
+ static Long = Long
+ /** @deprecated */
+ static Map = Map
+ static MaxKey = MaxKey
+ static MinKey = MinKey
+ static ObjectId = ObjectId
+ // special case for deprecated names
+ /** @deprecated */
+ static ObjectID = ObjectId
+ static BSONRegExp = BSONRegExp
+ /** @deprecated */
+ static Symbol = Symbol
+ static Timestamp = Timestamp
+
+ // Just add constants to the Native BSON parser
+ static readonly BSON_BINARY_SUBTYPE_DEFAULT = 0
+ static readonly BSON_BINARY_SUBTYPE_FUNCTION = 1
+ static readonly BSON_BINARY_SUBTYPE_BYTE_ARRAY = 2
+ static readonly BSON_BINARY_SUBTYPE_UUID = 3
+ static readonly BSON_BINARY_SUBTYPE_MD5 = 4
+ static readonly BSON_BINARY_SUBTYPE_USER_DEFINED = 128
+
+ /**
+ * Calculate the bson size for a passed in Javascript object.
+ *
+ * @param {Object} object the Javascript object to calculate the BSON byte size for.
+ * @param {CalculateObjectSizeOptions} Options
+ * @return {Number} returns the number of bytes the BSON object will take up.
+ */
+ calculateObjectSize(
+ object: any,
+ options?: CalculateObjectSizeOptions,
+ ): number
+
+ /**
+ * Serialize a Javascript object.
+ *
+ * @param object The Javascript object to serialize.
+ * @param options Serialize options.
+ * @return The Buffer object containing the serialized object.
+ */
+ serialize(object: any, options?: SerializeOptions): Buffer
+
+ /**
+ * Serialize a Javascript object using a predefined Buffer and index into the buffer, useful when pre-allocating the space for serialization.
+ *
+ * @param object The Javascript object to serialize.
+ * @param buffer The Buffer you pre-allocated to store the serialized BSON object.
+ * @param options Serialize options.
+ * @returns The index pointing to the last written byte in the buffer
+ */
+ serializeWithBufferAndIndex(
+ object: any,
+ buffer: Buffer,
+ options?: SerializeWithBufferAndIndexOptions,
+ ): number
+
+ /**
+ * Deserialize data as BSON.
+ *
+ * @param buffer The buffer containing the serialized set of BSON documents.
+ * @param options Deserialize options.
+ * @returns The deserialized Javascript Object.
+ */
+ deserialize(buffer: Buffer, options?: DeserializeOptions): any
+
+ /**
+ * Deserialize stream data as BSON documents.
+ *
+ * @param data The buffer containing the serialized set of BSON documents.
+ * @param startIndex The start index in the data Buffer where the deserialization is to start.
+ * @param numberOfDocuments Number of documents to deserialize
+ * @param documents An array where to store the deserialized documents
+ * @param docStartIndex The index in the documents array from where to start inserting documents
+ * @param options Additional options used for the deserialization
+ * @returns The next index in the buffer after deserialization of the `numberOfDocuments`
+ */
+ deserializeStream(
+ data: Buffer,
+ startIndex: number,
+ numberOfDocuments: number,
+ documents: Array,
+ docStartIndex: number,
+ options?: DeserializeOptions,
+ ): number
+ }
+
+ /**
+ * ObjectID (with capital "D") is deprecated. Use ObjectId (lowercase "d")
+ * instead.
+ * @deprecated
+ */
+ export { ObjectId as ObjectID }
+}
diff --git a/src/types/vendor/convict-format-with-validator.d.ts b/src/types/vendor/convict-format-with-validator.d.ts
new file mode 100644
index 0000000000..afe9b43f8c
--- /dev/null
+++ b/src/types/vendor/convict-format-with-validator.d.ts
@@ -0,0 +1,22 @@
+declare module 'convict-format-with-validator' {
+ function coerce(v): string
+ function validate(x): void
+
+ export const url = {
+ name: 'url' as const,
+ coerce,
+ validate,
+ }
+
+ export const ipaddress = {
+ name: 'ipaddress' as const,
+ coerce,
+ validate,
+ }
+
+ export const email = {
+ name: 'email' as const,
+ coerce,
+ validate,
+ }
+}
diff --git a/src/types/vendor/express-device.d.ts b/src/types/vendor/express-device.d.ts
new file mode 100644
index 0000000000..3fa6cd4e15
--- /dev/null
+++ b/src/types/vendor/express-device.d.ts
@@ -0,0 +1,23 @@
+declare module 'express-device' {
+ import { RequestHandler } from 'express'
+
+ type UserAgentDevice = 'desktop' | 'tv' | 'tablet' | 'phone' | 'bot' | 'car'
+
+ type CaptureOptions = {
+ emptyUserAgentDeviceType?: UserAgentDevice
+ unknownUserAgentDeviceType?: UserAgentDevice
+ botUserAgentDeviceType?: UserAgentDevice
+ carUserAgentDeviceType?: UserAgentDevice
+ parseUserAgent?: boolean
+ }
+
+ function capture(options: CaptureOptions): RequestHandler
+
+ // This typing is incomplete, to be typed when other functionality is needed.
+ // See https://github.com/rguerreiro/express-device/blob/master/lib/device.js
+ // for the module exports.
+
+ export default {
+ capture,
+ }
+}
diff --git a/src/types/verification.ts b/src/types/verification.ts
index 9d728784c3..570706987e 100644
--- a/src/types/verification.ts
+++ b/src/types/verification.ts
@@ -3,11 +3,15 @@ import { Document } from 'mongoose'
import { IFormSchema } from './form'
export interface IVerificationField {
+ // _id is basically a generated transactionId, so it has to be a string,
+ // instead of being converted to a string from ObjectId.
+ // This must be a string, or transaction fetching will fail.
+ _id: string
fieldType: string
- signedData: string | null
- hashedOtp: string | null
- hashCreatedAt: Date | null
- hashRetries: number
+ signedData?: string | null
+ hashedOtp?: string | null
+ hashCreatedAt?: Date | null
+ hashRetries?: number
}
export interface IVerificationFieldSchema extends IVerificationField, Document {
@@ -19,7 +23,7 @@ export interface IVerificationFieldSchema extends IVerificationField, Document {
export interface IVerification {
formId: IFormSchema['_id']
- expireAt: Date
+ expireAt?: Date
fields: IVerificationFieldSchema[]
}
diff --git a/tests/end-to-end/email-submission.e2e.js b/tests/end-to-end/email-submission.e2e.js
index 60a4c46aae..f49c4d3228 100644
--- a/tests/end-to-end/email-submission.e2e.js
+++ b/tests/end-to-end/email-submission.e2e.js
@@ -65,75 +65,60 @@ fixture('Email mode submissions')
// the form and reach the end page.
// Form with all basic field types
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(allFields)
- t.ctx.formData = formData
- })('Create and submit form with all form fields', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(allFields)
+ t.ctx.formData = formData
+})('Create and submit form with all form fields', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form where all basic field types are hidden by logic
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(hiddenFieldsData)
- formData.logicData = cloneDeep(hiddenFieldsLogicData)
- t.ctx.formData = formData
- })('Create and submit form with all field types hidden', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(hiddenFieldsData)
+ formData.logicData = cloneDeep(hiddenFieldsLogicData)
+ t.ctx.formData = formData
+})('Create and submit form with all field types hidden', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form where all fields are optional and no field is answered
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = allFields.map((field) => {
- return getBlankVersion(getOptionalVersion(field))
- })
- t.ctx.formData = formData
- })('Create and submit form with all field types optional', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = allFields.map((field) => {
+ return getBlankVersion(getOptionalVersion(field))
+ })
+ t.ctx.formData = formData
+})('Create and submit form with all field types optional', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form with three attachments to test de-duplication of attachment names
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(tripleAttachment)
- t.ctx.formData = formData
- })('Create and submit form with identical attachment names', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(tripleAttachment)
+ t.ctx.formData = formData
+})('Create and submit form with identical attachment names', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form with optional attachment in between mandatory ones
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(tripleAttachment)
- // Modify middle attachment field to be optional and unfilled
- formData.formFields[1] = getBlankVersion(
- getOptionalVersion(formData.formFields[1]),
- )
- // Modify first filename to account for middle field left blank
- formData.formFields[0].val = '1-test-att.txt'
- t.ctx.formData = formData
- })(
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(tripleAttachment)
+ // Modify middle attachment field to be optional and unfilled
+ formData.formFields[1] = getBlankVersion(
+ getOptionalVersion(formData.formFields[1]),
+ )
+ // Modify first filename to account for middle field left blank
+ formData.formFields[0].val = '1-test-att.txt'
+ t.ctx.formData = formData
+})(
'Create and submit form with optional and required attachments',
async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
@@ -142,15 +127,12 @@ test
)
// Form where submission is prevented using chained logic
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(chainDisabled.fields)
- formData.logicData = cloneDeep(chainDisabled.logicData)
- t.ctx.formData = formData
- })('Create and disable form with chained logic', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(chainDisabled.fields)
+ formData.logicData = cloneDeep(chainDisabled.logicData)
+ t.ctx.formData = formData
+})('Create and disable form with chained logic', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionDisabled(
t,
@@ -160,15 +142,12 @@ test
)
})
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- t.ctx.formData = formData
- // cloneDeep in case other tests in future import and modify templateFields
- t.ctx.formData.formFields = cloneDeep(templateFields)
- })('Create a form from COVID19 Templates', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ t.ctx.formData = formData
+ // cloneDeep in case other tests in future import and modify templateFields
+ t.ctx.formData.formFields = cloneDeep(templateFields)
+})('Create a form from COVID19 Templates', async (t) => {
t.ctx.form = await createFormFromTemplate(
t,
t.ctx.formData,
diff --git a/tests/end-to-end/encrypt-submission.e2e.js b/tests/end-to-end/encrypt-submission.e2e.js
index d3abe457f1..46f71abeef 100644
--- a/tests/end-to-end/encrypt-submission.e2e.js
+++ b/tests/end-to-end/encrypt-submission.e2e.js
@@ -75,57 +75,45 @@ fixture('Storage mode submissions')
})
// Form with all field types available in storage mode
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(allFields)
- t.ctx.formData = formData
- })('Create and submit form with all field types', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(allFields)
+ t.ctx.formData = formData
+})('Create and submit form with all field types', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form where all basic field types are hidden by logic
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(hiddenFieldsData)
- formData.logicData = cloneDeep(hiddenFieldsLogicData)
- t.ctx.formData = formData
- })('Create and submit form with all field types hidden', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(hiddenFieldsData)
+ formData.logicData = cloneDeep(hiddenFieldsLogicData)
+ t.ctx.formData = formData
+})('Create and submit form with all field types hidden', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form where all fields are optional and no field is answered
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = allFields.map((field) => {
- return getBlankVersion(getOptionalVersion(field))
- })
- t.ctx.formData = formData
- })('Create and submit form with all field types optional', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = allFields.map((field) => {
+ return getBlankVersion(getOptionalVersion(field))
+ })
+ t.ctx.formData = formData
+})('Create and submit form with all field types optional', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionE2e(t, t.ctx.form, t.ctx.formData)
})
// Form where submission is prevented using chained logic
-test
- .meta('basic-env', 'true')
- .meta('full-env', 'true')
- .before(async (t) => {
- const formData = await getDefaultFormOptions()
- formData.formFields = cloneDeep(chainDisabled.fields)
- formData.logicData = cloneDeep(chainDisabled.logicData)
- t.ctx.formData = formData
- })('Create and disable form with chained logic', async (t) => {
+test.meta('basic-env', 'true').before(async (t) => {
+ const formData = await getDefaultFormOptions()
+ formData.formFields = cloneDeep(chainDisabled.fields)
+ formData.logicData = cloneDeep(chainDisabled.logicData)
+ t.ctx.formData = formData
+})('Create and disable form with chained logic', async (t) => {
t.ctx.form = await createForm(t, t.ctx.formData, Form, captchaEnabled)
await verifySubmissionDisabled(
t,
diff --git a/tests/end-to-end/login.e2e.js b/tests/end-to-end/login.e2e.js
index ed4aaacfb0..7c7e4c0601 100644
--- a/tests/end-to-end/login.e2e.js
+++ b/tests/end-to-end/login.e2e.js
@@ -62,7 +62,6 @@ test.meta('basic-env', 'true').meta('full-env', 'true')(
test
.meta('basic-env', 'true')
- .meta('full-env', 'true')
.before(async (t) => {
t.ctx.user = await createUser('existinguser@data.gov.sg')
})
@@ -87,7 +86,6 @@ test
test
.meta('basic-env', 'true')
- .meta('full-env', 'true')
.before((t) => {
t.ctx.email = 'newuser@data.gov.sg'
})
@@ -111,7 +109,6 @@ test
test
.meta('basic-env', 'true')
- .meta('full-env', 'true')
.before(async (t) => {
t.ctx.user = await createUser('preventuseremail@data.gov.sg')
})
@@ -144,7 +141,6 @@ test
test
.meta('basic-env', 'true')
- .meta('full-env', 'true')
.before(async (t) => {
t.ctx.user = await createUser('resenduseremail@data.gov.sg')
})
@@ -179,7 +175,6 @@ test
test
.meta('basic-env', 'true')
- .meta('full-env', 'true')
.before(async (t) => {
t.ctx.user = await createUser('logoutuseremail@data.gov.sg')
})
diff --git a/tests/unit/backend/helpers/jest-db.ts b/tests/unit/backend/helpers/jest-db.ts
index 996ca47731..49b286cd26 100644
--- a/tests/unit/backend/helpers/jest-db.ts
+++ b/tests/unit/backend/helpers/jest-db.ts
@@ -1,4 +1,6 @@
+// @ts-ignore
import mongoSetup from '@shelf/jest-mongodb/setup'
+// @ts-ignore
import mongoTeardown from '@shelf/jest-mongodb/teardown'
import { ObjectID } from 'bson'
import mongoose from 'mongoose'
@@ -14,7 +16,7 @@ const connect = async () => {
// Do it here so each test can have it's own mongoose instance.
await mongoSetup()
// process.env.MONGO_URL is now set by jest-mongodb.
- const conn = await mongoose.connect(process.env.MONGO_URL, {
+ const conn = await mongoose.connect(process.env.MONGO_URL!, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
diff --git a/tests/unit/backend/helpers/jest-express.ts b/tests/unit/backend/helpers/jest-express.ts
index 6b04951830..46672e5324 100644
--- a/tests/unit/backend/helpers/jest-express.ts
+++ b/tests/unit/backend/helpers/jest-express.ts
@@ -1,12 +1,12 @@
import { Request, Response } from 'express'
-const mockRequest = ({
- body,
+const mockRequest = , B>({
params,
+ body,
session,
}: {
- body?: Record
- params?: Record
+ params?: P
+ body?: B
session?: any
} = {}) => {
return {
@@ -15,9 +15,9 @@ const mockRequest = ({
session: session ?? {},
get(name: string) {
if (name === 'cf-connecting-ip') return 'MOCK_IP'
- return null
+ return undefined
},
- } as Request
+ } as Request
}
const mockResponse = (extraArgs: Partial> = {}) => {
diff --git a/tests/unit/backend/models/admin_verification.server.model.spec.ts b/tests/unit/backend/models/admin_verification.server.model.spec.ts
index d14b12ccdb..5476933b23 100644
--- a/tests/unit/backend/models/admin_verification.server.model.spec.ts
+++ b/tests/unit/backend/models/admin_verification.server.model.spec.ts
@@ -87,6 +87,7 @@ describe('AdminVerification Model', () => {
}
// Act
+ // @ts-ignore
const actualPromise = AdminVerification.create(missingContactParams)
// Assert
@@ -103,6 +104,7 @@ describe('AdminVerification Model', () => {
}
// Act
+ // @ts-ignore
const actualPromise = AdminVerification.create(missingOtpParams)
// Assert
@@ -119,6 +121,7 @@ describe('AdminVerification Model', () => {
}
// Act
+ // @ts-ignore
const actualPromise = AdminVerification.create(missingExpireParams)
// Assert
diff --git a/tests/unit/backend/models/form.server.model.spec.ts b/tests/unit/backend/models/form.server.model.spec.ts
index 2ddf825d5c..02c8e926fb 100644
--- a/tests/unit/backend/models/form.server.model.spec.ts
+++ b/tests/unit/backend/models/form.server.model.spec.ts
@@ -10,6 +10,7 @@ import {
IAgencySchema,
IEncryptedForm,
IUserSchema,
+ Permission,
ResponseMode,
} from 'src/types'
@@ -167,7 +168,9 @@ describe('Form Model', () => {
// Remove indeterministic id from actual permission list
const actualPermissionList = saved
.toObject()
- .permissionList.map((permission) => omit(permission, '_id'))
+ .permissionList!.map((permission: Permission[]) =>
+ omit(permission, '_id'),
+ )
expect(actualPermissionList).toEqual(permissionList)
})
@@ -351,7 +354,7 @@ describe('Form Model', () => {
expect(actualSavedObject).toEqual(expectedObject)
// Remove indeterministic id from actual permission list
- const actualPermissionList = (saved.toObject() as IEncryptedForm).permissionList.map(
+ const actualPermissionList = (saved.toObject() as IEncryptedForm).permissionList!.map(
(permission) => omit(permission, '_id'),
)
expect(actualPermissionList).toEqual(permissionList)
@@ -555,7 +558,9 @@ describe('Form Model', () => {
// Remove indeterministic id from actual permission list
const actualPermissionList = saved
.toObject()
- .permissionList.map((permission) => omit(permission, '_id'))
+ .permissionList!.map((permission: Permission[]) =>
+ omit(permission, '_id'),
+ )
expect(actualPermissionList).toEqual(permissionList)
})
@@ -690,7 +695,7 @@ describe('Form Model', () => {
const form = (await Form.create(emailFormParams)).toObject()
// Act
- const actualForm = (await Form.getFullFormById(form._id)).toObject()
+ const actualForm = (await Form.getFullFormById(form._id))!.toObject()
// Assert
// Form should be returned
@@ -721,7 +726,7 @@ describe('Form Model', () => {
const form = (await Form.create(encryptFormParams)).toObject()
// Act
- const actualForm = (await Form.getFullFormById(form._id)).toObject()
+ const actualForm = (await Form.getFullFormById(form._id))!.toObject()
// Assert
// Form should be returned
diff --git a/tests/unit/backend/models/form_fields.schema.spec.ts b/tests/unit/backend/models/form_fields.schema.spec.ts
index ff0cd9b410..04f96228b2 100644
--- a/tests/unit/backend/models/form_fields.schema.spec.ts
+++ b/tests/unit/backend/models/form_fields.schema.spec.ts
@@ -2,7 +2,7 @@ import { ObjectID } from 'bson'
import mongoose from 'mongoose'
import getFormModel from 'src/app/models/form.server.model'
-import { BasicField, ResponseMode } from 'src/types'
+import { BasicField, IFieldSchema, ResponseMode } from 'src/types'
import dbHandler from '../helpers/jest-db'
@@ -121,9 +121,10 @@ const createAndReturnFormField = async (
const formParam = {
...baseParams,
- form_fields: [formFieldParams],
+ responseMode: formType,
+ form_fields: [formFieldParams] as IFieldSchema[],
}
const form = await Form.create(formParam)
- return form.form_fields[0]
+ return form.form_fields![0]
}
diff --git a/tests/unit/backend/models/sms_count.server.model.spec.ts b/tests/unit/backend/models/sms_count.server.model.spec.ts
index 5643225324..a3faddf2d2 100644
--- a/tests/unit/backend/models/sms_count.server.model.spec.ts
+++ b/tests/unit/backend/models/sms_count.server.model.spec.ts
@@ -194,7 +194,7 @@ describe('SmsCount', () => {
form: MOCK_FORM_ID,
}).lean()
- expect(actualLog._id).toBeDefined()
+ expect(actualLog!._id).toBeDefined()
// Retrieve object and compare to params, remove indeterministic keys
const actualSavedObject = omit(actualLog, ['_id', 'createdAt', '__v'])
expect(actualSavedObject).toEqual(expectedLog)
@@ -220,7 +220,7 @@ describe('SmsCount', () => {
form: MOCK_FORM_ID,
}).lean()
- expect(actualLog._id).toBeDefined()
+ expect(actualLog!._id).toBeDefined()
// Retrieve object and compare to params, remove indeterministic keys
const actualSavedObject = omit(actualLog, ['_id', 'createdAt', '__v'])
expect(actualSavedObject).toEqual(expectedLog)
diff --git a/tests/unit/backend/modules/submission/submission.service.spec.ts b/tests/unit/backend/modules/submission/submission.service.spec.ts
index 97936c8e34..ec61a325eb 100644
--- a/tests/unit/backend/modules/submission/submission.service.spec.ts
+++ b/tests/unit/backend/modules/submission/submission.service.spec.ts
@@ -13,6 +13,7 @@ import {
FieldResponse,
IEmailFormSchema,
IEncryptedFormSchema,
+ IFieldSchema,
IPreventSubmitLogicSchema,
ISingleAnswerResponse,
LogicType,
@@ -32,12 +33,12 @@ const MOCK_FORM_PARAMS = {
const MOCK_ENCRYPTED_FORM_PARAMS = {
...MOCK_FORM_PARAMS,
publicKey: 'mockPublicKey',
- responseMode: 'encrypt',
+ responseMode: ResponseMode.Encrypt,
}
const MOCK_EMAIL_FORM_PARAMS = {
...MOCK_FORM_PARAMS,
emails: ['test@example.com'],
- responseMode: 'email',
+ responseMode: ResponseMode.Email,
}
// Declare here so the array is static.
@@ -66,13 +67,14 @@ describe('submission.service', () => {
defaultFormFields,
ResponseMode.Email,
)) as IEmailFormSchema
+
defaultEncryptForm = (await createAndReturnFormWithFields(
defaultFormFields,
ResponseMode.Encrypt,
)) as IEncryptedFormSchema
// Process default responses
- defaultEmailResponses = defaultEmailForm.form_fields.map((field) => {
+ defaultEmailResponses = defaultEmailForm.form_fields!.map((field) => {
return {
_id: String(field._id),
fieldType: field.fieldType,
@@ -80,7 +82,8 @@ describe('submission.service', () => {
answer: '',
}
})
- defaultEncryptResponses = defaultEncryptForm.form_fields.map((field) => {
+
+ defaultEncryptResponses = defaultEncryptForm.form_fields!.map((field) => {
return {
_id: String(field._id),
fieldType: field.fieldType,
@@ -165,7 +168,7 @@ describe('submission.service', () => {
isVisible: true,
}
- if (defaultEmailForm.form_fields[index].isVerifiable) {
+ if (defaultEmailForm.form_fields![index].isVerifiable) {
expectedProcessed.isUserVerified = true
}
expectedParsed.push(expectedProcessed)
@@ -178,10 +181,10 @@ describe('submission.service', () => {
// Arrange
const extraFieldForm = cloneDeep(defaultEmailForm)
const secondMobileField = cloneDeep(
- extraFieldForm.form_fields[TYPE_TO_INDEX_MAP[BasicField.Mobile]],
+ extraFieldForm.form_fields![TYPE_TO_INDEX_MAP[BasicField.Mobile]],
)
secondMobileField._id = new ObjectID()
- extraFieldForm.form_fields.push(secondMobileField)
+ extraFieldForm.form_fields!.push(secondMobileField)
// Act + Assert
expect(() =>
@@ -196,10 +199,10 @@ describe('submission.service', () => {
// Arrange
const extraFieldForm = cloneDeep(defaultEncryptForm)
const secondMobileField = cloneDeep(
- extraFieldForm.form_fields[TYPE_TO_INDEX_MAP[BasicField.Mobile]],
+ extraFieldForm.form_fields![TYPE_TO_INDEX_MAP[BasicField.Mobile]],
)
secondMobileField._id = new ObjectID()
- extraFieldForm.form_fields.push(secondMobileField)
+ extraFieldForm.form_fields!.push(secondMobileField)
// Act + Assert
expect(() =>
@@ -217,7 +220,7 @@ describe('submission.service', () => {
const mobileFieldIndex = TYPE_TO_INDEX_MAP[BasicField.Mobile]
const requireMobileEncryptForm = cloneDeep(defaultEncryptForm)
- requireMobileEncryptForm.form_fields[mobileFieldIndex].required = true
+ requireMobileEncryptForm.form_fields![mobileFieldIndex].required = true
// Act + Assert
expect(() =>
@@ -233,7 +236,7 @@ describe('submission.service', () => {
// Set NRIC field in form as required.
const nricFieldIndex = TYPE_TO_INDEX_MAP[BasicField.Nric]
const requireNricEmailForm = cloneDeep(defaultEmailForm)
- requireNricEmailForm.form_fields[nricFieldIndex].required = true
+ requireNricEmailForm.form_fields![nricFieldIndex].required = true
// Act + Assert
expect(() =>
@@ -249,12 +252,12 @@ 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 IPreventSubmitLogicSchema)
+ } as unknown) as IPreventSubmitLogicSchema)
// Act + Assert
expect(() =>
@@ -267,15 +270,17 @@ describe('submission.service', () => {
it('should throw error when email form submission is prevented by logic', async () => {
// Arrange
- // Mock logic util to return non-empty to check if error is thrown
+ // Mock logic util to return non-empty to check if error is thrown.
+ const mockReturnLogicUnit = ({
+ preventSubmitMessage: 'mock prevent submit',
+ conditions: [],
+ logicType: LogicType.PreventSubmit,
+ _id: 'some id',
+ } as unknown) as IPreventSubmitLogicSchema
+
jest
.spyOn(LogicUtil, 'getLogicUnitPreventingSubmit')
- .mockReturnValueOnce({
- preventSubmitMessage: 'mock prevent submit',
- conditions: [],
- logicType: LogicType.PreventSubmit,
- _id: 'some id',
- } as IPreventSubmitLogicSchema)
+ .mockReturnValueOnce(mockReturnLogicUnit)
// Act + Assert
expect(() =>
@@ -300,9 +305,6 @@ const createAndReturnFormWithFields = async (
break
case ResponseMode.Encrypt:
baseParams = MOCK_ENCRYPTED_FORM_PARAMS
- break
- default:
- baseParams = MOCK_FORM_PARAMS
}
const processedParamList = formFieldParamsList.map((params) => {
@@ -325,7 +327,7 @@ const createAndReturnFormWithFields = async (
const formParam = {
...baseParams,
- form_fields: processedParamList,
+ form_fields: processedParamList as IFieldSchema[],
}
const form = await Form.create(formParam)
diff --git a/tests/unit/backend/modules/submission/submission.utils.spec.ts b/tests/unit/backend/modules/submission/submission.utils.spec.ts
index 854914e77b..0e7e9ed8b0 100644
--- a/tests/unit/backend/modules/submission/submission.utils.spec.ts
+++ b/tests/unit/backend/modules/submission/submission.utils.spec.ts
@@ -47,7 +47,7 @@ describe('submission.utils', () => {
it('should throw error if called with invalid responseMode', async () => {
// Act + Assert
- expect(() => getModeFilter(undefined)).toThrowError(
+ expect(() => getModeFilter(undefined!)).toThrowError(
'getResponsesForEachField: Invalid response mode parameter',
)
})
diff --git a/tests/unit/backend/modules/user/user.service.spec.ts b/tests/unit/backend/modules/user/user.service.spec.ts
index c37e4e24d8..d14e0a8447 100644
--- a/tests/unit/backend/modules/user/user.service.spec.ts
+++ b/tests/unit/backend/modules/user/user.service.spec.ts
@@ -221,7 +221,7 @@ describe('user.service', () => {
const actual = await UserService.getPopulatedUserById(USER_ID)
// Assert
- expect(actual.toObject()).toEqual(expected)
+ expect(actual!.toObject()).toEqual(expected)
})
it('should return null when user cannot be found', async () => {
diff --git a/tests/unit/backend/modules/verification/verification.controller.spec.ts b/tests/unit/backend/modules/verification/verification.controller.spec.ts
index 6de8a16cfc..d70bce024e 100644
--- a/tests/unit/backend/modules/verification/verification.controller.spec.ts
+++ b/tests/unit/backend/modules/verification/verification.controller.spec.ts
@@ -33,12 +33,10 @@ const invalidOtpError = 'INVALID_OTP'
describe('Verification controller', () => {
describe('createTransaction', () => {
- let mockReq
- afterEach(() => jest.clearAllMocks())
-
- beforeAll(() => {
- mockReq = expressHandler.mockRequest({ body: { formId: MOCK_FORM_ID } })
+ const MOCK_REQ = expressHandler.mockRequest({
+ body: { formId: MOCK_FORM_ID },
})
+ afterEach(() => jest.clearAllMocks())
it('should correctly return transaction when parameters are valid', async () => {
const returnValue = {
@@ -48,7 +46,7 @@ describe('Verification controller', () => {
MockVfnService.createTransaction.mockReturnValueOnce(
Promise.resolve(returnValue),
)
- await createTransaction(mockReq, MOCK_RES, noop)
+ await createTransaction(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.createTransaction).toHaveBeenCalledWith(
MOCK_FORM_ID,
)
@@ -57,8 +55,8 @@ describe('Verification controller', () => {
})
it('should correctly return 200 when transaction is not found', async () => {
- MockVfnService.createTransaction.mockReturnValueOnce(null)
- await createTransaction(mockReq, MOCK_RES, noop)
+ MockVfnService.createTransaction.mockResolvedValueOnce(null)
+ await createTransaction(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.createTransaction).toHaveBeenCalledWith(
MOCK_FORM_ID,
)
@@ -67,15 +65,11 @@ describe('Verification controller', () => {
})
describe('getTransactionMetadata', () => {
- let mockReq
- afterEach(() => jest.clearAllMocks())
-
- beforeAll(() => {
- mockReq = expressHandler.mockRequest({
- body: {},
- params: { transactionId: MOCK_TRANSACTION_ID },
- })
+ const MOCK_REQ = expressHandler.mockRequest({
+ body: {},
+ params: { transactionId: MOCK_TRANSACTION_ID },
})
+ afterEach(() => jest.clearAllMocks())
it('should correctly return metadata when parameters are valid', async () => {
// Coerce type
@@ -85,7 +79,7 @@ describe('Verification controller', () => {
MockVfnService.getTransactionMetadata.mockReturnValueOnce(
Promise.resolve(transaction),
)
- await getTransactionMetadata(mockReq, MOCK_RES, noop)
+ await getTransactionMetadata(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransactionMetadata).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
@@ -99,7 +93,7 @@ describe('Verification controller', () => {
error.name = notFoundError
throw error
})
- await getTransactionMetadata(mockReq, MOCK_RES, noop)
+ await getTransactionMetadata(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransactionMetadata).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
@@ -109,22 +103,18 @@ describe('Verification controller', () => {
})
describe('resetFieldInTransaction', () => {
- let mockReq
- afterEach(() => jest.clearAllMocks())
-
- beforeAll(() => {
- mockReq = expressHandler.mockRequest({
- body: { fieldId: MOCK_FIELD_ID },
- params: { transactionId: MOCK_TRANSACTION_ID },
- })
+ const MOCK_REQ = expressHandler.mockRequest({
+ body: { fieldId: MOCK_FIELD_ID },
+ params: { transactionId: MOCK_TRANSACTION_ID },
})
+ afterEach(() => jest.clearAllMocks())
it('should correctly call service when params are valid', async () => {
const transaction = new Verification({ formId: new ObjectId() })
MockVfnService.getTransaction.mockReturnValueOnce(
Promise.resolve(transaction),
)
- await resetFieldInTransaction(mockReq, MOCK_RES, noop)
+ await resetFieldInTransaction(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
@@ -141,7 +131,7 @@ describe('Verification controller', () => {
error.name = notFoundError
throw error
})
- await resetFieldInTransaction(mockReq, MOCK_RES, noop)
+ await resetFieldInTransaction(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
@@ -151,27 +141,25 @@ describe('Verification controller', () => {
})
describe('getNewOtp', () => {
- let mockReq, transaction
- afterEach(() => jest.clearAllMocks())
-
- beforeAll(() => {
- mockReq = expressHandler.mockRequest({
- body: { fieldId: MOCK_FIELD_ID, answer: MOCK_ANSWER },
- params: { transactionId: MOCK_TRANSACTION_ID },
- })
- transaction = new Verification({ formId: new ObjectId() })
- MockVfnService.getTransaction.mockReturnValue(
- Promise.resolve(transaction),
- )
+ const MOCK_REQ = expressHandler.mockRequest({
+ body: { fieldId: MOCK_FIELD_ID, answer: MOCK_ANSWER },
+ params: { transactionId: MOCK_TRANSACTION_ID },
})
+ const MOCK_TRANSACTION = new Verification({ formId: new ObjectId() })
+ MockVfnService.getTransaction.mockReturnValue(
+ Promise.resolve(MOCK_TRANSACTION),
+ )
+
+ afterEach(() => jest.clearAllMocks())
+
it('should call service correctly when params are valid', async () => {
- await getNewOtp(mockReq, MOCK_RES, noop)
+ await getNewOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.getNewOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_ANSWER,
)
@@ -184,12 +172,12 @@ describe('Verification controller', () => {
error.name = notFoundError
throw error
})
- await getNewOtp(mockReq, MOCK_RES, noop)
+ await getNewOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.getNewOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_ANSWER,
)
@@ -203,12 +191,12 @@ describe('Verification controller', () => {
error.name = waitOtpError
throw error
})
- await getNewOtp(mockReq, MOCK_RES, noop)
+ await getNewOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.getNewOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_ANSWER,
)
@@ -222,12 +210,12 @@ describe('Verification controller', () => {
error.name = sendOtpError
throw error
})
- await getNewOtp(mockReq, MOCK_RES, noop)
+ await getNewOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.getNewOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_ANSWER,
)
@@ -237,29 +225,28 @@ describe('Verification controller', () => {
})
describe('verifyOtp', () => {
- let mockReq, transaction
+ const MOCK_REQ = expressHandler.mockRequest({
+ body: { fieldId: MOCK_FIELD_ID, otp: MOCK_OTP },
+ params: { transactionId: MOCK_TRANSACTION_ID },
+ })
+ const MOCK_TRANSACTION = new Verification({ formId: new ObjectId() })
afterEach(() => jest.clearAllMocks())
beforeAll(() => {
- mockReq = expressHandler.mockRequest({
- body: { fieldId: MOCK_FIELD_ID, otp: MOCK_OTP },
- params: { transactionId: MOCK_TRANSACTION_ID },
- })
- transaction = new Verification({ formId: new ObjectId() })
MockVfnService.getTransaction.mockReturnValue(
- Promise.resolve(transaction),
+ Promise.resolve(MOCK_TRANSACTION),
)
})
it('should call service correctly when params are valid', async () => {
MockVfnService.verifyOtp.mockReturnValue(Promise.resolve(MOCK_DATA))
- await verifyOtp(mockReq, MOCK_RES, noop)
+ await verifyOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.verifyOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_OTP,
)
@@ -273,12 +260,12 @@ describe('Verification controller', () => {
error.name = invalidOtpError
throw error
})
- await verifyOtp(mockReq, MOCK_RES, noop)
+ await verifyOtp(MOCK_REQ, MOCK_RES, noop)
expect(MockVfnService.getTransaction).toHaveBeenCalledWith(
MOCK_TRANSACTION_ID,
)
expect(MockVfnService.verifyOtp).toHaveBeenCalledWith(
- transaction,
+ MOCK_TRANSACTION,
MOCK_FIELD_ID,
MOCK_OTP,
)
diff --git a/tests/unit/backend/modules/verification/verification.service.spec.ts b/tests/unit/backend/modules/verification/verification.service.spec.ts
index 052c1b6c78..babde3bff3 100644
--- a/tests/unit/backend/modules/verification/verification.service.spec.ts
+++ b/tests/unit/backend/modules/verification/verification.service.spec.ts
@@ -90,8 +90,8 @@ describe('Verification service', () => {
})
expect(foundTransaction).toBeTruthy()
expect(returnedTransaction).toEqual({
- transactionId: foundTransaction._id,
- expireAt: foundTransaction.expireAt,
+ transactionId: foundTransaction!._id,
+ expireAt: foundTransaction!.expireAt,
})
})
})
@@ -138,7 +138,7 @@ describe('Verification service', () => {
const hashRetries = 1
const transaction = new Verification({
formId,
- fields: testForm.form_fields.map(({ _id, fieldType }) => ({
+ fields: testForm.form_fields!.map(({ _id, fieldType }) => ({
_id,
fieldType,
hashCreatedAt,
@@ -148,19 +148,19 @@ describe('Verification service', () => {
})),
})
await transaction.save()
- await resetFieldInTransaction(transaction, testForm.form_fields[0]._id)
+ await resetFieldInTransaction(transaction, testForm.form_fields![0]._id)
const actual = await Verification.findOne({ formId })
- expect(actual.fields[0].toObject()).toEqual({
- _id: String(testForm.form_fields[0]._id),
- fieldType: testForm.form_fields[0].fieldType,
+ expect(actual!.fields[0].toObject()).toEqual({
+ _id: String(testForm.form_fields![0]._id),
+ fieldType: testForm.form_fields![0].fieldType,
hashCreatedAt: null,
hashedOtp: null,
signedData: null,
hashRetries: 0,
})
- expect(actual.fields[1].toObject()).toEqual({
- _id: String(testForm.form_fields[1]._id),
- fieldType: testForm.form_fields[1].fieldType,
+ expect(actual!.fields[1].toObject()).toEqual({
+ _id: String(testForm.form_fields![1]._id),
+ fieldType: testForm.form_fields![1].fieldType,
hashCreatedAt,
hashedOtp,
signedData,
@@ -221,7 +221,7 @@ describe('Verification service', () => {
})
MockGenerateOtp.mockReturnValue(mockOtp)
MockBcrypt.hash.mockReturnValue(Promise.resolve(hashedOtp))
- MockFormsgSdk.verification.generateSignature.mockReturnValue(signedData)
+ MockFormsgSdk.verification.generateSignature!.mockReturnValue(signedData)
})
afterEach(async () => await dbHandler.clearDatabase())
@@ -260,7 +260,7 @@ describe('Verification service', () => {
expect(MockGenerateOtp).toHaveBeenCalled()
expect(MockBcrypt.hash.mock.calls[0][0]).toBe(mockOtp)
expect(
- MockFormsgSdk.verification.generateSignature.mock.calls[0][0],
+ MockFormsgSdk.verification.generateSignature!.mock.calls[0][0],
).toEqual({
transactionId: transaction._id,
formId: transaction.formId,
@@ -274,10 +274,10 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashCreatedAt).toBeInstanceOf(Date)
- expect(foundTransaction.fields[0].hashedOtp).toBe(hashedOtp)
- expect(foundTransaction.fields[0].signedData).toBe(signedData)
- expect(foundTransaction.fields[0].hashRetries).toBe(0)
+ expect(foundTransaction!.fields[0].hashCreatedAt).toBeInstanceOf(Date)
+ expect(foundTransaction!.fields[0].hashedOtp).toBe(hashedOtp)
+ expect(foundTransaction!.fields[0].signedData).toBe(signedData)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(0)
})
it('should send OTP when params are valid for mobile field', async () => {
@@ -291,7 +291,7 @@ describe('Verification service', () => {
expect(MockGenerateOtp).toHaveBeenCalled()
expect(MockBcrypt.hash.mock.calls[0][0]).toBe(mockOtp)
expect(
- MockFormsgSdk.verification.generateSignature.mock.calls[0][0],
+ MockFormsgSdk.verification.generateSignature!.mock.calls[0][0],
).toEqual({
transactionId: transaction._id,
formId: transaction.formId,
@@ -306,10 +306,10 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[1].hashCreatedAt).toBeInstanceOf(Date)
- expect(foundTransaction.fields[1].hashedOtp).toBe(hashedOtp)
- expect(foundTransaction.fields[1].signedData).toBe(signedData)
- expect(foundTransaction.fields[1].hashRetries).toBe(0)
+ expect(foundTransaction!.fields[1].hashCreatedAt).toBeInstanceOf(Date)
+ expect(foundTransaction!.fields[1].hashedOtp).toBe(hashedOtp)
+ expect(foundTransaction!.fields[1].signedData).toBe(signedData)
+ expect(foundTransaction!.fields[1].hashRetries).toBe(0)
})
it('should catch and re-throw errors thrown when sending email', async () => {
@@ -390,7 +390,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries)
})
it('should throw error when field ID is invalid', async () => {
@@ -402,7 +402,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries)
})
it('should throw error when hashed OTP is invalid', async () => {
@@ -415,7 +415,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries)
})
it('should throw error when hashCreatedAt is invalid', async () => {
@@ -428,7 +428,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries)
})
it('should throw error when hash is expired', async () => {
@@ -442,7 +442,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries)
})
it('should throw error when retries are maxed out', async () => {
@@ -456,7 +456,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(tooManyRetries)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(tooManyRetries)
})
it('should reject when OTP is invalid', async () => {
@@ -469,7 +469,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries + 1)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries + 1)
})
it('should resolve when OTP is invalid', async () => {
@@ -482,7 +482,7 @@ describe('Verification service', () => {
const foundTransaction = await Verification.findOne({
_id: transaction._id,
})
- expect(foundTransaction.fields[0].hashRetries).toBe(hashRetries + 1)
+ expect(foundTransaction!.fields[0].hashRetries).toBe(hashRetries + 1)
})
})
})
diff --git a/tests/unit/backend/modules/webhook/webhook.service.spec.ts b/tests/unit/backend/modules/webhook/webhook.service.spec.ts
index 2e2d9dd27c..3dfd079ce1 100644
--- a/tests/unit/backend/modules/webhook/webhook.service.spec.ts
+++ b/tests/unit/backend/modules/webhook/webhook.service.spec.ts
@@ -76,7 +76,7 @@ describe('WebhooksService', () => {
let testEncryptSubmission: IEncryptedSubmissionSchema
let testConfig: AxiosRequestConfig
- let testSubmissionWebhookView: WebhookView
+ let testSubmissionWebhookView: WebhookView | null
let testSignature: string
beforeEach(async () => {
@@ -147,7 +147,7 @@ describe('WebhooksService', () => {
let submission = await EncryptSubmission.findById(
testEncryptSubmission._id,
)
- expect(submission.webhookResponses[0]).toEqual(
+ expect(submission!.webhookResponses[0]).toEqual(
expect.objectContaining({
webhookUrl: MOCK_WEBHOOK_URL,
signature: testSignature,
@@ -172,7 +172,7 @@ describe('WebhooksService', () => {
let submission = await EncryptSubmission.findById(
testEncryptSubmission._id,
)
- expect(submission.webhookResponses[0]).toEqual(
+ expect(submission!.webhookResponses[0]).toEqual(
expect.objectContaining({
webhookUrl: MOCK_WEBHOOK_URL,
signature: testSignature,
@@ -188,7 +188,7 @@ describe('WebhooksService', () => {
toJSON: () => {}
config: object
response: AxiosResponse
- constructor(msg, response) {
+ constructor(msg: string, response: AxiosResponse) {
super(msg)
this.isAxiosError = false
this.response = response
@@ -220,7 +220,7 @@ describe('WebhooksService', () => {
let submission = await EncryptSubmission.findById(
testEncryptSubmission._id,
)
- expect(submission.webhookResponses[0]).toEqual(
+ expect(submission!.webhookResponses[0]).toEqual(
expect.objectContaining({
webhookUrl: MOCK_WEBHOOK_URL,
signature: testSignature,
diff --git a/tests/unit/backend/services/mail.service.spec.ts b/tests/unit/backend/services/mail.service.spec.ts
index ddc8a6a462..5193414b88 100644
--- a/tests/unit/backend/services/mail.service.spec.ts
+++ b/tests/unit/backend/services/mail.service.spec.ts
@@ -850,7 +850,7 @@ describe('mail.service', () => {
html: expectedMailBody,
// Attachments should be concatted with mock pdf response
attachments: [
- ...MOCK_AUTOREPLY_PARAMS.attachments,
+ ...MOCK_AUTOREPLY_PARAMS.attachments!,
{
content: MOCK_PDF,
filename: 'response.pdf',
diff --git a/tests/unit/backend/utils/attachment.spec.ts b/tests/unit/backend/utils/attachment.spec.ts
index b651e115fa..800478ed77 100644
--- a/tests/unit/backend/utils/attachment.spec.ts
+++ b/tests/unit/backend/utils/attachment.spec.ts
@@ -11,6 +11,7 @@ import {
} from 'src/app/utils/attachment'
import {
BasicField,
+ FieldResponse,
IAttachmentResponse,
ISingleAnswerResponse,
} from 'src/types'
@@ -182,7 +183,7 @@ describe('addAttachmentToResponses', () => {
})
it('should do nothing when responses are empty', () => {
- const responses = []
+ const responses: FieldResponse[] = []
addAttachmentToResponses(responses, [validSingleFile])
expect(responses).toEqual([])
})
diff --git a/tsconfig.json b/tsconfig.json
index c63b3ba484..cb1031ed8d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,7 +27,7 @@
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
- // "strict": true, /* Enable all strict type-checking options. */
+ "strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */