diff --git a/.secrets.baseline b/.secrets.baseline index 952441d55..9c8e8b88a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -4,7 +4,7 @@ "files": "package-lock.json|^.secrets.baseline$", "lines": null }, - "generated_at": "2020-10-19T02:55:47Z", + "generated_at": "2021-04-09T07:48:36Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -55,10 +55,17 @@ } ], "results": { + "backend/src/test-utils/test-env.ts": [ + { + "hashed_secret": "c237a19676ad55d8904be354990dd54f92f9572c", + "is_verified": false, + "line_number": 4, + "type": "Base64 High Entropy String" + } + ], "frontend/.env-example": [ { "hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4", - "is_secret": false, "is_verified": false, "line_number": 5, "type": "Basic Auth Credentials" diff --git a/.travis.yml b/.travis.yml index 3bafa148e..3ae7b922d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ node_js: services: - docker + - postgresql + - redis-server cache: npm diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index ba6a5fab8..35589cd58 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -1,13 +1,14 @@ module.exports = { root: false, parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], + plugins: ['@typescript-eslint', "jest"], extends: [ 'eslint:recommended', // Recommended ESLint rules 'plugin:@typescript-eslint/eslint-recommended', // Disables rules from `eslint:recommended` that are already covered by the TypeScript typechecker 'plugin:@typescript-eslint/recommended', // Recommended TypeScript rules 'prettier/@typescript-eslint', // Disables rules from `@typescript-eslint/recommended` that are covered by Prettier 'plugin:prettier/recommended', // Recommended Prettier rules + "plugin:jest/recommended" // Recommended Jest rules ], parserOptions: { sourceType: 'module', diff --git a/backend/jest.config.js b/backend/jest.config.js index d6df66493..59a9518ef 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,14 +1,20 @@ module.exports = { roots: [''], - testMatch: ['**/__tests__/**/*.(spec|test).+(ts|tsx|js)'], + testMatch: ['**/tests/**/*.(spec|test).+(ts|tsx|js)'], + testPathIgnorePatterns: ['/build/', '/node_modules/'], moduleNameMapper: { '@core/(.*)': '/src/core/$1', '@sms/(.*)': '/src/sms/$1', '@email/(.*)': '/src/email/$1', + '@telegram/(.*)': '/src/telegram/$1', + '@test-utils/(.*)': '/src/test-utils/$1', }, transform: { '^.+\\.(ts|tsx)$': 'ts-jest', }, testEnvironment: 'node', - setupFilesAfterEnv: ['/__tests__/setup.js'], + globalSetup: '/src/test-utils/global-setup.ts', + globalTeardown: '/src/test-utils/global-teardown.ts', + setupFiles: ['/src/test-utils/test-env.ts'], + setupFilesAfterEnv: ['/src/test-utils/setup.ts'], } diff --git a/backend/package-lock.json b/backend/package-lock.json index e8e6d6a9b..e9af64d9a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1597,6 +1597,32 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, "@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -1890,6 +1916,12 @@ "@types/node": "*" } }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "@types/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz", @@ -2204,6 +2236,25 @@ "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/superagent": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.10.tgz", + "integrity": "sha512-xAgkb2CMWUMCyVc/3+7iQfOEBE75NvuZeezvmixbUw3nmENf2tCnQkW5yQLTYqvXUQ+R6EXxdqKKbal2zM5V/g==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.10.tgz", + "integrity": "sha512-Xt8TbEyZTnD5Xulw95GLMOkmjGICrOQyJ2jqgkSjAUR3mm7pAIzSR0NFBaMcwlzVvlpCjNwbATcWWwjNiZiFrQ==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, "@types/swagger-jsdoc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-3.0.2.tgz", @@ -2283,6 +2334,22 @@ "eslint-visitor-keys": "^1.1.0" } }, + "@typescript-eslint/scope-manager": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.12.0.tgz", + "integrity": "sha512-QVf9oCSVLte/8jvOsxmgBdOaoe2J0wtEmBr13Yz0rkBNkl5D8bfnf6G4Vhox9qqMIoG7QQoVwd2eG9DM/ge4Qg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/visitor-keys": "4.12.0" + } + }, + "@typescript-eslint/types": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.12.0.tgz", + "integrity": "sha512-N2RhGeheVLGtyy+CxRmxdsniB7sMSCfsnbh8K/+RUIXYYq3Ub5+sukRCjVE80QerrUBvuEvs4fDhz5AW/pcL6g==", + "dev": true + }, "@typescript-eslint/typescript-estree": { "version": "2.25.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.25.0.tgz", @@ -2335,6 +2402,24 @@ } } }, + "@typescript-eslint/visitor-keys": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.12.0.tgz", + "integrity": "sha512-hVpsLARbDh4B9TKYz5cLbcdMIOAoBYgFPCSP9FFS/liSF+b33gVNq8JHY3QGhHNVz85hObvL7BEYLlgx553WCw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.12.0", + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", + "dev": true + } + } + }, "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", @@ -2575,6 +2660,12 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", @@ -3498,6 +3589,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -3758,6 +3855,15 @@ "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4033,6 +4139,86 @@ "get-stdin": "^6.0.0" } }, + "eslint-plugin-jest": { + "version": "24.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.1.3.tgz", + "integrity": "sha512-dNGGjzuEzCE3d5EPZQ/QGtmlMotqnYWD/QpCZ1UuZlrMAdhG5rldh0N0haCvhGnUkSeuORS5VNROwF9Hrgn3Lg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^4.0.1" + }, + "dependencies": { + "@typescript-eslint/experimental-utils": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.12.0.tgz", + "integrity": "sha512-MpXZXUAvHt99c9ScXijx7i061o5HEjXltO+sbYfZAAHxv3XankQkPaNi5myy0Yh0Tyea3Hdq1pi7Vsh0GJb0fA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.12.0", + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/typescript-estree": "4.12.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.12.0.tgz", + "integrity": "sha512-gZkFcmmp/CnzqD2RKMich2/FjBTsYopjiwJCroxqHZIY11IIoN0l5lKqcgoAPKHt33H2mAkSfvzj8i44Jm7F4w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.12.0", + "@typescript-eslint/visitor-keys": "4.12.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "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" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "eslint-plugin-prettier": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", @@ -4468,6 +4654,20 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4489,6 +4689,15 @@ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.18.0.tgz", "integrity": "sha512-tRrwShhppv0K5GKEtuVs92W0VGDaVltZAwtHbpjNF+JOT7cjIFySBGTEOmdBslXYyWYaZwEX/g4Su8ZeKg0LKQ==" }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "fb-watchman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", @@ -4638,6 +4847,12 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -4858,6 +5073,28 @@ "type-fest": "^0.8.1" } }, + "globby": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -8033,6 +8270,12 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -15735,6 +15978,12 @@ "any-promise": "^1.3.0" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -15780,6 +16029,12 @@ "is-promise": "^2.1.0" } }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true + }, "rxjs": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", @@ -16643,6 +16898,125 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "superagent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz", + "integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "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" + } + }, + "mime": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", + "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "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" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "supertest": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.0.1.tgz", + "integrity": "sha512-8yDNdm+bbAN/jeDdXsRipbq9qMpVF7wRsbwLgsANHqdjPsCoecmlTuqEcLQMGpmojFBhxayZ0ckXmLXYq7e+0g==", + "dev": true, + "requires": { + "methods": "1.1.2", + "superagent": "6.1.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -17081,9 +17455,9 @@ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, "ts-jest": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.4.0.tgz", - "integrity": "sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==", + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.5.1.tgz", + "integrity": "sha512-kHEUlZMK8fn8vkxDjwbHlxXRB9dHYpyzqKIGDNxbzs+Rz+ssNDSDNusEK8Fk/sDd4xE6iKoQLfFkFVaskmTJyw==", "dev": true, "requires": { "bs-logger": "0.x", @@ -17093,18 +17467,11 @@ "lodash.memoize": "4.x", "make-error": "1.x", "micromatch": "4.x", - "mkdirp": "1.x", - "resolve": "1.x", + "mkdirp": "0.x", "semver": "6.x", "yargs-parser": "18.x" }, "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", diff --git a/backend/package.json b/backend/package.json index f04b23bd5..b5cc496be 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,14 +4,14 @@ "description": "Backend / API server for Postman", "main": "build/server.js", "scripts": { - "build": "rimraf build && tsc", + "build": "rimraf build && tsc -p tsconfig.build.json", "dev": "npm run postbuild && tsc-watch --onSuccess \"node ./build/server.js\"", "lint-no-fix": "tsc --noEmit && eslint --ext .js,.ts --cache .", "lint": "npm run lint-no-fix -- --fix", "postbuild": "npm run copy-assets", "start": "node build/server", "copy-assets": "copyfiles -u 1 src/assets/* src/**/*.sql build", - "test": "jest", + "test": "jest --maxWorkers=2", "test:watch": "jest --watch", "precommit": "lint-staged" }, @@ -83,6 +83,7 @@ "@types/papaparse": "^5.0.4", "@types/qs": "^6.9.4", "@types/redis": "^2.8.17", + "@types/supertest": "^2.0.10", "@types/swagger-jsdoc": "^3.0.2", "@types/swagger-ui-express": "^4.1.2", "@types/uuid": "^7.0.2", @@ -92,12 +93,14 @@ "copyfiles": "^2.2.0", "eslint": "^6.8.0", "eslint-config-prettier": "^6.11.0", + "eslint-plugin-jest": "^24.1.3", "eslint-plugin-prettier": "^3.1.3", "jest": "^25.5.2", "lint-staged": "^10.2.6", "prettier": "^2.0.5", "rimraf": "^3.0.2", - "ts-jest": "^25.4.0", + "supertest": "^6.0.1", + "ts-jest": "^25.5.1", "tsc-watch": "^4.2.3", "typescript": "^3.8.3" }, diff --git a/backend/src/core/routes/tests/auth.routes.test.ts b/backend/src/core/routes/tests/auth.routes.test.ts new file mode 100644 index 000000000..5b83574a9 --- /dev/null +++ b/backend/src/core/routes/tests/auth.routes.test.ts @@ -0,0 +1,132 @@ +import request from 'supertest' +import { Sequelize } from 'sequelize-typescript' +import bcrypt from 'bcrypt' +import initialiseServer from '@test-utils/server' +import sequelizeLoader from '@test-utils/sequelize-loader' +import { MailService, RedisService } from '@core/services' +import { User } from '@core/models' + +const app = initialiseServer() +const appWithUserSession = initialiseServer(true) +let sequelize: Sequelize + +beforeAll(async () => { + sequelize = await sequelizeLoader(process.env.JEST_WORKER_ID || '1') +}) + +afterEach(async () => { + await User.destroy({ where: {} }) +}) + +afterAll(async () => { + await sequelize.close() + RedisService.otpClient.quit() + RedisService.sessionClient.quit() +}) + +describe('POST /auth/otp', () => { + test('Invalid email format', async () => { + const res = await request(app) + .post('/auth/otp') + .send({ email: 'user!@open' }) + expect(res.status).toBe(400) + }) + + test('Non gov.sg and non-whitelisted email', async () => { + // There are no users in the db + const res = await request(app) + .post('/auth/otp') + .send({ email: 'user@agency.com.sg' }) + expect(res.status).toBe(401) + expect(res.body).toEqual({ message: 'User is not authorized' }) + }) + + test('OTP is generated and sent to user', async () => { + const res = await request(app) + .post('/auth/otp') + .send({ email: 'user@agency.gov.sg' }) + expect(res.status).toBe(200) + + expect(MailService.mailClient.sendMail).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.stringMatching(/Your OTP is [0-9]{6}<\/b>/), + }) + ) + }) +}) + +describe('POST /auth/login', () => { + test('Invalid otp format provided', async () => { + const res = await request(app) + .post('/auth/login') + .send({ email: 'user@agency.gov.sg', otp: '123' }) + expect(res.status).toBe(400) + }) + + test('Invalid otp provided', async () => { + const res = await request(app) + .post('/auth/login') + .send({ email: 'user@agency.gov.sg', otp: '000000' }) + expect(res.status).toBe(401) + }) + + test('OTP is invalidated after retries are exceeded', async () => { + const email = 'user@agency.gov.sg' + RedisService.otpClient.set( + email, + JSON.stringify({ + retries: 1, + hash: await bcrypt.hash('123456', 10), + createdAt: 123, + }) + ) + const res = await request(app) + .post('/auth/login') + .send({ email, otp: '000000' }) + expect(res.status).toBe(401) + + // OTP should be deleted after exceeding retries + RedisService.otpClient.get(email, (_err, value) => { + expect(value).toBe(null) + }) + }) + + test('Valid otp provided', async () => { + const email = 'user@agency.gov.sg' + RedisService.otpClient.set( + email, + JSON.stringify({ + retries: 1, + hash: await bcrypt.hash('123456', 10), + createdAt: 123, + }) + ) + + const res = await request(app) + .post('/auth/login') + .send({ email, otp: '123456' }) + expect(res.status).toBe(200) + }) +}) + +describe('GET /auth/userinfo', () => { + test('No existing session', async () => { + const res = await request(app).get('/auth/userinfo') + expect(res.status).toBe(200) + expect(res.body).toEqual({}) + }) + + test('Existing session found', async () => { + await User.create({ id: 1, email: 'user@agency.gov.sg' }) + const res = await request(appWithUserSession).get('/auth/userinfo') + expect(res.status).toBe(200) + expect(res.body).toEqual({ id: 1, email: 'user@agency.gov.sg' }) + }) +}) + +describe('GET /auth/logout', () => { + test('Successfully logged out', async () => { + const res = await request(appWithUserSession).get('/auth/logout') + expect(res.status).toBe(200) + }) +}) diff --git a/backend/src/core/routes/tests/campaign.routes.test.ts b/backend/src/core/routes/tests/campaign.routes.test.ts new file mode 100644 index 000000000..ba2329e49 --- /dev/null +++ b/backend/src/core/routes/tests/campaign.routes.test.ts @@ -0,0 +1,203 @@ +import request from 'supertest' +import { Sequelize } from 'sequelize-typescript' +import initialiseServer from '@test-utils/server' +import { Campaign, User, UserDemo } from '@core/models' +import sequelizeLoader from '@test-utils/sequelize-loader' +import { RedisService } from '@core/services' +import { ChannelType } from '@core/constants' + +const app = initialiseServer(true) +let sequelize: Sequelize + +beforeAll(async () => { + sequelize = await sequelizeLoader(process.env.JEST_WORKER_ID || '1') + await User.create({ id: 1, email: 'user@agency.gov.sg' }) +}) + +afterEach(async () => { + await Campaign.destroy({ where: {} }) +}) + +afterAll(async () => { + await User.destroy({ where: {} }) + await sequelize.close() + RedisService.otpClient.quit() + RedisService.sessionClient.quit() +}) + +describe('GET /campaigns', () => { + test('List campaigns with default limit and offset', async () => { + await Campaign.create({ + name: 'campaign-1', + userId: 1, + type: 'SMS', + valid: false, + protect: false, + }) + await Campaign.create({ + name: 'campaign-2', + userId: 1, + type: 'SMS', + valid: false, + protect: false, + }) + + const res = await request(app).get('/campaigns') + expect(res.status).toBe(200) + expect(res.body).toEqual({ + total_count: 2, + campaigns: expect.arrayContaining([ + expect.objectContaining({ id: expect.any(Number) }), + ]), + }) + }) + + test('List campaigns with defined limit and offset', async () => { + for (let i = 1; i <= 3; i++) { + await Campaign.create({ + name: `campaign-${i}`, + userId: 1, + type: 'SMS', + valid: false, + protect: false, + }) + } + + const res = await request(app) + .get('/campaigns') + .query({ limit: 1, offset: 2 }) + expect(res.status).toBe(200) + expect(res.body).toEqual({ + total_count: 3, + campaigns: expect.arrayContaining([ + expect.objectContaining({ name: 'campaign-1' }), + ]), + }) + }) +}) + +describe('POST /campaigns', () => { + test('Successfully create SMS campaign', async () => { + const res = await request(app).post('/campaigns').send({ + name: 'test', + type: ChannelType.SMS, + }) + expect(res.status).toBe(201) + expect(res.body).toEqual( + expect.objectContaining({ + name: 'test', + type: ChannelType.SMS, + protect: false, + }) + ) + }) + + test('Successfully create Email campaign', async () => { + const res = await request(app).post('/campaigns').send({ + name: 'test', + type: ChannelType.Email, + }) + expect(res.status).toBe(201) + expect(res.body).toEqual( + expect.objectContaining({ + name: 'test', + type: ChannelType.Email, + protect: false, + }) + ) + }) + + test('Successfully create Protected Email campaign', async () => { + const campaign = { + name: 'test', + type: ChannelType.Email, + protect: true, + } + const res = await request(app).post('/campaigns').send(campaign) + expect(res.status).toBe(201) + expect(res.body).toEqual(expect.objectContaining(campaign)) + }) + + test('Successfully create Telegram campaign', async () => { + const res = await request(app).post('/campaigns').send({ + name: 'test', + type: ChannelType.Telegram, + }) + expect(res.status).toBe(201) + expect(res.body).toEqual( + expect.objectContaining({ + name: 'test', + type: ChannelType.Telegram, + protect: false, + }) + ) + }) + + test('Successfully create demo SMS campaign', async () => { + const campaign = { + name: 'demo', + type: ChannelType.SMS, + demo_message_limit: 10, + } + const res = await request(app).post('/campaigns').send(campaign) + expect(res.status).toBe(201) + expect(res.body).toEqual( + expect.objectContaining({ + ...campaign, + demo_message_limit: 10, + }) + ) + + const demo = await UserDemo.findOne({ where: { userId: 1 } }) + expect(demo?.numDemosSms).toEqual(2) + }) + + test('Successfully create demo Telegram campaign', async () => { + const campaign = { + name: 'demo', + type: ChannelType.Telegram, + demo_message_limit: 10, + } + const res = await request(app).post('/campaigns').send(campaign) + expect(res.status).toBe(201) + expect(res.body).toEqual( + expect.objectContaining({ + ...campaign, + demo_message_limit: 10, + }) + ) + + const demo = await UserDemo.findOne({ where: { userId: 1 } }) + expect(demo?.numDemosTelegram).toEqual(2) + }) + + test('Unable to create demo Telegram campaign after user has no demos left', async () => { + const campaign = { + name: 'demo', + type: ChannelType.Telegram, + demo_message_limit: 10, + } + await UserDemo.update({ numDemosTelegram: 0 }, { where: { userId: 1 } }) + const res = await request(app).post('/campaigns').send(campaign) + expect(res.status).toBe(400) + }) + + test('Unable to create demo campaign for unsupported channel', async () => { + const campaign = { + name: 'demo', + type: ChannelType.Email, + demo_message_limit: 10, + } + const res = await request(app).post('/campaigns').send(campaign) + expect(res.status).toBe(400) + }) + + test('Unable to create protected campaign for unsupported channel', async () => { + const res = await request(app).post('/campaigns').send({ + name: 'test', + type: 'SMS', + protect: true, + }) + expect(res.status).toBe(403) + }) +}) diff --git a/backend/__tests__/parse-csv.service.test.ts b/backend/src/core/services/tests/parse-csv.service.test.ts similarity index 100% rename from backend/__tests__/parse-csv.service.test.ts rename to backend/src/core/services/tests/parse-csv.service.test.ts diff --git a/backend/__tests__/phone-number.service.test.ts b/backend/src/core/services/tests/phone-number.service.test.ts similarity index 100% rename from backend/__tests__/phone-number.service.test.ts rename to backend/src/core/services/tests/phone-number.service.test.ts diff --git a/backend/src/test-utils/global-setup.ts b/backend/src/test-utils/global-setup.ts new file mode 100644 index 000000000..f80e22037 --- /dev/null +++ b/backend/src/test-utils/global-setup.ts @@ -0,0 +1,29 @@ +import { Sequelize, SequelizeOptions } from 'sequelize-typescript' +import config from '../core/config' + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace NodeJS { + interface Global { + sequelize: Sequelize + } + } +} +const DB_URI = 'postgres://localhost:5432/postgres' +const TEST_DB = 'postmangovsg_test' +// The number of workers should match the maxWorkers +// defined in npm test command +const JEST_WORKERS = 2 + +module.exports = async () => { + global.sequelize = new Sequelize(DB_URI, { + dialect: 'postgres', + logging: false, + pool: config.get('database.poolOptions'), + } as SequelizeOptions) + + for (let i = 1; i <= JEST_WORKERS; i++) { + await global.sequelize.query(`DROP DATABASE IF EXISTS ${TEST_DB}_${i}`) + await global.sequelize.query(`CREATE DATABASE ${TEST_DB}_${i}`) + } +} diff --git a/backend/src/test-utils/global-teardown.ts b/backend/src/test-utils/global-teardown.ts new file mode 100644 index 000000000..cf5586169 --- /dev/null +++ b/backend/src/test-utils/global-teardown.ts @@ -0,0 +1,3 @@ +module.exports = async function () { + await global.sequelize.close() +} diff --git a/backend/src/test-utils/sequelize-loader.ts b/backend/src/test-utils/sequelize-loader.ts new file mode 100644 index 000000000..0cdca8fec --- /dev/null +++ b/backend/src/test-utils/sequelize-loader.ts @@ -0,0 +1,100 @@ +import { Sequelize, SequelizeOptions } from 'sequelize-typescript' +import config from '@core/config' +import { + Credential, + JobQueue, + Campaign, + Worker, + User, + UserFeature, + UserCredential, + UserDemo, + Statistic, + ProtectedMessage, + Unsubscriber, +} from '@core/models' +import { + EmailMessage, + EmailTemplate, + EmailOp, + EmailBlacklist, + EmailFromAddress, +} from '@email/models' +import { SmsMessage, SmsTemplate, SmsOp } from '@sms/models' +import { + BotSubscriber, + TelegramMessage, + TelegramOp, + TelegramSubscriber, + TelegramTemplate, +} from '@telegram/models' + +import { DefaultCredentialName } from '@core/constants' +import { formatDefaultCredentialName } from '@core/utils' + +const DB_TEST_URI = config.get('database.databaseUri') + +const sequelizeLoader = async (dbName: string): Promise => { + const sequelize = new Sequelize(`${DB_TEST_URI}_${dbName}`, { + dialect: 'postgres', + logging: false, + pool: config.get('database.poolOptions'), + } as SequelizeOptions) + + const coreModels = [ + Credential, + JobQueue, + Campaign, + Worker, + User, + UserFeature, + UserCredential, + UserDemo, + Statistic, + Unsubscriber, + ] + const emailModels = [ + EmailMessage, + EmailTemplate, + EmailOp, + EmailBlacklist, + ProtectedMessage, + EmailFromAddress, + ] + const smsModels = [SmsMessage, SmsTemplate, SmsOp] + const telegramModels = [ + BotSubscriber, + TelegramOp, + TelegramMessage, + TelegramTemplate, + TelegramSubscriber, + ] + sequelize.addModels([ + ...coreModels, + ...emailModels, + ...smsModels, + ...telegramModels, + ]) + + try { + await sequelize.sync() + console.log({ message: 'Test Database loaded.' }) + } catch (error) { + console.log(error.message) + console.error({ message: 'Unable to connect to test database', error }) + process.exit(1) + } + // Create the default credential names in the credentials table + // Each name should be accompanied by an entry in Secrets Manager + await Promise.all( + [ + DefaultCredentialName.Email, + formatDefaultCredentialName(DefaultCredentialName.SMS), + formatDefaultCredentialName(DefaultCredentialName.Telegram), + ].map((name) => Credential.upsert({ name })) + ) + + return sequelize +} + +export default sequelizeLoader diff --git a/backend/src/test-utils/server.ts b/backend/src/test-utils/server.ts new file mode 100644 index 000000000..d127e16a3 --- /dev/null +++ b/backend/src/test-utils/server.ts @@ -0,0 +1,29 @@ +import express, { Request, Response, NextFunction } from 'express' +import { errors as celebrateErrorMiddleware } from 'celebrate' +import bodyParser from 'body-parser' +import sessionLoader from '@core/loaders/session.loader' +import routes from '@core/routes' + +const initialiseServer = (session?: boolean): express.Application => { + const app: express.Application = express() + sessionLoader({ app }) + app.use(bodyParser.json()) + app.use(bodyParser.urlencoded({ extended: false })) + + app.use((req: Request, _res: Response, next: NextFunction): void => { + if (session && req.session) { + req.session.user = { + id: 1, + email: 'user@agency.gov.sg', + } + } + next() + }) + + app.use(routes) + app.use(celebrateErrorMiddleware()) + + return app +} + +export default initialiseServer diff --git a/backend/__tests__/setup.js b/backend/src/test-utils/setup.ts similarity index 70% rename from backend/__tests__/setup.js rename to backend/src/test-utils/setup.ts index d88a56f05..c8ff81811 100644 --- a/backend/__tests__/setup.js +++ b/backend/src/test-utils/setup.ts @@ -1,8 +1,12 @@ /* eslint-disable no-console */ global.console = { + ...global.console, log: jest.fn(), // console.log are ignored in tests error: console.error, warn: console.warn, info: console.info, debug: console.debug, } + +// Mock services +jest.mock('@core/services/mail-client.class') diff --git a/backend/src/test-utils/test-env.ts b/backend/src/test-utils/test-env.ts new file mode 100644 index 000000000..7d33c5b18 --- /dev/null +++ b/backend/src/test-utils/test-env.ts @@ -0,0 +1,6 @@ +process.env.REDIS_OTP_URI = 'redis://localhost:6379/3' +process.env.REDIS_SESSION_URI = 'redis://localhost:6379/4' +process.env.SENDGRID_PUBLIC_KEY = + 'MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEKWFCI/58CSJe4uz9WX7VZZBIoeb3c1UEJ+pe3HL0ywyGA6c3Bq92+1YVKv0HHxf5mjm+t47P672gcaYarlp2LA==' +process.env.SESSION_SECRET = 'SESSIONSECRET' +process.env.DB_URI = 'postgres://localhost:5432/postmangovsg_test' diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json new file mode 100644 index 000000000..b792dc00f --- /dev/null +++ b/backend/tsconfig.build.json @@ -0,0 +1,5 @@ +// Excludes test in builds +{ + "extends": "./tsconfig.json", + "exclude": ["tests/"] +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 32df37e0d..4803d7878 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,6 +1,7 @@ { "include": [ "./src/**/*", + "./tests/**/*", ], "exclude": [ "node_modules" @@ -22,7 +23,7 @@ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ "outDir": "./build", /* Redirect output structure to the directory. */ - "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "incremental": true, /* Enable incremental compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -50,12 +51,13 @@ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */ + "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ "paths": { - "@core/*": ["core/*"], - "@sms/*": ["sms/*"], - "@email/*": ["email/*"], - "@telegram/*": ["telegram/*"], + "@core/*": ["src/core/*"], + "@sms/*": ["src/sms/*"], + "@email/*": ["src/email/*"], + "@telegram/*": ["src/telegram/*"], + "@test-utils/*": ["src/test-utils/*"], }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ @@ -74,4 +76,4 @@ "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ }, -} \ No newline at end of file +}