diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e84c5a..a838dc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,41 @@ name: Testes Unitarios on: [push, pull_request] +env: + POSTGRES_USER: ${{vars.POSTGRES_USER}} + POSTGRES_PASSWORD: ${{vars.POSTGRES_PASSWORD}} + POSTGRES_HOST: ${{vars.POSTGRES_HOST}} + POSTGRES_PORT: ${{vars.POSTGRES_PORT}} + POSTGRES_DATABASE: ${{vars.POSTGRES_DATABASE}} + JWT_SECRET: ${{secrets.JWT_SECRET}} jobs: CI: runs-on: ubuntu-latest steps: - - name: Verificação do Repositorio - uses: actions/checkout@v4.1.7 - - name: Configurando Node.Js - uses: actions/setup-node@v4.0.3 - with: - node-version: '20' - - name: Instalando Dependencias - run: npm clean-install - - name: Executando Testes - run: npm run test + - name: Verificação do Repositorio + uses: actions/checkout@v4.1.7 + - name: Iniciando Docker Compose + uses: hoverkraft-tech/compose-action@v2.0.1 + with: + services: database + compose-file: ./docker-compose.yml + + - name: Configurando Node.Js + uses: actions/setup-node@v4.0.3 + with: + node-version: '20' + + - name: Instalando Dependencias + run: npm clean-install + + - name: Executando Testes + run: npm run test + env: + POSTGRES_HOST: ${{env.POSTGRES_HOST}} + POSTGRES_PORT: ${{env.POSTGRES_PORT}} + POSTGRES_USER: ${{env.POSTGRES_USER}} + POSTGRES_PASSWORD: ${{env.POSTGRES_PASSWORD}} + POSTGRES_DATABASE: ${{env.POSTGRES_DATABASE}} + JWT_SECRET: ${{env.JWT_SECRET}} diff --git a/jest.config.mjs b/jest.config.mjs index e3e017e..30a2336 100644 --- a/jest.config.mjs +++ b/jest.config.mjs @@ -24,7 +24,7 @@ const config = { // collectCoverageFrom: undefined, // The directory where Jest should output its coverage files - coverageDirectory: "coverage", + coverageDirectory: 'coverage', // An array of regexp pattern strings used to skip coverage collection // coveragePathIgnorePatterns: [ @@ -32,7 +32,7 @@ const config = { // ], // Indicates which provider should be used to instrument code for coverage - coverageProvider: "v8", + coverageProvider: 'v8', // A list of reporter names that Jest uses when writing coverage reports // coverageReporters: [ @@ -60,7 +60,7 @@ const config = { // forceCoverageMatch: [], // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: undefined, + globalSetup: './jest.setup.js', // A path to a module which exports an async function that is triggered once after all test suites // globalTeardown: undefined, diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..c0663ce --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,5 @@ +import { configDotenv } from 'dotenv'; + +export default () => { + configDotenv(); +}; diff --git a/package-lock.json b/package-lock.json index ef1eb3d..5b82599 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,10 @@ "cors": "^2.8.5", "express": "^4.19.2", "helmet": "^7.1.0", - "pg": "^8.12.0" + "jsonwebtoken": "^9.0.2", + "moment": "^2.30.1", + "pg": "^8.12.0", + "supertest": "^7.0.0" }, "devDependencies": { "@commitlint/config-angular": "^19.3.0", @@ -23,7 +26,12 @@ "@semantic-release/release-notes-generator": "^14.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/jsonwebtoken": "^9.0.6", + "@types/pg": "^8.11.6", + "@types/supertest": "^6.0.2", "commitlint": "^19.3.0", + "dotenv": "^16.4.5", "husky": "^9.1.4", "jest": "^29.7.0", "semantic-release": "^24.0.0", @@ -2268,10 +2276,11 @@ "dev": true }, "node_modules/@semantic-release/github": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-10.1.3.tgz", - "integrity": "sha512-QVw7YT3J4VqyVjOnlRsFA3OCERAJHER4QbSPupbav3ER0fawrs2BAWbQFjsr24OAD4KTTKMZsVzF+GYFWCDtaQ==", + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-10.1.7.tgz", + "integrity": "sha512-QnhP4k1eqzYLz6a4kpWrUQeKJYXqHggveMykvUFbSquq07GF85BXvr/QLhpOD7bpDcmEfL8VnphRA7KT5i9lzQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/core": "^6.0.0", "@octokit/plugin-paginate-rest": "^11.0.0", @@ -2780,6 +2789,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true + }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -2852,6 +2867,32 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", + "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2873,6 +2914,74 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/pg": { + "version": "8.11.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz", + "integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.2.tgz", + "integrity": "sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.1.0", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz", + "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -2918,6 +3027,28 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true }, + "node_modules/@types/superagent": { + "version": "8.1.8", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.8.tgz", + "integrity": "sha512-nTqHJ2OTa7PFEpLahzSEEeFeqbMpmcN7OeayiOc7v+xk+/vyTKljRe+o4MPqSnPeRCMvtxuLG+5QqluUVQJOnA==", + "dev": true, + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3090,6 +3221,16 @@ "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3335,6 +3476,12 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3594,6 +3741,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commitlint": { "version": "19.3.0", "resolved": "https://registry.npmjs.org/commitlint/-/commitlint-19.3.0.tgz", @@ -3620,6 +3778,14 @@ "dot-prop": "^5.1.0" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3759,6 +3925,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3966,6 +4137,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3992,6 +4171,15 @@ "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==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -4025,6 +4213,19 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -4034,6 +4235,15 @@ "readable-stream": "^2.0.2" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4446,6 +4656,11 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "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==" + }, "node_modules/fast-uri": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", @@ -4559,6 +4774,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", + "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.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", @@ -4936,6 +5177,14 @@ "node": ">=16.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==", + "engines": { + "node": ">=8" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -6586,6 +6835,55 @@ "node": "*" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6683,17 +6981,39 @@ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", "dev": true }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", @@ -6713,6 +7033,12 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -6926,6 +7252,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9620,6 +9955,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -9635,7 +9976,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -9914,6 +10254,15 @@ "node": ">=4.0.0" } }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/pg-pool": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", @@ -10174,6 +10523,12 @@ "node": ">=0.10.0" } }, + "node_modules/postgres-range": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz", + "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==", + "dev": true + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -10560,10 +10915,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semantic-release": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.0.0.tgz", - "integrity": "sha512-v46CRPw+9eI3ZuYGF2oAjqPqsfbnfFTwLBgQsv/lch4goD09ytwOTESMN4QIrx/wPLxUGey60/NMx+ANQtWRsA==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.1.0.tgz", + "integrity": "sha512-FwaE2hKDHQn9G6GA7xmqsc9WnsjaFD/ppLM5PUg56Do9oKSCf+vH6cPeb3hEBV/m06n8Sh9vbVqPjHu/1onzQw==", "dev": true, + "license": "MIT", "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -10843,7 +11199,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -11310,6 +11665,69 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11782,8 +12200,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 7c8327b..74fd3da 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "cors": "^2.8.5", "express": "^4.19.2", "helmet": "^7.1.0", - "pg": "^8.12.0" + "jsonwebtoken": "^9.0.2", + "pg": "^8.12.0", + "supertest": "^7.0.0" }, "devDependencies": { "@commitlint/config-angular": "^19.3.0", @@ -34,7 +36,12 @@ "@semantic-release/release-notes-generator": "^14.0.1", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/jsonwebtoken": "^9.0.6", + "@types/pg": "^8.11.6", + "@types/supertest": "^6.0.2", "commitlint": "^19.3.0", + "dotenv": "^16.4.5", "husky": "^9.1.4", "jest": "^29.7.0", "semantic-release": "^24.0.0", diff --git a/src/database/database.js b/src/database/database.js new file mode 100644 index 0000000..45388f9 --- /dev/null +++ b/src/database/database.js @@ -0,0 +1,13 @@ +import pg from 'pg'; +const { Pool } = pg; + +let pool = new Pool({ + database: process.env['POSTGRES_DATABASE'], + user: process.env['POSTGRES_USER'], + password: process.env['POSTGRES_PASSWORD'], + host: process.env['POSTGRES_HOST'], + // @ts-ignore + port: process.env['POSTGRES_PORT'], +}); + +export default pool; diff --git a/src/helpers/auth.js b/src/helpers/auth.js new file mode 100644 index 0000000..94d4f4f --- /dev/null +++ b/src/helpers/auth.js @@ -0,0 +1,88 @@ +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * + */ +import jwt from 'jsonwebtoken'; +const JWT_SECRET = process.env['JWT_SECRET']; +if (!JWT_SECRET) { + process.exit(1); +} + +const defaultExpirationDate = '1hr'; + +class TokenExpiredError extends Error { } + +class JsonWebTokenError extends Error { } + +/** + * + * + * @param {String} token + * @throws {JsonWebTokenError} + * @throws {TokenExpiredError} + */ +function decodeJWT(token) { + try { + return jwt.verify(token, JWT_SECRET); + } catch (/** @type {any}*/ err) { + if (err?.name === 'TokenExpiredError') { + throw new TokenExpiredError('token expirado'); + } + if (err?.name === 'JsonWebTokenError') { + throw new JsonWebTokenError('token invalido'); + } + throw err; + } +} + +/** + * + * @param {Request} req + * @throws {Error} + */ +function getToken(req) { + const bearerToken = req.header('Authorization'); + if (!bearerToken) { + throw new Error('sem token de autorização ou nome errado'); + } + const headerDiv = bearerToken.split(' '); + if (headerDiv[0] != 'Bearer' || headerDiv.length !== 2) { + throw new Error('formato invalido'); + } + return headerDiv[1]; +} +/** + * + * @param {Object.} payload + * @param {jwt.SignOptions} [options={ expiresIn: defaultTokenExpiration }] + * @returns {String} + */ +function testsignJWT(payload, options = {}) { + options.expiresIn = options.expiresIn || defaultExpirationDate; + options.subject = `${payload.id}`; + payload.iat = Math.floor(Date.now() / 1000); + return jwt.sign(payload, JWT_SECRET, options); +} + +/** + * + * @param {Object.} payload + * @returns {String} + */ +function signJWT(payload) { + /** @type {jwt.SignOptions} */ + let options = {}; + options.expiresIn = options.expiresIn || defaultExpirationDate; + options.subject = `${payload.id}`; + payload.iat = Math.floor(Date.now() / 1000); + return jwt.sign(payload, JWT_SECRET, options); +} +export { + decodeJWT, + getToken, + TokenExpiredError, + JsonWebTokenError, + testsignJWT, + signJWT +}; diff --git a/src/helpers/general_helpers.js b/src/helpers/general_helpers.js new file mode 100644 index 0000000..5dfe589 --- /dev/null +++ b/src/helpers/general_helpers.js @@ -0,0 +1,15 @@ +/** + * + * + * @param {Object} obj - + * @returns {String} + */ +function formatObject(obj) { + return JSON.stringify( + obj, + /** null keys value*/ null, + /** space indentation*/ 2 + ); +} + +export { formatObject }; diff --git a/src/middlewares/auth_middleware.js b/src/middlewares/auth_middleware.js new file mode 100644 index 0000000..cbaffeb --- /dev/null +++ b/src/middlewares/auth_middleware.js @@ -0,0 +1,82 @@ +/** + * @typedef {import('express').Request} Request + * @typedef {import('express').Response} Response + * + */ + +import { + decodeJWT, + getToken, + JsonWebTokenError, + TokenExpiredError, +} from '../helpers/auth.js'; +import pool from '../database/database.js'; +import { error } from 'console'; + +export default { + /** + * + * + * @param {Request} req + * @param {Response} res + * @param {Function} next + * @async + */ + // @ts-ignore + async authenticateJWT(req, res, next) { + let token; + try { + token = getToken(req); + } catch (err) { + return res.status(401).json({ + error: 'invalido', + }); + } + + let decoded; + try { + //@ts-ignore + decoded = decodeJWT(token); + } catch (err) { + if (err instanceof TokenExpiredError) { + return res.status(401).json({ + error: 'expirado', + }); + } + if (err instanceof JsonWebTokenError) { + return res.status(401).json({ + error: 'invalido', + }); + } + return res.status(500).json({ + error: 'server error', + }); + } + + // req.user = user + try { + // @ts-ignore + let id = decoded.sub; + if (Number(id) <= 0) { + return res.status(400).json({ error: "id invalido"}) + } + let result = await pool.query('SELECT * FROM users WHERE id = $1 ', [id]); + if (result.rows.length == 0) { + return res.status(404).json({ error: 'usuario nao encontrado' }); + } + if (result.rows.length > 1) { + console.log(`ERROR: Mais de um usuário foi encontrado com o id ${id}`); + return res.status(500).json({ + error: 'Houve um erro no banco de dados ao autenticar o usuário', + }); + } + // @ts-ignore + req.user = result.rows[0]; + next(); + } catch (err) { + return res + .status(500) + .json({ error: 'não foi possivel pegar o usuario', error_infos: err }); + } + }, +}; diff --git a/src/routes/routes.js b/src/routes/routes.js index a9ffe6c..0668e8c 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -3,5 +3,4 @@ import { Router } from "express"; const router = Router() - export default router \ No newline at end of file diff --git a/test/auth.test.js b/test/auth.test.js new file mode 100644 index 0000000..5dd58f3 --- /dev/null +++ b/test/auth.test.js @@ -0,0 +1,141 @@ +import supertest from 'supertest'; +import test_app from './test.router.js'; +import { testsignJWT } from '../src/helpers/auth.js'; +import { formatObject } from '../src/helpers/general_helpers.js'; +import pool from '../src/database/database.js'; + +describe('AUTH TEST', () => { + afterAll(async () => { + await pool.query('TRUNCATE TABLE users CASCADE'); + }); + const path = '/test/middlware/autenticacao'; + + it('401: token nao veio na autenticacao', async () => { + await supertest(test_app) + .post(path) + .send() + .expect((res) => { + const { status, body } = res; + try { + expect(status).toBe(401); + expect(body).toHaveProperty('error', 'invalido'); + } catch (error) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${error}\n + `); + } + }); + }); + + it('401: token deve expirar quando passar o tempo', async () => { + const token = testsignJWT({ id: 1 }, { expiresIn: '1ms' }); + await supertest(test_app) + .post(path) + .set('Authorization', `Bearer ${token}`) + .expect((res) => { + const { status, body } = res; + try { + expect(status).toBe(401); + expect(body).toHaveProperty('error', 'expirado'); + } catch (error) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${error}\n + `); + } + }); + }); + + it('401: token deve ser invalido', async () => { + await supertest(test_app) + .post(path) + .set('Authorization', `Bearer lalaal`) + .expect((res) => { + const { status, body } = res; + try { + expect(status).toBe(401); + expect(body).toHaveProperty('error', 'invalido'); + } catch (error) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${error}\n + `); + } + }); + }); + it('404: Usuario nao encontrado', async () => { + const token = testsignJWT({ id: 8 }); + await supertest(test_app) + .post(path) + .set('Authorization', `Bearer ${token}`) + .expect((res) => { + const { status, body } = res; + try { + expect(status).toBe(404); + expect(body).toHaveProperty('error', 'usuario nao encontrado'); + } catch (error) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${error}\n + `); + } + }); + }); + + it('200: deve dar certo se o token for valido e o usuario existir', async () => { + const result = await pool.query(` + INSERT INTO users (id, username, password) + VALUES (10, 'tales', '123') + RETURNING * + `); + const user = result.rows[0]; + const token = testsignJWT({ + id: user.id, + }); + + await supertest(test_app) + .post(path) + .set('Authorization', `Bearer ${token}`) + .expect((res) => { + const { status, body } = res; + body.created_at = new Date(body.created_at); + try { + expect(status).toBe(200); + expect(body).toEqual(user); + } catch (error) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${error}\n + `); + } + }); + }); + + it('400: id abaixo de 0 ou 0 deve ser invalido', async () => { + for (let id = -3; id <= 0; id++) { + const token = testsignJWT({ id: id }); + await supertest(test_app) + .post(path) + .set('Authorization', `Bearer ${token}`) + .expect((res) => { + const { status, body } = res; + try { + expect(status).toBe(400); + expect(body).toHaveProperty('error', 'id invalido'); + } catch (err) { + throw new Error(` + \n${status}\n + \n${formatObject(body)}\n + \n${err}\n + `); + } + }); + } + }); +}); diff --git a/test/test.router.js b/test/test.router.js new file mode 100644 index 0000000..7f50482 --- /dev/null +++ b/test/test.router.js @@ -0,0 +1,24 @@ +import express from 'express'; +import auth_middleware from '../src/middlewares/auth_middleware.js'; + +let test_app = express(); +test_app.use(express.json()); + +test_app.post( + '/test/middlware/autenticacao', + auth_middleware.authenticateJWT, + (req, res) => { + // @ts-ignore + console.log(req.user); + // @ts-ignore + return res.json(req.user); + } +); + +const port = 3000; + +test_app.listen(port, () => { + console.log(`test app escutando na porta: ${port}`); +}); + +export default test_app;