From 321f960b80f59c2188fa0230966dbc7258004521 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 18:05:44 +0000 Subject: [PATCH 01/32] fix(deps): bump mongoose from 5.13.5 to 6.0.12 Bumps [mongoose](https://github.com/Automattic/mongoose) from 5.13.5 to 6.0.12. - [Release notes](https://github.com/Automattic/mongoose/releases) - [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md) - [Commits](https://github.com/Automattic/mongoose/compare/5.13.5...6.0.12) --- updated-dependencies: - dependency-name: mongoose dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 144 ++++++++++++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 89 insertions(+), 57 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1d970548de..aa94110b86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5331,6 +5331,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.4.tgz", "integrity": "sha512-awqorHvQS0DqxkHQ/FxcPX9E+H7Du51Qw/2F+5TBMSaE3G0hm+8D3eXJ6MAzFw75nE8V7xF0QvzUSdxIjJb/GA==", + "dev": true, "requires": { "@types/node": "*" } @@ -5766,6 +5767,7 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", + "dev": true, "requires": { "@types/bson": "*", "@types/node": "*" @@ -5989,6 +5991,20 @@ "integrity": "sha512-ilpDKpjjq/w/IyyTuQ38mABdaEzTzTugPyU7DlMCMKd8MMYngnPKhA2TgdO1MfEDED9KVV7uSOL1fDkgwJp/wg==", "dev": true }, + "@types/webidl-conversions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz", + "integrity": "sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q==" + }, + "@types/whatwg-url": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz", + "integrity": "sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ==", + "requires": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "@types/yargs": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", @@ -10054,6 +10070,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" }, @@ -10061,7 +10078,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, @@ -17892,6 +17910,34 @@ } } }, + "mongodb-connection-string-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.1.0.tgz", + "integrity": "sha512-Qf9Zw7KGiRljWvMrrUFDdVqo46KIEiDuCzvEN97rh/PcKzk2bd6n9KuzEwBwW9xo5glwx69y1mI6s+jFUD/aIQ==", + "requires": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^9.1.0" + }, + "dependencies": { + "tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "requires": { + "punycode": "^2.1.1" + } + }, + "whatwg-url": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-9.1.0.tgz", + "integrity": "sha512-CQ0UcrPHyomtlOCot1TL77WyMIm/bCwrJ2D6AOKGwEczU9EpyoqAokfqrf/MioU9kHcMsmJZcg1egXix2KYEsA==", + "requires": { + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + } + } + } + }, "mongodb-memory-server-core": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-6.9.6.tgz", @@ -18160,50 +18206,47 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "5.13.5", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.13.5.tgz", - "integrity": "sha512-sSUAk9GWgA8r3w3nVNrNjBaDem86aevwXO8ltDMKzCf+rjnteMMQkXHQdn1ePkt7alROEPZYCAjiRjptWRSPiQ==", + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz", + "integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==", "requires": { - "@types/mongodb": "^3.5.27", - "bson": "^1.1.4", + "bson": "^4.2.2", "kareem": "2.3.2", - "mongodb": "3.6.10", - "mongoose-legacy-pluralize": "1.0.2", - "mpath": "0.8.3", - "mquery": "3.2.5", + "mongodb": "4.1.3", + "mpath": "0.8.4", + "mquery": "4.0.0", "ms": "2.1.2", - "optional-require": "1.0.x", "regexp-clone": "1.0.0", - "safe-buffer": "5.2.1", "sift": "13.5.2", "sliced": "1.0.1" }, "dependencies": { "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.3.tgz", + "integrity": "sha512-qVX7LX79Mtj7B3NPLzCfBiCP6RAsjiV8N63DjlaVVpZW+PFoDTxQ4SeDbSpcqgE6mXksM5CAwZnXxxxn/XwC0g==", + "requires": { + "buffer": "^5.6.0" + } + }, + "denque": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" }, "mongodb": { - "version": "3.6.10", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.10.tgz", - "integrity": "sha512-fvIBQBF7KwCJnDZUnFFy4WqEFP8ibdXeFANnylW19+vOwdjOAvqIzPdsNCEMT6VKTHnYu4K64AWRih0mkFms6Q==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz", + "integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==", "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.0.3", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "bson": "^4.5.2", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.0.0", + "saslprep": "^1.0.3" } } } }, - "mongoose-legacy-pluralize": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" - }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -18266,31 +18309,27 @@ } }, "mpath": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz", - "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.4.tgz", + "integrity": "sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g==" }, "mquery": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", - "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.0.tgz", + "integrity": "sha512-nGjm89lHja+T/b8cybAby6H0YgA4qYC/lx6UlwvHGqvTq8bDaNeCwl1sY8uRELrNbVWJzIihxVd+vphGGn1vBw==", "requires": { - "bluebird": "3.5.1", - "debug": "3.1.0", + "debug": "4.x", "regexp-clone": "^1.0.0", - "safe-buffer": "5.1.2", "sliced": "1.0.1" }, "dependencies": { - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } } } }, @@ -19038,11 +19077,6 @@ "last-call-webpack-plugin": "^3.0.0" } }, - "optional-require": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", - "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==" - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -20401,8 +20435,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "puppeteer-core": { "version": "5.3.1", @@ -25579,8 +25612,7 @@ "webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { "version": "4.46.0", diff --git a/package.json b/package.json index a56084bebc..e138874c5e 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "lodash": "^4.17.21", "moment-timezone": "0.5.33", "mongodb-uri": "^0.9.7", - "mongoose": "^5.13.5", + "mongoose": "^6.0.12", "multiparty": ">=4.2.2", "neverthrow": "^4.3.0", "ng-infinite-scroll": "^1.3.0", From 5cdd3aaeb25e8a8c1c14f7d0b8bb334689aae0ba Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 13:34:46 +0800 Subject: [PATCH 02/32] fix(deps): update connect-mongo to v4.6.0 --- package-lock.json | 33 +++++++-------------------------- package.json | 2 +- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa94110b86..e87c3bf6b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9209,40 +9209,21 @@ } }, "connect-mongo": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.4.1.tgz", - "integrity": "sha512-I1QUE2tSGPtIBDAL2sFqUEPspDeJOR0u4g+N41ARJZk958pncu2PBG48Ev++fnldljobpIfdafak7hSlPYarvA==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/connect-mongo/-/connect-mongo-4.6.0.tgz", + "integrity": "sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==", "requires": { "debug": "^4.3.1", - "kruptein": "^3.0.0", - "mongodb": "3.6.5" + "kruptein": "^3.0.0" }, "dependencies": { - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" - }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } - }, - "mongodb": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz", - "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==", - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - } } } }, diff --git a/package.json b/package.json index e138874c5e..35478cafd3 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "celebrate": "^15.0.0", "compression": "~1.7.2", "connect-datadog": "0.0.9", - "connect-mongo": "^4.4.1", + "connect-mongo": "^4.6.0", "convict": "^6.2.1", "convict-format-with-validator": "^6.2.0", "cookie-parser": "~1.4.0", From 8c70677a6b37d5af2b4c11a0b0598f85fcd3c866 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 13:35:33 +0800 Subject: [PATCH 03/32] chore(dev-deps): update @types/mongodb to v4.0.7 --- package-lock.json | 36 ++++++++++++++---------------------- package.json | 2 +- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index e87c3bf6b6..3a975067db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5327,15 +5327,6 @@ "@types/node": "*" } }, - "@types/bson": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.4.tgz", - "integrity": "sha512-awqorHvQS0DqxkHQ/FxcPX9E+H7Du51Qw/2F+5TBMSaE3G0hm+8D3eXJ6MAzFw75nE8V7xF0QvzUSdxIjJb/GA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/busboy": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-0.3.1.tgz", @@ -5764,13 +5755,12 @@ "dev": true }, "@types/mongodb": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.20.tgz", - "integrity": "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-4.0.7.tgz", + "integrity": "sha512-lPUYPpzA43baXqnd36cZ9xxorprybxXDzteVKCPAdp14ppHtFJHnXYvNpmBvtMUTb5fKXVv6sVbzo1LHkWhJlw==", "dev": true, "requires": { - "@types/bson": "*", - "@types/node": "*" + "mongodb": "*" } }, "@types/mongodb-uri": { @@ -7749,6 +7739,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dev": true, "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -10378,7 +10369,8 @@ "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", + "dev": true }, "depd": { "version": "1.1.2", @@ -16543,9 +16535,9 @@ "dev": true }, "kruptein": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.0.tgz", - "integrity": "sha512-Fh5sIb+3XI9L12GsgeBQqXVRPLB1HVViKSUkqPPOcqTEX4NwoF8Z3pEfMSl3Psd1j+QlloV8Uxxwp4gk3aFBGA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kruptein/-/kruptein-3.0.3.tgz", + "integrity": "sha512-v5mqSHKS2M1xWUo5V7Q6TMcj1vjTgKWvfspizn6Z939Cmv8NNn5E+Z4LeGBEKDL3yT4pMXaRTjh98oksGTDntA==", "requires": { "asn1.js": "^5.4.1" } @@ -17872,7 +17864,6 @@ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", "dev": true, - "optional": true, "requires": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -17886,8 +17877,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", - "dev": true, - "optional": true + "dev": true } } }, @@ -21085,6 +21075,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dev": true, "requires": { "resolve-from": "^2.0.0", "semver": "^5.1.0" @@ -21177,7 +21168,8 @@ "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "dev": true }, "resolve-url": { "version": "0.2.1", diff --git a/package.json b/package.json index 35478cafd3..1f42067ced 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "@types/ip": "^1.1.0", "@types/jest": "^27.0.2", "@types/json-stringify-safe": "^5.0.0", - "@types/mongodb": "^3.6.20", + "@types/mongodb": "^4.0.7", "@types/mongodb-uri": "^0.9.1", "@types/node": "^14.17.32", "@types/nodemailer": "^6.4.4", From 23862aa598595a417f9a25fc9cb383088c3e4a2f Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 14:10:08 +0800 Subject: [PATCH 04/32] fix(FormModel): update schema validators to 6.x syntax --- src/app/models/form.server.model.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index fb1b76b921..1cd1563e30 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -132,15 +132,10 @@ const EncryptedFormSchema = new Schema({ const EmailFormSchema = new Schema({ emails: { - type: [ - { - type: String, - trim: true, - }, - ], + type: [{ type: String, trim: true }], set: transformEmails, validate: { - validator: (v: string[]) => { + validator: (v: unknown) => { if (!Array.isArray(v)) return false if (v.length === 0) return false return v.every((email) => validator.isEmail(email)) @@ -162,11 +157,13 @@ const compileFormModel = (db: Mongoose): IFormModel => { { title: { type: String, - validate: [ - /^[a-zA-Z0-9_\-./() &`;'"]*$/, - 'Form name cannot contain special characters', - ], - required: 'Form name cannot be blank', + validate: { + validator: (v: string) => { + return /^[a-zA-Z0-9_\-./() &`;'"]*$/.test(v) + }, + message: 'Form name cannot contain special characters', + }, + required: [true, 'Form name cannot be blank'], minlength: [4, 'Form name must be at least 4 characters'], maxlength: [200, 'Form name can have a maximum of 200 characters'], // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -734,7 +731,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { logicId: string, ): Promise { return this.findByIdAndUpdate( - mongoose.Types.ObjectId(formId), + formId, { $pull: { form_logics: { _id: logicId } }, }, From 7c6fed4739db42c1f9b355adea119a2350d4bba2 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 14:11:01 +0800 Subject: [PATCH 05/32] fix(config): use ConnectOptions type declaration from mongoose@6 --- src/types/config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/config.ts b/src/types/config.ts index da4de06ca4..79354ee5a3 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,7 +1,7 @@ import { PackageMode } from '@opengovsg/formsg-sdk/dist/types' import aws from 'aws-sdk' import { SessionOptions } from 'express-session' -import { ConnectionOptions } from 'mongoose' +import { ConnectOptions } from 'mongoose' import Mail from 'nodemailer/lib/mailer' // Enums @@ -23,7 +23,7 @@ export type AppConfig = { export type DbConfig = { uri: string - options: ConnectionOptions + options: ConnectOptions } export type AwsConfig = { From 3c61aa4ea862fb04ed52f90b238095373c08a68f Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 14:11:45 +0800 Subject: [PATCH 06/32] fix(agency): correct types to use mongoose@6 types --- src/app/models/agency.server.model.ts | 1 - src/types/agency.ts | 10 +++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/models/agency.server.model.ts b/src/app/models/agency.server.model.ts index 8d159bf3ed..51dd4fb3e6 100644 --- a/src/app/models/agency.server.model.ts +++ b/src/app/models/agency.server.model.ts @@ -21,7 +21,6 @@ export const AGENCY_PUBLIC_FIELDS = [ const AgencySchema = new Schema< IAgencyDocument, IAgencyModel, - undefined, AgencyInstanceMethods >( { diff --git a/src/types/agency.ts b/src/types/agency.ts index 0bd310fa08..4d1ba5357b 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -20,8 +20,12 @@ export interface IAgencyDocument extends IAgencySchema { // Used to cast created documents whenever needed. export type AgencyDocument = EnforceDocument< IAgencyDocument, - AgencyInstanceMethods + AgencyInstanceMethods, + Record > -// eslint-disable-next-line @typescript-eslint/ban-types -export type IAgencyModel = Model +export type IAgencyModel = Model< + IAgencyDocument, + Record, + AgencyInstanceMethods +> From cdd269d6625971dc6e2dc352fda03b0746e87c38 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 14:47:19 +0800 Subject: [PATCH 07/32] fix(config): remove unsupported options (now default) see https://mongoosejs.com/docs/migrating_to_6.html#no-more-deprecation-warning-options --- src/app/config/config.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/app/config/config.ts b/src/app/config/config.ts index 2b7a916c13..6ce896dd47 100644 --- a/src/app/config/config.ts +++ b/src/app/config/config.ts @@ -110,14 +110,6 @@ const dbConfig: DbConfig = { pass: '', // Only create indexes in dev env to avoid adverse production impact. autoIndex: isDev, - // Avoid using deprecated URL string parser in MongoDB driver - useNewUrlParser: true, - useUnifiedTopology: true, - // Avoid using deprecated collection.ensureIndex internally - useCreateIndex: true, - // upgrade to mongo driver's native findOneAndUpdate function instead of - // findAndModify. - useFindAndModify: false, promiseLibrary: global.Promise, }, } @@ -180,7 +172,7 @@ const cookieSettings: SessionOptions['cookie'] = { /** * Fetches AWS credentials */ -const configureAws = async () => { +const configureAws = async (): Promise => { if (!isDev) { const getCredentials = () => { return new Promise((resolve, reject) => { From 07e4d43100d2436fef755f8b73635b7911fbe914 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:21:33 +0800 Subject: [PATCH 08/32] fix: misc type errors due to mongoose@6 --- src/app/loaders/express/session.ts | 3 ++- src/app/models/agency.server.model.ts | 10 +++------ src/app/models/form.server.model.ts | 22 ++++++++----------- .../form_statistics_total.server.model.ts | 4 ++-- src/app/models/submission.server.model.ts | 1 + src/app/modules/auth/auth.service.ts | 2 +- src/app/modules/examples/examples.queries.ts | 3 +++ src/app/services/mail/mail.types.ts | 3 +++ src/app/services/sms/sms.types.ts | 12 ++++++---- .../__tests__/attachment-validation.spec.ts | 2 +- .../__tests__/checkbox-validation.spec.ts | 2 +- src/types/agency.ts | 7 ++---- src/types/form_logic.ts | 3 ++- src/types/form_statistics_total.ts | 4 +--- 14 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/app/loaders/express/session.ts b/src/app/loaders/express/session.ts index fb00658852..259bc68f9f 100644 --- a/src/app/loaders/express/session.ts +++ b/src/app/loaders/express/session.ts @@ -2,6 +2,7 @@ import MongoStore from 'connect-mongo' import cookieParser from 'cookie-parser' import { RequestHandler } from 'express' import session from 'express-session' +import { MongoClient } from 'mongodb' import { Connection } from 'mongoose' import config from '../../config/config' @@ -15,7 +16,7 @@ const sessionMiddlewares = (connection: Connection): RequestHandler[] => { cookie: config.cookieSettings, name: 'connect.sid', store: MongoStore.create({ - client: connection.getClient(), + client: connection.getClient() as MongoClient, }), }) diff --git a/src/app/models/agency.server.model.ts b/src/app/models/agency.server.model.ts index 51dd4fb3e6..8061651678 100644 --- a/src/app/models/agency.server.model.ts +++ b/src/app/models/agency.server.model.ts @@ -2,11 +2,7 @@ import { pick } from 'lodash' import { Mongoose, Schema } from 'mongoose' import { PublicAgencyDto } from '../../../shared/types' -import { - AgencyInstanceMethods, - IAgencyDocument, - IAgencyModel, -} from '../../types' +import { AgencyInstanceMethods, IAgencyModel, IAgencySchema } from '../../types' export const AGENCY_SCHEMA_ID = 'Agency' @@ -19,7 +15,7 @@ export const AGENCY_PUBLIC_FIELDS = [ ] const AgencySchema = new Schema< - IAgencyDocument, + IAgencySchema, IAgencyModel, AgencyInstanceMethods >( @@ -64,7 +60,7 @@ AgencySchema.methods.getPublicView = function (): PublicAgencyDto { } const compileAgencyModel = (db: Mongoose): IAgencyModel => { - return db.model(AGENCY_SCHEMA_ID, AgencySchema) + return db.model(AGENCY_SCHEMA_ID, AgencySchema) as IAgencyModel } /** diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index 1cd1563e30..c8721d79a5 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -1,12 +1,6 @@ import BSON, { ObjectId } from 'bson-ext' import { compact, omit, pick, uniq } from 'lodash' -import mongoose, { - Mongoose, - Query, - Schema, - SchemaOptions, - Types, -} from 'mongoose' +import mongoose, { Mongoose, Schema, SchemaOptions, Types } from 'mongoose' import validator from 'validator' import { @@ -676,12 +670,14 @@ const compileFormModel = (db: Mongoose): IFormModel => { formId: string, fields?: (keyof IPopulatedForm)[], ): Promise { - return this.findById(formId, fields).populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) as Query + return this.findById(formId, fields) + .populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) + .exec() as Promise } // Deactivate form by ID diff --git a/src/app/models/form_statistics_total.server.model.ts b/src/app/models/form_statistics_total.server.model.ts index 76aa6962c7..20dec2c88b 100644 --- a/src/app/models/form_statistics_total.server.model.ts +++ b/src/app/models/form_statistics_total.server.model.ts @@ -53,7 +53,7 @@ const compileFormStatisticsTotalModel = (db: Mongoose) => { FormStatisticsTotalSchema.statics.aggregateFormCount = function ( minSubCount: number, ): Promise { - return this.aggregate([ + return this.aggregate<{ numActiveForms: number }>([ { $match: { totalCount: { @@ -64,7 +64,7 @@ const compileFormStatisticsTotalModel = (db: Mongoose) => { { $count: 'numActiveForms', }, - ]).exec() + ]).exec() as Promise } const FormStatisticsTotalModel = db.model< diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index db42913419..e42e5c536a 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -299,6 +299,7 @@ EncryptSubmissionSchema.statics.findAllMetadataByFormId = function ( .match({ // Casting to ObjectId as Mongoose does not cast pipeline stages. // See https://mongoosejs.com/docs/api.html#aggregate_Aggregate. + // @ts-expect-error Type error in definitions, see https://github.com/Automattic/mongoose/issues/10960. form: mongoose.Types.ObjectId(formId), submissionType: SubmissionType.Encrypt, }) diff --git a/src/app/modules/auth/auth.service.ts b/src/app/modules/auth/auth.service.ts index 197b9d5a70..901445f0da 100644 --- a/src/app/modules/auth/auth.service.ts +++ b/src/app/modules/auth/auth.service.ts @@ -55,7 +55,7 @@ export const validateEmailDomain = ( const emailDomain = email.split('@').pop() return ResultAsync.fromPromise( - AgencyModel.findOne({ emailDomain }).exec(), + AgencyModel.findOne({ emailDomain }).exec() as Promise, (error) => { logger.error({ message: 'Database error when retrieving agency', diff --git a/src/app/modules/examples/examples.queries.ts b/src/app/modules/examples/examples.queries.ts index af8287aee0..1c0892228b 100644 --- a/src/app/modules/examples/examples.queries.ts +++ b/src/app/modules/examples/examples.queries.ts @@ -33,6 +33,7 @@ export const searchFormsWithText = ( export const searchFormsById = (formId: string): Record[] => [ { $match: { + // @ts-expect-error Type error in definitions, see https://github.com/Automattic/mongoose/issues/10960. _id: mongoose.Types.ObjectId(formId), }, }, @@ -107,6 +108,7 @@ export const filterByAgencyId = ( ): Record[] => [ { $match: { + // @ts-expect-error Type error in definitions, see https://github.com/Automattic/mongoose/issues/10960. 'agencyInfo._id': mongoose.Types.ObjectId(agencyId), }, }, @@ -348,6 +350,7 @@ export const searchSubmissionsForForm = ( ): Record[] => [ { $match: { + // @ts-expect-error Type error in definitions, see https://github.com/Automattic/mongoose/issues/10960. [key]: mongoose.Types.ObjectId(formId), }, }, diff --git a/src/app/services/mail/mail.types.ts b/src/app/services/mail/mail.types.ts index 2432b59a3e..a25f50d59b 100644 --- a/src/app/services/mail/mail.types.ts +++ b/src/app/services/mail/mail.types.ts @@ -91,6 +91,9 @@ export type BounceNotificationHtmlData = { } export type AdminSmsDisabledData = { + // Type instantiation is excessively deep and possibly infinite. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore forms: FormLinkView[] } & SmsVerificationTiers diff --git a/src/app/services/sms/sms.types.ts b/src/app/services/sms/sms.types.ts index 0862c886d6..37a996969d 100644 --- a/src/app/services/sms/sms.types.ts +++ b/src/app/services/sms/sms.types.ts @@ -61,17 +61,21 @@ export interface IVerificationSmsCount extends ISmsCount { userId: IUserSchema['_id'] } isOnboardedAccount: boolean + smsType: SmsType.Verification } -export interface IVerificationSmsCountSchema extends ISmsCountSchema { - isOnboardedAccount: boolean -} +export interface IVerificationSmsCountSchema + extends IVerificationSmsCount, + Document {} export interface IAdminContactSmsCount extends ISmsCount { admin: IUserSchema['_id'] + smsType: SmsType.AdminContact } -export type IAdminContactSmsCountSchema = ISmsCountSchema +export interface IAdminContactSmsCountSchema + extends IAdminContactSmsCount, + Document {} export interface IFormDeactivatedSmsCount extends ISmsCount, diff --git a/src/app/utils/field-validation/validators/__tests__/attachment-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/attachment-validation.spec.ts index d43255c567..0806e06f7a 100644 --- a/src/app/utils/field-validation/validators/__tests__/attachment-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/attachment-validation.spec.ts @@ -1,4 +1,4 @@ -import { ObjectId } from 'mongodb' +import { ObjectId } from 'bson-ext' import { ValidateFieldError } from 'src/app/modules/submission/submission.errors' import { validateField } from 'src/app/utils/field-validation/' diff --git a/src/app/utils/field-validation/validators/__tests__/checkbox-validation.spec.ts b/src/app/utils/field-validation/validators/__tests__/checkbox-validation.spec.ts index c1740b4ebd..f9886ff1a4 100644 --- a/src/app/utils/field-validation/validators/__tests__/checkbox-validation.spec.ts +++ b/src/app/utils/field-validation/validators/__tests__/checkbox-validation.spec.ts @@ -1,4 +1,4 @@ -import { ObjectId } from 'mongodb' +import { ObjectId } from 'bson-ext' import { ValidateFieldError } from 'src/app/modules/submission/submission.errors' import { validateField } from 'src/app/utils/field-validation' diff --git a/src/types/agency.ts b/src/types/agency.ts index 4d1ba5357b..e9d83c1345 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -24,8 +24,5 @@ export type AgencyDocument = EnforceDocument< Record > -export type IAgencyModel = Model< - IAgencyDocument, - Record, - AgencyInstanceMethods -> +// eslint-disable-next-line @typescript-eslint/ban-types +export type IAgencyModel = Model diff --git a/src/types/form_logic.ts b/src/types/form_logic.ts index 1d884216a3..492459b5eb 100644 --- a/src/types/form_logic.ts +++ b/src/types/form_logic.ts @@ -10,7 +10,7 @@ import { ShowFieldLogic, } from '../../shared/types' -import { IFieldSchema } from './field' +import { FormFieldSchema, IFieldSchema } from './field' export interface ICondition extends FormCondition { field: IFieldSchema['_id'] @@ -27,6 +27,7 @@ export interface IShowFieldsLogicSchema extends ILogicSchema, ShowFieldLogic, Document { + show: FormFieldSchema['_id'][] logicType: LogicType.ShowFields conditions: IConditionSchema[] } diff --git a/src/types/form_statistics_total.ts b/src/types/form_statistics_total.ts index a3ff50e67a..9152cd2bf2 100644 --- a/src/types/form_statistics_total.ts +++ b/src/types/form_statistics_total.ts @@ -12,9 +12,7 @@ export interface IFormStatisticsTotalSchema extends IFormStatisticsTotal, Document {} -export type AggregateFormCountResult = - | [{ numActiveForms: number } | undefined] - | never[] +export type AggregateFormCountResult = [{ numActiveForms: number }] | never[] export interface IFormStatisticsTotalModel extends Model { From 830b1fe27fb9d79a2825a199e319ea8a0e0553f6 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:22:14 +0800 Subject: [PATCH 09/32] chore(deps): add mongodb dependency --- package-lock.json | 153 ++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 101 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a975067db..908ff08820 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5754,15 +5754,6 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, - "@types/mongodb": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-4.0.7.tgz", - "integrity": "sha512-lPUYPpzA43baXqnd36cZ9xxorprybxXDzteVKCPAdp14ppHtFJHnXYvNpmBvtMUTb5fKXVv6sVbzo1LHkWhJlw==", - "dev": true, - "requires": { - "mongodb": "*" - } - }, "@types/mongodb-uri": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@types/mongodb-uri/-/mongodb-uri-0.9.1.tgz", @@ -7735,16 +7726,6 @@ "file-uri-to-path": "1.0.0" } }, - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -10367,10 +10348,9 @@ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", - "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", + "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" }, "depd": { "version": "1.1.2", @@ -17860,24 +17840,23 @@ } }, "mongodb": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", - "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", - "dev": true, + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.4.tgz", + "integrity": "sha512-Cv/sk8on/tpvvqbEvR1h03mdyNdyvvO+WhtFlL4jrZ+DSsN/oSQHVqmJQI/sBCqqbOArFcYCAYDfyzqFwV4GSQ==", "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "require_optional": "^1.0.1", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" + "bson": "^4.5.4", + "denque": "^2.0.1", + "mongodb-connection-string-url": "^2.1.0", + "saslprep": "^1.0.3" }, "dependencies": { "bson": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", - "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", - "dev": true + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz", + "integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==", + "requires": { + "buffer": "^5.6.0" + } } } }, @@ -17954,6 +17933,13 @@ "readable-stream": "^3.4.0" } }, + "bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "dev": true, + "optional": true + }, "camelcase": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", @@ -17980,6 +17966,13 @@ "ms": "2.1.2" } }, + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "dev": true, + "optional": true + }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -18052,6 +18045,59 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mongodb": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", + "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", + "dev": true, + "optional": true, + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.1.8", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + }, + "dependencies": { + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dev": true, + "optional": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + } + } + } + } + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -19048,6 +19094,16 @@ "last-call-webpack-plugin": "^3.0.0" } }, + "optional-require": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", + "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", + "dev": true, + "optional": true, + "requires": { + "require-at": "^1.0.6" + } + }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -21053,6 +21109,13 @@ "tough-cookie": "^2.3.3" } }, + "require-at": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", + "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", + "dev": true, + "optional": true + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -21071,16 +21134,6 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "dev": true, - "requires": { - "resolve-from": "^2.0.0", - "semver": "^5.1.0" - } - }, "requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -21165,12 +21218,6 @@ } } }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", - "dev": true - }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", diff --git a/package.json b/package.json index 1f42067ced..b239d27146 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "libphonenumber-js": "^1.9.38", "lodash": "^4.17.21", "moment-timezone": "0.5.33", + "mongodb": "^4.1.4", "mongodb-uri": "^0.9.7", "mongoose": "^6.0.12", "multiparty": ">=4.2.2", @@ -181,7 +182,6 @@ "@types/ip": "^1.1.0", "@types/jest": "^27.0.2", "@types/json-stringify-safe": "^5.0.0", - "@types/mongodb": "^4.0.7", "@types/mongodb-uri": "^0.9.1", "@types/node": "^14.17.32", "@types/nodemailer": "^6.4.4", From ebd8945b67683ad6353c8076c310d4d9648c7677 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:22:55 +0800 Subject: [PATCH 10/32] fix(UserModel): update schema to fit new syntax and MongoServerError --- src/app/models/user.server.model.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts index a6b063910f..9920e6127a 100644 --- a/src/app/models/user.server.model.ts +++ b/src/app/models/user.server.model.ts @@ -1,5 +1,5 @@ import { parsePhoneNumberFromString } from 'libphonenumber-js/mobile' -import { MongoError } from 'mongodb' +import { MongoServerError } from 'mongodb' import { CallbackError, Mongoose, Schema } from 'mongoose' import validator from 'validator' @@ -22,11 +22,9 @@ const compileUserModel = (db: Mongoose) => { { email: { type: String, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore trim: true, unique: true, - required: 'Please enter your email', + required: [true, 'Please enter your email'], validate: { // Check if email entered exists in the Agency collection validator: async (value: string) => { @@ -94,7 +92,11 @@ const compileUserModel = (db: Mongoose) => { 'save', function (err: Error, _doc: unknown, next: (err?: CallbackError) => void) { if (err) { - if (err.name === 'MongoError' && (err as MongoError)?.code === 11000) { + if ( + // https://mongoosejs.com/docs/migrating_to_6.html#mongoerror-is-now-mongoservererror + (err.name === 'MongoError' || err.name === 'MongoServerError') && + (err as MongoServerError)?.code === 11000 + ) { next(new Error('Account already exists with this email')) } else { next(err) From 393128f9d6a63489208794f643b6477dc9e7743b Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:23:47 +0800 Subject: [PATCH 11/32] fix(MyInfoModel): use Mixed type instead of undocumented Object type --- src/app/modules/myinfo/myinfo_hash.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/modules/myinfo/myinfo_hash.model.ts b/src/app/modules/myinfo/myinfo_hash.model.ts index 28bbdb4d5d..9769d8b8dd 100644 --- a/src/app/modules/myinfo/myinfo_hash.model.ts +++ b/src/app/modules/myinfo/myinfo_hash.model.ts @@ -22,7 +22,7 @@ const MyInfoHashSchema = new Schema( required: true, }, fields: { - type: Object, + type: Schema.Types.Mixed, required: true, }, expireAt: { From e64843cb1469aeb5e4bd77edecb1387eeab31971 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:24:33 +0800 Subject: [PATCH 12/32] fix: add typecasts to get around excessively deep TS error --- .../form/admin-form/admin-form.service.ts | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts index e6e3716c81..211db2aa76 100644 --- a/src/app/modules/form/admin-form/admin-form.service.ts +++ b/src/app/modules/form/admin-form/admin-form.service.ts @@ -336,23 +336,21 @@ export const transferFormOwnership = ( ), ), ) - // Step 4: Populate updated form. - .andThen((updatedForm) => - ResultAsync.fromPromise( - updatedForm - .populate({ path: 'admin', populate: { path: 'agency' } }) - .execPopulate(), - (error) => { - logger.error({ - message: 'Error occurred whilst populating form with admin', - meta: logMeta, - error, - }) + .andThen((updatedForm) => { + const populatePromise = updatedForm.populate({ + path: 'admin', + populate: { path: 'agency' }, + }) as Promise + return ResultAsync.fromPromise(populatePromise, (error) => { + logger.error({ + message: 'Error occurred whilst populating form with admin', + meta: logMeta, + error, + }) - return new DatabaseError(getMongoErrorMessage(error)) - }, - ), - ) + return new DatabaseError(getMongoErrorMessage(error)) + }) + }) ) } @@ -642,7 +640,9 @@ export const editFormFields = ( ).asyncAndThen((newFormFields) => { // Update form fields of original form. originalForm.form_fields = newFormFields - return ResultAsync.fromPromise(originalForm.save(), (error) => { + + const savePromise = originalForm.save() as Promise + return ResultAsync.fromPromise(savePromise, (error) => { logger.error({ message: 'Error encountered while editing form fields', meta: { @@ -683,8 +683,12 @@ export const updateForm = ( // Updating some part of form, override original form with new updated form. assignIn(originalForm, scrubbedFormParams) + // Type instantiation is excessively deep and possibly infinite. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const savePromise = originalForm.save() as Promise - return ResultAsync.fromPromise(originalForm.save(), (error) => { + return ResultAsync.fromPromise(savePromise, (error) => { logger.error({ message: 'Error encountered while updating form', meta: { From 30d768078edcf12bc8a2347fa59300967aa016ec Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 8 Nov 2021 16:25:17 +0800 Subject: [PATCH 13/32] fix(FormLogicModel): add typecast for schema Schema.Types.Mixed --- src/app/models/form_logic.server.schema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/models/form_logic.server.schema.ts b/src/app/models/form_logic.server.schema.ts index b50d7df861..e505700626 100644 --- a/src/app/models/form_logic.server.schema.ts +++ b/src/app/models/form_logic.server.schema.ts @@ -26,7 +26,8 @@ const LogicConditionSchema = new Schema({ enum: Object.values(LogicConditionState), }, value: { - type: Schema.Types.Mixed, + // Bug where Mixed is not an `any` type, resulting in TypeScript errors. + type: Schema.Types.Mixed as any, required: true, }, ifValueType: { From 03e8d342c469739d0ffa57894453361d9c4fd0bc Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 9 Nov 2021 10:48:31 +0800 Subject: [PATCH 14/32] fix(agency): further update types --- src/types/agency.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types/agency.ts b/src/types/agency.ts index e9d83c1345..cb55ead61a 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -21,7 +21,8 @@ export interface IAgencyDocument extends IAgencySchema { export type AgencyDocument = EnforceDocument< IAgencyDocument, AgencyInstanceMethods, - Record + // eslint-disable-next-line @typescript-eslint/ban-types + {} > // eslint-disable-next-line @typescript-eslint/ban-types From 587da8e3cefd12e013bd369856f7158131a24da1 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 22 Nov 2021 13:32:26 +0800 Subject: [PATCH 15/32] fix(packages): update bson-ext to v4.0.2, and mongoose to v6.0.13 --- package-lock.json | 214 ++++-------- package.json | 4 +- src/app/loaders/mongoose.ts | 2 +- src/app/models/form.server.model.ts | 27 +- src/types/agency.ts | 4 +- src/types/vendor/bson-ext.d.ts | 505 +--------------------------- 6 files changed, 73 insertions(+), 683 deletions(-) diff --git a/package-lock.json b/package-lock.json index 908ff08820..b6aa29762b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7995,19 +7995,29 @@ } }, "bson": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/bson/-/bson-2.0.8.tgz", - "integrity": "sha512-0F0T3gHeOwJzHWcN60BZomqj5hCBDRk4b3fANuruvDTnyJJ8sggABKSaePM2F34THNZZSIlB2P1mk2nQWgBr9w==" + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz", + "integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==", + "requires": { + "buffer": "^5.6.0" + } }, "bson-ext": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/bson-ext/-/bson-ext-2.0.5.tgz", - "integrity": "sha512-N88i6ikegUf/DeIZ2qziWPLeNjXG0pr+a5vMZdC+ZjQAYdLELP2ReWO0GE4FEApoZmtO574QRInHQUTvXmux6Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bson-ext/-/bson-ext-4.0.2.tgz", + "integrity": "sha512-Nrf6KyyBtTLVrns1++ODGkRgR4UDU2LkkVb7UZvr1Q7YxNd23omlyDdh1yva2Nm4ehGJ9hceIJzFfIZaf2UoCQ==", "requires": { - "bindings": "^1.3.0", - "bson": "^2.0.2", - "nan": "^2.14.0", - "prebuild-install": "5.3.0" + "bindings": "^1.5.0", + "bson": "^4.5.2", + "nan": "^2.14.2", + "prebuild-install": "^6.1.2" + }, + "dependencies": { + "nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + } } }, "buffer": { @@ -8019,20 +8029,6 @@ "ieee754": "^1.1.4" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" - }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -8043,11 +8039,6 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" - }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -10073,11 +10064,18 @@ "dev": true }, "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "^2.0.0" + }, + "dependencies": { + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + } } }, "decompress-zip": { @@ -17787,6 +17785,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, "requires": { "minimist": "^1.2.5" } @@ -18223,13 +18222,13 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.12.tgz", - "integrity": "sha512-BvsZk7zEEhb1AgQFLtxN9C+7qgy5edRuA3ZDDwHU+kHG/HM44vI6FdKV5m6HVdAUeCHHQTiVv+YQh8BRsToSHw==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.0.13.tgz", + "integrity": "sha512-/M/YKgx23fCX+j0lwObaHbCibXnMjyWeQrXZf0WaQeS/hL86wQVSmaOxh+kZXfyLOUr+vT2Hl44o50GZHUrKWw==", "requires": { "bson": "^4.2.2", "kareem": "2.3.2", - "mongodb": "4.1.3", + "mongodb": "4.1.4", "mpath": "0.8.4", "mquery": "4.0.0", "ms": "2.1.2", @@ -18239,28 +18238,12 @@ }, "dependencies": { "bson": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.3.tgz", - "integrity": "sha512-qVX7LX79Mtj7B3NPLzCfBiCP6RAsjiV8N63DjlaVVpZW+PFoDTxQ4SeDbSpcqgE6mXksM5CAwZnXxxxn/XwC0g==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.5.4.tgz", + "integrity": "sha512-wIt0bPACnx8Ju9r6IsS2wVtGDHBr9Dxb+U29A1YED2pu8XOhS8aKjOnLZ8sxyXkPwanoK7iWWVhS1+coxde6xA==", "requires": { "buffer": "^5.6.0" } - }, - "denque": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz", - "integrity": "sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ==" - }, - "mongodb": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.1.3.tgz", - "integrity": "sha512-lHvTqODBiSpuqjpCj48DOyYWS6Iq6ElJNUiH9HWdQtONyOfjgsKzJULipWduMGsSzaNO4nFi/kmlMFCLvjox/Q==", - "requires": { - "bson": "^4.5.2", - "denque": "^2.0.1", - "mongodb-connection-string-url": "^2.0.0", - "saslprep": "^1.0.3" - } } } }, @@ -18393,7 +18376,8 @@ "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true }, "nanoid": { "version": "1.3.4", @@ -18528,9 +18512,9 @@ "integrity": "sha512-Gh39xwJwBKy0OvFmWfBs/vDO4Nl7JhnJtkqNP76OUinQz7BiMoszHYrIDHHAaqVl/QKVxCEy4ZxC/XZninu7nQ==" }, "node-abi": { - "version": "2.19.1", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.19.1.tgz", - "integrity": "sha512-HbtmIuByq44yhAzK7b9j/FelKlHYISKQn0mtvcBrU5QBkhoCMp5bu8Hv5AI34DcKfOAcJBcOEMwLlwO62FFu9A==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz", + "integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==", "requires": { "semver": "^5.4.1" } @@ -18747,11 +18731,6 @@ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.0.tgz", "integrity": "sha512-AtiTVUFHLiiDnMQ43zi0YgkzHOEWUkhDgPlBXrsDzJiJvB29Alo4OKxHQ0ugF3gRqRQIneCLtZU3yiUo7pItZw==" }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" - }, "nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -19129,11 +19108,6 @@ "integrity": "sha512-E3Orl5pvDJXnVmpaAA2TeNNpNhTMl4o5HghuWhOivBjEiTnJSrMYSa5uZMek1lBEvu8kKEsa2YgVcGFVDqX/9w==", "dev": true }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -20115,82 +20089,23 @@ "dev": true }, "prebuild-install": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", - "integrity": "sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-6.1.4.tgz", + "integrity": "sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==", "requires": { "detect-libc": "^1.0.3", "expand-template": "^2.0.3", "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", - "node-abi": "^2.7.0", - "noop-logger": "^0.1.1", + "node-abi": "^2.21.0", "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", + "pump": "^3.0.0", "rc": "^1.2.7", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "bl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", - "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" - }, - "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - } - } + "simple-get": "^3.0.3", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" } }, "prelude-ls": { @@ -21760,11 +21675,11 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", "requires": { - "decompress-response": "^3.3.0", + "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } @@ -24311,11 +24226,6 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -26108,11 +26018,6 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" - }, "which-promise": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-promise/-/which-promise-1.0.0.tgz", @@ -26494,7 +26399,8 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "4.0.1", diff --git a/package.json b/package.json index b239d27146..a0cc7a900a 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "body-parser": "^1.18.3", "bootstrap": "3.4.1", "boxicons": "1.8.0", - "bson-ext": "^2.0.5", + "bson-ext": "^4.0.2", "busboy": "^0.3.1", "celebrate": "^15.0.0", "compression": "~1.7.2", @@ -125,7 +125,7 @@ "moment-timezone": "0.5.33", "mongodb": "^4.1.4", "mongodb-uri": "^0.9.7", - "mongoose": "^6.0.12", + "mongoose": "^6.0.13", "multiparty": ">=4.2.2", "neverthrow": "^4.3.0", "ng-infinite-scroll": "^1.3.0", diff --git a/src/app/loaders/mongoose.ts b/src/app/loaders/mongoose.ts index 0ac7dbdde4..46fd01c059 100644 --- a/src/app/loaders/mongoose.ts +++ b/src/app/loaders/mongoose.ts @@ -38,7 +38,7 @@ export default async (): Promise => { }) // Store the uri to connect to later on - config.db.uri = await mongod.getConnectionString() + config.db.uri = await mongod.getUri() } // Actually connect to the database diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index c8721d79a5..d911cd6962 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -1,4 +1,4 @@ -import BSON, { ObjectId } from 'bson-ext' +import { calculateObjectSize } from 'bson-ext' import { compact, omit, pick, uniq } from 'lodash' import mongoose, { Mongoose, Schema, SchemaOptions, Types } from 'mongoose' import validator from 'validator' @@ -87,23 +87,6 @@ import getUserModel from './user.server.model' export const FORM_SCHEMA_ID = 'Form' -const bson = new BSON([ - BSON.Binary, - BSON.Code, - BSON.DBRef, - BSON.Decimal128, - BSON.Double, - BSON.Int32, - BSON.Long, - BSON.Map, - BSON.MaxKey, - BSON.MinKey, - BSON.ObjectId, - BSON.BSONRegExp, - BSON.Symbol, - BSON.Timestamp, -]) - const formSchemaOptions: SchemaOptions = { id: false, toJSON: { @@ -216,8 +199,10 @@ const compileFormModel = (db: Mongoose): IFormModel => { const { field, state, - }: { field: ObjectId | string; state: LogicConditionState } = - condition + }: { + field: Types.ObjectId | string + state: LogicConditionState + } = condition return { state, fieldType: this.form_fields?.find( @@ -853,7 +838,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Hooks FormSchema.pre('validate', function (next) { // Reject save if form document is too large - if (bson.calculateObjectSize(this) > 10 * MB) { + if (calculateObjectSize(this) > 10 * MB) { const err = new Error('Form size exceeded.') err.name = 'FormSizeError' return next(err) diff --git a/src/types/agency.ts b/src/types/agency.ts index cb55ead61a..f3157e2a17 100644 --- a/src/types/agency.ts +++ b/src/types/agency.ts @@ -1,4 +1,4 @@ -import { EnforceDocument, Model } from 'mongoose' +import { HydratedDocument, Model } from 'mongoose' import { AgencyBase, PublicAgencyDto } from '../../shared/types' @@ -18,7 +18,7 @@ export interface IAgencyDocument extends IAgencySchema { } // Used to cast created documents whenever needed. -export type AgencyDocument = EnforceDocument< +export type AgencyDocument = HydratedDocument< IAgencyDocument, AgencyInstanceMethods, // eslint-disable-next-line @typescript-eslint/ban-types diff --git a/src/types/vendor/bson-ext.d.ts b/src/types/vendor/bson-ext.d.ts index cc9ca0836c..12f11091e9 100644 --- a/src/types/vendor/bson-ext.d.ts +++ b/src/types/vendor/bson-ext.d.ts @@ -1,509 +1,8 @@ -/* 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 !!! +// Internal type definitions for bson-ext basically re-exporting bson's own types. // 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 { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - 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 } + export * from 'bson' } From b98b64fa41252c82c538d6684d40960c65cfa8f4 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 22 Nov 2021 13:52:50 +0800 Subject: [PATCH 16/32] fix: update mongodb-memory-server-core to 8.0.2 required since bson-ext won't work with old memory core versions --- package-lock.json | 315 +++++++++++++----------------------- package.json | 2 +- src/app/loaders/mongoose.ts | 2 +- 3 files changed, 110 insertions(+), 209 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6aa29762b..1ea63b32dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5937,9 +5937,9 @@ } }, "@types/tmp": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.0.tgz", - "integrity": "sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.2.tgz", + "integrity": "sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==", "dev": true }, "@types/triple-beam": { @@ -7027,6 +7027,23 @@ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, + "async-mutex": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.2.tgz", + "integrity": "sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==", + "dev": true, + "requires": { + "tslib": "^2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7726,6 +7743,30 @@ "file-uri-to-path": "1.0.0" } }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "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" + } + } + } + }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -12500,12 +12541,6 @@ "pkg-dir": "^3.0.0" } }, - "find-package-json": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", - "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==", - "dev": true - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -16911,15 +16946,6 @@ "path-exists": "^3.0.0" } }, - "lockfile": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", - "dev": true, - "requires": { - "signal-exit": "^3.0.2" - } - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -17888,27 +17914,26 @@ } }, "mongodb-memory-server-core": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-6.9.6.tgz", - "integrity": "sha512-ZcXHTI2TccH3L5N9JyAMGm8bbAsfLn8SUWOeYGHx/vDx7vu4qshyaNXTIxeHjpUQA29N+Z1LtTXA6vXjl1eg6w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-8.0.2.tgz", + "integrity": "sha512-dGSC9azBU/lku/3g+PyBl3yjSKhjNzszCg44yoS9sUcNboBaRhMSFb8G57j4YP+rVV/ebg7JGCxRlWwPMTl5nA==", "dev": true, "requires": { - "@types/tmp": "^0.2.0", - "camelcase": "^6.0.0", - "cross-spawn": "^7.0.3", + "@types/tmp": "^0.2.2", + "async-mutex": "^0.3.2", + "camelcase": "^6.1.0", "debug": "^4.2.0", - "find-cache-dir": "^3.3.1", - "find-package-json": "^1.2.0", + "find-cache-dir": "^3.3.2", "get-port": "^5.1.1", "https-proxy-agent": "^5.0.0", - "lockfile": "^1.0.4", "md5-file": "^5.0.0", - "mkdirp": "^1.0.4", - "mongodb": "^3.6.2", - "semver": "^7.3.2", + "mongodb": "^4.1.3", + "new-find-package-json": "^1.1.0", + "semver": "^7.3.5", "tar-stream": "^2.1.4", "tmp": "^0.2.1", - "uuid": "^8.3.0", + "tslib": "^2.3.1", + "uuid": "^8.3.1", "yauzl": "^2.10.0" }, "dependencies": { @@ -17921,61 +17946,25 @@ "debug": "4" } }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "bson": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", - "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", - "dev": true, - "optional": true - }, "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.1.tgz", + "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "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": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "requires": { "ms": "2.1.2" } }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "dev": true, - "optional": true - }, "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -18012,15 +18001,6 @@ "p-locate": "^4.1.0" } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -18038,65 +18018,6 @@ } } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mongodb": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", - "integrity": "sha512-Psm+g3/wHXhjBEktkxXsFMZvd3nemI0r3IPsE0bU+4//PnvNWKkzhZcEsbPcYiWqe8XqXJJEg4Tgtr7Raw67Yw==", - "dev": true, - "optional": true, - "requires": { - "bl": "^2.2.1", - "bson": "^1.1.4", - "denque": "^1.4.1", - "optional-require": "^1.1.8", - "safe-buffer": "^5.1.2", - "saslprep": "^1.0.0" - }, - "dependencies": { - "bl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", - "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", - "dev": true, - "optional": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true - } - } - } - } - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -18127,12 +18048,6 @@ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "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 - }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -18154,29 +18069,14 @@ } }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, - "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 - }, "tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -18190,28 +18090,10 @@ "readable-stream": "^3.1.1" } }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "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" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "dev": true } } @@ -18442,6 +18324,33 @@ "resolved": "https://registry.npmjs.org/neverthrow/-/neverthrow-4.3.0.tgz", "integrity": "sha512-fS2sZGHeAaHCjoqspmZb0Kcc7Vvnnz+QuKl0MRq1+l77PsGL+449cPY2JfWbdnXRuk5jaP8kXfYeZVvuisAeiw==" }, + "new-find-package-json": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-1.1.0.tgz", + "integrity": "sha512-KOH3BNZcTKPzEkaJgG2iSUaurxKmefqRKmCOYH+8xqJytNIgjqU4J88BHfK+gy/UlEzlhccLyuJDJAcCgexSwA==", + "dev": true, + "requires": { + "debug": "^4.3.2", + "tslib": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "ng-infinite-scroll": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ng-infinite-scroll/-/ng-infinite-scroll-1.3.0.tgz", @@ -19073,16 +18982,6 @@ "last-call-webpack-plugin": "^3.0.0" } }, - "optional-require": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz", - "integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==", - "dev": true, - "optional": true, - "requires": { - "require-at": "^1.0.6" - } - }, "optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -21024,13 +20923,6 @@ "tough-cookie": "^2.3.3" } }, - "require-at": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", - "integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==", - "dev": true, - "optional": true - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -24178,6 +24070,15 @@ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, "tmp-promise": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", diff --git a/package.json b/package.json index a0cc7a900a..b614fe88ac 100644 --- a/package.json +++ b/package.json @@ -229,7 +229,7 @@ "mini-css-extract-plugin": "^0.5.0", "mockdate": "^3.0.5", "mockingoose": "^2.13.2", - "mongodb-memory-server-core": "^6.9.6", + "mongodb-memory-server-core": "^8.0.2", "ngrok": "^4.2.2", "optimize-css-assets-webpack-plugin": "^5.0.8", "prettier": "^2.4.1", diff --git a/src/app/loaders/mongoose.ts b/src/app/loaders/mongoose.ts index 46fd01c059..e686f40d19 100644 --- a/src/app/loaders/mongoose.ts +++ b/src/app/loaders/mongoose.ts @@ -28,7 +28,7 @@ export default async (): Promise => { process.exit(1) } - const mongod = new MongoMemoryServer({ + const mongod = await MongoMemoryServer.create({ binary: { version: String(process.env.MONGO_BINARY_VERSION) }, instance: { port: 3000, From e4c38ab807795827698673fd0ce9e69903d5e9f5 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 22 Nov 2021 16:24:55 +0800 Subject: [PATCH 17/32] fix(tests): update instantiation of test db --- tests/database.js | 22 ++++++++++++++++------ tests/unit/backend/helpers/jest-db.ts | 9 +-------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/database.js b/tests/database.js index bef5ff0b06..aece6b7cdb 100644 --- a/tests/database.js +++ b/tests/database.js @@ -1,26 +1,36 @@ const { MongoMemoryServer } = require('mongodb-memory-server-core') class MemoryDatabaseServer { - constructor() { - this.mongod = new MongoMemoryServer({ + constructor() {} + + async init() { + this.mongod = await MongoMemoryServer.create({ binary: { version: process.env.MONGO_BINARY_VERSION || '4.0.22', - checkMD5: true, + skipMD5: true, }, instance: {}, autoStart: false, }) } - start() { + async start() { + if (!this.mongod) { + await this.init() + } return this.mongod.start() } stop() { - return this.mongod.stop() + if (this.mongod) { + return this.mongod.stop() + } } - getConnectionString() { + async getConnectionString() { + if (!this.mongod) { + await this.init() + } return this.mongod.getUri(true) } } diff --git a/tests/unit/backend/helpers/jest-db.ts b/tests/unit/backend/helpers/jest-db.ts index 2fa724879b..23bf306315 100644 --- a/tests/unit/backend/helpers/jest-db.ts +++ b/tests/unit/backend/helpers/jest-db.ts @@ -26,14 +26,7 @@ import MemoryDatabaseServer from 'tests/database' */ const connect = async (): Promise => { const dbUrl = await MemoryDatabaseServer.getConnectionString() - - const conn = await mongoose.connect(dbUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - useFindAndModify: false, - }) - return conn + return mongoose.connect(dbUrl) } /** From b74a87f5300a22b959fe28654b5833d65e255202 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 22 Nov 2021 16:26:15 +0800 Subject: [PATCH 18/32] fix(tests): update mongoose toEqual matchers to toMatchObject matchers somewhere along the way mongoose changed the shape of their returned objects, causing toEqual match to fail even if they serialize to the same string. --- .../admin_verification.server.model.spec.ts | 39 ++-- .../encrypt-submission.server.model.spec.ts | 19 +- .../__tests__/form.server.model.spec.ts | 169 +++++++++--------- .../form_feedback.server.model.spec.ts | 26 ++- .../__tests__/login.server.model.spec.ts | 33 ++-- .../__tests__/submission.server.model.spec.ts | 16 +- .../__tests__/user.server.model.spec.ts | 4 +- .../models/admin_verification.server.model.ts | 2 +- .../__tests__/bounce.controller.spec.ts | 4 +- .../bounce/__tests__/bounce.model.spec.ts | 44 ++--- .../bounce/__tests__/bounce.service.spec.ts | 46 ++--- .../__tests__/examples.service.spec.ts | 20 +-- .../__tests__/admin-form.routes.spec.ts | 36 ++-- .../__tests__/admin-form.service.spec.ts | 128 +++++++------ .../__tests__/myinfo_hash.model.spec.ts | 6 +- .../email-submission.service.spec.ts | 2 +- .../encrypt-submission.service.spec.ts | 2 +- .../__tests__/verification.model.spec.ts | 12 +- .../__tests__/verification.service.spec.ts | 45 +++-- .../admin-forms.preview.routes.spec.ts | 28 ++- .../__tests__/sms_count.server.model.spec.ts | 22 +-- 21 files changed, 323 insertions(+), 380 deletions(-) diff --git a/src/app/models/__tests__/admin_verification.server.model.spec.ts b/src/app/models/__tests__/admin_verification.server.model.spec.ts index 930f9cdd3d..7708fccd43 100644 --- a/src/app/models/__tests__/admin_verification.server.model.spec.ts +++ b/src/app/models/__tests__/admin_verification.server.model.spec.ts @@ -1,4 +1,3 @@ -import { ObjectID } from 'bson' import mongoose from 'mongoose' import getAdminVerificationModel from 'src/app/models/admin_verification.server.model' @@ -18,7 +17,7 @@ describe('AdminVerification Model', () => { describe('Schema', () => { const DEFAULT_PARAMS: IAdminVerification = { - admin: new ObjectID(), + admin: new mongoose.Types.ObjectId(), expireAt: new Date(), hashedContact: 'mockHashedContact', hashedOtp: 'mockHashedOtp', @@ -57,13 +56,11 @@ describe('AdminVerification Model', () => { expect(actual._id).toBeDefined() expect(actual.createdAt).toBeInstanceOf(Date) expect(actual.updatedAt).toBeInstanceOf(Date) - expect(actual).toEqual( - expect.objectContaining({ - ...customParams, - // Add defaults that has not been overridden. - numOtpSent: 0, - }), - ) + expect(actual).toMatchObject({ + ...customParams, + // Add defaults that has not been overridden. + numOtpSent: 0, + }) }) it('should throw validation error on missing admin', async () => { @@ -133,7 +130,7 @@ describe('AdminVerification Model', () => { it('should create successfully when document does not exist', async () => { // Arrange const params: UpsertOtpParams = { - admin: new ObjectID(), + admin: new mongoose.Types.ObjectId(), expireAt: new Date(), hashedContact: 'mockHashedContact', hashedOtp: 'mockHashedOtp', @@ -149,13 +146,13 @@ describe('AdminVerification Model', () => { const expected = { ...params, numOtpAttempts: 0, numOtpSent: 1 } // Should now have one document. await expect(AdminVerification.countDocuments()).resolves.toEqual(1) - expect(actual).toEqual(expect.objectContaining(expected)) + expect(actual).toMatchObject(expected) }) it('should update successfully when a document already exists', async () => { // Arrange // Insert mock document into collection. - const adminId = new ObjectID() + const adminId = new mongoose.Types.ObjectId() const oldExpireAt = new Date() const newExpireAt = new Date(Date.now() + 9000000) const oldNumOtpSent = 3 @@ -190,14 +187,14 @@ describe('AdminVerification Model', () => { } // Should still only have one document. await expect(AdminVerification.countDocuments()).resolves.toEqual(1) - expect(actual).toEqual(expect.objectContaining(expected)) + expect(actual).toMatchObject(expected) }) it('should throw error if validation fails due to invalid upsert parameters', async () => { // Arrange const invalidParams: UpsertOtpParams = { // Invalid admin parameter. - admin: undefined, + admin: null, expireAt: new Date(), hashedContact: 'mockHashedContact', hashedOtp: 'mockHashedOtp', @@ -219,7 +216,7 @@ describe('AdminVerification Model', () => { it('should increment successfully', async () => { // Arrange // Insert mock document into collection. - const adminId = new ObjectID() + const adminId = new mongoose.Types.ObjectId() const initialOtpAttempts = 5 const adminVerificationParams = { admin: adminId, @@ -240,19 +237,17 @@ describe('AdminVerification Model', () => { // Assert // Exactly the same as initial params, but with numOtpAttempts // incremented by 1. - await expect(actualPromise).resolves.toEqual( - expect.objectContaining({ - ...adminVerificationParams, - numOtpAttempts: initialOtpAttempts + 1, - }), - ) + await expect(actualPromise).resolves.toMatchObject({ + ...adminVerificationParams, + numOtpAttempts: initialOtpAttempts + 1, + }) }) it('should return null if document cannot be retrieved', async () => { // Arrange // Should have no documents yet. await expect(AdminVerification.countDocuments()).resolves.toEqual(0) - const freshAdminId = new ObjectID() + const freshAdminId = new mongoose.Types.ObjectId() // Act const actualPromise = diff --git a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts index a497d164db..bdae0d708e 100644 --- a/src/app/models/__tests__/encrypt-submission.server.model.spec.ts +++ b/src/app/models/__tests__/encrypt-submission.server.model.spec.ts @@ -53,7 +53,7 @@ describe('Encrypt Submission Model', () => { .tz('Asia/Singapore') .format('Do MMM YYYY, h:mm:ss a'), } - expect(result).toEqual(expected) + expect(result).toMatchObject(expected) }) it('should return null when submission is of SubmissionType.Email', async () => { @@ -137,7 +137,7 @@ describe('Encrypt Submission Model', () => { })) .reverse(), } - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) it('should return offset metadata with correct count when page number is provided', async () => { @@ -179,7 +179,7 @@ describe('Encrypt Submission Model', () => { }, ], } - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) it('should return offset metadata with correct count when page size is provided', async () => { @@ -221,7 +221,7 @@ describe('Encrypt Submission Model', () => { }, ], } - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) it('should return empty metadata array when given page has no metadata', async () => { @@ -254,7 +254,7 @@ describe('Encrypt Submission Model', () => { // show metadata: [], } - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) it('should return empty metadata array when formId has no metadata', async () => { @@ -272,7 +272,7 @@ describe('Encrypt Submission Model', () => { // show metadata: [], } - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) }) @@ -284,6 +284,7 @@ describe('Encrypt Submission Model', () => { submissionType: SubmissionType.Encrypt, form: validFormId, encryptedContent: 'mock encrypted content abc', + verifiedContent: 'mock verified content', version: 3, }) const expectedSubmission = pick( @@ -309,7 +310,7 @@ describe('Encrypt Submission Model', () => { retrievedSubmissions.push(submission) } // Cursor stream should contain only that single submission. - expect(retrievedSubmissions).toEqual([expectedSubmission]) + expect(retrievedSubmissions).toMatchObject([expectedSubmission]) }) it('should return cursor even if no submissions are found', async () => { @@ -329,7 +330,7 @@ describe('Encrypt Submission Model', () => { retrievedSubmissions.push(submission) } // Cursor stream should return nothing. - expect(retrievedSubmissions).toEqual([]) + expect(retrievedSubmissions).toMatchObject([]) }) }) @@ -362,7 +363,7 @@ describe('Encrypt Submission Model', () => { 'version', ) expect(actual).not.toBeNull() - expect(actual?.toJSON()).toEqual(expected) + expect(actual?.toJSON()).toMatchObject(expected) }) it('should return null when submission id does not exist', async () => { diff --git a/src/app/models/__tests__/form.server.model.spec.ts b/src/app/models/__tests__/form.server.model.spec.ts index c96933c9e3..84a69ca01f 100644 --- a/src/app/models/__tests__/form.server.model.spec.ts +++ b/src/app/models/__tests__/form.server.model.spec.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { ObjectId } from 'bson-ext' import { cloneDeep, map, merge, omit, orderBy, pick, range } from 'lodash' import mongoose, { Types } from 'mongoose' import { @@ -42,7 +41,7 @@ const Form = getFormModel(mongoose) const EncryptedForm = getEncryptedFormModel(mongoose) const EmailForm = getEmailFormModel(mongoose) -const MOCK_ADMIN_OBJ_ID = new ObjectId() +const MOCK_ADMIN_OBJ_ID = new mongoose.Types.ObjectId() const MOCK_ADMIN_DOMAIN = 'example.com' const MOCK_ADMIN_EMAIL = `test@${MOCK_ADMIN_DOMAIN}` @@ -126,7 +125,7 @@ describe('Form Model', () => { '__v', ]) const expectedObject = merge({}, FORM_DEFAULTS, MOCK_FORM_PARAMS) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) }) it('should create and save successfully with valid esrvcId', async () => { @@ -154,7 +153,7 @@ describe('Form Model', () => { '__v', ]) const expectedObject = merge({}, FORM_DEFAULTS, validFormParams) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) }) it('should save successfully, but not save fields that is not defined in the schema', async () => { @@ -181,7 +180,7 @@ describe('Form Model', () => { '__v', ]) const expectedObject = merge({}, FORM_DEFAULTS, MOCK_FORM_PARAMS) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Extra key should not be saved expect(Object.keys(saved)).not.toContain('extra') @@ -195,7 +194,7 @@ describe('Form Model', () => { conditions: [ { _id: '', - field: new ObjectId(), + field: new mongoose.Types.ObjectId(), state: 'is equals to', value: '', ifValueType: 'number', @@ -235,7 +234,7 @@ describe('Form Model', () => { MOCK_FORM_PARAMS, FORM_LOGICS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) }) it('should create and save successfully with valid permissionList emails', async () => { @@ -270,7 +269,7 @@ describe('Form Model', () => { omit(FORM_DEFAULTS, 'permissionList'), MOCK_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Remove indeterministic id from actual permission list const actualPermissionList = saved @@ -306,7 +305,7 @@ describe('Form Model', () => { it('should reject when admin id is invalid', async () => { // Arrange - const invalidAdminId = new ObjectId() + const invalidAdminId = new mongoose.Types.ObjectId() const paramsWithInvalidAdmin = merge({}, MOCK_FORM_PARAMS, { admin: invalidAdminId, }) @@ -428,7 +427,7 @@ describe('Form Model', () => { ENCRYPT_FORM_DEFAULTS, MOCK_ENCRYPTED_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) }) it('should save successfully, but not save fields that is not defined in the schema', async () => { @@ -459,7 +458,7 @@ describe('Form Model', () => { ENCRYPT_FORM_DEFAULTS, MOCK_ENCRYPTED_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Extra key should not be saved expect(Object.keys(saved)).not.toContain('extra') @@ -497,7 +496,7 @@ describe('Form Model', () => { omit(ENCRYPT_FORM_DEFAULTS, 'permissionList'), MOCK_ENCRYPTED_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Remove indeterministic id from actual permission list const actualPermissionList = ( @@ -547,7 +546,7 @@ describe('Form Model', () => { it('should reject when admin id is invalid', async () => { // Arrange - const invalidAdminId = new ObjectId() + const invalidAdminId = new mongoose.Types.ObjectId() const paramsWithInvalidAdmin = merge({}, MOCK_ENCRYPTED_FORM_PARAMS, { admin: invalidAdminId, }) @@ -679,7 +678,7 @@ describe('Form Model', () => { EMAIL_FORM_DEFAULTS, MOCK_EMAIL_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) }) it('should save successfully, but not save fields that is not defined in the schema', async () => { @@ -710,7 +709,7 @@ describe('Form Model', () => { EMAIL_FORM_DEFAULTS, MOCK_EMAIL_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Extra key should not be saved expect(Object.keys(saved)).not.toContain('extra') @@ -747,7 +746,7 @@ describe('Form Model', () => { omit(EMAIL_FORM_DEFAULTS, 'permissionList'), omit(MOCK_EMAIL_FORM_PARAMS, 'emails'), ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) const actualEmails = saved.toObject().emails expect(actualEmails).toEqual(mockEmailsArray) @@ -785,7 +784,7 @@ describe('Form Model', () => { omit(EMAIL_FORM_DEFAULTS, 'permissionList'), MOCK_EMAIL_FORM_PARAMS, ) - expect(actualSavedObject).toEqual(expectedObject) + expect(actualSavedObject).toMatchObject(expectedObject) // Remove indeterministic id from actual permission list const actualPermissionList = saved @@ -850,7 +849,7 @@ describe('Form Model', () => { it('should reject when admin id is invalid', async () => { // Arrange - const invalidAdminId = new ObjectId() + const invalidAdminId = new mongoose.Types.ObjectId() const paramsWithInvalidAdmin = merge({}, MOCK_EMAIL_FORM_PARAMS, { admin: invalidAdminId, }) @@ -953,7 +952,9 @@ describe('Form Model', () => { }) it('should return null for invalid form ID', async () => { - const returned = await Form.deactivateById(String(new ObjectId())) + const returned = await Form.deactivateById( + String(new mongoose.Types.ObjectId()), + ) expect(returned).toBeNull() }) }) @@ -961,7 +962,7 @@ describe('Form Model', () => { describe('getFullFormById', () => { it('should return null when the formId is invalid', async () => { // Arrange - const invalidFormId = new ObjectId() + const invalidFormId = new mongoose.Types.ObjectId() // Act const form = await Form.getFullFormById(String(invalidFormId)) @@ -985,7 +986,7 @@ describe('Form Model', () => { // Form should be returned expect(actualForm).not.toBeNull() // Omit admin key since it is populated is not ObjectId anymore. - expect(omit(actualForm, 'admin')).toEqual(omit(form, 'admin')) + expect(omit(actualForm, 'admin')).toMatchObject(omit(form, 'admin')) // Verify populated admin shape expect(actualForm?.admin).not.toBeNull() expect(actualForm?.admin.email).toEqual(populatedAdmin.email) @@ -996,9 +997,7 @@ describe('Form Model', () => { 'lastModified', '__v', ]) - expect(actualForm?.admin.agency).toEqual( - expect.objectContaining(expectedAgency), - ) + expect(actualForm?.admin.agency).toMatchObject(expectedAgency) }) it('should return the populated encrypt form when formId is valid', async () => { @@ -1016,7 +1015,7 @@ describe('Form Model', () => { // Form should be returned expect(actualForm).not.toBeNull() // Omit admin key since it is populated is not ObjectId anymore. - expect(omit(actualForm, 'admin')).toEqual(omit(form, 'admin')) + expect(omit(actualForm, 'admin')).toMatchObject(omit(form, 'admin')) // Verify populated admin shape expect(actualForm?.admin).not.toBeNull() expect(actualForm?.admin.email).toEqual(populatedAdmin.email) @@ -1027,16 +1026,14 @@ describe('Form Model', () => { 'lastModified', '__v', ]) - expect(actualForm?.admin.agency).toEqual( - expect.objectContaining(expectedAgency), - ) + expect(actualForm?.admin.agency).toMatchObject(expectedAgency) }) }) describe('getOtpData', () => { it('should return null when formId does not exist', async () => { // Arrange - const invalidFormId = new ObjectId() + const invalidFormId = new mongoose.Types.ObjectId() // Act const form = await Form.getOtpData(String(invalidFormId)) @@ -1068,7 +1065,7 @@ describe('Form Model', () => { }, msgSrvcName: emailFormParams.msgSrvcName, } - expect(actualOtpData).toEqual(expectedOtpData) + expect(actualOtpData).toMatchObject(expectedOtpData) }) it('should return otpData of an encrypt form when formId is valid', async () => { @@ -1094,14 +1091,14 @@ describe('Form Model', () => { }, msgSrvcName: encryptFormParams.msgSrvcName, } - expect(actualOtpData).toEqual(expectedOtpData) + expect(actualOtpData).toMatchObject(expectedOtpData) }) }) describe('getMetaByUserIdOrEmail', () => { it('should return empty array when user has no forms to view', async () => { // Arrange - const randomUserId = new ObjectId() + const randomUserId = new mongoose.Types.ObjectId() const invalidEmail = 'not-valid@example.com' // Act @@ -1117,7 +1114,7 @@ describe('Form Model', () => { it('should return array of forms user is permitted to view', async () => { // Arrange // Add additional user. - const differentUserId = new ObjectId() + const differentUserId = new mongoose.Types.ObjectId() const diffPreload = await dbHandler.insertFormCollectionReqs({ userId: differentUserId, mailName: 'something-else', @@ -1192,12 +1189,12 @@ describe('Form Model', () => { // even though there are 5 forms in the collection. await expect(Form.countDocuments()).resolves.toEqual(5) expect(actual.length).toEqual(3) - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) }) describe('createFormLogic', () => { - const logicId = new ObjectId().toHexString() + const logicId = new mongoose.Types.ObjectId().toHexString() const mockExistingFormLogic = { form_logics: [ @@ -1325,7 +1322,7 @@ describe('Form Model', () => { it('should return null if formId is invalid', async () => { // arrange - const invalidFormId = new ObjectId().toHexString() + const invalidFormId = new mongoose.Types.ObjectId().toHexString() // act const modifiedForm = await Form.createFormLogic( @@ -1340,7 +1337,7 @@ describe('Form Model', () => { }) describe('deleteFormLogic', () => { - const logicId = new ObjectId().toHexString() + const logicId = new mongoose.Types.ObjectId().toHexString() const mockFormLogic = { form_logics: [ { @@ -1380,7 +1377,7 @@ describe('Form Model', () => { it('should return form with remaining logic upon successful delete of one logic', async () => { // arrange - const logicId2 = new ObjectId().toHexString() + const logicId2 = new mongoose.Types.ObjectId().toHexString() const mockFormLogicMultiple = { form_logics: [ { @@ -1425,7 +1422,7 @@ describe('Form Model', () => { it('should return null if formId is invalid', async () => { // arrange - const invalidFormId = new ObjectId().toHexString() + const invalidFormId = new mongoose.Types.ObjectId().toHexString() // act const modifiedForm = await Form.deleteFormLogic(invalidFormId, logicId) @@ -1460,7 +1457,7 @@ describe('Form Model', () => { expect(actual?.toObject().form_fields).toEqual(expectedFormFields) // Check db state expect(retrievedForm).not.toBeNull() - expect(retrievedForm?.form_fields).toEqual(expectedFormFields) + expect(retrievedForm?.form_fields).toMatchObject(expectedFormFields) }) it('should return form unchanged when field id is invalid', async () => { @@ -1476,11 +1473,11 @@ describe('Form Model', () => { // Act const actual = await Form.deleteFormFieldById( form._id, - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), ) // Assert - expect(actual?.toObject()).toEqual({ + expect(actual?.toObject()).toMatchObject({ ...form.toObject(), lastModified: expect.any(Date), }) @@ -1508,7 +1505,7 @@ describe('Form Model', () => { // Assert // Should have defaults populated but also replace the endpage with the new params - expect(actual?.toObject()).toEqual({ + expect(actual?.toObject()).toMatchObject({ ...form, lastModified: expect.any(Date), endPage: { ...updatedEndPage }, @@ -1531,7 +1528,7 @@ describe('Form Model', () => { // Assert // Should have defaults populated but also replace the endpage with the new params - expect(actual?.toObject()).toEqual({ + expect(actual?.toObject()).toMatchObject({ ...form, lastModified: expect.any(Date), endPage: { @@ -1554,7 +1551,7 @@ describe('Form Model', () => { // Act const actual = await Form.updateEndPageById( - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), updatedEndPage, ) @@ -1565,8 +1562,8 @@ describe('Form Model', () => { }) describe('updateFormLogic', () => { - const logicId1 = new ObjectId().toHexString() - const logicId2 = new ObjectId().toHexString() + const logicId1 = new mongoose.Types.ObjectId().toHexString() + const logicId2 = new mongoose.Types.ObjectId().toHexString() const mockExistingFormLogic = { form_logics: [ @@ -1670,7 +1667,7 @@ describe('Form Model', () => { it('should return null if formId is invalid', async () => { // arrange - const invalidFormId = new ObjectId().toHexString() + const invalidFormId = new mongoose.Types.ObjectId().toHexString() // act const modifiedForm = await Form.updateFormLogic( @@ -1686,7 +1683,7 @@ describe('Form Model', () => { it('should return unmodified form if logicId is invalid', async () => { // arrange - const invalidLogicId = new ObjectId().toHexString() + const invalidLogicId = new mongoose.Types.ObjectId().toHexString() const mockExistingFormLogicSingle = { form_logics: [ { @@ -1789,7 +1786,7 @@ describe('Form Model', () => { // Assert // Should have defaults populated but also replace the start page with the new params - expect(actual?.toObject()).toEqual({ + expect(actual?.toObject()).toMatchObject({ ...form, lastModified: expect.any(Date), startPage: { @@ -1816,7 +1813,7 @@ describe('Form Model', () => { // Act const actual = await Form.updateStartPageById( - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), updatedStartPage, ) @@ -1910,7 +1907,7 @@ describe('Form Model', () => { it('should only disable sms verifications for a particular user', async () => { // Arrange - const MOCK_USER_ID = new ObjectId() + const MOCK_USER_ID = new mongoose.Types.ObjectId() await dbHandler.insertFormCollectionReqs({ userId: MOCK_USER_ID, mailDomain: 'something.com', @@ -2113,7 +2110,7 @@ describe('Form Model', () => { expect(form).toBeDefined() // Add additional user. const diffPreload = await dbHandler.insertFormCollectionReqs({ - userId: new ObjectId(), + userId: new mongoose.Types.ObjectId(), mailName: 'another', mailDomain: MOCK_ADMIN_DOMAIN, }) @@ -2125,7 +2122,7 @@ describe('Form Model', () => { const actual = form.getDashboardView(diffPopulatedAdmin) // Assert - expect(actual).toEqual({ + expect(actual).toMatchObject({ _id: form._id, title: form.title, status: form.status, @@ -2147,7 +2144,7 @@ describe('Form Model', () => { expect(form).toBeDefined() // Add additional user. const diffPreload = await dbHandler.insertFormCollectionReqs({ - userId: new ObjectId(), + userId: new mongoose.Types.ObjectId(), mailName: 'another-thing', mailDomain: MOCK_ADMIN_DOMAIN, }) @@ -2159,7 +2156,7 @@ describe('Form Model', () => { const actual = form.getDashboardView(diffPopulatedAdmin) // Assert - expect(actual).toEqual({ + expect(actual).toMatchObject({ _id: form._id, title: form.title, status: form.status, @@ -2208,9 +2205,9 @@ describe('Form Model', () => { const actual = emailForm.getPublicView() // Assert - expect(actual).toEqual(pick(emailForm, EMAIL_PUBLIC_FORM_FIELDS)) + expect(actual).toMatchObject(pick(emailForm, EMAIL_PUBLIC_FORM_FIELDS)) // Admin should be plain admin id since form is not populated. - expect(actual.admin).toBeInstanceOf(ObjectId) + expect(actual.admin).toBeInstanceOf(mongoose.Types.ObjectId) }) it('should correctly return public view of populated email mode form', async () => { @@ -2230,15 +2227,13 @@ describe('Form Model', () => { // Assert const expectedPublicAgencyView = populatedAdmin.agency.getPublicView() - expect(JSON.stringify(actual)).toEqual( - JSON.stringify({ - ...pick(populatedEmailForm, STORAGE_PUBLIC_FORM_FIELDS), - // Admin should only contain public view of agency since agency is populated. - admin: { - agency: expectedPublicAgencyView, - }, - }), - ) + expect(actual).toMatchObject({ + ...pick(populatedEmailForm, STORAGE_PUBLIC_FORM_FIELDS), + // Admin should only contain public view of agency since agency is populated. + admin: { + agency: expectedPublicAgencyView, + }, + }) }) it('should correctly return public view of unpopulated encrypt mode form', async () => { @@ -2254,9 +2249,11 @@ describe('Form Model', () => { const actual = encryptForm.getPublicView() // Assert - expect(actual).toEqual(pick(encryptForm, STORAGE_PUBLIC_FORM_FIELDS)) + expect(actual).toMatchObject( + pick(encryptForm, STORAGE_PUBLIC_FORM_FIELDS), + ) // Admin should be plain admin id since form is not populated. - expect(actual.admin).toBeInstanceOf(ObjectId) + expect(actual.admin).toBeInstanceOf(mongoose.Types.ObjectId) }) it('should correctly return public view of populated encrypt mode form', async () => { @@ -2276,15 +2273,13 @@ describe('Form Model', () => { // Assert const expectedPublicAgencyView = populatedAdmin.agency.getPublicView() - expect(JSON.stringify(actual)).toEqual( - JSON.stringify({ - ...pick(populatedEncryptForm, STORAGE_PUBLIC_FORM_FIELDS), - // Admin should only contain public view of agency since agency is populated. - admin: { - agency: expectedPublicAgencyView, - }, - }), - ) + expect(actual).toMatchObject({ + ...pick(populatedEncryptForm, STORAGE_PUBLIC_FORM_FIELDS), + // Admin should only contain public view of agency since agency is populated. + admin: { + agency: expectedPublicAgencyView, + }, + }) }) }) @@ -2331,7 +2326,7 @@ describe('Form Model', () => { it('should return null if fieldId does not correspond to any field in the form', async () => { // Arrange - const invalidFieldId = new ObjectId().toHexString() + const invalidFieldId = new mongoose.Types.ObjectId().toHexString() const someNewField = { description: 'this does not matter', } as FormFieldDto @@ -2406,10 +2401,9 @@ describe('Form Model', () => { // Assert const expectedField = { ...omit(newField, 'getQuestion'), - _id: new ObjectId(newField._id), + _id: new mongoose.Types.ObjectId(newField._id), } - // @ts-ignore - expect(actual?.form_fields.toObject()).toEqual([expectedField]) + expect(actual?.form_fields).toMatchObject([expectedField]) }) it('should return validation error if model validation fails whilst creating field', async () => { @@ -2450,7 +2444,7 @@ describe('Form Model', () => { // Assert const expectedOriginalField = { ...omit(fieldToDuplicate, ['getQuestion']), - _id: new ObjectId(fieldToDuplicate._id), + _id: new mongoose.Types.ObjectId(fieldToDuplicate._id), } const expectedDuplicatedField = omit(fieldToDuplicate, [ '_id', @@ -2458,14 +2452,13 @@ describe('Form Model', () => { 'getQuestion', ]) - // @ts-ignore - expect(actual?.form_fields.toObject()[0]).toEqual(expectedOriginalField) + expect(actual?.form_fields?.[0]).toMatchObject(expectedOriginalField) expect(actualDuplicatedField).toEqual(expectedDuplicatedField) }) it('should return null if given fieldId is invalid', async () => { const updatedForm = await validForm.duplicateFormFieldById( - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), ) // Assert @@ -2475,7 +2468,7 @@ describe('Form Model', () => { describe('reorderFormFieldById', () => { let form: IFormSchema - const FIELD_ID_TO_REORDER = new ObjectId().toHexString() + const FIELD_ID_TO_REORDER = new mongoose.Types.ObjectId().toHexString() beforeEach(async () => { form = await Form.create({ @@ -2548,7 +2541,7 @@ describe('Form Model', () => { it('should return null if given fieldId is invalid', async () => { const updatedForm = await form.reorderFormFieldById( - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), 3, ) diff --git a/src/app/models/__tests__/form_feedback.server.model.spec.ts b/src/app/models/__tests__/form_feedback.server.model.spec.ts index 444a73dfc3..cbd5d3e7e1 100644 --- a/src/app/models/__tests__/form_feedback.server.model.spec.ts +++ b/src/app/models/__tests__/form_feedback.server.model.spec.ts @@ -26,13 +26,11 @@ describe('form_feedback.server.model', () => { const actual = await FeedbackModel.create(DEFAULT_PARAMS) // Assert - expect(actual).toEqual( - expect.objectContaining({ - ...DEFAULT_PARAMS, - created: expect.any(Date), - lastModified: expect.any(Date), - }), - ) + expect(actual).toMatchObject({ + ...DEFAULT_PARAMS, + created: expect.any(Date), + lastModified: expect.any(Date), + }) }) it('should save successfully even when comment param is missing', async () => { @@ -42,13 +40,11 @@ describe('form_feedback.server.model', () => { const actual = await FeedbackModel.create(paramsWithoutComment) // Assert - expect(actual).toEqual( - expect.objectContaining({ - ...paramsWithoutComment, - created: expect.any(Date), - lastModified: expect.any(Date), - }), - ) + expect(actual).toMatchObject({ + ...paramsWithoutComment, + created: expect.any(Date), + lastModified: expect.any(Date), + }) }) it('should throw validation error when formId param is missing', async () => { @@ -99,7 +95,7 @@ describe('form_feedback.server.model', () => { // Assert // Cursor should return a lean object instead of a document. - expect(actualFeedback).toEqual([mockFeedbackDoc.toObject()]) + expect(actualFeedback).toMatchObject([mockFeedbackDoc.toObject()]) }) }) }) diff --git a/src/app/models/__tests__/login.server.model.spec.ts b/src/app/models/__tests__/login.server.model.spec.ts index 7d4e3ce828..923c22224d 100644 --- a/src/app/models/__tests__/login.server.model.spec.ts +++ b/src/app/models/__tests__/login.server.model.spec.ts @@ -37,12 +37,10 @@ describe('login.server.model', () => { const actual = await LoginModel.create(DEFAULT_PARAMS) // Assert - expect(actual).toEqual( - expect.objectContaining({ - ...DEFAULT_PARAMS, - created: expect.any(Date), - }), - ) + expect(actual).toMatchObject({ + ...DEFAULT_PARAMS, + created: expect.any(Date), + }) }) it('should throw validation error when admin param is missing', async () => { @@ -129,18 +127,19 @@ describe('login.server.model', () => { it('should save the correct form data', async () => { const saved = await LoginModel.addLoginFromForm(fullForm) const found = await LoginModel.findOne({ form: formId }) + + const expected = { + form: formId, + admin: adminId, + agency: agencyId, + authType: mockAuthType, + esrvcId: mockEsrvcId, + } + // Returned document should match - expect(saved.form).toEqual(formId) - expect(saved.admin).toEqual(adminId) - expect(saved.agency).toEqual(agencyId) - expect(saved.authType).toBe(mockAuthType) - expect(saved.esrvcId).toBe(mockEsrvcId) + expect(saved).toMatchObject(expected) // Found document should match - expect(found!.form).toEqual(formId) - expect(found!.admin).toEqual(adminId) - expect(found!.agency).toEqual(agencyId) - expect(found!.authType).toBe(mockAuthType) - expect(found!.esrvcId).toBe(mockEsrvcId) + expect(found).toMatchObject(expected) }) it('should reject when the form does not contain an e-service ID', async () => { @@ -256,7 +255,7 @@ describe('login.server.model', () => { total: loginsInRange.length, }, ] - expect(result).toEqual(expected) + expect(result).toMatchObject(expected) }) it('should return empty array when given dates do not correspond to any login documents', async () => { diff --git a/src/app/models/__tests__/submission.server.model.spec.ts b/src/app/models/__tests__/submission.server.model.spec.ts index 4aa6773359..91faf3af53 100644 --- a/src/app/models/__tests__/submission.server.model.spec.ts +++ b/src/app/models/__tests__/submission.server.model.spec.ts @@ -116,7 +116,7 @@ describe('Submission Model', () => { String(submission._id), ) - expect(result).toEqual({ + expect(result).toMatchObject({ webhookUrl: '', isRetryEnabled: false, webhookView: { @@ -152,7 +152,7 @@ describe('Submission Model', () => { String(submission._id), ) - expect(result).toEqual({ + expect(result).toMatchObject({ webhookUrl: MOCK_WEBHOOK_URL, isRetryEnabled: false, webhookView: { @@ -175,7 +175,7 @@ describe('Submission Model', () => { // Arrange const formCounts = [4, 2, 4] const formIdsAndCounts = times(formCounts.length, (it) => ({ - _id: mongoose.Types.ObjectId(), + _id: new mongoose.Types.ObjectId(), count: formCounts[it], })) const submissionPromises: Promise[] = [] @@ -204,14 +204,14 @@ describe('Submission Model', () => { const expectedResult = formIdsAndCounts.filter( ({ count }) => count > minSubCount, ) - expect(actualResult).toEqual(expect.arrayContaining(expectedResult)) + expect(actualResult).toMatchObject(expectedResult) }) it('should return an empty array if no forms have submission counts higher than given count', async () => { // Arrange const formCounts = [1, 1, 2] const formIdsAndCounts = times(formCounts.length, (it) => ({ - _id: mongoose.Types.ObjectId(), + _id: new mongoose.Types.ObjectId(), count: formCounts[it], })) const submissionPromises: Promise[] = [] @@ -264,7 +264,7 @@ describe('Submission Model', () => { const actualWebhookView = submission.getWebhookView() // Assert - expect(actualWebhookView).toEqual({ + expect(actualWebhookView).toMatchObject({ data: { formId: expect.any(String), submissionId: expect.any(String), @@ -296,7 +296,7 @@ describe('Submission Model', () => { const actualWebhookView = submission.getWebhookView() // Assert - expect(actualWebhookView).toEqual({ + expect(actualWebhookView).toMatchObject({ data: { attachmentDownloadUrls: {}, formId: expect.any(String), @@ -339,7 +339,7 @@ describe('Submission Model', () => { const actualWebhookView = populatedSubmission!.getWebhookView() // Assert - expect(actualWebhookView).toEqual({ + expect(actualWebhookView).toMatchObject({ data: { attachmentDownloadUrls: {}, formId: expect.any(String), diff --git a/src/app/models/__tests__/user.server.model.spec.ts b/src/app/models/__tests__/user.server.model.spec.ts index 1c6e83c2d4..24f2b0350b 100644 --- a/src/app/models/__tests__/user.server.model.spec.ts +++ b/src/app/models/__tests__/user.server.model.spec.ts @@ -175,9 +175,7 @@ describe('User Model', () => { agency: agency._id, email: VALID_USER_EMAIL, }) - const populatedUser = await user - .populate({ path: 'agency' }) - .execPopulate() + const populatedUser = await user.populate({ path: 'agency' }) expect(populatedUser).toBeDefined() // Act diff --git a/src/app/models/admin_verification.server.model.ts b/src/app/models/admin_verification.server.model.ts index e6ff3e895c..7d5d0133ef 100644 --- a/src/app/models/admin_verification.server.model.ts +++ b/src/app/models/admin_verification.server.model.ts @@ -20,7 +20,7 @@ const AdminVerificationSchema = new Schema< admin: { type: Schema.Types.ObjectId, ref: USER_SCHEMA_ID, - required: 'AdminVerificationSchema must have an Admin', + required: [true, 'AdminVerificationSchema must have an Admin'], }, hashedContact: { type: String, diff --git a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts index e835515e2c..7f66a62e62 100644 --- a/src/app/modules/bounce/__tests__/bounce.controller.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.controller.spec.ts @@ -90,9 +90,7 @@ describe('handleSns', () => { _id: bounceDoc.formId, admin: user._id, title: 'mockTitle', - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm }) afterAll(async () => await dbHandler.closeDatabase()) diff --git a/src/app/modules/bounce/__tests__/bounce.model.spec.ts b/src/app/modules/bounce/__tests__/bounce.model.spec.ts index ca99883323..f596a9ada5 100644 --- a/src/app/modules/bounce/__tests__/bounce.model.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.model.spec.ts @@ -36,7 +36,7 @@ describe('Bounce Model', () => { const savedBounceObject = extractBounceObject(savedBounce) expect(savedBounce._id).toBeDefined() expect(savedBounce.expireAt).toBeInstanceOf(Date) - expect(omit(savedBounceObject, 'expireAt')).toEqual({ + expect(omit(savedBounceObject, 'expireAt')).toMatchObject({ formId, bounces: [{ email: MOCK_EMAIL, hasBounced: false }], hasAutoEmailed: false, @@ -56,7 +56,7 @@ describe('Bounce Model', () => { } const savedBounce = await new Bounce(params).save() const savedBounceObject = extractBounceObject(savedBounce) - expect(savedBounceObject).toEqual(params) + expect(savedBounceObject).toMatchObject(params) }) it('should reject emails when they are invalid', async () => { @@ -195,7 +195,7 @@ describe('Bounce Model', () => { { email: MOCK_EMAIL_2, hasBounced: false }, ], }) - expect(bounce.getEmails()).toEqual([MOCK_EMAIL, MOCK_EMAIL_2]) + expect(bounce.getEmails()).toMatchObject([MOCK_EMAIL, MOCK_EMAIL_2]) }) it('should return the full email list when hasBounced is true for all', () => { @@ -206,7 +206,7 @@ describe('Bounce Model', () => { { email: MOCK_EMAIL_2, hasBounced: true, bounceType: 'Transient' }, ], }) - expect(bounce.getEmails()).toEqual([MOCK_EMAIL, MOCK_EMAIL_2]) + expect(bounce.getEmails()).toMatchObject([MOCK_EMAIL, MOCK_EMAIL_2]) }) it('should return the full email list when hasBounced is mixed', () => { @@ -217,7 +217,7 @@ describe('Bounce Model', () => { { email: MOCK_EMAIL_2, hasBounced: true, bounceType: 'Transient' }, ], }) - expect(bounce.getEmails()).toEqual([MOCK_EMAIL, MOCK_EMAIL_2]) + expect(bounce.getEmails()).toMatchObject([MOCK_EMAIL, MOCK_EMAIL_2]) }) }) @@ -318,7 +318,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Transient, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Transient' }, @@ -339,7 +339,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Permanent, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Permanent' }, @@ -360,7 +360,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Permanent, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: false }, @@ -384,7 +384,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Permanent, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Transient' }, @@ -406,7 +406,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Permanent, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: false }, @@ -429,7 +429,7 @@ describe('Bounce Model', () => { bounceType: BounceType.Permanent, }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Permanent' }, @@ -452,7 +452,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [{ email: MOCK_EMAIL, hasBounced: false }], }) @@ -470,7 +470,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [{ email: MOCK_EMAIL, hasBounced: false }], }) @@ -488,7 +488,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL_2], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: false }, @@ -511,7 +511,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL_2], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Transient' }, @@ -532,7 +532,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL_2], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: false }, @@ -553,7 +553,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL_2, MOCK_EMAIL_2], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: false }, @@ -574,7 +574,7 @@ describe('Bounce Model', () => { deliveredList: [MOCK_EMAIL_2], }) const updated = bounce.updateBounceInfo(snsInfo) - expect(pick(updated.toObject(), ['formId', 'bounces'])).toEqual({ + expect(pick(updated.toObject(), ['formId', 'bounces'])).toMatchObject({ formId, bounces: [{ email: MOCK_EMAIL_2, hasBounced: false }], }) @@ -595,7 +595,7 @@ describe('Bounce Model', () => { }) const actual = Bounce.fromSnsNotification(notification, String(formId)) - expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({ + expect(omit(extractBounceObject(actual!), 'expireAt')).toMatchObject({ formId, bounces: [{ email: MOCK_EMAIL, hasBounced: false }], hasAutoEmailed: false, @@ -616,7 +616,7 @@ describe('Bounce Model', () => { }) const actual = Bounce.fromSnsNotification(notification, String(formId)) - expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({ + expect(omit(extractBounceObject(actual!), 'expireAt')).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Transient' }, @@ -639,7 +639,7 @@ describe('Bounce Model', () => { }) const actual = Bounce.fromSnsNotification(notification, String(formId)) - expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({ + expect(omit(extractBounceObject(actual!), 'expireAt')).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Permanent' }, @@ -662,7 +662,7 @@ describe('Bounce Model', () => { }) const actual = Bounce.fromSnsNotification(notification, String(formId)) - expect(omit(extractBounceObject(actual!), 'expireAt')).toEqual({ + expect(omit(extractBounceObject(actual!), 'expireAt')).toMatchObject({ formId, bounces: [ { email: MOCK_EMAIL, hasBounced: true, bounceType: 'Permanent' }, diff --git a/src/app/modules/bounce/__tests__/bounce.service.spec.ts b/src/app/modules/bounce/__tests__/bounce.service.spec.ts index 1c9c807c8a..b0fb1c1d47 100644 --- a/src/app/modules/bounce/__tests__/bounce.service.spec.ts +++ b/src/app/modules/bounce/__tests__/bounce.service.spec.ts @@ -152,7 +152,7 @@ describe('BounceService', () => { 'bounces', 'hasAutoEmailed', ]) - expect(actual).toEqual({ + expect(actual).toMatchObject({ formId: MOCK_FORM_ID, bounces: [], hasAutoEmailed: false, @@ -288,9 +288,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [ @@ -317,9 +315,7 @@ describe('BounceService', () => { admin: testUser._id, title: MOCK_FORM_TITLE, permissionList: [{ email: collabEmail, write: true }], - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [ @@ -344,9 +340,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [ @@ -367,9 +361,7 @@ describe('BounceService', () => { admin: testUser._id, title: MOCK_FORM_TITLE, permissionList: [{ email: collabEmail, write: false }], - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [ @@ -405,9 +397,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [], @@ -447,9 +437,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm const bounceDoc = new Bounce({ formId: form._id, bounces: [], @@ -814,9 +802,7 @@ describe('BounceService', () => { { email: MOCK_EMAIL, write: true }, { email: MOCK_EMAIL_2, write: false }, ], - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm MockUserService.findContactsForEmails.mockReturnValueOnce( okAsync([MOCK_CONTACT]), ) @@ -838,9 +824,7 @@ describe('BounceService', () => { { email: MOCK_EMAIL, write: true }, { email: MOCK_EMAIL_2, write: false }, ], - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm MockUserService.findContactsForEmails.mockReturnValueOnce( okAsync([omit(MOCK_CONTACT, 'contact'), MOCK_CONTACT_2]), ) @@ -862,9 +846,7 @@ describe('BounceService', () => { { email: MOCK_EMAIL, write: true }, { email: MOCK_EMAIL_2, write: false }, ], - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm MockUserService.findContactsForEmails.mockReturnValueOnce( errAsync(new DatabaseError()), ) @@ -898,9 +880,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm MockSmsFactory.sendFormDeactivatedSms.mockReturnValue(okAsync(true)) const result = await BounceService.notifyAdminsOfDeactivation(form, [ @@ -932,9 +912,7 @@ describe('BounceService', () => { const form = (await new Form({ admin: testUser._id, title: MOCK_FORM_TITLE, - }) - .populate('admin') - .execPopulate()) as IPopulatedForm + }).populate('admin')) as IPopulatedForm MockSmsFactory.sendFormDeactivatedSms .mockReturnValueOnce(okAsync(true)) .mockReturnValueOnce(errAsync(new SmsSendError())) diff --git a/src/app/modules/examples/__tests__/examples.service.spec.ts b/src/app/modules/examples/__tests__/examples.service.spec.ts index 35b7783009..5106b14ff3 100644 --- a/src/app/modules/examples/__tests__/examples.service.spec.ts +++ b/src/app/modules/examples/__tests__/examples.service.spec.ts @@ -43,9 +43,9 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ + expect(actualResults._unsafeUnwrap()).toMatchObject({ totalNumResults: testData.second.formCount, - forms: expect.arrayContaining(testData.second.expectedFormInfo), + forms: testData.second.expectedFormInfo, }) }) @@ -77,8 +77,8 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: expect.arrayContaining(testData.first.expectedFormInfo), + expect(actualResults._unsafeUnwrap()).toMatchObject({ + forms: testData.first.expectedFormInfo, }) }) @@ -110,9 +110,9 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ + expect(actualResults._unsafeUnwrap()).toMatchObject({ totalNumResults: testData.total.formCount, - forms: expect.arrayContaining(testData.total.expectedFormInfo), + forms: testData.total.expectedFormInfo, }) }) @@ -128,7 +128,7 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ + expect(actualResults._unsafeUnwrap()).toMatchObject({ forms: [], totalNumResults: testData.total.formCount, }) @@ -145,8 +145,8 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ - forms: expect.arrayContaining(testData.total.expectedFormInfo), + expect(actualResults._unsafeUnwrap()).toMatchObject({ + forms: testData.total.expectedFormInfo, }) }) @@ -182,7 +182,7 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toEqual({ + expect(actualResults._unsafeUnwrap()).toMatchObject({ form: expectedFormInfo, }) }) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts index 602d39e6ff..d50169ccdf 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts @@ -2424,9 +2424,11 @@ describe('admin-form.routes', () => { ) // Assert - const populatedForm = await publicForm - .populate({ path: 'admin', populate: { path: 'agency' } }) - .execPopulate() + const populatedForm = await publicForm.populate({ + path: 'admin', + populate: { path: 'agency' }, + }) + expect(response.status).toEqual(200) expect(response.body).toEqual({ form: jsonParseStringify(populatedForm.getPublicView()), @@ -2935,14 +2937,12 @@ describe('admin-form.routes', () => { // Assert const expectedForm = ( - await formToPreview - .populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) - .execPopulate() + await formToPreview.populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) ).getPublicView() expect(response.status).toEqual(200) expect(response.body).toEqual({ @@ -2974,14 +2974,12 @@ describe('admin-form.routes', () => { // Assert const expectedForm = ( - await collabFormToPreview - .populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) - .execPopulate() + await collabFormToPreview.populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) ).getPublicView() expect(response.status).toEqual(200) expect(response.body).toEqual({ form: jsonParseStringify(expectedForm) }) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts index a02558147c..db77935d91 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { PresignedPost } from 'aws-sdk/clients/s3' -import { ObjectId } from 'bson-ext' import { assignIn, cloneDeep, merge, omit, pick } from 'lodash' import mongoose from 'mongoose' import { err, errAsync, ok, okAsync } from 'neverthrow' @@ -351,8 +350,8 @@ describe('admin-form.service', () => { it('should true when form is successfully archived', async () => { // Arrange const mockArchivedForm = { - _id: new ObjectId(), - admin: new ObjectId(), + _id: new mongoose.Types.ObjectId(), + admin: new mongoose.Types.ObjectId(), status: FormStatus.Archived, } as IEmailFormSchema const mockArchiveFn = jest.fn().mockResolvedValue(mockArchivedForm) @@ -390,10 +389,10 @@ describe('admin-form.service', () => { }) describe('duplicateForm', () => { - const MOCK_NEW_ADMIN_ID = new ObjectId().toHexString() + const MOCK_NEW_ADMIN_ID = new mongoose.Types.ObjectId().toHexString() const MOCK_VALID_FORM = { - _id: new ObjectId(), - admin: new ObjectId(), + _id: new mongoose.Types.ObjectId(), + admin: new mongoose.Types.ObjectId(), endPage: { buttonLink: 'original form endpage link', }, @@ -424,7 +423,7 @@ describe('admin-form.service', () => { it('should successfully duplicate form', async () => { // Arrange - const mockNewAdminId = new ObjectId().toHexString() + const mockNewAdminId = new mongoose.Types.ObjectId().toHexString() const expectedParams: PickDuplicateForm & OverrideProps = { admin: MOCK_NEW_ADMIN_ID, ...MOCK_ENCRYPT_OVERRIDE_PARAMS, @@ -465,7 +464,7 @@ describe('admin-form.service', () => { it('should omit buttonLink if original form link is to the form itself', async () => { // Arrange - const mockNewAdminId = new ObjectId().toHexString() + const mockNewAdminId = new mongoose.Types.ObjectId().toHexString() const expectedParams: PickDuplicateForm & OverrideProps = { admin: MOCK_NEW_ADMIN_ID, ...omit(MOCK_ENCRYPT_OVERRIDE_PARAMS, 'isTemplate'), @@ -514,7 +513,7 @@ describe('admin-form.service', () => { it('should return DatabaseError if error occurred during the duplication', async () => { // Arrange - const mockNewAdminId = new ObjectId().toHexString() + const mockNewAdminId = new mongoose.Types.ObjectId().toHexString() const expectedParams: PickDuplicateForm & OverrideProps = { admin: MOCK_NEW_ADMIN_ID, ...omit(MOCK_ENCRYPT_OVERRIDE_PARAMS, 'isTemplate'), @@ -559,11 +558,11 @@ describe('admin-form.service', () => { describe('transferFormOwnership', () => { const MOCK_NEW_OWNER_EMAIL = 'random@example.com' const MOCK_CURRENT_OWNER = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), email: 'someemail@example.com', } as IUserSchema const MOCK_NEW_OWNER = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), email: MOCK_NEW_OWNER_EMAIL, } as IUserSchema @@ -574,14 +573,12 @@ describe('admin-form.service', () => { } as IPopulatedForm const mockUpdatedForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), admin: MOCK_CURRENT_OWNER, emails: [MOCK_NEW_OWNER_EMAIL], responseMode: FormResponseMode.Email, title: 'some mock form', - populate: jest.fn().mockReturnValue({ - execPopulate: jest.fn().mockResolvedValue(expectedPopulateResult), - }), + populate: jest.fn().mockResolvedValue(expectedPopulateResult), } as unknown as IFormSchema const mockValidForm = { @@ -751,17 +748,12 @@ describe('admin-form.service', () => { // Arrange const mockPopulateErrorStr = 'population failed!' const mockUpdatedForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), admin: MOCK_CURRENT_OWNER, emails: [MOCK_NEW_OWNER_EMAIL], responseMode: FormResponseMode.Email, title: 'some mock form', - populate: jest.fn().mockReturnValue({ - // Mock populate error. - execPopulate: jest - .fn() - .mockRejectedValue(new Error(mockPopulateErrorStr)), - }), + populate: jest.fn().mockRejectedValue(new Error(mockPopulateErrorStr)), } as unknown as IFormSchema const mockValidForm = { @@ -801,12 +793,12 @@ describe('admin-form.service', () => { // Arrange const formParams: Parameters[0] = { title: 'create form title', - admin: new ObjectId().toHexString(), + admin: new mongoose.Types.ObjectId().toHexString(), responseMode: FormResponseMode.Email, emails: 'example@example.com', } const expectedForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), ...formParams, } as IFormSchema const createSpy = jest @@ -825,7 +817,7 @@ describe('admin-form.service', () => { // Arrange const formParams: Parameters[0] = { title: 'create form title', - admin: new ObjectId().toHexString(), + admin: new mongoose.Types.ObjectId().toHexString(), responseMode: FormResponseMode.Encrypt, publicKey: 'some key', } @@ -849,7 +841,7 @@ describe('admin-form.service', () => { // Arrange const formParams: Parameters[0] = { title: 'create form title', - admin: new ObjectId().toHexString(), + admin: new mongoose.Types.ObjectId().toHexString(), responseMode: FormResponseMode.Encrypt, publicKey: 'some key', } @@ -872,7 +864,7 @@ describe('admin-form.service', () => { // Arrange const formParams: Parameters[0] = { title: 'create form title', - admin: new ObjectId().toHexString(), + admin: new mongoose.Types.ObjectId().toHexString(), responseMode: FormResponseMode.Encrypt, publicKey: 'some key', } @@ -900,7 +892,7 @@ describe('admin-form.service', () => { // Arrange const formParams: Parameters[0] = { title: 'create form title', - admin: new ObjectId().toHexString(), + admin: new mongoose.Types.ObjectId().toHexString(), responseMode: FormResponseMode.Encrypt, publicKey: 'some key', } @@ -922,8 +914,8 @@ describe('admin-form.service', () => { describe('editFormFields', () => { const MOCK_UPDATED_FORM = { - _id: new ObjectId(), - admin: new ObjectId(), + _id: new mongoose.Types.ObjectId(), + admin: new mongoose.Types.ObjectId(), form_fields: [ generateDefaultField(BasicField.Email), generateDefaultField(BasicField.Mobile), @@ -1025,8 +1017,8 @@ describe('admin-form.service', () => { describe('updateForm', () => { const MOCK_UPDATED_FORM = { - _id: new ObjectId(), - admin: new ObjectId(), + _id: new mongoose.Types.ObjectId(), + admin: new mongoose.Types.ObjectId(), status: FormStatus.Private, form_fields: [ generateDefaultField(BasicField.Mobile), @@ -1115,12 +1107,12 @@ describe('admin-form.service', () => { } as unknown as IFormDocument const MOCK_EMAIL_FORM = mocked({ - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), status: FormStatus.Public, responseMode: FormResponseMode.Email, } as unknown as IPopulatedForm) const MOCK_ENCRYPT_FORM = mocked({ - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), status: FormStatus.Public, responseMode: FormResponseMode.Encrypt, } as unknown as IPopulatedForm) @@ -1266,7 +1258,7 @@ describe('admin-form.service', () => { updateFormFieldById: jest.fn().mockResolvedValue(null), } as unknown as IPopulatedForm - const invalidFieldId = new ObjectId().toHexString() + const invalidFieldId = new mongoose.Types.ObjectId().toHexString() const mockNewField = generateDefaultField( BasicField.Number, ) as FieldUpdateDto @@ -1293,7 +1285,7 @@ describe('admin-form.service', () => { ), } as unknown as IPopulatedForm - const invalidFieldId = new ObjectId().toHexString() + const invalidFieldId = new mongoose.Types.ObjectId().toHexString() const mockNewField = generateDefaultField( BasicField.Number, ) as FieldUpdateDto @@ -1377,7 +1369,7 @@ describe('admin-form.service', () => { }) describe('deleteFormLogic', () => { - const logicId = new ObjectId().toHexString() + const logicId = new mongoose.Types.ObjectId().toHexString() const mockFormLogic = { form_logics: [ { @@ -1393,13 +1385,13 @@ describe('admin-form.service', () => { beforeEach(() => { mockEmailForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), status: FormStatus.Public, responseMode: FormResponseMode.Email, ...mockFormLogic, } as unknown as IPopulatedForm mockEncryptForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), status: FormStatus.Public, responseMode: FormResponseMode.Encrypt, ...mockFormLogic, @@ -1427,7 +1419,7 @@ describe('admin-form.service', () => { expect(actualResult._unsafeUnwrap()).toEqual(mockEmailForm) expect(UPDATE_SPY).toHaveBeenCalledWith( - mockEmailForm._id, + String(mockEmailForm._id), { $pull: { form_logics: { _id: logicId } }, }, @@ -1464,7 +1456,7 @@ describe('admin-form.service', () => { expect(actualResult._unsafeUnwrap()).toEqual(mockEncryptForm) expect(UPDATE_SPY).toHaveBeenCalledWith( - mockEncryptForm._id, + String(mockEncryptForm._id), { $pull: { form_logics: { _id: logicId } }, }, @@ -1482,7 +1474,7 @@ describe('admin-form.service', () => { it('should return LogicNotFoundError if logic does not exist on form', async () => { // Act - const wrongLogicId = new ObjectId().toHexString() + const wrongLogicId = new mongoose.Types.ObjectId().toHexString() const actualResult = await AdminFormService.deleteFormLogic( mockEmailForm, wrongLogicId, @@ -1508,7 +1500,7 @@ describe('admin-form.service', () => { const mockForm = { title: 'some mock form', form_fields: [fieldToDuplicate], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), duplicateFormFieldById: jest.fn().mockResolvedValue(mockUpdatedForm), } as unknown as IPopulatedForm @@ -1538,7 +1530,7 @@ describe('admin-form.service', () => { const mockForm = { title: 'some mock form', form_fields: [fieldToDuplicate], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), duplicateFormFieldById: jest.fn().mockResolvedValue(null), } as unknown as IPopulatedForm @@ -1613,7 +1605,7 @@ describe('admin-form.service', () => { form_fields: [generateDefaultField(BasicField.YesNo)], reorderFormFieldById: jest.fn().mockResolvedValue(null), } as unknown as IPopulatedForm - const fieldToReorder = new ObjectId().toHexString() + const fieldToReorder = new mongoose.Types.ObjectId().toHexString() const newPosition = 2 // Act @@ -1640,7 +1632,7 @@ describe('admin-form.service', () => { .fn() .mockRejectedValue(new Error('some error')), } as unknown as IPopulatedForm - const fieldToReorder = new ObjectId().toHexString() + const fieldToReorder = new mongoose.Types.ObjectId().toHexString() const newPosition = 2 // Act @@ -1720,11 +1712,11 @@ describe('admin-form.service', () => { }) describe('createFormLogic', () => { - const logicId1 = new ObjectId() - const logicId2 = new ObjectId() - const logicId3 = new ObjectId() - const mockEmailFormId = new ObjectId() - const mockEncryptFormId = new ObjectId() + const logicId1 = new mongoose.Types.ObjectId() + const logicId2 = new mongoose.Types.ObjectId() + const logicId3 = new mongoose.Types.ObjectId() + const mockEmailFormId = new mongoose.Types.ObjectId() + const mockEncryptFormId = new mongoose.Types.ObjectId() const mockFormLogicOld = { form_logics: [ @@ -1924,7 +1916,7 @@ describe('admin-form.service', () => { const mockForm = { title: 'some mock form', form_fields: initialFields, - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } as unknown as IPopulatedForm deleteSpy.mockResolvedValueOnce(mockUpdatedForm) @@ -1947,13 +1939,13 @@ describe('admin-form.service', () => { const mockForm = { title: 'some mock form', form_fields: [generateDefaultField(BasicField.Nric)], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } as unknown as IPopulatedForm // Act const actual = await AdminFormService.deleteFormField( mockForm, - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), ) // Assert @@ -1967,7 +1959,7 @@ describe('admin-form.service', () => { const mockForm = { title: 'some mock form', form_fields: [fieldToDelete], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } as unknown as IPopulatedForm deleteSpy.mockResolvedValueOnce(null) @@ -1988,7 +1980,7 @@ describe('admin-form.service', () => { describe('updateEndPage', () => { const updateSpy = jest.spyOn(FormModel, 'updateEndPageById') - const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_FORM_ID = new mongoose.Types.ObjectId().toHexString() const MOCK_NEW_END_PAGE: FormEndPage = { title: 'expected end page title', buttonLink: 'https://some-button-link.example.com', @@ -2050,7 +2042,7 @@ describe('admin-form.service', () => { describe('updateStartPage', () => { const updateSpy = jest.spyOn(FormModel, 'updateStartPageById') - const MOCK_FORM_ID = new ObjectId().toHexString() + const MOCK_FORM_ID = new mongoose.Types.ObjectId().toHexString() const MOCK_NEW_START_PAGE: FormStartPage = { colorTheme: FormColorTheme.Blue, paragraph: 'some paragraph', @@ -2113,10 +2105,10 @@ describe('admin-form.service', () => { }) describe('updateFormLogic', () => { - const logicId1 = new ObjectId() - const logicId2 = new ObjectId() - const mockEmailFormId = new ObjectId() - const mockEncryptFormId = new ObjectId() + const logicId1 = new mongoose.Types.ObjectId() + const logicId2 = new mongoose.Types.ObjectId() + const mockEmailFormId = new mongoose.Types.ObjectId() + const mockEncryptFormId = new mongoose.Types.ObjectId() const mockFormLogicOld = { form_logics: [ @@ -2230,7 +2222,7 @@ describe('admin-form.service', () => { it('should return LogicNotFoundError if logic does not exist on form', async () => { // Act - const wrongLogicId = new ObjectId().toHexString() + const wrongLogicId = new mongoose.Types.ObjectId().toHexString() const actualResult = await AdminFormService.updateFormLogic( mockEmailForm, wrongLogicId, @@ -2252,7 +2244,7 @@ describe('admin-form.service', () => { title: 'some mock form', // Append created field to end of form_fields. form_fields: [MOCK_FIELD], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } as IPopulatedForm // Act @@ -2267,12 +2259,12 @@ describe('admin-form.service', () => { it("should return FieldNotFoundError when the fieldId does not exist in the form's fields", async () => { // Arrange - const MOCK_ID = new ObjectId().toHexString() + const MOCK_ID = new mongoose.Types.ObjectId().toHexString() const MOCK_FORM = { title: 'some mock form', // Append created field to end of form_fields. form_fields: [], - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } as unknown as IPopulatedForm const expectedError = new FieldNotFoundError( `Attempted to retrieve field ${MOCK_ID} from ${MOCK_FORM._id} but field was not present`, @@ -2289,7 +2281,7 @@ describe('admin-form.service', () => { describe('disableSmsVerificationsForUser', () => { it('should return true when the forms are updated successfully', async () => { // Arrange - const MOCK_ADMIN_ID = new ObjectId().toHexString() + const MOCK_ADMIN_ID = new mongoose.Types.ObjectId().toHexString() const disableSpy = jest.spyOn(FormModel, 'disableSmsVerificationsForUser') disableSpy.mockResolvedValueOnce({ n: 0, nModified: 0, ok: 0 }) @@ -2305,7 +2297,7 @@ describe('admin-form.service', () => { it('should return a database error when the operation fails', async () => { // Arrange - const MOCK_ADMIN_ID = new ObjectId().toHexString() + const MOCK_ADMIN_ID = new mongoose.Types.ObjectId().toHexString() const disableSpy = jest.spyOn(FormModel, 'disableSmsVerificationsForUser') disableSpy.mockRejectedValueOnce('whoops') @@ -2323,7 +2315,7 @@ describe('admin-form.service', () => { describe('shouldUpdateFormField', () => { const MOCK_FORM = { admin: { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), }, } as unknown as IPopulatedForm diff --git a/src/app/modules/myinfo/__tests__/myinfo_hash.model.spec.ts b/src/app/modules/myinfo/__tests__/myinfo_hash.model.spec.ts index c09a9df56d..21f494a8dd 100644 --- a/src/app/modules/myinfo/__tests__/myinfo_hash.model.spec.ts +++ b/src/app/modules/myinfo/__tests__/myinfo_hash.model.spec.ts @@ -53,7 +53,7 @@ describe('MyInfo Hash Model', () => { expect(actual._id).toBeDefined() expect( pick(actual, ['uinFin', 'form', 'fields', 'created', 'expireAt']), - ).toEqual(DEFAULT_INPUT_PARAMS) + ).toMatchObject(DEFAULT_INPUT_PARAMS) }) it('should throw validation error on missing uinFin', async () => { @@ -134,10 +134,10 @@ describe('MyInfo Hash Model', () => { const found = await MyInfoHash.findOne({}) // Both the returned document and the found document should match // Note: we are checking that the document contains the HASHED uinFin - expect(pick(actual, ['uinFin', 'form', 'fields'])).toEqual( + expect(pick(actual, ['uinFin', 'form', 'fields'])).toMatchObject( pick(DEFAULT_SAVED_PARAMS, ['uinFin', 'form', 'fields']), ) - expect(pick(found, ['uinFin', 'form', 'fields'])).toEqual( + expect(pick(found, ['uinFin', 'form', 'fields'])).toMatchObject( pick(DEFAULT_SAVED_PARAMS, ['uinFin', 'form', 'fields']), ) }) diff --git a/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts b/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts index fbf435de25..652067825f 100644 --- a/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts +++ b/src/app/modules/submission/email-submission/__tests__/email-submission.service.spec.ts @@ -72,7 +72,7 @@ describe('email-submission.service', () => { _id: result._id, }) - expect(result.form).toEqual(MOCK_FORM._id) + expect(result.form).toMatchObject(MOCK_FORM._id) expect(result.responseHash).toEqual(MOCK_RESPONSE_HASH) expect(result.responseSalt).toEqual(MOCK_RESPONSE_SALT) expect(foundInDatabase).toBeNull() diff --git a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts index 5b1b9cdf07..c1bbe6c8a9 100644 --- a/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts +++ b/src/app/modules/submission/encrypt-submission/__tests__/encrypt-submission.service.spec.ts @@ -71,7 +71,7 @@ describe('encrypt-submission.service', () => { }) expect(result.encryptedContent).toBe(MOCK_ENCRYPTED_CONTENT) - expect(result.form).toEqual(MOCK_FORM._id) + expect(result.form).toMatchObject(MOCK_FORM._id) expect(result.verifiedContent).toEqual(MOCK_VERIFIED_CONTENT) expect(Object.fromEntries(result.attachmentMetadata!)).toEqual( Object.fromEntries(MOCK_ATTACHMENT_METADATA), diff --git a/src/app/modules/verification/__tests__/verification.model.spec.ts b/src/app/modules/verification/__tests__/verification.model.spec.ts index 0d73b6fe73..f9979ab7b0 100644 --- a/src/app/modules/verification/__tests__/verification.model.spec.ts +++ b/src/app/modules/verification/__tests__/verification.model.spec.ts @@ -38,7 +38,7 @@ describe('Verification Model', () => { '__v', ]) const expectedSavedFields = merge({}, VFN_DEFAULTS, VFN_PARAMS) - expect(actualSavedFields).toEqual(expectedSavedFields) + expect(actualSavedFields).toMatchObject(expectedSavedFields) }) it('should save successfully when expireAt is specified', async () => { @@ -51,7 +51,7 @@ describe('Verification Model', () => { '__v', ]) const expectedSavedFields = merge({}, VFN_DEFAULTS, params) - expect(actualSavedFields).toEqual(expectedSavedFields) + expect(actualSavedFields).toMatchObject(expectedSavedFields) }) it('should save successfully with defaults when field keys are not specified', async () => { @@ -67,7 +67,7 @@ describe('Verification Model', () => { '__v', ]) const expectedSavedFields = merge({}, VFN_DEFAULTS, VFN_PARAMS, vfnParams) - expect(actualSavedFields).toEqual(expectedSavedFields) + expect(actualSavedFields).toMatchObject(expectedSavedFields) }) it('should save successfully when field keys are specified', async () => { @@ -88,7 +88,7 @@ describe('Verification Model', () => { '__v', ]) const expectedSavedFields = merge({}, VFN_DEFAULTS, VFN_PARAMS, vfnParams) - expect(actualSavedFields).toEqual(expectedSavedFields) + expect(actualSavedFields).toMatchObject(expectedSavedFields) }) it('should not save when field IDs are identical', async () => { @@ -127,7 +127,7 @@ describe('Verification Model', () => { const result = transaction.getField(field1._id)!.toJSON() - expect(omit(result, '_id')).toEqual(omit(field1, '_id')) + expect(omit(result, '_id')).toMatchObject(omit(field1, '_id')) expect(String(result._id)).toEqual(field1._id) }) @@ -156,7 +156,7 @@ describe('Verification Model', () => { verificationSaved._id, ) const expected = pick(verificationSaved, ['formId', 'expireAt', '_id']) - expect(actual).toEqual(expected) + expect(actual).toMatchObject(expected) }) it('should return null when transaction does not exist', async () => { diff --git a/src/app/modules/verification/__tests__/verification.service.spec.ts b/src/app/modules/verification/__tests__/verification.service.spec.ts index 3e19d3d01b..aa61956831 100644 --- a/src/app/modules/verification/__tests__/verification.service.spec.ts +++ b/src/app/modules/verification/__tests__/verification.service.spec.ts @@ -1,5 +1,4 @@ /* eslint-disable import/first */ -import { ObjectId } from 'bson' import { addHours, subHours, subMinutes, subSeconds } from 'date-fns' import mongoose from 'mongoose' import { errAsync, okAsync } from 'neverthrow' @@ -81,11 +80,11 @@ jest.mock('src/app/utils/hash') const MockHashUtils = mocked(HashUtils, true) describe('Verification service', () => { - const mockFieldIdObj = new ObjectId() + const mockFieldIdObj = new mongoose.Types.ObjectId() const mockFieldId = mockFieldIdObj.toHexString() const mockField = { ...generateFieldParams(), _id: mockFieldId } - const mockTransactionId = new ObjectId().toHexString() - const mockFormId = new ObjectId().toHexString() + const mockTransactionId = new mongoose.Types.ObjectId().toHexString() + const mockFormId = new mongoose.Types.ObjectId().toHexString() let mockTransaction: IVerificationSchema beforeAll(async () => await dbHandler.connect()) @@ -107,7 +106,7 @@ describe('Verification service', () => { describe('createTransaction', () => { const mockForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), title: 'mockForm', form_fields: [], } as unknown as IFormSchema @@ -171,7 +170,7 @@ describe('Verification service', () => { mockPublicView = { expireAt: mockTransaction.expireAt, formId: mockTransaction.formId, - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), } getPublicViewByIdSpy = jest .spyOn(VerificationModel, 'getPublicViewById') @@ -224,7 +223,7 @@ describe('Verification service', () => { it('should return TransactionNotFoundError when transaction ID does not exist', async () => { const result = await VerificationService.resetFieldForTransaction( // non-existent transaction ID - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), mockFieldId, ) @@ -252,7 +251,7 @@ describe('Verification service', () => { const result = await VerificationService.resetFieldForTransaction( mockTransactionId, // ObjectId which does not exist in mockTransaction - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), ) expect(resetFieldSpy).not.toHaveBeenCalled() @@ -293,7 +292,7 @@ describe('Verification service', () => { > const mockForm = { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), title: 'mockForm', form_fields: [ generateDefaultField(BasicField.Mobile, { @@ -352,7 +351,7 @@ describe('Verification service', () => { it('should return TransactionNotFoundError when transaction ID does not exist', async () => { const result = await VerificationService.sendNewOtp({ // non-existent transaction ID - transactionId: new ObjectId().toHexString(), + transactionId: new mongoose.Types.ObjectId().toHexString(), fieldId: mockFieldId, hashedOtp: MOCK_HASHED_OTP, otp: MOCK_OTP, @@ -396,7 +395,7 @@ describe('Verification service', () => { const result = await VerificationService.sendNewOtp({ transactionId: mockTransactionId, // ObjectId which does not exist in mockTransaction - fieldId: new ObjectId().toHexString(), + fieldId: new mongoose.Types.ObjectId().toHexString(), hashedOtp: MOCK_HASHED_OTP, otp: MOCK_OTP, recipient: MOCK_RECIPIENT, @@ -498,7 +497,7 @@ describe('Verification service', () => { expect(MockSmsFactory.sendVerificationOtp).toHaveBeenCalledWith( MOCK_RECIPIENT, MOCK_OTP, - new ObjectId(mockFormId), + new mongoose.Types.ObjectId(mockFormId), ) expect( MockFormsgSdk.verification.generateSignature, @@ -524,12 +523,12 @@ describe('Verification service', () => { expect(MockSmsFactory.sendVerificationOtp).toHaveBeenCalledWith( MOCK_RECIPIENT, MOCK_OTP, - new ObjectId(mockFormId), + new mongoose.Types.ObjectId(mockFormId), ) expect(MockFormsgSdk.verification.generateSignature).toHaveBeenCalledWith( { transactionId: mockTransactionId, - formId: new ObjectId(mockFormId), + formId: new mongoose.Types.ObjectId(mockFormId), fieldId: mockFieldId, answer: MOCK_RECIPIENT, }, @@ -595,7 +594,7 @@ describe('Verification service', () => { it('should return TransactionNotFoundError when transaction ID does not exist', async () => { const result = await VerificationService.verifyOtp( - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), mockFieldId, MOCK_OTP, ) @@ -624,7 +623,7 @@ describe('Verification service', () => { it('should return FieldNotFoundInTransactionError when field ID does not exist', async () => { const result = await VerificationService.verifyOtp( mockTransactionId, - new ObjectId().toHexString(), + new mongoose.Types.ObjectId().toHexString(), MOCK_OTP, ) @@ -729,9 +728,9 @@ describe('Verification service', () => { describe('disableVerifiedFieldsIfRequired', () => { const MOCK_FORM = { title: 'some mock form', - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), admin: { - _id: new ObjectId(), + _id: new mongoose.Types.ObjectId(), }, permissionList: [{ email: 'some@user.gov.sg' }], } as IPopulatedForm @@ -949,7 +948,7 @@ describe('Verification service', () => { describe('shouldGenerateMobileOtp', () => { it('should return true when the fieldId is valid and verifiable', async () => { // Arrange - const fieldId = new ObjectId().toHexString() + const fieldId = new mongoose.Types.ObjectId().toHexString() const mockForm = { form_fields: [ generateDefaultField(BasicField.Mobile, { @@ -971,7 +970,7 @@ describe('Verification service', () => { it('should return OtpRequestError when an OTP is requested on a field that is not verifiable', async () => { // Arrange - const fieldId = new ObjectId().toHexString() + const fieldId = new mongoose.Types.ObjectId().toHexString() const mockForm = { // Not enabled. form_fields: [ @@ -997,12 +996,12 @@ describe('Verification service', () => { const mockForm = { form_fields: [ generateDefaultField(BasicField.Mobile, { - _id: new ObjectId().toHexString(), + _id: new mongoose.Types.ObjectId().toHexString(), isVerifiable: true, }), ], } - const fieldIdOtherString = new ObjectId().toHexString() + const fieldIdOtherString = new mongoose.Types.ObjectId().toHexString() // Act const actual = await VerificationService.shouldGenerateMobileOtp( @@ -1019,7 +1018,7 @@ describe('Verification service', () => { const mockForm = { form_fields: [], } - const fieldId = new ObjectId().toHexString() + const fieldId = new mongoose.Types.ObjectId().toHexString() // Act const actual = await VerificationService.shouldGenerateMobileOtp( diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.preview.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.preview.routes.spec.ts index b1c7ccf01b..9d50ed6c57 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.preview.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.preview.routes.spec.ts @@ -97,14 +97,12 @@ describe('admin-form.preview.routes', () => { // Assert const expectedForm = ( - await formToPreview - .populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) - .execPopulate() + await formToPreview.populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) ).getPublicView() expect(response.status).toEqual(200) expect(response.body).toEqual({ @@ -136,14 +134,12 @@ describe('admin-form.preview.routes', () => { // Assert const expectedForm = ( - await collabFormToPreview - .populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) - .execPopulate() + await collabFormToPreview.populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) ).getPublicView() expect(response.status).toEqual(200) expect(response.body).toEqual({ form: jsonParseStringify(expectedForm) }) diff --git a/src/app/services/sms/__tests__/sms_count.server.model.spec.ts b/src/app/services/sms/__tests__/sms_count.server.model.spec.ts index 4399941b83..b86b4bfbd7 100644 --- a/src/app/services/sms/__tests__/sms_count.server.model.spec.ts +++ b/src/app/services/sms/__tests__/sms_count.server.model.spec.ts @@ -68,7 +68,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual(MOCK_FORM_DEACTIVATED_PARAMS) + expect(actualSavedObject).toMatchObject(MOCK_FORM_DEACTIVATED_PARAMS) }) it('should reject if form is missing', async () => { @@ -157,7 +157,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual(MOCK_BOUNCED_SUBMISSION_PARAMS) + expect(actualSavedObject).toMatchObject(MOCK_BOUNCED_SUBMISSION_PARAMS) }) it('should reject if form is missing', async () => { @@ -268,7 +268,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual(expected) + expect(actualSavedObject).toMatchObject(expected) }) it('should save successfully, but not save fields that is not defined in the schema', async () => { @@ -300,7 +300,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual(expected) + expect(actualSavedObject).toMatchObject(expected) }) it('should save successfully and set isOnboarded to true when the credentials are different from default', async () => { @@ -328,7 +328,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) as IVerificationSmsCountSchema - expect(omit(actualSavedObject, 'isOnboardedAccount')).toEqual( + expect(omit(actualSavedObject, 'isOnboardedAccount')).toMatchObject( verificationParams, ) expect(actualSavedObject.isOnboardedAccount).toBe(true) @@ -451,7 +451,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual( + expect(actualSavedObject).toMatchObject( merge({}, MOCK_BOUNCED_SUBMISSION_PARAMS, { logType: LogType.success, }), @@ -477,7 +477,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual( + expect(actualSavedObject).toMatchObject( merge({}, MOCK_BOUNCED_SUBMISSION_PARAMS, { logType: LogType.failure, }), @@ -503,7 +503,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual( + expect(actualSavedObject).toMatchObject( merge({}, MOCK_FORM_DEACTIVATED_PARAMS, { logType: LogType.success }), ) }) @@ -527,7 +527,7 @@ describe('SmsCount', () => { 'createdAt', '__v', ]) - expect(actualSavedObject).toEqual( + expect(actualSavedObject).toMatchObject( merge({}, MOCK_FORM_DEACTIVATED_PARAMS, { logType: LogType.failure }), ) }) @@ -555,7 +555,7 @@ describe('SmsCount', () => { expect(actualLog?._id).toBeDefined() // Retrieve object and compare to params, remove indeterministic keys const actualSavedObject = omit(actualLog, ['_id', 'createdAt', '__v']) - expect(actualSavedObject).toEqual(expectedLog) + expect(actualSavedObject).toMatchObject(expectedLog) }) it('should successfully log verification failures in the collection', async () => { @@ -581,7 +581,7 @@ describe('SmsCount', () => { expect(actualLog?._id).toBeDefined() // Retrieve object and compare to params, remove indeterministic keys const actualSavedObject = omit(actualLog, ['_id', 'createdAt', '__v']) - expect(actualSavedObject).toEqual(expectedLog) + expect(actualSavedObject).toMatchObject(expectedLog) }) it('should reject if smsType is invalid', async () => { From b644fb8937a311ce41eed1f64b5cc7e0e993cb24 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 22 Nov 2021 17:41:44 +0800 Subject: [PATCH 19/32] fix(e2e): correctly return promise of mongoose.createConnection v6 change --- src/app/loaders/mongoose.ts | 2 +- tests/end-to-end/helpers/util.js | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/app/loaders/mongoose.ts b/src/app/loaders/mongoose.ts index e686f40d19..d76b7a0d84 100644 --- a/src/app/loaders/mongoose.ts +++ b/src/app/loaders/mongoose.ts @@ -38,7 +38,7 @@ export default async (): Promise => { }) // Store the uri to connect to later on - config.db.uri = await mongod.getUri() + config.db.uri = mongod.getUri() } // Actually connect to the database diff --git a/tests/end-to-end/helpers/util.js b/tests/end-to-end/helpers/util.js index 0f0bf14ef8..3581f23842 100644 --- a/tests/end-to-end/helpers/util.js +++ b/tests/end-to-end/helpers/util.js @@ -217,10 +217,7 @@ function spec(path) { * Connects to mongo-memory-server instance. */ async function makeMongooseFixtures() { - const connection = await mongoose.createConnection(dbUri, { - reconnectTries: 5, - useNewUrlParser: true, - }) + const connection = await mongoose.createConnection(dbUri).asPromise() return connection } From 7af2d5be3f0a4ce7acfe8c692c3447f6420a5ba9 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 10:53:05 +0800 Subject: [PATCH 20/32] fix(FormModel): move esrvcId schema prop to be above status Mongoose now saves objects with keys in the order the keys are specified in the schema, not in the user-defined object. --- src/app/models/form.server.model.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index d911cd6962..f69d576507 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -335,6 +335,16 @@ const compileFormModel = (db: Mongoose): IFormModel => { }, }, + // This must be before `status` since `status` has setters reliant on + // whether esrvcId is available, and mongoose@v6 now saves objects with keys + // in the order the keys are specifified in the schema instead of the object. + // See https://mongoosejs.com/docs/migrating_to_6.html#schema-defined-document-key-order. + esrvcId: { + type: String, + required: false, + validate: [/^\S*$/i, 'e-service ID must not contain whitespace'], + }, + status: { type: String, enum: Object.values(FormStatus), @@ -365,11 +375,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { type: Boolean, default: true, }, - esrvcId: { - type: String, - required: false, - validate: [/^\S*$/i, 'e-service ID must not contain whitespace'], - }, webhook: { // TODO: URL validation, encrypt mode validation From 16791e4697d77a997f04ac521f201c62a8f531c5 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 11:34:07 +0800 Subject: [PATCH 21/32] fix(spcp.service.spec): remove unused dbhandler --- src/app/modules/spcp/__tests__/spcp.service.spec.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/app/modules/spcp/__tests__/spcp.service.spec.ts b/src/app/modules/spcp/__tests__/spcp.service.spec.ts index 71d1008678..fd6c018fea 100644 --- a/src/app/modules/spcp/__tests__/spcp.service.spec.ts +++ b/src/app/modules/spcp/__tests__/spcp.service.spec.ts @@ -7,8 +7,6 @@ import { mocked } from 'ts-jest/utils' import { ISpcpMyInfo } from 'src/app/config/features/spcp-myinfo.config' import { MOCK_COOKIE_AGE } from 'src/app/modules/myinfo/__tests__/myinfo.test.constants' -import dbHandler from 'tests/unit/backend/helpers/jest-db' - import { FormAuthType } from '../../../../../shared/types' import { CreateRedirectUrlError, @@ -55,12 +53,9 @@ jest.mock('axios') const MockAxios = mocked(axios, true) describe('spcp.service', () => { - beforeAll(async () => await dbHandler.connect()) beforeEach(async () => { - await dbHandler.clearDatabase() jest.clearAllMocks() }) - afterAll(async () => await dbHandler.closeDatabase()) describe('class constructor', () => { it('should instantiate auth clients with the correct params', () => { const spcpServiceClass = new SpcpServiceClass(MOCK_PARAMS) From f502f70c6739099b9265de7ab2aa849249b91a8c Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 11:43:22 +0800 Subject: [PATCH 22/32] feat: remove mockingoose package was underutilitized and broke during mongoose v6 migration, so deleting it --- package-lock.json | 6 ---- package.json | 1 - .../__tests__/public-form.service.spec.ts | 31 +++++++++++++------ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ea63b32dd..fa71150b3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17833,12 +17833,6 @@ "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==", "dev": true }, - "mockingoose": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/mockingoose/-/mockingoose-2.13.2.tgz", - "integrity": "sha512-KP+rweeXjP5P1SMoBQTS7NQG4xIZLbrlJ24dKDjuRRX3kzn+pCAg4w1e+Er6UQEoBtqjbXDM9PTbQkXUk4S7iw==", - "dev": true - }, "module-not-found-error": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", diff --git a/package.json b/package.json index b614fe88ac..2d9682c60e 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,6 @@ "maildev": "^1.1.0", "mini-css-extract-plugin": "^0.5.0", "mockdate": "^3.0.5", - "mockingoose": "^2.13.2", "mongodb-memory-server-core": "^8.0.2", "ngrok": "^4.2.2", "optimize-css-assets-webpack-plugin": "^5.0.8", diff --git a/src/app/modules/form/public-form/__tests__/public-form.service.spec.ts b/src/app/modules/form/public-form/__tests__/public-form.service.spec.ts index cc50bfbd85..0b35ace90c 100644 --- a/src/app/modules/form/public-form/__tests__/public-form.service.spec.ts +++ b/src/app/modules/form/public-form/__tests__/public-form.service.spec.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { ObjectId } from 'bson-ext' -import mockingoose from 'mockingoose' import mongoose from 'mongoose' import { PartialDeep } from 'type-fest' @@ -79,8 +78,10 @@ describe('public-form.service', () => { it('should return DatabaseError when error occurs whilst inserting feedback', async () => { // Arrange // Mock failure - mockingoose(FormFeedbackModel).toReturn(new Error('some error'), 'save') - const insertSpy = jest.spyOn(FormFeedbackModel, 'create') + const insertSpy = jest + .spyOn(FormFeedbackModel, 'create') + // @ts-ignore + .mockRejectedValueOnce(new Error('some error')) // Act const actualResult = await PublicFormService.insertFormFeedback( @@ -151,8 +152,12 @@ describe('public-form.service', () => { } // Mock form return. - mockingoose(FormModel).toReturn(mockForm, 'findOne') - const findByIdSpy = jest.spyOn(FormModel, 'findById') + const findByIdSpy = jest + .spyOn(FormModel, 'findById') + // @ts-ignore + .mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(mockForm), + }) // Act const createResult = await PublicFormService.createMetatags( @@ -180,8 +185,12 @@ describe('public-form.service', () => { it('should return FormNotFoundError when form cannot be retrieved with given formId', async () => { // Arrange // Mock null form return. - mockingoose(FormModel).toReturn(null, 'findOne') - const findByIdSpy = jest.spyOn(FormModel, 'findById') + const findByIdSpy = jest + .spyOn(FormModel, 'findById') + // @ts-ignore + .mockReturnValueOnce({ + exec: jest.fn().mockResolvedValue(null), + }) // Act const createResult = await PublicFormService.createMetatags( @@ -199,8 +208,12 @@ describe('public-form.service', () => { it('should return DatabaseError when error occurs whilst querying database', async () => { // Arrange // Mock failure - mockingoose(FormModel).toReturn(new Error('some error'), 'findOne') - const findByIdSpy = jest.spyOn(FormModel, 'findById') + const findByIdSpy = jest + .spyOn(FormModel, 'findById') + // @ts-ignore + .mockReturnValueOnce({ + exec: jest.fn().mockRejectedValue(new Error('some error')), + }) // Act const createResult = await PublicFormService.createMetatags( From a1b850d0bb162cb0868b77136b5b41286d37bfdc Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 13:42:11 +0800 Subject: [PATCH 23/32] fix(test): fix failing tests due to immutable create field mongoose v6 sets the createdAt value to be immutable. See https://mongoosejs.com/docs/migrating_to_6.html#immutable-createdat --- .../__tests__/admin-form.routes.spec.ts | 86 +++++++------ .../admin-forms.submissions.routes.spec.ts | 119 +++++++++--------- 2 files changed, 109 insertions(+), 96 deletions(-) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts index d50169ccdf..03e20c2dab 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts @@ -2,6 +2,7 @@ import { ObjectId } from 'bson-ext' import { format, subDays } from 'date-fns' import { cloneDeep, omit, times } from 'lodash' +import MockDate from 'mockdate' import mongoose from 'mongoose' import { errAsync, okAsync } from 'neverthrow' import SparkMD5 from 'spark-md5' @@ -3967,7 +3968,6 @@ describe('admin-form.routes', () => { it('should return 200 with counts of submissions made between given start and end dates.', async () => { // Arrange - const expectedSubmissionCount = 3 const newForm = (await EmailFormModel.create({ title: 'new form', responseMode: FormResponseMode.Email, @@ -3979,16 +3979,15 @@ describe('admin-form.routes', () => { hash: 'some hash', salt: 'some salt', } - const results = await Promise.all( - times(expectedSubmissionCount, () => - saveSubmissionMetadata(newForm, mockSubmissionHash), - ), + // Inconsequential submissions + await Promise.all( + times(2, () => saveSubmissionMetadata(newForm, mockSubmissionHash)), ) - // Update first submission to be 5 days ago. + + // Add submission with specific date. const now = new Date() - const firstSubmission = results[0]._unsafeUnwrap() - firstSubmission.created = subDays(now, 5) - await firstSubmission.save() + MockDate.set(subDays(now, 5)) + await saveSubmissionMetadata(newForm, mockSubmissionHash) // Act const response = await request @@ -4000,12 +3999,14 @@ describe('admin-form.routes', () => { // Assert expect(response.status).toEqual(200) + // Should have a single submission that fits the date range expect(response.body).toEqual(1) + + MockDate.reset() }) it('should return 200 with counts of submissions made with same start and end dates', async () => { // Arrange - const expectedSubmissionCount = 3 const newForm = (await EmailFormModel.create({ title: 'new form', responseMode: FormResponseMode.Email, @@ -4017,15 +4018,15 @@ describe('admin-form.routes', () => { hash: 'some hash', salt: 'some salt', } - const results = await Promise.all( - times(expectedSubmissionCount, () => - saveSubmissionMetadata(newForm, mockSubmissionHash), - ), + + // Save two inconsequential submissions. + await Promise.all( + times(2, () => saveSubmissionMetadata(newForm, mockSubmissionHash)), ) + // Save a submission with a specific expected date. const expectedDate = '2021-04-04' - const firstSubmission = results[0]._unsafeUnwrap() - firstSubmission.created = new Date(expectedDate) - await firstSubmission.save() + MockDate.set(expectedDate) + await saveSubmissionMetadata(newForm, mockSubmissionHash) // Act const response = await request @@ -4037,7 +4038,10 @@ describe('admin-form.routes', () => { // Assert expect(response.status).toEqual(200) + // Should only have 1 submission from the specific date. expect(response.body).toEqual(1) + + MockDate.reset() }) it('should return 400 when query.startDate is missing when query.endDate is provided', async () => { @@ -4588,6 +4592,18 @@ describe('admin-form.routes', () => { })) as IFormDocument }) + const createEncryptSubmissionBody = (metaString: unknown) => { + return { + form: defaultForm, + encryptedContent: `any encrypted content ${metaString}`, + verifiedContent: `any verified content ${metaString}`, + attachmentMetadata: new Map([ + ['fieldId1', `some.attachment.url.${metaString}`], + ['fieldId2', `some.other.attachment.url.${metaString}`], + ]), + } + } + it('should return 200 with stream of encrypted responses without attachment URLs when query.downloadAttachments is false', async () => { // Arrange const submissions = await Promise.all( @@ -4712,31 +4728,24 @@ describe('admin-form.routes', () => { it('should return 200 with stream of encrypted responses between given query.startDate and query.endDate', async () => { // Arrange - const submissions = await Promise.all( - times(5, (count) => - createEncryptSubmission({ - form: defaultForm, - encryptedContent: `any encrypted content ${count}`, - verifiedContent: `any verified content ${count}`, - attachmentMetadata: new Map([ - ['fieldId1', `some.attachment.url.${count}`], - ['fieldId2', `some.other.attachment.url.${count}`], - ]), - }), + // Inconsequential submissions + await Promise.all( + times(3, (count) => + createEncryptSubmission(createEncryptSubmissionBody(count)), ), ) const startDateStr = '2020-02-03' const endDateStr = '2020-02-04' + MockDate.set(startDateStr) // Set 2 submissions to be submitted with specific date - submissions[2].created = new Date(startDateStr) - submissions[4].created = new Date(endDateStr) - await submissions[2].save() - await submissions[4].save() - const expectedSubmissionIds = [ - String(submissions[2]._id), - String(submissions[4]._id), - ] + const specificSubmission1 = await createEncryptSubmission( + createEncryptSubmissionBody(startDateStr), + ) + MockDate.set(endDateStr) + const specificSubmission2 = await createEncryptSubmission( + createEncryptSubmissionBody(endDateStr), + ) // Act const response = await request @@ -4755,7 +4764,7 @@ describe('admin-form.routes', () => { }) // Assert - const expectedSorted = submissions + const expectedSorted = [specificSubmission1, specificSubmission2] .map((s) => jsonParseStringify({ _id: s._id, @@ -4768,7 +4777,6 @@ describe('admin-form.routes', () => { version: s.version, }), ) - .filter((s) => expectedSubmissionIds.includes(s._id)) .sort((a, b) => String(a._id).localeCompare(String(b._id))) const actualSorted = (response.body as string) @@ -4781,6 +4789,8 @@ describe('admin-form.routes', () => { expect(response.status).toEqual(200) expect(actualSorted).toEqual(expectedSorted) + + MockDate.reset() }) it('should return 400 when form of given formId is not an encrypt mode form', async () => { diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts index bed079d8a2..2dd84f21e9 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.submissions.routes.spec.ts @@ -2,6 +2,7 @@ import { ObjectId } from 'bson-ext' import { format, subDays } from 'date-fns' import { times } from 'lodash' +import MockDate from 'mockdate' import mongoose from 'mongoose' import supertest, { Session } from 'supertest-session' @@ -181,7 +182,6 @@ describe('admin-form.submissions.routes', () => { it('should return 200 with counts of submissions made between given start and end dates.', async () => { // Arrange - const expectedSubmissionCount = 3 const newForm = (await EmailFormModel.create({ title: 'new form', responseMode: FormResponseMode.Email, @@ -193,15 +193,15 @@ describe('admin-form.submissions.routes', () => { hash: 'some hash', salt: 'some salt', } - const results = await Promise.all( - times(expectedSubmissionCount, () => - saveSubmissionMetadata(newForm, mockSubmissionHash), - ), + // Inconsequential submissions + await Promise.all( + times(2, () => saveSubmissionMetadata(newForm, mockSubmissionHash)), ) + + // Add submission with specific date. const now = new Date() - const firstSubmission = results[0]._unsafeUnwrap() - firstSubmission.created = subDays(now, 5) - await firstSubmission.save() + MockDate.set(subDays(now, 5)) + await saveSubmissionMetadata(newForm, mockSubmissionHash) // Act const response = await request @@ -213,12 +213,14 @@ describe('admin-form.submissions.routes', () => { // Assert expect(response.status).toEqual(200) + // Should have a single submission that fits the date range expect(response.body).toEqual(1) + + MockDate.reset() }) it('should return 200 with counts of submissions made with same start and end dates', async () => { // Arrange - const expectedSubmissionCount = 3 const newForm = (await EmailFormModel.create({ title: 'new form', responseMode: FormResponseMode.Email, @@ -230,15 +232,15 @@ describe('admin-form.submissions.routes', () => { hash: 'some hash', salt: 'some salt', } - const results = await Promise.all( - times(expectedSubmissionCount, () => - saveSubmissionMetadata(newForm, mockSubmissionHash), - ), + + // Save two inconsequential submissions. + await Promise.all( + times(2, () => saveSubmissionMetadata(newForm, mockSubmissionHash)), ) + // Save a submission with a specific expected date. const expectedDate = '2021-04-04' - const firstSubmission = results[0]._unsafeUnwrap() - firstSubmission.created = new Date(expectedDate) - await firstSubmission.save() + MockDate.set(expectedDate) + await saveSubmissionMetadata(newForm, mockSubmissionHash) // Act const response = await request @@ -250,7 +252,10 @@ describe('admin-form.submissions.routes', () => { // Assert expect(response.status).toEqual(200) + // Should only have 1 submission from the specific date. expect(response.body).toEqual(1) + + MockDate.reset() }) it('should return 400 when query.startDate is missing when query.endDate is provided', async () => { @@ -533,6 +538,18 @@ describe('admin-form.submissions.routes', () => { })) as IFormDocument }) + const createEncryptSubmissionBody = (metaString: unknown) => { + return { + form: defaultForm, + encryptedContent: `any encrypted content ${metaString}`, + verifiedContent: `any verified content ${metaString}`, + attachmentMetadata: new Map([ + ['fieldId1', `some.attachment.url.${metaString}`], + ['fieldId2', `some.other.attachment.url.${metaString}`], + ]), + } + } + it('should return 200 with stream of encrypted responses without attachment URLs when query.downloadAttachments is false', async () => { // Arrange const submissions = await Promise.all( @@ -657,30 +674,21 @@ describe('admin-form.submissions.routes', () => { it('should return 200 with stream of encrypted responses when query.startDate is the same as query.endDate', async () => { // Arrange - const submissions = await Promise.all( - times(5, (count) => - createEncryptSubmission({ - form: defaultForm, - encryptedContent: `any encrypted content ${count}`, - verifiedContent: `any verified content ${count}`, - attachmentMetadata: new Map([ - ['fieldId1', `some.attachment.url.${count}`], - ['fieldId2', `some.other.attachment.url.${count}`], - ]), - }), + // Inconsequential submissions + await Promise.all( + times(3, (count) => + createEncryptSubmission(createEncryptSubmissionBody(count)), ), ) const expectedDate = '2020-02-03' + MockDate.set(expectedDate) // Set 2 submissions to be submitted with specific date - submissions[2].created = new Date(expectedDate) - submissions[4].created = new Date(expectedDate) - await submissions[2].save() - await submissions[4].save() - const expectedSubmissionIds = [ - String(submissions[2]._id), - String(submissions[4]._id), - ] + const specificSubmissions = await Promise.all( + times(2, (count) => + createEncryptSubmission(createEncryptSubmissionBody(count)), + ), + ) // Act const response = await request @@ -699,7 +707,7 @@ describe('admin-form.submissions.routes', () => { }) // Assert - const expectedSorted = submissions + const expectedSorted = specificSubmissions .map((s) => jsonParseStringify({ _id: s._id, @@ -712,7 +720,6 @@ describe('admin-form.submissions.routes', () => { version: s.version, }), ) - .filter((s) => expectedSubmissionIds.includes(s._id)) .sort((a, b) => String(a._id).localeCompare(String(b._id))) const actualSorted = (response.body as string) @@ -725,35 +732,30 @@ describe('admin-form.submissions.routes', () => { expect(response.status).toEqual(200) expect(actualSorted).toEqual(expectedSorted) + + MockDate.reset() }) it('should return 200 with stream of encrypted responses between given query.startDate and query.endDate', async () => { // Arrange - const submissions = await Promise.all( - times(5, (count) => - createEncryptSubmission({ - form: defaultForm, - encryptedContent: `any encrypted content ${count}`, - verifiedContent: `any verified content ${count}`, - attachmentMetadata: new Map([ - ['fieldId1', `some.attachment.url.${count}`], - ['fieldId2', `some.other.attachment.url.${count}`], - ]), - }), + // Inconsequential submissions + await Promise.all( + times(3, (count) => + createEncryptSubmission(createEncryptSubmissionBody(count)), ), ) const startDateStr = '2020-02-03' const endDateStr = '2020-02-04' + MockDate.set(startDateStr) // Set 2 submissions to be submitted with specific date - submissions[2].created = new Date(startDateStr) - submissions[4].created = new Date(endDateStr) - await submissions[2].save() - await submissions[4].save() - const expectedSubmissionIds = [ - String(submissions[2]._id), - String(submissions[4]._id), - ] + const specificSubmission1 = await createEncryptSubmission( + createEncryptSubmissionBody(startDateStr), + ) + MockDate.set(endDateStr) + const specificSubmission2 = await createEncryptSubmission( + createEncryptSubmissionBody(endDateStr), + ) // Act const response = await request @@ -772,7 +774,7 @@ describe('admin-form.submissions.routes', () => { }) // Assert - const expectedSorted = submissions + const expectedSorted = [specificSubmission1, specificSubmission2] .map((s) => jsonParseStringify({ _id: s._id, @@ -785,7 +787,6 @@ describe('admin-form.submissions.routes', () => { version: s.version, }), ) - .filter((s) => expectedSubmissionIds.includes(s._id)) .sort((a, b) => String(a._id).localeCompare(String(b._id))) const actualSorted = (response.body as string) @@ -798,6 +799,8 @@ describe('admin-form.submissions.routes', () => { expect(response.status).toEqual(200) expect(actualSorted).toEqual(expectedSorted) + + MockDate.reset() }) it('should return 400 when form of given formId is not an encrypt mode form', async () => { From 8b600c697fbad13648bb03271f57420d2f301052 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 13:46:31 +0800 Subject: [PATCH 24/32] fix(test): remaining toEqual -> toMatchObject matcher changes --- .../__tests__/admin-form.routes.spec.ts | 10 ++++----- .../__tests__/admin-forms.form.routes.spec.ts | 21 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts index 03e20c2dab..838ca3f5f3 100644 --- a/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts +++ b/src/app/modules/form/admin-form/__tests__/admin-form.routes.spec.ts @@ -1288,8 +1288,8 @@ describe('admin-form.routes', () => { .lean() expect(response.status).toEqual(200) expect(response.body).not.toBeNull() - expect(response.body).toEqual({ - form: jsonParseStringify(expected), + expect(response.body).toMatchObject({ + form: expected, }) }) @@ -1325,8 +1325,8 @@ describe('admin-form.routes', () => { .lean() expect(response.status).toEqual(200) expect(response.body).not.toBeNull() - expect(response.body).toEqual({ - form: jsonParseStringify(expected), + expect(response.body).toMatchObject({ + form: expected, }) }) @@ -1473,7 +1473,7 @@ describe('admin-form.routes', () => { expect(response.status).toEqual(200) expect(expected?.form_fields![0].description).toEqual(updatedDescription) expect(expected?.__v).toEqual(1) - expect(response.body).toEqual(jsonParseStringify(expected)) + expect(response.body).toMatchObject(expected) }) it('should return 401 when user is not logged in', async () => { diff --git a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.form.routes.spec.ts b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.form.routes.spec.ts index 3a2461cace..7c8a231e52 100644 --- a/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.form.routes.spec.ts +++ b/src/app/routes/api/v3/admin/forms/__tests__/admin-forms.form.routes.spec.ts @@ -25,7 +25,6 @@ import { setupApp } from 'tests/integration/helpers/express-setup' import { buildCelebrateError } from 'tests/unit/backend/helpers/celebrate' import { generateDefaultField } from 'tests/unit/backend/helpers/generate-form-data' import dbHandler from 'tests/unit/backend/helpers/jest-db' -import { jsonParseStringify } from 'tests/unit/backend/helpers/serialize-data' import { BasicField, @@ -55,7 +54,7 @@ const app = setupApp('/admin/forms', AdminFormsRouter, { setupWithAuth: true, }) -describe('admin-form.form.routes', () => { +describe('admin-forms.form.routes', () => { let request: Session let defaultUser: IUserSchema @@ -141,7 +140,7 @@ describe('admin-form.form.routes', () => { }, }) .lean() - expect(response.body).toEqual(jsonParseStringify(expected)) + expect(response.body).toMatchObject(expected) expect(response.status).toEqual(200) }) @@ -545,8 +544,8 @@ describe('admin-form.form.routes', () => { .lean() expect(response.status).toEqual(200) expect(response.body).not.toBeNull() - expect(response.body).toEqual({ - form: jsonParseStringify(expected), + expect(response.body).toMatchObject({ + form: expected, }) }) @@ -582,8 +581,8 @@ describe('admin-form.form.routes', () => { .lean() expect(response.status).toEqual(200) expect(response.body).not.toBeNull() - expect(response.body).toEqual({ - form: jsonParseStringify(expected), + expect(response.body).toMatchObject({ + form: expected, }) }) @@ -1470,7 +1469,7 @@ describe('admin-form.form.routes', () => { // Assert expect(response.status).toEqual(200) - expect(response.body).toEqual(jsonParseStringify(MOCK_FIELD)) + expect(response.body).toMatchObject(MOCK_FIELD) }) it('should return 403 when user does not have permissions to retrieve form field', async () => { @@ -1857,7 +1856,7 @@ describe('admin-form.form.routes', () => { // Assert expect(resp.status).toBe(200) - expect(resp.body).toEqual(jsonParseStringify(MOCK_UPDATED_START_PAGE)) + expect(resp.body).toMatchObject(MOCK_UPDATED_START_PAGE) }) it('should return 403 when the user does not have permission to update the start page', async () => { @@ -1884,7 +1883,7 @@ describe('admin-form.form.routes', () => { // Assert expect(resp.status).toBe(403) - expect(resp.body).toEqual(jsonParseStringify(expectedResponse)) + expect(resp.body).toMatchObject(expectedResponse) }) it('should return 404 when the form cannot be found', async () => { @@ -1896,7 +1895,7 @@ describe('admin-form.form.routes', () => { // Assert expect(resp.status).toBe(404) - expect(resp.body).toEqual(jsonParseStringify(expectedResponse)) + expect(resp.body).toMatchObject(expectedResponse) }) it('should return 410 when updating the start page for a form that has been archived', async () => { From 72b1e426e32453905f71d666427adb07372442c0 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 13:51:22 +0800 Subject: [PATCH 25/32] fix(test): replace esrvcId values with more obvious values --- .../public-forms.auth.routes.spec.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/routes/api/v3/forms/__tests__/public-forms.auth.routes.spec.ts b/src/app/routes/api/v3/forms/__tests__/public-forms.auth.routes.spec.ts index 96a3254761..e4f4a2c7d1 100644 --- a/src/app/routes/api/v3/forms/__tests__/public-forms.auth.routes.spec.ts +++ b/src/app/routes/api/v3/forms/__tests__/public-forms.auth.routes.spec.ts @@ -47,7 +47,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.SP, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) @@ -72,7 +72,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.CP, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) @@ -97,7 +97,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.MyInfo, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) @@ -122,7 +122,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.MyInfo, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) const expectedResponse = buildCelebrateError({ @@ -147,7 +147,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEncryptForm({ formOptions: { status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) const expectedResponse = jsonParseStringify({ @@ -194,10 +194,11 @@ describe('public-form.auth.routes', () => { message: 'Could not find the form requested. Please refresh and try again.', }) + const mockFormId = new ObjectId().toHexString() // Act const response = await request - .get(`/forms/${new ObjectId().toHexString()}/auth/redirect`) + .get(`/forms/${mockFormId}/auth/redirect`) .query({ isPersistentLogin: false }) // Assert @@ -211,7 +212,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.SP, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) const expectedResponse = jsonParseStringify({ @@ -237,7 +238,7 @@ describe('public-form.auth.routes', () => { formOptions: { authType: FormAuthType.SP, status: FormStatus.Public, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', }, }) const expectedResponse = jsonParseStringify({ @@ -264,7 +265,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.SP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -284,7 +285,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.SP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -309,7 +310,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.NIL, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -331,7 +332,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.CP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -375,11 +376,10 @@ describe('public-form.auth.routes', () => { message: 'Could not find the form requested. Please refresh and try again.', }) + const mockFormId = new ObjectId().toHexString() // Act - const response = await request.get( - `/forms/${new ObjectId().toHexString()}/auth/validate`, - ) + const response = await request.get(`/forms/${mockFormId}/auth/validate`) // Assert expect(response.status).toEqual(404) @@ -391,7 +391,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.SP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -415,7 +415,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.SP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) @@ -440,7 +440,7 @@ describe('public-form.auth.routes', () => { const { form } = await dbHandler.insertEmailForm({ formOptions: { authType: FormAuthType.SP, - esrvcId: new ObjectId().toHexString(), + esrvcId: 'rubbish-esrvcId', status: FormStatus.Public, }, }) From c65d4e0a4cc9ef5d555bf870b9a12c7fbf731197 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 14:23:49 +0800 Subject: [PATCH 26/32] fix(test): freeze date in example related tests fixes issue where expect.anything() is not matching with whatever mongoose object is returning... --- .../__tests__/examples.routes.spec.ts | 39 +++++++------------ .../__tests__/examples.service.spec.ts | 36 +++++++++++------ .../__tests__/helpers/prepareTestData.ts | 2 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/app/modules/examples/__tests__/examples.routes.spec.ts b/src/app/modules/examples/__tests__/examples.routes.spec.ts index 13f9a1a4f4..b8f8b71cd1 100644 --- a/src/app/modules/examples/__tests__/examples.routes.spec.ts +++ b/src/app/modules/examples/__tests__/examples.routes.spec.ts @@ -1,5 +1,6 @@ import { ObjectId } from 'bson-ext' import { keyBy } from 'lodash' +import MockDate from 'mockdate' import { errAsync } from 'neverthrow' import supertest, { Session } from 'supertest-session' @@ -9,11 +10,11 @@ import { createAuthedSession } from 'tests/integration/helpers/express-auth' import { setupApp } from 'tests/integration/helpers/express-setup' import { buildCelebrateError } from 'tests/unit/backend/helpers/celebrate' import dbHandler from 'tests/unit/backend/helpers/jest-db' +import { jsonParseStringify } from 'tests/unit/backend/helpers/serialize-data' import { DatabaseError } from '../../core/core.errors' import { ExamplesRouter } from '../examples.routes' import * as ExamplesService from '../examples.service' -import { FormInfo } from '../examples.types' import prepareTestData, { SearchTerm, @@ -39,6 +40,8 @@ describe('examples.routes', () => { let orgAgency: IAgencySchema beforeAll(async () => { + // Mockdate requires for deterministic return of example objects. + MockDate.set(new Date()) await dbHandler.connect() const orgData = await dbHandler.insertFormCollectionReqs({ mailDomain: 'example.org', @@ -63,7 +66,10 @@ describe('examples.routes', () => { afterEach(async () => { jest.clearAllMocks() }) - afterAll(async () => await dbHandler.closeDatabase()) + afterAll(async () => { + MockDate.reset() + await dbHandler.closeDatabase() + }) describe('GET /examples', () => { it('should return 200 with array of example forms for all agencies when query.agency is missing', async () => { @@ -80,8 +86,8 @@ describe('examples.routes', () => { // agencies. const expectedBody = keyBy( [ - ...stringifyFormInfoArray(comTestData.total.expectedFormInfo), - ...stringifyFormInfoArray(orgTestData.total.expectedFormInfo), + ...jsonParseStringify(comTestData.total.expectedFormInfo), + ...jsonParseStringify(orgTestData.total.expectedFormInfo), ], '_id', ) @@ -107,7 +113,7 @@ describe('examples.routes', () => { // Assert // Should only have orgTestData since its agency id is provided. const expectedBody = keyBy( - stringifyFormInfoArray(orgTestData.total.expectedFormInfo), + jsonParseStringify(orgTestData.total.expectedFormInfo), '_id', ) const actualBody = keyBy(response.body.forms, '_id') @@ -152,7 +158,7 @@ describe('examples.routes', () => { // Assert // Should only have comTestData since its agency id is provided. const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.first.expectedFormInfo), + jsonParseStringify(comTestData.first.expectedFormInfo), '_id', ) const actualBody = keyBy(response.body.forms, '_id') @@ -205,7 +211,7 @@ describe('examples.routes', () => { // Assert // Should only have comTestData since its agency id is provided. const expectedBody = keyBy( - stringifyFormInfoArray(comTestData.total.expectedFormInfo), + jsonParseStringify(comTestData.total.expectedFormInfo), '_id', ) const actualBody = keyBy(response.body.forms, '_id') @@ -347,7 +353,7 @@ describe('examples.routes', () => { const response = await session.get(`/examples/${validFormId}`) // Assert - const expectedFormInfo = stringifyFormInfo( + const expectedFormInfo = jsonParseStringify( comTestData.second.expectedFormInfo[0], ) @@ -402,20 +408,3 @@ describe('examples.routes', () => { }) }) }) - -// Helper functions -/** - * Stringifies expected form info arrays. Unable to do the usual JSON.stringify - * -> parse combination due to mongoose dates being converted to an empty - * object. - */ -const stringifyFormInfoArray = (array: FormInfo[]) => { - return array.map(stringifyFormInfo) -} - -const stringifyFormInfo = (formInfo: FormInfo) => { - return { - ...formInfo, - _id: formInfo._id.toString(), - } -} diff --git a/src/app/modules/examples/__tests__/examples.service.spec.ts b/src/app/modules/examples/__tests__/examples.service.spec.ts index 5106b14ff3..2dc3fa5499 100644 --- a/src/app/modules/examples/__tests__/examples.service.spec.ts +++ b/src/app/modules/examples/__tests__/examples.service.spec.ts @@ -1,9 +1,11 @@ import { ObjectId } from 'bson-ext' +import MockDate from 'mockdate' import mongoose from 'mongoose' import getFormStatisticsTotalModel from 'src/app/models/form_statistics_total.server.model' import dbHandler from 'tests/unit/backend/helpers/jest-db' +import { jsonParseStringify } from 'tests/unit/backend/helpers/serialize-data' import { PAGE_SIZE } from '../examples.constants' import { ResultsNotFoundError } from '../examples.errors' @@ -24,11 +26,15 @@ describe('examples.service', () => { let testData: TestData beforeAll(async () => { + MockDate.set(new Date()) await dbHandler.connect() const { user, agency } = await dbHandler.insertFormCollectionReqs() testData = await prepareTestData(user, agency) }) - afterAll(async () => await dbHandler.closeDatabase()) + afterAll(async () => { + MockDate.reset() + await dbHandler.closeDatabase() + }) describe('getExampleForms', () => { describe('when query.searchTerm exists', () => { @@ -43,9 +49,9 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ + expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ totalNumResults: testData.second.formCount, - forms: testData.second.expectedFormInfo, + forms: jsonParseStringify(testData.second.expectedFormInfo), }) }) @@ -77,8 +83,10 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ - forms: testData.first.expectedFormInfo, + expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ + forms: expect.arrayContaining( + jsonParseStringify(testData.first.expectedFormInfo), + ), }) }) @@ -110,9 +118,11 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ + expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ totalNumResults: testData.total.formCount, - forms: testData.total.expectedFormInfo, + forms: expect.arrayContaining( + jsonParseStringify(testData.total.expectedFormInfo), + ), }) }) @@ -128,7 +138,7 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ + expect(actualResults._unsafeUnwrap()).toEqual({ forms: [], totalNumResults: testData.total.formCount, }) @@ -145,8 +155,10 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ - forms: testData.total.expectedFormInfo, + expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ + forms: expect.arrayContaining( + jsonParseStringify(testData.total.expectedFormInfo), + ), }) }) @@ -182,8 +194,8 @@ describe('examples.service', () => { // Assert expect(actualResults.isOk()).toEqual(true) - expect(actualResults._unsafeUnwrap()).toMatchObject({ - form: expectedFormInfo, + expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ + form: jsonParseStringify(expectedFormInfo), }) }) diff --git a/src/app/modules/examples/__tests__/helpers/prepareTestData.ts b/src/app/modules/examples/__tests__/helpers/prepareTestData.ts index 32b221fda0..f53b4d6d45 100644 --- a/src/app/modules/examples/__tests__/helpers/prepareTestData.ts +++ b/src/app/modules/examples/__tests__/helpers/prepareTestData.ts @@ -209,7 +209,7 @@ const prepareTestData = async ( form_fields: [], logo: agency.logo, timeText: 'less than 1 day ago', - lastSubmission: expect.anything(), + lastSubmission: new Date(), title: form.title, authType: form.authType, })) From 3d534b1e1a8a6e6a0f3bd1faeab779376c1556dc Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 15:03:46 +0800 Subject: [PATCH 27/32] fix(test): remove flakeyness of returned examples by sorting in result --- .../__tests__/examples.service.spec.ts | 65 ++++++++++++------- tests/unit/backend/helpers/serialize-data.ts | 2 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/app/modules/examples/__tests__/examples.service.spec.ts b/src/app/modules/examples/__tests__/examples.service.spec.ts index 2dc3fa5499..1cecb9d667 100644 --- a/src/app/modules/examples/__tests__/examples.service.spec.ts +++ b/src/app/modules/examples/__tests__/examples.service.spec.ts @@ -10,6 +10,7 @@ import { jsonParseStringify } from 'tests/unit/backend/helpers/serialize-data' import { PAGE_SIZE } from '../examples.constants' import { ResultsNotFoundError } from '../examples.errors' import * as ExamplesService from '../examples.service' +import { QueryPageResultWithTotal } from '../examples.types' import prepareTestData, { TestData } from './helpers/prepareTestData' @@ -48,11 +49,19 @@ describe('examples.service', () => { }) // Assert - expect(actualResults.isOk()).toEqual(true) - expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ - totalNumResults: testData.second.formCount, - forms: jsonParseStringify(testData.second.expectedFormInfo), - }) + const actualParsed = jsonParseStringify( + actualResults._unsafeUnwrap() as QueryPageResultWithTotal, + ) + const actualFormsSorted = actualParsed.forms.sort((a, b) => + String(a._id).localeCompare(String(b._id)), + ) + const expectedFormsSorted = jsonParseStringify( + testData.second.expectedFormInfo, + ).sort((a, b) => String(a._id).localeCompare(String(b._id))) + expect(actualParsed.totalNumResults).toEqual( + testData.second.formCount, + ) + expect(actualFormsSorted).toEqual(expectedFormsSorted) }) it('should return empty list if no forms match search term with 0 result count', async () => { @@ -82,12 +91,14 @@ describe('examples.service', () => { }) // Assert - expect(actualResults.isOk()).toEqual(true) - expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ - forms: expect.arrayContaining( - jsonParseStringify(testData.first.expectedFormInfo), - ), - }) + const actualParsed = jsonParseStringify(actualResults._unsafeUnwrap()) + const actualFormsSorted = actualParsed.forms.sort((a, b) => + String(a._id).localeCompare(String(b._id)), + ) + const expectedFormsSorted = jsonParseStringify( + testData.first.expectedFormInfo, + ).sort((a, b) => String(a._id).localeCompare(String(b._id))) + expect(actualFormsSorted).toEqual(expectedFormsSorted) }) it('should return empty list if no forms match search term', async () => { @@ -117,13 +128,17 @@ describe('examples.service', () => { }) // Assert - expect(actualResults.isOk()).toEqual(true) - expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ - totalNumResults: testData.total.formCount, - forms: expect.arrayContaining( - jsonParseStringify(testData.total.expectedFormInfo), - ), - }) + const actualParsed = jsonParseStringify( + actualResults._unsafeUnwrap() as QueryPageResultWithTotal, + ) + const actualFormsSorted = actualParsed.forms.sort((a, b) => + String(a._id).localeCompare(String(b._id)), + ) + const expectedFormsSorted = jsonParseStringify( + testData.total.expectedFormInfo, + ).sort((a, b) => String(a._id).localeCompare(String(b._id))) + expect(actualParsed.totalNumResults).toEqual(testData.total.formCount) + expect(actualFormsSorted).toEqual(expectedFormsSorted) }) it('should return empty list with number of forms with submissions when offset is more than number of documents in collection', async () => { @@ -154,12 +169,14 @@ describe('examples.service', () => { }) // Assert - expect(actualResults.isOk()).toEqual(true) - expect(jsonParseStringify(actualResults._unsafeUnwrap())).toEqual({ - forms: expect.arrayContaining( - jsonParseStringify(testData.total.expectedFormInfo), - ), - }) + const actualParsed = jsonParseStringify(actualResults._unsafeUnwrap()) + const actualFormsSorted = actualParsed.forms.sort((a, b) => + String(a._id).localeCompare(String(b._id)), + ) + const expectedFormsSorted = jsonParseStringify( + testData.total.expectedFormInfo, + ).sort((a, b) => String(a._id).localeCompare(String(b._id))) + expect(actualFormsSorted).toEqual(expectedFormsSorted) }) it('should return empty list when offset is more than number of documents', async () => { diff --git a/tests/unit/backend/helpers/serialize-data.ts b/tests/unit/backend/helpers/serialize-data.ts index 06950f933d..7618e800a6 100644 --- a/tests/unit/backend/helpers/serialize-data.ts +++ b/tests/unit/backend/helpers/serialize-data.ts @@ -1,3 +1,3 @@ -export const jsonParseStringify = (obj: unknown) => { +export const jsonParseStringify = (obj: T): T => { return JSON.parse(JSON.stringify(obj)) } From 87777850d2a96706cd7d8cbd2f851cb756fd828b Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Tue, 23 Nov 2021 15:31:36 +0800 Subject: [PATCH 28/32] fix: update package-lock for snyk reported vulns --- package-lock.json | 201 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 171 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 427fc1f784..aafabfcad2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4836,19 +4836,19 @@ } }, "@mapbox/node-pre-gyp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", - "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", + "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", "requires": { "detect-libc": "^1.0.3", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.5", "nopt": "^5.0.0", - "npmlog": "^4.1.2", + "npmlog": "^6.0.0", "rimraf": "^3.0.2", - "semver": "^7.3.4", - "tar": "^6.1.0" + "semver": "^7.3.5", + "tar": "^6.1.11" }, "dependencies": { "agent-base": { @@ -4859,6 +4859,20 @@ "debug": "4" } }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -4867,6 +4881,22 @@ "ms": "2.1.2" } }, + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "requires": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -4876,6 +4906,11 @@ "debug": "4" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -4891,6 +4926,35 @@ } } }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "npmlog": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.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==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -4898,6 +4962,43 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } } } }, @@ -8843,6 +8944,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dev": true, "requires": { "color-convert": "^1.9.1", "color-string": "^1.5.2" @@ -8865,11 +8967,17 @@ "version": "1.5.3", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "dev": true, "requires": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "colorette": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", @@ -8882,12 +8990,32 @@ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "colorspace": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", - "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", "requires": { - "color": "3.0.x", + "color": "^3.1.3", "text-hex": "1.0.x" + }, + "dependencies": { + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "color-string": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + } } }, "combined-stream": { @@ -10710,8 +10838,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "emojis-list": { "version": "3.0.0", @@ -12416,9 +12543,9 @@ } }, "fecha": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", - "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.1.tgz", + "integrity": "sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==" }, "fetch-readablestream": { "version": "0.2.0", @@ -17127,14 +17254,14 @@ } }, "logform": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", - "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz", + "integrity": "sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ==", "requires": { "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", "fecha": "^4.2.0", "ms": "^2.1.1", + "safe-stable-stringify": "^1.1.0", "triple-beam": "^1.3.0" } }, @@ -18448,7 +18575,8 @@ "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "dev": true }, "node-forge": { "version": "0.10.0", @@ -21146,6 +21274,11 @@ "ret": "~0.1.10" } }, + "safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -21546,8 +21679,16 @@ "requires": { "async": "^2.6.0", "cardinal": "^1.0.0", + "csv-parse": "^4.6.5", "humanize": "^0.0.9", "optimist": "^0.6.1" + }, + "dependencies": { + "csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==" + } } }, "signal-exit": { @@ -23005,9 +23146,9 @@ "dev": true }, "tar": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz", - "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -25967,14 +26108,14 @@ }, "dependencies": { "async": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", - "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" }, "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==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "readable-stream": { "version": "3.6.0", From 01a78cf78f439209fa395906695401e98697ebcf Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 6 Dec 2021 10:51:38 +0800 Subject: [PATCH 29/32] fix(deps): update mongodb-memory-server-core to 8.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03b448e602..dad637fad8 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "maildev": "^1.1.0", "mini-css-extract-plugin": "^0.5.0", "mockdate": "^3.0.5", - "mongodb-memory-server-core": "^8.0.2", + "mongodb-memory-server-core": "^8.0.4", "ngrok": "^4.2.2", "optimize-css-assets-webpack-plugin": "^5.0.8", "prettier": "^2.5.1", From 71c49db55c3815a33648a0549752ceb447e5893e Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Mon, 6 Dec 2021 11:17:23 +0800 Subject: [PATCH 30/32] fix: ignore excessively deep type-instantiation ts type error lots of issues on mongoose repo which seems semi related but not really related, ignoring for now till we overhaul the types mongoose uses --- src/app/models/form.server.model.ts | 19 +++++++++++-------- src/types/form.ts | 3 ++- src/types/user.ts | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index f69d576507..d2144d6521 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -660,14 +660,17 @@ const compileFormModel = (db: Mongoose): IFormModel => { formId: string, fields?: (keyof IPopulatedForm)[], ): Promise { - return this.findById(formId, fields) - .populate({ - path: 'admin', - populate: { - path: 'agency', - }, - }) - .exec() as Promise + return ( + // @ts-expect-error Type instantiation excessively deep, mongoose type bug. + this.findById(formId, fields) + .populate({ + path: 'admin', + populate: { + path: 'agency', + }, + }) + .exec() as Promise + ) } // Deactivate form by ID diff --git a/src/types/form.ts b/src/types/form.ts index 2f1157024d..af76f80614 100644 --- a/src/types/form.ts +++ b/src/types/form.ts @@ -2,6 +2,7 @@ import { Document, LeanDocument, Model, + PopulatedDoc, ToObjectOptions, Types, UpdateWriteOpResult, @@ -67,7 +68,7 @@ export type IForm = Merge< SetOptional, { // Loosen types here to allow for IPopulatedForm extension - admin: any + admin: PopulatedDoc permission?: FormPermission[] form_fields?: FormFieldSchema[] form_logics?: FormLogicSchema[] diff --git a/src/types/user.ts b/src/types/user.ts index 04bb09fcb9..cc6dcbd385 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -1,5 +1,5 @@ import { ObjectId } from 'bson-ext' -import { Document, Model } from 'mongoose' +import { Document, Model, PopulatedDoc } from 'mongoose' import { SetOptional } from 'type-fest' import { PublicAgencyDto, UserBase } from '../../shared/types' @@ -17,7 +17,7 @@ export type AdminContactOtpData = { export interface IUser extends SetOptional { - agency: IAgencySchema['_id'] + agency: PopulatedDoc } export type UserContactView = Pick From 10b1c4eaea57f4268fa95459c4142f275825993a Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Thu, 9 Dec 2021 16:19:04 +0800 Subject: [PATCH 31/32] fix: assign table column discrim before setting field discrim Mongoose now saves objects with keys in the order the keys are specified in the schema, meaning the table column discriminator would not have been applied at the time the table field was assigned as a discriminator to the form's form fields. This resulted in a bug where table columns did not have a discriminated schema causing uncaught errors to be thrown when a table field was submitted, such as "UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'selectedValidation' of undefined" as the field did not contain the necessary discriminated column schema props due to not being assigned prior to assigning the TableFieldSchema discriminator. See https://mongoosejs.com/docs/migrating_to_6.html#schema-defined-document-key-order --- src/app/models/form.server.model.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index d2144d6521..fe8306d25e 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -414,6 +414,17 @@ const compileFormModel = (db: Mongoose): IFormModel => { ) as Schema.Types.DocumentArray const TableFieldSchema = createTableFieldSchema() + const TableColumnPath = TableFieldSchema.path( + 'columns', + ) as Schema.Types.DocumentArray + TableColumnPath.discriminator( + BasicField.ShortText, + createShortTextFieldSchema(), + ) + TableColumnPath.discriminator( + BasicField.Dropdown, + createDropdownFieldSchema(), + ) FormFieldPath.discriminator(BasicField.Email, createEmailFieldSchema()) FormFieldPath.discriminator(BasicField.Rating, createRatingFieldSchema()) @@ -444,17 +455,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { ) FormFieldPath.discriminator(BasicField.Section, createSectionFieldSchema()) FormFieldPath.discriminator(BasicField.Table, TableFieldSchema) - const TableColumnPath = TableFieldSchema.path( - 'columns', - ) as Schema.Types.DocumentArray - TableColumnPath.discriminator( - BasicField.ShortText, - createShortTextFieldSchema(), - ) - TableColumnPath.discriminator( - BasicField.Dropdown, - createDropdownFieldSchema(), - ) // Discriminator defines all possible values of startPage.logo const StartPageLogoPath = FormSchema.path( From fd2654b0750286500b169bfb674670c553fd42e1 Mon Sep 17 00:00:00 2001 From: Kar Rui Lau Date: Thu, 9 Dec 2021 16:45:22 +0800 Subject: [PATCH 32/32] test: add success test case for submissions containing table fields this broke in mongoose v6 upgrade before being fixed, adding a test to ensure that this does not break in the future --- .../public-forms.routes.spec.constants.ts | 38 ++++++++++++++++++- .../public-forms.submissions.routes.spec.ts | 37 +++++++++++++++++- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts b/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts index 2ad24dc931..5793a83794 100644 --- a/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts +++ b/src/app/routes/api/v3/forms/__tests__/public-forms.routes.spec.constants.ts @@ -1,6 +1,10 @@ import fs from 'fs' -import { IAttachmentFieldSchema, ICheckboxFieldSchema } from 'src/types' +import { + IAttachmentFieldSchema, + ICheckboxFieldSchema, + ITableFieldSchema, +} from 'src/types' import { generateAttachmentResponse, @@ -9,15 +13,45 @@ import { generateSingleAnswerResponse, } from 'tests/unit/backend/helpers/generate-form-data' -import { BasicField, FieldBase } from '../../../../../../../shared/types' +import { + BasicField, + Column, + FieldBase, +} from '../../../../../../../shared/types' export const MOCK_NO_RESPONSES_BODY = { responses: [], } +const MOCK_TABLE_COLUMNS = [ + { + title: 'Test Column Title 1', + required: true, + columnType: BasicField.ShortText, + }, + { + title: 'Test Column Title 2', + required: true, + columnType: BasicField.Dropdown, + fieldOptions: ['Option 1', 'Option 2', 'Option 3'], + }, +] as const export const MOCK_TEXT_FIELD = generateDefaultField(BasicField.ShortText) +export const MOCK_TABLE_FIELD = generateDefaultField(BasicField.Table, { + minimumRows: 2, + columns: MOCK_TABLE_COLUMNS as unknown as Column[], +}) as ITableFieldSchema export const MOCK_TEXTFIELD_RESPONSE = generateSingleAnswerResponse(MOCK_TEXT_FIELD) +export const MOCK_TABLE_RESPONSE = { + _id: MOCK_TABLE_FIELD._id, + fieldType: BasicField.Table, + question: MOCK_TABLE_FIELD.title, + answerArray: [ + ['Test', MOCK_TABLE_COLUMNS[1].fieldOptions[1]], + ['Test 2', MOCK_TABLE_COLUMNS[1].fieldOptions[2]], + ], +} export const MOCK_ATTACHMENT_FIELD = generateDefaultField(BasicField.Attachment) export const MOCK_ATTACHMENT_RESPONSE = generateAttachmentResponse( diff --git a/src/app/routes/api/v3/forms/__tests__/public-forms.submissions.routes.spec.ts b/src/app/routes/api/v3/forms/__tests__/public-forms.submissions.routes.spec.ts index b8e6db7c4a..fe2e5be398 100644 --- a/src/app/routes/api/v3/forms/__tests__/public-forms.submissions.routes.spec.ts +++ b/src/app/routes/api/v3/forms/__tests__/public-forms.submissions.routes.spec.ts @@ -27,6 +27,8 @@ import { MOCK_OPTIONAL_VERIFIED_RESPONSE, MOCK_SECTION_FIELD, MOCK_SECTION_RESPONSE, + MOCK_TABLE_FIELD, + MOCK_TABLE_RESPONSE, MOCK_TEXT_FIELD, MOCK_TEXTFIELD_RESPONSE, MOCK_UINFIN, @@ -87,7 +89,7 @@ describe('public-form.submissions.routes', () => { const mockCpClient = mocked(MockAuthClient.mock.instances[1], true) describe('Joi validation', () => { - it('should return 200 when submission is valid', async () => { + it('should return 200 when submission is valid for non-table field', async () => { // Arrange const { form } = await dbHandler.insertEmailForm({ formOptions: { @@ -117,6 +119,39 @@ describe('public-form.submissions.routes', () => { }) }) + /** + * This test tests to ensure that deep nested discriminator fields still continue to work. + */ + it('should return 200 when submission is valid for table field', async () => { + // Arrange + const { form } = await dbHandler.insertEmailForm({ + formOptions: { + hasCaptcha: false, + status: FormStatus.Public, + form_fields: [MOCK_TABLE_FIELD], + }, + }) + + // Act + const response = await request + .post(`/forms/${form._id}/submissions/email`) + // MOCK_RESPONSE contains all required keys + .field( + 'body', + JSON.stringify({ + responses: [MOCK_TABLE_RESPONSE], + }), + ) + .query({ captchaResponse: 'null' }) + + // Assert + expect(response.status).toBe(200) + expect(response.body).toEqual({ + message: 'Form submission successful.', + submissionId: expect.any(String), + }) + }) + it('should return 200 when answer is empty string for optional field', async () => { // Arrange const { form } = await dbHandler.insertEmailForm({