From fc906e7dc756fed7b23bc6803bff506bde700a0f Mon Sep 17 00:00:00 2001 From: Reginald Bondoc Date: Tue, 20 Dec 2022 00:56:53 +0100 Subject: [PATCH] Setup test, break apart entrypoint, & align GHA workflow --- .github/workflows/be-test-report.yml | 30 + .github/workflows/check-be-pull-request.yml | 33 +- .gitignore | 4 +- backend/__tests__/healthcheck.test.ts | 19 + backend/package-lock.json | 575 ++++++++++++++---- backend/package.json | 32 +- backend/src/app.ts | 72 +++ backend/src/helpers/nodemailer.ts | 42 +- backend/src/index.ts | 136 +---- backend/src/services/database.ts | 15 + backend/src/services/health.ts | 32 + backend/src/services/smtp.ts | 33 + .../test-resources/docker-compose.test.yml | 12 + backend/test-resources/env-vars.js | 5 + backend/tsconfig.json | 11 +- 15 files changed, 748 insertions(+), 303 deletions(-) create mode 100644 .github/workflows/be-test-report.yml create mode 100644 backend/__tests__/healthcheck.test.ts create mode 100644 backend/src/app.ts create mode 100644 backend/src/services/database.ts create mode 100644 backend/src/services/health.ts create mode 100644 backend/src/services/smtp.ts create mode 100644 backend/test-resources/docker-compose.test.yml create mode 100644 backend/test-resources/env-vars.js diff --git a/.github/workflows/be-test-report.yml b/.github/workflows/be-test-report.yml new file mode 100644 index 000000000..dc3e10307 --- /dev/null +++ b/.github/workflows/be-test-report.yml @@ -0,0 +1,30 @@ +name: 'Backend Test Report' + +on: + workflow_run: + workflows: ['Check Backend Pull Request'] + types: + - completed + +jobs: + be-report: + name: Backend test report + runs-on: ubuntu-latest + + steps: + - uses: dorny/test-reporter@v1 + with: + path: '*.xml' # Path to test results (inside artifact .zip) + reporter: jest-junit # Format of test results + - name: ๐Ÿ“„ Publish test results + uses: dorny/test-reporter@v1 + with: + name: Test Results + artifact: be-test-results + path: reports/jest-*.xml + reporter: jest-junit + - uses: ArtiomTr/jest-coverage-report-action@v2 + id: coverage + with: + output: report-markdown + coverage-file: backend/coverage/report.json diff --git a/.github/workflows/check-be-pull-request.yml b/.github/workflows/check-be-pull-request.yml index f17d8c5c8..49440f486 100644 --- a/.github/workflows/check-be-pull-request.yml +++ b/.github/workflows/check-be-pull-request.yml @@ -1,41 +1,42 @@ -name: Check Backend Pull Request +name: 'Check Backend Pull Request' on: pull_request: - types: [ opened, synchronize ] + types: [opened, synchronize] paths: - 'backend/**' - '!backend/README.md' - '!backend/.*' - 'backend/.eslintrc.js' - jobs: - check-be-pr: name: Check runs-on: ubuntu-latest steps: - - - name: โ˜๏ธ Checkout source + - name: โ˜๏ธ Checkout source uses: actions/checkout@v3 - - - name: ๐Ÿ”ง Setup Node 16 + - name: ๐Ÿ”ง Setup Node 16 uses: actions/setup-node@v3 with: node-version: '16' cache: 'npm' cache-dependency-path: backend/package-lock.json - - - name: ๐Ÿ“ฆ Install dependencies + - name: ๐Ÿ“ฆ Install dependencies run: npm ci --only-production --ignore-scripts working-directory: backend - # - - # name: ๐Ÿงช Run tests - # run: npm run test:ci - # working-directory: backend - - - name: ๐Ÿ—๏ธ Run build + - name: ๐Ÿงช Run tests + run: npm run test:ci + working-directory: backend + - name: ๐Ÿ“ Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: be-test-results + path: | + backend/reports + backend/coverage + - name: ๐Ÿ—๏ธ Run build run: npm run build working-directory: backend diff --git a/.gitignore b/.gitignore index f32a51384..6c4414313 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,9 @@ node_modules .env # testing -/coverage +coverage +reports +junit.xml # next.js /.next/ diff --git a/backend/__tests__/healthcheck.test.ts b/backend/__tests__/healthcheck.test.ts new file mode 100644 index 000000000..234d2d8eb --- /dev/null +++ b/backend/__tests__/healthcheck.test.ts @@ -0,0 +1,19 @@ +import { server } from '../src/app'; +import { describe, expect, it, beforeAll, afterAll } from '@jest/globals'; +import supertest from 'supertest'; +import { setUpHealthEndpoint } from '../src/services/health'; + +const requestWithSupertest = supertest(server); +describe('Healthcheck endpoint', () => { + beforeAll(async () => { + setUpHealthEndpoint(server); + }); + afterAll(async () => { + server.close(); + }); + + it('GET /healthcheck should return OK', async () => { + const res = await requestWithSupertest.get('/healthcheck'); + expect(res.status).toEqual(200); + }); +}); diff --git a/backend/package-lock.json b/backend/package-lock.json index c13f138d4..82301465c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -37,25 +37,32 @@ "typescript": "^4.9.3" }, "devDependencies": { + "@jest/globals": "^29.3.1", "@posthog/plugin-scaffold": "^1.3.4", "@types/cookie-parser": "^1.4.3", "@types/cors": "^2.8.12", "@types/express": "^4.17.14", + "@types/jest": "^29.2.4", "@types/jsonwebtoken": "^8.5.9", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", + "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/parser": "^5.40.1", + "cross-env": "^7.0.3", "eslint": "^8.26.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "install": "^0.13.0", "jest": "^29.3.1", + "jest-junit": "^15.0.0", "nodemon": "^2.0.19", "npm": "^8.19.3", "prettier": "^2.7.1", + "supertest": "^6.3.3", + "ts-jest": "^29.0.3", "ts-node": "^10.9.1" } }, @@ -1076,6 +1083,7 @@ "version": "3.188.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-browser/-/util-base64-browser-3.188.0.tgz", "integrity": "sha512-qlH+5NZBLiyKziL335BEPedYxX6j+p7KFRWXvDQox9S+s+gLCayednpK+fteOhBenCcR9fUZOVuAPScy1I8qCg==", + "deprecated": "The package @aws-sdk/util-base64-browser has been renamed to @aws-sdk/util-base64. Please install the renamed package.", "optional": true, "dependencies": { "tslib": "^2.3.1" @@ -1091,6 +1099,7 @@ "version": "3.201.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-base64-node/-/util-base64-node-3.201.0.tgz", "integrity": "sha512-ydZqNpB3l5kiicInpPDExPb5xHI7uyVIa1vMupnuIrJ412iNb0F2+K8LlFynzw6fSJShVKnqFcWOYRA96z1iIw==", + "deprecated": "The package @aws-sdk/util-base64-node has been renamed to @aws-sdk/util-base64. Please install the renamed package.", "optional": true, "dependencies": { "@aws-sdk/util-buffer-from": "3.201.0", @@ -2816,6 +2825,12 @@ "@types/express": "*" } }, + "node_modules/@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 + }, "node_modules/@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -2883,6 +2898,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz", + "integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -2958,6 +2983,25 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.16.tgz", + "integrity": "sha512-tLfnlJf6A5mB6ddqF159GqcDizfzbMUB1/DeT59/wBNqzRTNNKsaw79A/1TZ84X+f/EwWH8FeuSkjlCLyqS/zQ==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", + "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, "node_modules/@types/swagger-jsdoc": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz", @@ -3355,6 +3399,12 @@ "node": ">=8" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -3602,6 +3652,18 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -3875,6 +3937,12 @@ "node": ">= 0.8" } }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3938,6 +4006,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -3974,6 +4048,24 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4080,6 +4172,16 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -4662,6 +4764,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fast-xml-parser": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", @@ -4825,6 +4933,21 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5079,6 +5202,15 @@ "node": ">=12.0.0" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -5678,6 +5810,21 @@ "fsevents": "^2.3.2" } }, + "node_modules/jest-junit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", + "integrity": "sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/jest-leak-detector": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", @@ -6224,6 +6371,12 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6440,6 +6593,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mmdb-lib": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.0.2.tgz", @@ -6740,129 +6905,7 @@ "treeverse", "validate-npm-package-name", "which", - "write-file-atomic", - "@colors/colors", - "@gar/promisify", - "@npmcli/disparity-colors", - "@npmcli/git", - "@npmcli/installed-package-contents", - "@npmcli/metavuln-calculator", - "@npmcli/move-file", - "@npmcli/name-from-folder", - "@npmcli/node-gyp", - "@npmcli/promise-spawn", - "@npmcli/query", - "@tootallnate/once", - "agent-base", - "agentkeepalive", - "aggregate-error", - "ansi-regex", - "ansi-styles", - "aproba", - "are-we-there-yet", - "asap", - "balanced-match", - "bin-links", - "binary-extensions", - "brace-expansion", - "builtins", - "cidr-regex", - "clean-stack", - "clone", - "cmd-shim", - "color-convert", - "color-name", - "color-support", - "common-ancestor-path", - "concat-map", - "console-control-strings", - "cssesc", - "debug", - "debuglog", - "defaults", - "delegates", - "depd", - "dezalgo", - "diff", - "emoji-regex", - "encoding", - "env-paths", - "err-code", - "fs.realpath", - "function-bind", - "gauge", - "has", - "has-flag", - "has-unicode", - "http-cache-semantics", - "http-proxy-agent", - "https-proxy-agent", - "humanize-ms", - "iconv-lite", - "ignore-walk", - "imurmurhash", - "indent-string", - "infer-owner", - "inflight", - "inherits", - "ip", - "ip-regex", - "is-core-module", - "is-fullwidth-code-point", - "is-lambda", - "isexe", - "json-stringify-nice", - "jsonparse", - "just-diff", - "just-diff-apply", - "lru-cache", - "minipass-collect", - "minipass-fetch", - "minipass-flush", - "minipass-json-stream", - "minipass-sized", - "minizlib", - "mute-stream", - "negotiator", - "normalize-package-data", - "npm-bundled", - "npm-normalize-package-bin", - "npm-packlist", - "once", - "path-is-absolute", - "postcss-selector-parser", - "promise-all-reject-late", - "promise-call-limit", - "promise-inflight", - "promise-retry", - "promzard", - "read-cmd-shim", - "readable-stream", - "retry", - "safe-buffer", - "safer-buffer", - "set-blocking", - "signal-exit", - "smart-buffer", - "socks", - "socks-proxy-agent", - "spdx-correct", - "spdx-exceptions", - "spdx-expression-parse", - "spdx-license-ids", - "string_decoder", - "string-width", - "strip-ansi", - "supports-color", - "unique-filename", - "unique-slug", - "util-deprecate", - "validate-npm-package-license", - "walk-up-path", - "wcwidth", - "wide-align", - "wrappy", - "yallist" + "write-file-atomic" ], "dev": true, "dependencies": { @@ -10435,6 +10478,52 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "optional": true }, + "node_modules/superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10546,6 +10635,49 @@ "node": ">=12" } }, + "node_modules/ts-jest": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", + "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -10754,7 +10886,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true, + "devOptional": true, "bin": { "uuid": "dist/bin/uuid" } @@ -10918,6 +11050,12 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -13406,6 +13544,12 @@ "@types/express": "*" } }, + "@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.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -13473,6 +13617,16 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz", + "integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -13548,6 +13702,25 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "@types/superagent": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.16.tgz", + "integrity": "sha512-tLfnlJf6A5mB6ddqF159GqcDizfzbMUB1/DeT59/wBNqzRTNNKsaw79A/1TZ84X+f/EwWH8FeuSkjlCLyqS/zQ==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", + "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, "@types/swagger-jsdoc": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz", @@ -13810,6 +13983,12 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -13995,6 +14174,15 @@ "update-browserslist-db": "^1.0.9" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -14186,6 +14374,12 @@ "delayed-stream": "~1.0.0" } }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -14236,6 +14430,12 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -14269,6 +14469,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -14342,6 +14551,16 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -14784,6 +15003,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "fast-xml-parser": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.0.11.tgz", @@ -14904,6 +15129,18 @@ "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", + "integrity": "sha512-0EcS9wCFEzLvfiks7omJ+SiYJAiD+TzK4Pcw1UlUoGnhUxDcMKjt0P7x8wEb0u6OHu8Nb98WG3nxtlF5C7bvUQ==", + "dev": true, + "requires": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -15076,6 +15313,12 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.1.tgz", "integrity": "sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==" }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -15507,6 +15750,18 @@ "walker": "^1.0.8" } }, + "jest-junit": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", + "integrity": "sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + } + }, "jest-leak-detector": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.3.1.tgz", @@ -15959,6 +16214,12 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -16122,6 +16383,12 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "mmdb-lib": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.0.2.tgz", @@ -18860,6 +19127,42 @@ "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", "optional": true }, + "superagent": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.6.tgz", + "integrity": "sha512-HqSe6DSIh3hEn6cJvCkaM1BLi466f1LHi4yubR0tpewlMpk4RUFFy35bKz8SsPBwYfIIJy5eclp+3tCYAuX0bw==", + "dev": true, + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.3", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "dependencies": { + "mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true + } + } + }, + "supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + } + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18941,6 +19244,22 @@ "punycode": "^2.1.1" } }, + "ts-jest": { + "version": "29.0.3", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.3.tgz", + "integrity": "sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.1", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + } + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -19071,7 +19390,7 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true + "devOptional": true }, "v8-compile-cache-lib": { "version": "3.0.1", @@ -19198,6 +19517,12 @@ "signal-exit": "^3.0.7" } }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/backend/package.json b/backend/package.json index 32ca68b19..215012a79 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,11 @@ "lint": "eslint . --ext .ts", "lint-and-fix": "eslint . --ext .ts --fix", "prettier-format": "prettier --config .prettierrc 'src/**/*.ts' --write", - "lint-staged": "lint-staged" + "lint-staged": "lint-staged", + "pretest": "docker compose -f test-resources/docker-compose.test.yml up -d", + "test": "cross-env NODE_ENV=test jest --testTimeout=10000 --detectOpenHandles", + "test:ci": "npm test -- --watchAll=false --ci --reporters=default --reporters=jest-junit --coverage --testLocationInResults", + "posttest": "docker compose -f test-resources/docker-compose.test.yml down" }, "repository": { "type": "git", @@ -51,25 +55,51 @@ "homepage": "https://github.com/Infisical/infisical-api#readme", "description": "", "devDependencies": { + "@jest/globals": "^29.3.1", "@posthog/plugin-scaffold": "^1.3.4", "@types/cookie-parser": "^1.4.3", "@types/cors": "^2.8.12", "@types/express": "^4.17.14", + "@types/jest": "^29.2.4", "@types/jsonwebtoken": "^8.5.9", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", + "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", "@typescript-eslint/eslint-plugin": "^5.40.1", "@typescript-eslint/parser": "^5.40.1", + "cross-env": "^7.0.3", "eslint": "^8.26.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "install": "^0.13.0", "jest": "^29.3.1", + "jest-junit": "^15.0.0", "nodemon": "^2.0.19", "npm": "^8.19.3", "prettier": "^2.7.1", + "supertest": "^6.3.3", + "ts-jest": "^29.0.3", "ts-node": "^10.9.1" + }, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "coveragePathIgnorePatterns": [ + "/node_modules/" + ], + "setupFiles": [ + "/test-resources/env-vars.js" + ] + }, + "jest-junit": { + "outputDirectory": "reports", + "outputName": "jest-junit.xml", + "ancestorSeparator": " โ€บ ", + "uniqueOutputName": "false", + "suiteNameTemplate": "{filepath}", + "classNameTemplate": "{classname}", + "titleTemplate": "{title}" } } diff --git a/backend/src/app.ts b/backend/src/app.ts new file mode 100644 index 000000000..1d00c03d9 --- /dev/null +++ b/backend/src/app.ts @@ -0,0 +1,72 @@ +/* eslint-disable no-console */ + +import express from 'express'; +import helmet from 'helmet'; +import cors from 'cors'; +import cookieParser from 'cookie-parser'; +import dotenv from 'dotenv'; + +dotenv.config(); +import { PORT, NODE_ENV, SITE_URL } from './config'; +import { apiLimiter } from './helpers/rateLimiter'; + +import { + signup as signupRouter, + auth as authRouter, + organization as organizationRouter, + workspace as workspaceRouter, + membershipOrg as membershipOrgRouter, + membership as membershipRouter, + key as keyRouter, + inviteOrg as inviteOrgRouter, + user as userRouter, + userAction as userActionRouter, + secret as secretRouter, + serviceToken as serviceTokenRouter, + password as passwordRouter, + stripe as stripeRouter, + integration as integrationRouter, + integrationAuth as integrationAuthRouter +} from './routes'; + +export const app = express(); + +app.enable('trust proxy'); +app.use(express.json()); +app.use(cookieParser()); +app.use( + cors({ + credentials: true, + origin: SITE_URL + }) +); + +if (NODE_ENV === 'production') { + // enable app-wide rate-limiting + helmet security + // in production + app.disable('x-powered-by'); + app.use(apiLimiter); + app.use(helmet()); +} + +// routers +app.use('/api/v1/signup', signupRouter); +app.use('/api/v1/auth', authRouter); +app.use('/api/v1/user', userRouter); +app.use('/api/v1/user-action', userActionRouter); +app.use('/api/v1/organization', organizationRouter); +app.use('/api/v1/workspace', workspaceRouter); +app.use('/api/v1/membership-org', membershipOrgRouter); +app.use('/api/v1/membership', membershipRouter); +app.use('/api/v1/key', keyRouter); +app.use('/api/v1/invite-org', inviteOrgRouter); +app.use('/api/v1/secret', secretRouter); +app.use('/api/v1/service-token', serviceTokenRouter); +app.use('/api/v1/password', passwordRouter); +app.use('/api/v1/stripe', stripeRouter); +app.use('/api/v1/integration', integrationRouter); +app.use('/api/v1/integration-auth', integrationAuthRouter); + +export const server = app.listen(PORT, () => { + console.log(`Listening on PORT ${[PORT]}`); +}); diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 1c92af93f..7f5fb1564 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -2,40 +2,10 @@ import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; import nodemailer from 'nodemailer'; -import { - SMTP_HOST, - SMTP_PORT, - SMTP_NAME, - SMTP_USERNAME, - SMTP_PASSWORD -} from '../config'; -import SMTPConnection from 'nodemailer/lib/smtp-connection'; +import { SMTP_NAME, SMTP_USERNAME } from '../config'; import * as Sentry from '@sentry/node'; -const mailOpts: SMTPConnection.Options = { - host: SMTP_HOST, - port: SMTP_PORT as number -}; -if (SMTP_USERNAME && SMTP_PASSWORD) { - mailOpts.auth = { - user: SMTP_USERNAME, - pass: SMTP_PASSWORD - }; -} -// create nodemailer transporter -const transporter = nodemailer.createTransport(mailOpts); -transporter - .verify() - .then(() => { - Sentry.setUser(null); - Sentry.captureMessage('SMTP - Successfully connected'); - }) - .catch((err) => { - Sentry.setUser(null); - Sentry.captureException( - `SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}` - ); - }); +let smtpTransporter: nodemailer.Transporter; /** * @param {Object} obj @@ -63,7 +33,7 @@ const sendMail = async ({ const temp = handlebars.compile(html); const htmlToSend = temp(substitutions); - await transporter.sendMail({ + await smtpTransporter.sendMail({ from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`, to: recipients.join(', '), subject: subjectLine, @@ -75,4 +45,8 @@ const sendMail = async ({ } }; -export { sendMail }; +const setTransporter = (transporter: nodemailer.Transporter) => { + smtpTransporter = transporter; +}; + +export { sendMail, setTransporter }; diff --git a/backend/src/index.ts b/backend/src/index.ts index 28e27cc9a..d182c2655 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,127 +1,25 @@ -/* eslint-disable no-console */ -import http from 'http'; -import express from 'express'; -import helmet from 'helmet'; -import cors from 'cors'; -import cookieParser from 'cookie-parser'; -import mongoose from 'mongoose'; import dotenv from 'dotenv'; - dotenv.config(); + import * as Sentry from '@sentry/node'; -import { PORT, SENTRY_DSN, NODE_ENV, MONGO_URL, SITE_URL } from './config'; -import { apiLimiter } from './helpers/rateLimiter'; -import { createTerminus } from '@godaddy/terminus'; +import { SENTRY_DSN, NODE_ENV, MONGO_URL } from './config'; +import { server } from './app'; +import { initDatabase } from './services/database'; +import { setUpHealthEndpoint } from './services/health'; +import { initSmtp } from './services/smtp'; +import { setTransporter } from './helpers/nodemailer'; -const app = express(); +initDatabase(MONGO_URL); -Sentry.init({ - dsn: SENTRY_DSN, - tracesSampleRate: 1.0, - debug: NODE_ENV === 'production' ? false : true, - environment: NODE_ENV -}); +setUpHealthEndpoint(server); -import { - signup as signupRouter, - auth as authRouter, - organization as organizationRouter, - workspace as workspaceRouter, - membershipOrg as membershipOrgRouter, - membership as membershipRouter, - key as keyRouter, - inviteOrg as inviteOrgRouter, - user as userRouter, - userAction as userActionRouter, - secret as secretRouter, - serviceToken as serviceTokenRouter, - password as passwordRouter, - stripe as stripeRouter, - integration as integrationRouter, - integrationAuth as integrationAuthRouter -} from './routes'; +setTransporter(initSmtp()); -const connectWithRetry = () => { - mongoose - .connect(MONGO_URL) - .then(() => console.log('Successfully connected to DB')) - .catch((e) => { - console.log('Failed to connect to DB ', e); - setTimeout(() => { - console.log(e); - }, 5000); - }); - return mongoose.connection; -}; - -const dbConnection = connectWithRetry(); - -app.enable('trust proxy'); -app.use(cookieParser()); -app.use( - cors({ - credentials: true, - origin: SITE_URL - }) -); - -if (NODE_ENV === 'production') { - // enable app-wide rate-limiting + helmet security - // in production - app.disable('x-powered-by'); - app.use(apiLimiter); - app.use(helmet()); +if (NODE_ENV !== 'test') { + Sentry.init({ + dsn: SENTRY_DSN, + tracesSampleRate: 1.0, + debug: NODE_ENV === 'production' ? false : true, + environment: NODE_ENV + }); } - -app.use(express.json()); - -// routers -app.use('/api/v1/signup', signupRouter); -app.use('/api/v1/auth', authRouter); -app.use('/api/v1/user', userRouter); -app.use('/api/v1/user-action', userActionRouter); -app.use('/api/v1/organization', organizationRouter); -app.use('/api/v1/workspace', workspaceRouter); -app.use('/api/v1/membership-org', membershipOrgRouter); -app.use('/api/v1/membership', membershipRouter); -app.use('/api/v1/key', keyRouter); -app.use('/api/v1/invite-org', inviteOrgRouter); -app.use('/api/v1/secret', secretRouter); -app.use('/api/v1/service-token', serviceTokenRouter); -app.use('/api/v1/password', passwordRouter); -app.use('/api/v1/stripe', stripeRouter); -app.use('/api/v1/integration', integrationRouter); -app.use('/api/v1/integration-auth', integrationAuthRouter); - -const server = http.createServer(app); - -const onSignal = () => { - console.log('Server is starting clean-up'); - return Promise.all([ - () => { - dbConnection.close(() => { - console.info('Database connection closed'); - }); - } - ]); -}; - -const healthCheck = () => { - // `state.isShuttingDown` (boolean) shows whether the server is shutting down or not - return Promise - .resolve - // optionally include a resolve value to be included as - // info in the health check response - (); -}; - -createTerminus(server, { - healthChecks: { - '/healthcheck': healthCheck, - onSignal - } -}); - -server.listen(PORT, () => { - console.log('Listening on PORT ' + PORT); -}); diff --git a/backend/src/services/database.ts b/backend/src/services/database.ts new file mode 100644 index 000000000..b28195ab2 --- /dev/null +++ b/backend/src/services/database.ts @@ -0,0 +1,15 @@ +/* eslint-disable no-console */ +import mongoose from 'mongoose'; + +export const initDatabase = (MONGO_URL: string) => { + mongoose + .connect(MONGO_URL) + .then(() => console.log('Successfully connected to DB')) + .catch((e) => { + console.log('Failed to connect to DB ', e); + setTimeout(() => { + console.log(e); + }, 5000); + }); + return mongoose.connection; +}; diff --git a/backend/src/services/health.ts b/backend/src/services/health.ts new file mode 100644 index 000000000..95fef514f --- /dev/null +++ b/backend/src/services/health.ts @@ -0,0 +1,32 @@ +/* eslint-disable no-console */ +import mongoose from 'mongoose'; +import { createTerminus } from '@godaddy/terminus'; + +export const setUpHealthEndpoint = (server: T) => { + const onSignal = () => { + console.log('Server is starting clean-up'); + return Promise.all([ + () => { + if (mongoose.connection && mongoose.connection.readyState == 1) { + mongoose.connection.close(); + () => { + console.info('Database connection closed'); + }; + } + } + ]); + }; + + const healthCheck = () => { + // `state.isShuttingDown` (boolean) shows whether the server is shutting down or not + // optionally include a resolve value to be included as info in the health check response + return Promise.resolve(); + }; + + createTerminus(server, { + healthChecks: { + '/healthcheck': healthCheck, + onSignal + } + }); +}; diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts new file mode 100644 index 000000000..dd502750f --- /dev/null +++ b/backend/src/services/smtp.ts @@ -0,0 +1,33 @@ +import nodemailer from 'nodemailer'; +import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD } from '../config'; +import SMTPConnection from 'nodemailer/lib/smtp-connection'; +import * as Sentry from '@sentry/node'; + +const mailOpts: SMTPConnection.Options = { + host: SMTP_HOST, + port: SMTP_PORT as number +}; +if (SMTP_USERNAME && SMTP_PASSWORD) { + mailOpts.auth = { + user: SMTP_USERNAME, + pass: SMTP_PASSWORD + }; +} + +export const initSmtp = () => { + const transporter = nodemailer.createTransport(mailOpts); + transporter + .verify() + .then(() => { + Sentry.setUser(null); + Sentry.captureMessage('SMTP - Successfully connected'); + }) + .catch((err) => { + Sentry.setUser(null); + Sentry.captureException( + `SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}` + ); + }); + + return transporter; +}; diff --git a/backend/test-resources/docker-compose.test.yml b/backend/test-resources/docker-compose.test.yml new file mode 100644 index 000000000..e9a8c519a --- /dev/null +++ b/backend/test-resources/docker-compose.test.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + mongo-test: + image: mongo + container_name: infisical-test-mongo + restart: always + ports: + - 27018:27017 + environment: + - MONGO_INITDB_ROOT_USERNAME=test + - MONGO_INITDB_ROOT_PASSWORD=test1234 diff --git a/backend/test-resources/env-vars.js b/backend/test-resources/env-vars.js new file mode 100644 index 000000000..a7542728f --- /dev/null +++ b/backend/test-resources/env-vars.js @@ -0,0 +1,5 @@ +/* eslint-disable no-undef */ +process.env.MONGO_URL = + 'mongodb://test:test1234@localhost:27018/?authSource=admin'; +process.env.MONGO_USERNAME = 'test'; +process.env.MONGO_PASSWORD = 'test1234'; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index d6d293372..0bfe3c372 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -8,16 +8,13 @@ "allowJs": true, "outDir": "build", "esModuleInterop": true, + "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitAny": true, "skipLibCheck": true, - "typeRoots" : ["./src/types", "./node_modules/@types"] + "typeRoots": ["./src/types", "./node_modules/@types"] }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules" - ] + "include": ["src/**/*"], + "exclude": ["node_modules"] }