From 7d4f7dcd7afc8bea3cddc0bf8532ae3c4495e49f Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:07:06 +0200 Subject: [PATCH 1/8] add health to api --- api/src/app.ts | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index 85492154..da1b5afe 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -2,19 +2,38 @@ import type { Express } from 'express'; import express from 'express'; import expressPlayground from 'graphql-playground-middleware-express'; import { getChildLogger } from './logger'; +import prisma from './database'; const logger = getChildLogger({ msgPrefix: 'APP' }); +const startupTime = new Date(); export const createApp = (): Express => { logger.debug('Creating app'); const app = express(); - app.get('/', expressPlayground({ - endpoint: '/api/graphql', - settings: { - 'editor.theme': 'light', - }, - })); + app.get('/healthz', async (_req, res) => { + try { + await prisma.$queryRaw`SELECT 1;`; + } catch (error) { + // TODO: test after migration to postgres, since sqlite will + // still return success if the file is missing + res.status(500).send('DB failed initialization check'); + } + return res.json({ + status: 'healthy', + time: new Date(), + startupTime, + }); + }); + app.get( + '/', + expressPlayground({ + endpoint: '/api/graphql', + settings: { + 'editor.theme': 'light', + }, + }), + ); return app; }; From 62e788b4f8bc99c4a4e6f3021279178cb4c91340 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:21:19 +0200 Subject: [PATCH 2/8] healthz api endpoing --- frontend/server/api/healthz.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 frontend/server/api/healthz.ts diff --git a/frontend/server/api/healthz.ts b/frontend/server/api/healthz.ts new file mode 100644 index 00000000..dc15e566 --- /dev/null +++ b/frontend/server/api/healthz.ts @@ -0,0 +1,9 @@ +const startupTime = new Date() + +export default eventHandler(async () => { + return { + status: 'healthy', + time: new Date(), + startupTime, + } +}) From 5091e9ebdc768ae3c8ac993e0ea1d71391915da5 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:33:10 +0200 Subject: [PATCH 3/8] add readme --- README.md | 6 ++++++ api/README.md | 4 ++++ frontend/README.md | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/README.md b/README.md index 0c945083..f5726ec7 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,9 @@ To understand what is planned you can read and ask questions here: To install correct node version, we recommend that you use [nvm](https://github.com/nvm-sh/nvm). If you have `nvm` installed you can run `nvm install && nvm use` to automatically use the correct node version. The version is detected from the [.nvmrc](./.nvmrc). If you do not have a code editor setup, we recommend that you use [Visual Studio Code](https://code.visualstudio.com/) to get started. It is very beginner friendly and you can move on to something else down the road if you want to. + +## Health endpoints + +Both api and frontend have health endpoints that can be used to check if the service is up and running. + +See the respective descriptions in the [api](./api/README.md#health-endpoint) and [frontend](./frontend/README.md#health-endpoint) READMEs. diff --git a/api/README.md b/api/README.md index 7ce389fe..ed33d344 100644 --- a/api/README.md +++ b/api/README.md @@ -53,3 +53,7 @@ npx prisma studio ### Logging configuration The configuration is received from the `logger.config.ts` file at the root of the project. Adjust the file parameters to control the logger behaviour. + +## Health endpoint + +Endpoint available at `/healthz` path. Provides response if api is currently running and prisma (orm) is able to execute queries. diff --git a/frontend/README.md b/frontend/README.md index 1d5de788..ca54ff6c 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -13,3 +13,7 @@ Please refer to the root readme to learn more about general development setup. ## Production You can build application for production using `npm run build` and then locally preview production build via `npm run preview`. + +## Health endpoint + +Endpoint available at `/healthz` path. Provides response if frontend is currently running. From c5fc0610c3dfd15d09d8ed48de9072442f7de848 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 17:45:57 +0200 Subject: [PATCH 4/8] query a table at healthz --- api/src/app.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/api/src/app.ts b/api/src/app.ts index da1b5afe..81666f98 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -13,11 +13,14 @@ export const createApp = (): Express => { app.get('/healthz', async (_req, res) => { try { - await prisma.$queryRaw`SELECT 1;`; + // TODO: after migration to postgres, do SELECT 1 + await prisma.user.findMany(); } catch (error) { - // TODO: test after migration to postgres, since sqlite will - // still return success if the file is missing - res.status(500).send('DB failed initialization check'); + res.status(500).json({ + status: 'DB failed initialization check', + time: new Date(), + startupTime, + }); } return res.json({ status: 'healthy', From d2d7ea23041858d29fa6cd9f127448baa50556c1 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 18:25:41 +0200 Subject: [PATCH 5/8] tests --- api/package-lock.json | 243 +++++++++++++++++++++++++++++++--- api/package.json | 4 +- api/src/__mocks__/database.ts | 11 ++ api/src/app.ts | 2 +- api/tests/auxilary.test.ts | 29 +++- api/tests/helpers/server.ts | 9 +- 6 files changed, 276 insertions(+), 22 deletions(-) create mode 100644 api/src/__mocks__/database.ts diff --git a/api/package-lock.json b/api/package-lock.json index 62b7ac62..78ed501b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -46,8 +46,10 @@ "eslint-config-airbnb-typescript": "^17.0.0", "gql-query-builder": "^3.8.0", "graphql-request": "^5.2.0", + "node-fetch": "^3.3.1", "prisma": "^4.11.0", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "vitest-mock-extended": "^1.1.3" } }, "node_modules/@ampproject/remapping": { @@ -148,6 +150,25 @@ "graphql": "14.x || 15.x || 16.x" } }, + "node_modules/@apollo/server/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@apollo/usage-reporting-protobuf": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@apollo/usage-reporting-protobuf/-/usage-reporting-protobuf-4.1.0.tgz", @@ -1344,6 +1365,25 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2579,6 +2619,15 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, "node_modules/dataloader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", @@ -3454,6 +3503,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3565,6 +3637,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4987,23 +5071,41 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-releases": { @@ -6259,6 +6361,15 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-essentials": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.1.tgz", + "integrity": "sha512-9CChSvQMyVRo29Vb1A2jbs+LKo3d/bAf+ndSaX0T8cEiy/HChVaRN/HY5DqUryZ8hZ6uol9bEgCnGmnDbwBR9Q==", + "dev": true, + "peerDependencies": { + "typescript": ">=4.1.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -6622,6 +6733,28 @@ } } }, + "node_modules/vitest-mock-extended": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-1.1.3.tgz", + "integrity": "sha512-MiaKYZbTg+fjozKnCpoTTva0BnlSNYyk4jiPuM2xVhg4aou112QIrALdH3/ZKK6qfXWh0A17gFIjWJjylOlXxg==", + "dev": true, + "dependencies": { + "ts-essentials": "^9.3.1" + }, + "peerDependencies": { + "typescript": "3.x || 4.x || 5.x", + "vitest": ">=0.29.2" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6867,6 +7000,16 @@ "node-fetch": "^2.6.7", "uuid": "^9.0.0", "whatwg-mimetype": "^3.0.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "@apollo/server-gateway-interface": { @@ -7662,6 +7805,16 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.11" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "@nodelib/fs.scandir": { @@ -8570,6 +8723,12 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true + }, "dataloader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", @@ -9271,6 +9430,16 @@ "reusify": "^1.0.4" } }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9363,6 +9532,15 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "requires": { + "fetch-blob": "^3.1.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10391,12 +10569,21 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true + }, "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dev": true, "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "node-releases": { @@ -11305,6 +11492,13 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "ts-essentials": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.1.tgz", + "integrity": "sha512-9CChSvQMyVRo29Vb1A2jbs+LKo3d/bAf+ndSaX0T8cEiy/HChVaRN/HY5DqUryZ8hZ6uol9bEgCnGmnDbwBR9Q==", + "dev": true, + "requires": {} + }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -11520,6 +11714,21 @@ "why-is-node-running": "^2.2.2" } }, + "vitest-mock-extended": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitest-mock-extended/-/vitest-mock-extended-1.1.3.tgz", + "integrity": "sha512-MiaKYZbTg+fjozKnCpoTTva0BnlSNYyk4jiPuM2xVhg4aou112QIrALdH3/ZKK6qfXWh0A17gFIjWJjylOlXxg==", + "dev": true, + "requires": { + "ts-essentials": "^9.3.1" + } + }, + "web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true + }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/api/package.json b/api/package.json index 2fd415a2..d95ebf88 100644 --- a/api/package.json +++ b/api/package.json @@ -52,7 +52,9 @@ "eslint-config-airbnb-typescript": "^17.0.0", "gql-query-builder": "^3.8.0", "graphql-request": "^5.2.0", + "node-fetch": "^3.3.1", "prisma": "^4.11.0", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "vitest-mock-extended": "^1.1.3" } } diff --git a/api/src/__mocks__/database.ts b/api/src/__mocks__/database.ts new file mode 100644 index 00000000..8551c946 --- /dev/null +++ b/api/src/__mocks__/database.ts @@ -0,0 +1,11 @@ +import { PrismaClient } from '@prisma/client'; +import { beforeEach } from 'vitest'; +import { mockDeep, mockReset } from 'vitest-mock-extended'; + +const prisma = mockDeep(); + +beforeEach(() => { + mockReset(prisma); +}); + +export default prisma; diff --git a/api/src/app.ts b/api/src/app.ts index 81666f98..cd80cc02 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -16,7 +16,7 @@ export const createApp = (): Express => { // TODO: after migration to postgres, do SELECT 1 await prisma.user.findMany(); } catch (error) { - res.status(500).json({ + return res.status(500).json({ status: 'DB failed initialization check', time: new Date(), startupTime, diff --git a/api/tests/auxilary.test.ts b/api/tests/auxilary.test.ts index 053ce60f..89d9cbc6 100644 --- a/api/tests/auxilary.test.ts +++ b/api/tests/auxilary.test.ts @@ -1,6 +1,11 @@ -import { test, expect } from 'vitest'; +import { + test, expect, vi, describe, +} from 'vitest'; +import fetch from 'node-fetch'; import { getJwtSecret, getJwtExpirationPeriod } from '../src/env/getters'; import { restoreEnvAfterEach } from './helpers/env'; +import { ctx } from './helpers/server'; +import prisma from '../src/__mocks__/database'; restoreEnvAfterEach(); @@ -34,3 +39,25 @@ test('Env: jwt expiration in seconds format', async () => { process.env.JWT_EXPIRATION_PERIOD = '3600'; expect(getJwtExpirationPeriod()).toBe('1h'); }); + +describe('Healthz', () => { + vi.mock('../src/database'); + + test('healthz: returns 200', async () => { + prisma.user.findMany.mockResolvedValueOnce([]); + const url = `${ctx.baseUrl}/healthz`; + const res = await fetch(url); + expect(res.status).toBe(200); + const json: any = await res.json(); + expect(json.status).toBe('healthy'); + }); + + test('healthz: returns 500', async () => { + prisma.user.findMany.mockRejectedValueOnce(new Error('test')); + const url = `${ctx.baseUrl}/healthz`; + const res = await fetch(url); + expect(res.status).toBe(500); + const json: any = await res.json(); + expect(json.status).toBe('DB failed initialization check'); + }); +}); diff --git a/api/tests/helpers/server.ts b/api/tests/helpers/server.ts index 52ceee99..58408d1e 100644 --- a/api/tests/helpers/server.ts +++ b/api/tests/helpers/server.ts @@ -6,10 +6,12 @@ import { createApp } from '../../src/app'; interface TestContext { client: GraphQLClient; + baseUrl: string; } function getGraphqlTestContext() { let serverInstance: Server | null = null; + let baseUrl: string | null = null; return { async before() { const app = createApp(); @@ -19,10 +21,12 @@ function getGraphqlTestContext() { throw new Error('Unexpected server address format'); } const { port } = serverAddress; - return new GraphQLClient(`http://0.0.0.0:${port}/graphql`); + baseUrl = `http://0.0.0.0:${port}`; + return { client: new GraphQLClient(`${baseUrl}/graphql`), baseUrl }; }, async after() { serverInstance?.close(); + baseUrl = null; }, }; } @@ -31,8 +35,9 @@ function createTestContext(): TestContext { const context = {} as TestContext; const graphqlTestContext = getGraphqlTestContext(); beforeEach(async () => { - const client = await graphqlTestContext.before(); + const { client, baseUrl } = await graphqlTestContext.before(); context.client = client; + context.baseUrl = baseUrl; }); afterEach(async () => { await graphqlTestContext.after(); From 29711e2bcd028e2ea15f019e1640b2ddf7f8fe65 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Thu, 30 Mar 2023 18:30:32 +0200 Subject: [PATCH 6/8] lint --- frontend/server/api/healthz.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/server/api/healthz.ts b/frontend/server/api/healthz.ts index dc15e566..d1de2977 100644 --- a/frontend/server/api/healthz.ts +++ b/frontend/server/api/healthz.ts @@ -1,9 +1,9 @@ const startupTime = new Date() -export default eventHandler(async () => { +export default eventHandler(() => { return { status: 'healthy', time: new Date(), - startupTime, + startupTime } }) From b9db6069ed976827d913ab14870f46e907e24381 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:03:18 +0200 Subject: [PATCH 7/8] nits --- api/package-lock.json | 139 ------------------------------------- api/package.json | 1 - api/src/app.ts | 3 +- api/tests/auxilary.test.ts | 5 +- 4 files changed, 4 insertions(+), 144 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 78ed501b..a50099b1 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -46,7 +46,6 @@ "eslint-config-airbnb-typescript": "^17.0.0", "gql-query-builder": "^3.8.0", "graphql-request": "^5.2.0", - "node-fetch": "^3.3.1", "prisma": "^4.11.0", "typescript": "^4.9.5", "vitest-mock-extended": "^1.1.3" @@ -2619,15 +2618,6 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/dataloader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", @@ -3503,29 +3493,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3637,18 +3604,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5071,43 +5026,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -6746,15 +6664,6 @@ "vitest": ">=0.29.2" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -8723,12 +8632,6 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, - "data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true - }, "dataloader": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.1.0.tgz", @@ -9430,16 +9333,6 @@ "reusify": "^1.0.4" } }, - "fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "requires": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - } - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -9532,15 +9425,6 @@ "mime-types": "^2.1.12" } }, - "formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "requires": { - "fetch-blob": "^3.1.2" - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10569,23 +10453,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, - "node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true - }, - "node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", - "dev": true, - "requires": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - } - }, "node-releases": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", @@ -11723,12 +11590,6 @@ "ts-essentials": "^9.3.1" } }, - "web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "dev": true - }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/api/package.json b/api/package.json index d95ebf88..d8ceac88 100644 --- a/api/package.json +++ b/api/package.json @@ -52,7 +52,6 @@ "eslint-config-airbnb-typescript": "^17.0.0", "gql-query-builder": "^3.8.0", "graphql-request": "^5.2.0", - "node-fetch": "^3.3.1", "prisma": "^4.11.0", "typescript": "^4.9.5", "vitest-mock-extended": "^1.1.3" diff --git a/api/src/app.ts b/api/src/app.ts index cd80cc02..b7aa47df 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -14,7 +14,7 @@ export const createApp = (): Express => { app.get('/healthz', async (_req, res) => { try { // TODO: after migration to postgres, do SELECT 1 - await prisma.user.findMany(); + await prisma.user.findFirst(); } catch (error) { return res.status(500).json({ status: 'DB failed initialization check', @@ -28,6 +28,7 @@ export const createApp = (): Express => { startupTime, }); }); + app.get( '/', expressPlayground({ diff --git a/api/tests/auxilary.test.ts b/api/tests/auxilary.test.ts index 89d9cbc6..6306894b 100644 --- a/api/tests/auxilary.test.ts +++ b/api/tests/auxilary.test.ts @@ -1,7 +1,6 @@ import { test, expect, vi, describe, } from 'vitest'; -import fetch from 'node-fetch'; import { getJwtSecret, getJwtExpirationPeriod } from '../src/env/getters'; import { restoreEnvAfterEach } from './helpers/env'; import { ctx } from './helpers/server'; @@ -44,7 +43,7 @@ describe('Healthz', () => { vi.mock('../src/database'); test('healthz: returns 200', async () => { - prisma.user.findMany.mockResolvedValueOnce([]); + prisma.user.findFirst.mockResolvedValueOnce({'id': '1', 'username': 'asdf', 'password': 'asdf'}); const url = `${ctx.baseUrl}/healthz`; const res = await fetch(url); expect(res.status).toBe(200); @@ -53,7 +52,7 @@ describe('Healthz', () => { }); test('healthz: returns 500', async () => { - prisma.user.findMany.mockRejectedValueOnce(new Error('test')); + prisma.user.findFirst.mockRejectedValueOnce(new Error('test')); const url = `${ctx.baseUrl}/healthz`; const res = await fetch(url); expect(res.status).toBe(500); From 2e778991b56dce218867e400962a087f9692d9d0 Mon Sep 17 00:00:00 2001 From: KirillDogadin-std <59374892+KirillDogadin-std@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:07:37 +0200 Subject: [PATCH 8/8] lint --- api/tests/auxilary.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/auxilary.test.ts b/api/tests/auxilary.test.ts index 6306894b..360f5e99 100644 --- a/api/tests/auxilary.test.ts +++ b/api/tests/auxilary.test.ts @@ -43,7 +43,7 @@ describe('Healthz', () => { vi.mock('../src/database'); test('healthz: returns 200', async () => { - prisma.user.findFirst.mockResolvedValueOnce({'id': '1', 'username': 'asdf', 'password': 'asdf'}); + prisma.user.findFirst.mockResolvedValueOnce({ id: '1', username: 'asdf', password: 'asdf' }); const url = `${ctx.baseUrl}/healthz`; const res = await fetch(url); expect(res.status).toBe(200);