diff --git a/.github/workflows/cd_dev.yaml b/.github/workflows/cd_dev.yaml index a46d4e8..7f696b5 100644 --- a/.github/workflows/cd_dev.yaml +++ b/.github/workflows/cd_dev.yaml @@ -17,22 +17,24 @@ jobs: message: Merge main into this branch to deploy to dev for testing. test: needs: merge-branch + strategy: + matrix: + machines: + - vlcdhp02 runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Create .env from secrets uses: SpicyPizza/create-envfile@v2 with: - envkey_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} - envkey_REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} - envkey_RERUM_REGISTRATION_URL: ${{ secrets.RERUM_REGISTRATION_URL }} - envkey_RERUM_API_ADDR: ${{ secrets.RERUM_API_ADDR }} - envkey_RERUM_ID_PATTERN: ${{ secrets.RERUM_ID_PATTERN }} - envkey_RERUM_ACCESS_TOKEN_URL: ${{ secrets.RERUM_ACCESS_TOKEN_URL }} + envkey_RERUM_REGISTRATION_URL: https://devstore.rerum.io/v1 + envkey_RERUM_API_ADDR: https://devstore.rerum.io/v1/api/ + envkey_RERUM_ID_PATTERN: https://devstore.rerum.io/v1/id/ + envkey_RERUM_ACCESS_TOKEN_URL: https://devstore.rerum.io/client/request-new-access-token - name: Setup Node.js uses: actions/setup-node@master with: - node-version: "21" + node-version: "20" - name: Cache node modules uses: actions/cache@master env: @@ -48,7 +50,7 @@ jobs: - name: Install dependencies and run the test run: | npm install - npm run runtest + npm run functionalTests deploy: if: github.event.pull_request.draft == false needs: @@ -56,6 +58,8 @@ jobs: - test strategy: matrix: + node-version: + - 20 machines: - vlcdhp02 runs-on: ${{ matrix.machines }} @@ -70,6 +74,7 @@ jobs: cd /srv/node/tiny-node/ pm2 stop tinyNode git stash + git checkout ${{ github.head_ref }} git pull npm install pm2 start -i max bin/tinyNode.js diff --git a/.github/workflows/cd_prod.yaml b/.github/workflows/cd_prod.yaml index 3c73cc6..09ce21d 100644 --- a/.github/workflows/cd_prod.yaml +++ b/.github/workflows/cd_prod.yaml @@ -4,38 +4,21 @@ on: branches: main jobs: test: - runs-on: ubuntu-latest + strategy: + matrix: + machines: + - vlcdhprdp02 + runs-on: ${{ matrix.machines }} steps: - uses: actions/checkout@master - - name: Create .env from secrets - uses: SpicyPizza/create-envfile@v2 - with: - envkey_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} - envkey_REFRESH_TOKEN: ${{ secrets.REFRESH_TOKEN }} - envkey_RERUM_REGISTRATION_URL: ${{ secrets.RERUM_REGISTRATION_URL }} - envkey_RERUM_API_ADDR: ${{ secrets.RERUM_API_ADDR }} - envkey_RERUM_ID_PATTERN: ${{ secrets.RERUM_ID_PATTERN }} - envkey_RERUM_ACCESS_TOKEN_URL: ${{ secrets.RERUM_ACCESS_TOKEN_URL }} - - name: Setup Node.js - uses: actions/setup-node@master - with: - node-version: "21" - - name: Cache node modules - uses: actions/cache@master - env: - cache-name: cache-node-modules - with: - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ - hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Install dependencies and run the test + - name: Test the app on the server run: | + cd /srv/node/tiny-node/ + git stash + git checkout main + git pull npm install - npm run runtest + npm run allTests deploy: needs: - test @@ -55,6 +38,7 @@ jobs: cd /srv/node/tiny-node/ pm2 stop tinyNode git stash + git checkout main git pull npm install pm2 start -i max bin/tinyNode.js diff --git a/__tests__/exist.test.js b/__tests__/exist.test.js deleted file mode 100644 index 76048ca..0000000 --- a/__tests__/exist.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import request from "supertest" -//Fun fact, if you don't require app, you don't get coverage even though the tests run just fine. -import app from "../app.js" - -it('/ -- Make sure index exists', function(done) { - request(app) - .get("/index.html") - .expect(200) - .then(response => { - done() - }) - .catch(err => done(err)) -}) diff --git a/app.js b/app.js index 1486988..c8bdb7d 100644 --- a/app.js +++ b/app.js @@ -13,10 +13,10 @@ import updateRouter from "./routes/update.js" import deleteRouter from "./routes/delete.js" import overwriteRouter from "./routes/overwrite.js" import cors from "cors" -import {updateExpiredToken } from "./tokens.js" +import { updateExpiredToken } from "./tokens.js" // Check for and update token on app start -updateExpiredToken () +//updateExpiredToken() let app = express() diff --git a/bin/testApp.js b/bin/testApp.js deleted file mode 100644 index 9d9ce3a..0000000 --- a/bin/testApp.js +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env node - -/** - * @author thehabes - * */ - -/** - * Module dependencies. - */ -import dotenv from "dotenv" -import dotenvExpand from "dotenv-expand" -const storedEnv = dotenv.config() -dotenvExpand.expand(storedEnv) - -import jest from "jest" -import runCLI from "jest-cli" -import app from "../app.js" -import http from "http" - - -/** - * Get port from environment and store in Express. - */ - -const port = normalizePort('3333') -app.set('port', port) - -/** - * Create HTTP server. - */ - -const server = http.createServer(app) - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(port) -server.on('error', onError) -server.on('listening', onListening) - -/** - * Control the keep alive header - */ -// Ensure all inactive connections are terminated by the ALB, by setting this a few seconds higher than the ALB idle timeout -server.keepAliveTimeout = 8 * 1000 //8 seconds -// Ensure the headersTimeout is set higher than the keepAliveTimeout due to this nodejs regression bug: https://github.com/nodejs/node/issues/27363 -server.headersTimeout = 8.5 * 1000 //8 seconds - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort(val) { - const portCheck = parseInt(val, 10) - - if (isNaN(portCheck)) { - // named pipe - return val - } - - if (portCheck >= 0) { - // port number - return portCheck - } - - return false -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error - } - - const bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges') - process.exit(1) - break - case 'EADDRINUSE': - console.error(bind + ' is already in use') - process.exit(1) - break - default: - throw error - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -async function onListening() { - console.log("LISTENING ON "+port) - jest.runCLI( - { - "colors" : "true" - }, - ["jest.config.js"]) - .then(({ results }) => { - if (results.success) { - console.log('Tests completed') - process.exit(0) - } - else { - console.error('Tests failed') - process.exit(1) - } - }) -} - diff --git a/jest.config.js b/jest.config.js index 3286333..130df2f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -188,7 +188,7 @@ const config = { // timers: "real", // A map from regular expressions to paths to transformers - // transform: undefined, + transform: {}, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation // transformIgnorePatterns: [ diff --git a/package-lock.json b/package-lock.json index dbaf4f1..ba3e515 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1431,9 +1431,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -1443,7 +1443,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -1511,12 +1511,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1578,12 +1578,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1794,6 +1800,14 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1891,6 +1905,22 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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", @@ -1991,9 +2021,9 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -2021,6 +2051,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -2134,36 +2183,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -2174,14 +2223,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/express/node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2292,9 +2333,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2304,12 +2345,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -2429,9 +2470,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -2452,13 +2496,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2502,6 +2551,17 @@ "node": ">=4" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2512,6 +2572,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -2528,10 +2589,32 @@ "node": ">=8" } }, - "node_modules/has-symbols": { + "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -2539,6 +2622,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -3642,9 +3736,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -3661,12 +3758,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3835,9 +3932,12 @@ } }, "node_modules/object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3996,9 +4096,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/picocolors": { "version": "1.0.0", @@ -4107,11 +4207,11 @@ ] }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -4271,9 +4371,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -4301,6 +4401,14 @@ "node": ">= 0.8" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4346,19 +4454,35 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -4391,13 +4515,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6057,9 +6185,9 @@ } }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -6069,7 +6197,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6123,12 +6251,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -6164,12 +6292,15 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -6308,6 +6439,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -6380,6 +6516,16 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6452,9 +6598,9 @@ "dev": true }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "envfile": { "version": "7.1.0", @@ -6470,6 +6616,19 @@ "is-arrayish": "^0.2.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6548,36 +6707,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6585,11 +6744,6 @@ "vary": "~1.1.2" }, "dependencies": { - "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -6663,21 +6817,21 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -6765,9 +6919,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "gensync": { "version": "1.0.0-beta.2", @@ -6782,13 +6936,15 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-package-type": { @@ -6817,6 +6973,14 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -6827,6 +6991,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -6837,10 +7002,31 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { + "has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } }, "hexoid": { "version": "1.0.0", @@ -7673,9 +7859,9 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -7689,12 +7875,12 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -7807,9 +7993,9 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "on-finished": { "version": "2.3.0", @@ -7919,9 +8105,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "picocolors": { "version": "1.0.0", @@ -7995,11 +8181,11 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "range-parser": { @@ -8116,9 +8302,9 @@ "dev": true }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -8140,6 +8326,11 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -8178,14 +8369,27 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" } }, "setimmediate": { @@ -8214,13 +8418,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { diff --git a/package.json b/package.json index 0240333..26f3b31 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,11 @@ }, "scripts": { "start": "node ./bin/tinyNode.js", - "test": "jest", - "runtest": "node --experimental-vm-modules ./bin/testApp.js" + "allTests": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "E2Etests": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t __e2e ", + "existsTests": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t __exists ", + "coreTests": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t __core ", + "functionalTests": "node --experimental-vm-modules node_modules/jest/bin/jest.js -t __mock_functions " }, "dependencies": { "cors": "^2.8.5", diff --git a/routes/__later__/api.later.js b/routes/__later__/api.later.js deleted file mode 100644 index 6a3542a..0000000 --- a/routes/__later__/api.later.js +++ /dev/null @@ -1,16 +0,0 @@ -import request from "supertest" -import app from "../../app.js" - -describe("Test /create", () => { - // TODO: This needs to be not creating new objects all the time. - test("New Object saved", async () => { - const response = await request(app) - .post("/create") - .send({test:"item"}) - - expect(response.header).toHaveProperty('location') - expect(response.header).toHaveProperty('etag') - expect(response.header).toHaveProperty('keep-alive') - expect(response.statusCode).toBe(201) - }) -}) diff --git a/routes/__tests__/create.test.js b/routes/__tests__/create.test.js new file mode 100644 index 0000000..9b21d83 --- /dev/null +++ b/routes/__tests__/create.test.js @@ -0,0 +1,126 @@ +import express from "express" +import request from "supertest" +import { jest } from "@jest/globals" + +import createRoute from "../create.js" +//import app from "../../app.js" + +const routeTester = new express() +routeTester.use(express.json()) +routeTester.use(express.urlencoded({ extended: false })) +routeTester.use("/create", createRoute) +routeTester.use("/app/create", createRoute) + +const rerum_uri = `${process.env.RERUM_ID_PATTERN}_not_` + +beforeEach(() => { + /** + * Request/Response Mock Using manual fetch replacement + * This is overruling the fetch(store.rerum.io/v1/api/create) call in create.js + */ + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ "@id": rerum_uri, "test": "item", "__rerum": { "stuff": "here" } }) + }) + ) +}) + +afterEach(() => { + /** + * Food for thought: delete data generated by tests? + * Make a test.store available that uses the same annotationStoreTesting as RERUM tests? + */ +}) + +/** + * This test suite runs the logic of the route file 'create.js' but does not actually communicate with RERUM. + * It will confirm the following: + * - Is the express req/resp sent into the route + * - Can the route read the JSON body + * - Does the route add @id and __rerum + * - Does the route respond 201 + * - Does the route respond with the object that was in the request body + * - Does the route respond with the proper 'Location' header + * + * Note: /app/create uses the same logic and would be a redundant test. + */ +describe("Check that the request/response behavior of the TinyNode create route functions. Mock the connection to RERUM. __mock_functions", () => { + it("'/create' route request and response behavior is functioning.", async () => { + + const response = await request(routeTester) + .post("/create") + .send({ "test": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.header.location).toBe(rerum_uri) + expect(response.statusCode).toBe(201) + expect(response.body.test).toBe("item") + }) +}) + +/** + * This test suite checks the RESTful responses when using the TinyNode create endpoint incorrectly. + * + * - Incorrect HTTP method + * - Invalid JSON body + * + * Note: /app/create uses the same logic and would be a redundant test + */ +describe("Check that incorrect TinyNode create route usage results in expected RESTful responses from RERUM. __rest __core", () => { + it("Incorrect '/create' route usage has expected RESTful responses.", async () => { + let response = null + + response = await request(routeTester) + .get("/create") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .put("/create") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .patch("/create") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .delete("/create") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .post("/create") + .set("Content-Type", "application/json") + .send("not json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + }) +}) + +/** + * Full integration test. Checks the TinyNode app create endpoint functionality and RERUM connection. + * + * Note: /app/create uses the same logic and would be a redundant test + */ +describe("Check that the properly used create endpoints function and interact with RERUM. __e2e", () => { + it("'/create' route can save an object to RERUM.", async () => { + const response = await request(routeTester) + .post("/create") + .send({ "test": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.header).toHaveProperty('location') + expect(response.statusCode).toBe(201) + expect(response.body.test).toBe("item") + }) +}) \ No newline at end of file diff --git a/routes/__tests__/delete.test.js b/routes/__tests__/delete.test.js new file mode 100644 index 0000000..eeb6e11 --- /dev/null +++ b/routes/__tests__/delete.test.js @@ -0,0 +1,112 @@ +import express from "express" +import request from "supertest" +import { jest } from "@jest/globals" +import deleteRoute from "../delete.js" +//import app from "../../app.js" + +const routeTester = new express() +routeTester.use(express.json()) +routeTester.use(express.urlencoded({ extended: false })) +routeTester.use("/delete", deleteRoute) +routeTester.use("/app/delete", deleteRoute) + +const rerum_uri = `${process.env.RERUM_ID_PATTERN}_not_` + +beforeEach(() => { + /** + * Request/Response Mock Using manual fetch replacement + * This is overruling the fetch(store.rerum.io/v1/api/delete) call in delete.js + */ + global.fetch = jest.fn(() => + Promise.resolve({ + text: () => Promise.resolve("") + }) + ) +}) + +/** + * This test suite runs the logic of the route file 'delete.js' but does not actually communicate with RERUM. + * It will confirm the following: + * - Is the express req/resp sent into the route + * - Can the route read the JSON body + * - Does the route respond 204 + * + * Note: /app/delete uses the same logic and would be a redundant test. + */ +describe("Check that the request/response behavior of the TinyNode delete route functions. Mock the connection to RERUM. __mock_functions", () => { + it("'/delete' route request and response behavior is functioning.", async () => { + let response = null + + response = await request(routeTester) + .delete("/delete") + .send({ "@id": rerum_uri, "test": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(204) + + response = await request(routeTester) + .delete("/delete/00000") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(204) + }) +}) + +describe("Check that incorrect TinyNode delete route usage results in expected RESTful responses from RERUM. __rest __core", () => { + it("Incorrect '/delete' route usage has expected RESTful responses.", async () => { + let response = null + + response = await request(routeTester) + .get("/delete") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .put("/delete") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .patch("/delete") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .post("/delete") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + //Bad request body + //FIXME to uncomment: https://github.com/CenterForDigitalHumanities/TinyNode/issues/89 + // response = await request(routeTester) + // .delete("/delete") + // .set("Content-Type", "application/json") + // .then(resp => resp) + // .catch(err => err) + // expect(response.statusCode).toBe(400) + + }) +}) + +/** + * TODO - skipped for now. + * Full integration test. Checks the TinyNode app delete endpoint functionality and RERUM connection. + * + * Note: /app/delete uses the same logic and would be a redundant test. + */ +describe.skip("Check that the properly used delete endpoints function and interact with RERUM. __e2e", () => { + it("'/delete' route can delete an object in RERUM. __e2e", async () => { + const response = await request(routeTester) + .delete("/app/delete/00000") + .then(response => { + expect(response.statusCode).toBe(204) + }) + .catch(err => err) + expect(response.statusCode).toBe(204) + }) +}) \ No newline at end of file diff --git a/routes/__tests__/mount.test.js b/routes/__tests__/mount.test.js new file mode 100644 index 0000000..ccb043b --- /dev/null +++ b/routes/__tests__/mount.test.js @@ -0,0 +1,154 @@ +import request from "supertest" +import { jest } from "@jest/globals" +import app from "../../app.js" + +beforeEach(() => { + // This comes from tokens.js in the app.js import. This apps tries to read env.ACCESS_TOKEN to refresh expired tokens. + // We don't care whether or not the token is expired here, so let's just state we don't care about tokens in tests. + updateExpiredToken = jest.fn(() => true) + isTokenExpired = jest.fn(() => false) +}) + +afterEach(() => { + /** + * Food for thought: delete data generated by tests? + * Make a test.store available that uses the same annotationStoreTesting as RERUM tests? + */ +}) + +describe("Make sure TinyNode demo interface is present. __core", () => { + it("/index.html", async () => { + const response = await request(app) + .get("/index.html") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(200) + expect(response.header["content-type"]).toMatch(/html/) + }) +}) + +/** + * This test suite uses the built app.js app and checks that the expected create endpoints are registered. + * - /create + * - /app/create + */ +describe("Check that the expected TinyNode create route patterns are registered.", () => { + it("'/app/create' and '/create' are registered routes in the app. __exists __core", () => { + let exists = false + let count = 0 + const stack = app._router.stack + for (const middleware of stack) { + if (middleware.regexp && middleware.regexp.toString().includes("/app/create")) { + count++ + } else if (middleware.regexp && middleware.regexp.toString().includes("/create")) { + count++ + } + if (count === 2) { + exists = true + break + } + } + expect(exists).toBe(true) + }) +}) + +/** + * This test suite uses the built app.js app and checks that the expected query endpoints are registered. + * - /query + * - /app/query + */ +describe("Check that the expected TinyNode query route patterns are registered.", () => { + it("'/app/query' and '/query' are registered routes in the app. __exists __core", () => { + let exists = false + let count = 0 + const stack = app._router.stack + for (const middleware of stack) { + if (middleware.regexp && middleware.regexp.toString().includes("/app/query")) { + count++ + } else if (middleware.regexp && middleware.regexp.toString().includes("/query")) { + count++ + } + if (count === 2) { + exists = true + break + } + } + expect(exists).toBe(true) + }) +}) + +/** + * This test suite uses the built app.js app and checks that the expected update endpoints are registered. + * - /update + * - /app/update + */ +describe("Check that the expected TinyNode update route patterns are registered.", () => { + it("'/app/update' and '/update' are registered routes in the app. __exists __core", () => { + let exists = false + let count = 0 + const stack = app._router.stack + for (const middleware of stack) { + if (middleware.regexp && middleware.regexp.toString().includes("/app/update")) { + count++ + } else if (middleware.regexp && middleware.regexp.toString().includes("/update")) { + count++ + } + if (count === 2) { + exists = true + break + } + } + expect(exists).toBe(true) + }) +}) + + +/** + * This test suite uses the built app.js app and checks that the expected overwrite endpoints are registered. + * - /overwrite + * - /app/overwrite + */ +describe("Check that the expected TinyNode overwrite route patterns are registered.", () => { + it("'/app/overwrite' and '/overwrite' are registered routes in the app. __exists __core", () => { + let exists = false + let count = 0 + const stack = app._router.stack + for (const middleware of stack) { + if (middleware.regexp && middleware.regexp.toString().includes("/app/overwrite")) { + count++ + } else if (middleware.regexp && middleware.regexp.toString().includes("/overwrite")) { + count++ + } + if (count === 2) { + exists = true + break + } + } + expect(exists).toBe(true) + }) +}) + +/** + * This test suite uses the built app.js app and checks that the expected delete endpoints are registered. + * - /delete + * - /app/delete + */ +describe("Combined unit tests for the '/delete' route.", () => { + it("'/app/delete' and '/delete' are registered routes in the app. __exists __core", () => { + let exists = false + let count = 0 + const stack = app._router.stack + for (const middleware of stack) { + if (middleware.regexp && middleware.regexp.toString().includes("/app/delete")) { + count++ + } else if (middleware.regexp && middleware.regexp.toString().includes("/delete")) { + count++ + } + if (count === 2) { + exists = true + break + } + } + expect(exists).toBe(true) + }) +}) diff --git a/routes/__tests__/overwrite.test.js b/routes/__tests__/overwrite.test.js new file mode 100644 index 0000000..4caf484 --- /dev/null +++ b/routes/__tests__/overwrite.test.js @@ -0,0 +1,134 @@ +import express from "express" +import request from "supertest" +import { jest } from "@jest/globals" +import overwriteRoute from "../overwrite.js" +//import app from "../../app.js" + +const routeTester = new express() +routeTester.use(express.json()) +routeTester.use(express.urlencoded({ extended: false })) +routeTester.use("/overwrite", overwriteRoute) +routeTester.use("/app/overwrite", overwriteRoute) + +const rerum_tiny_test_obj_id = `${process.env.RERUM_ID_PATTERN}tiny_tester` + +beforeEach(() => { + /** + * Request/Response Mock Using manual fetch replacement + * This is overruling the fetch(store.rerum.io/v1/api/create) call in create.js + */ + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ "@id": rerum_tiny_test_obj_id, "testing": "item", "__rerum": { "stuff": "here" } }) + }) + ) +}) + +afterEach(() => { + /** + * Food for thought: delete data generated by tests? + * Make a test.store available that uses the same annotationStoreTesting as RERUM tests? + */ +}) + +/** + * This test suite runs the logic of the route file 'overwrite.js' but does not actually communicate with RERUM. + * It will confirm the following: + * - Is the express req/resp sent into the route + * - Can the route read the JSON body + * - Does the route add @id and __rerum + * - Does the route respond 200 + * - Does the route respond with the object that was in the request body + * - Does the route respond with the proper 'Location' header + * + * Note: /app/overwrite uses the same logic and would be a redundant test. + */ +describe("Check that the request/response behavior of the TinyNode overwrite route functions. Mock the connection to RERUM. __mock_functions", () => { + it("'/overwrite' route request and response behavior is functioning.", async () => { + const response = await request(routeTester) + .put("/overwrite") + .send({ "@id": rerum_tiny_test_obj_id, "testing": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.header.location).toBe(rerum_tiny_test_obj_id) + expect(response.statusCode).toBe(200) + expect(response.body.testing).toBe("item") + }) +}) + +/** + * This test suite checks the RESTful responses when using the TinyNode overwrite endpoint incorrectly. + * + * - Incorrect HTTP method + * - Invalid JSON body + * + * Note: /app/overwrite uses the same logic and would be a redundant test. + */ +describe("Check that incorrect TinyNode overwrite route usage results in expected RESTful responses from RERUM. __rest __core", () => { + it("Incorrect '/overwrite' route usage has expected RESTful responses.", async () => { + let response = null + + response = await request(routeTester) + .get("/overwrite") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .post("/overwrite") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .patch("/overwrite") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .delete("/overwrite") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + //Bad request body + response = await request(routeTester) + .put("/overwrite") + .set("Content-Type", "application/json") + .send("not json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + response = await request(routeTester) + .put("/overwrite") + .set("Content-Type", "application/json") + .send({ "no": "@id" }) + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + }) +}) + +/** + * Full integration test. Checks the TinyNode app overwrite endpoint functionality and RERUM connection. + * + * Note: /app/update uses the same logic. + */ +describe("Check that the properly used overwrite endpoints function and interact with RERUM. __e2e", () => { + it("'/overwrite' route can overwrite an object in RERUM.", async () => { + const response = await request(routeTester) + .put("/overwrite") + .send({ "@id": rerum_tiny_test_obj_id, "testing": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.header).toHaveProperty("location") + expect(response.header.location).toBe(rerum_tiny_test_obj_id) + expect(response.statusCode).toBe(200) + expect(response.body.testing).toBe("item") + }) +}) \ No newline at end of file diff --git a/routes/__tests__/query.test.js b/routes/__tests__/query.test.js new file mode 100644 index 0000000..c7caed8 --- /dev/null +++ b/routes/__tests__/query.test.js @@ -0,0 +1,115 @@ +import express from "express" +import request from "supertest" +import { jest } from "@jest/globals" +import queryRoute from "../query.js" +//import app from "../../app.js" + +const routeTester = new express() +routeTester.use(express.json()) +routeTester.use(express.urlencoded({ extended: false })) +routeTester.use("/query", queryRoute) +routeTester.use("/app/query", queryRoute) + +const rerum_uri = `${process.env.RERUM_ID_PATTERN}_not_` + +beforeEach(() => { + /** + * Request/Response Mock Using manual fetch replacement + * This is overruling the fetch(store.rerum.io/v1/api/query) call in query.js + */ + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve([{ "@id": rerum_uri, "test": "item", "__rerum": { "stuff": "here" } }]) + }) + ) +}) + +/** + * This test suite runs the logic of the route file 'query.js' but does not actually communicate with RERUM. + * It will confirm the following: + * - Is the express req/resp sent into the route + * - Can the route read the JSON body + * - Does the route add @id and __rerum + * - Does the route respond 201 + * - Does the route respond with the object that was in the request body + * - Does the route respond with the proper 'Location' header + * + * Note: /app/query uses the same logic and would be a redundant test. + */ +describe("Check that the request/response behavior of the TinyNode query route functions. Mock the connection to RERUM. __mock_functions", () => { + it("'/query' route request and response behavior is functioning.", async () => { + const response = await request(routeTester) + .post("/query") + .send({ "test": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(200) + expect(response.body[0].test).toBe("item") + }) +}) + +/** + * This test suite checks the RESTful responses when using the TinyNode query endpoint incorrectly. + * + * - Incorrect HTTP method + * - Invalid JSON body + * + * Note: /app/query uses the same logic and would be a redundant test. + */ +describe("Check that incorrect TinyNode query route usage results in expected RESTful responses from RERUM. __rest __core", () => { + it("Incorrect '/query' route usage has expected RESTful responses.", async () => { + let response = null + + response = await request(routeTester) + .get("/query") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .put("/query") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .patch("/query") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .delete("/query") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .post("/query") + .set("Content-Type", "application/json") + .send("not json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + }) +}) + +/** + * Full integration test. Checks the TinyNode app query endpoint functionality and RERUM connection. + * + * Note: /app/query uses the same logic and would be a redundant test. + */ +describe("Check that the properly used query endpoints function and interact with RERUM. __e2e", () => { + it("'/query' route can save an object to RERUM.", async () => { + const response = await request(routeTester) + .post("/query") + .send({ "test": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(200) + expect(response.body[0].test).toBe("item") + }) +}) \ No newline at end of file diff --git a/routes/__tests__/update.test.js b/routes/__tests__/update.test.js new file mode 100644 index 0000000..c9357a6 --- /dev/null +++ b/routes/__tests__/update.test.js @@ -0,0 +1,137 @@ +import express from "express" +import request from "supertest" +import { jest } from "@jest/globals" +import updateRoute from "../update.js" +//import app from "../../app.js" + +const routeTester = new express() +routeTester.use(express.json()) +routeTester.use(express.urlencoded({ extended: false })) +routeTester.use("/update", updateRoute) +routeTester.use("/app/update", updateRoute) + +const rerum_uri_orig = `${process.env.RERUM_ID_PATTERN}_not_` +const rerum_uri_updated = `${process.env.RERUM_ID_PATTERN}_updated_` +const rerum_tiny_test_obj_id = `${process.env.RERUM_ID_PATTERN}tiny_tester` + +beforeEach(() => { + /** + * Request/Response Mock Using manual fetch replacement + * This is overruling the fetch(store.rerum.io/v1/api/create) call in update.js + */ + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ "@id": rerum_uri_updated, "testing": "item", "__rerum": { "stuff": "here" } }) + }) + ) +}) + +afterEach(() => { + /** + * Food for thought: delete data generated by tests? + * Make a test.store available that uses the same annotationStoreTesting as RERUM tests? + */ +}) + +/** + * This test suite runs the logic of the route file 'update.js' but does not actually communicate with RERUM. + * It will confirm the following: + * - Is the express req/resp sent into the route + * - Can the route read the JSON body + * - Does the route add @id and __rerum + * - Does the route respond 200 + * - Does the route respond with the object that was in the request body + * - Does the route respond with the proper 'Location' header + * + * Note: /app/update uses the same logic and would be a redundant test. + */ +describe("Check that the request/response behavior of the TinyNode update route functions. Mock the connection to RERUM. __mock_functions", () => { + it("'/update' route request and response behavior is functioning.", async () => { + const response = await request(routeTester) + .put("/update") + .send({ "@id": rerum_uri_orig, "testing": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + //FIXME to uncomment these: https://github.com/CenterForDigitalHumanities/TinyNode/issues/88 + expect(response.header.location).toBe(rerum_uri_updated) + expect(response.statusCode).toBe(200) + expect(response.body.testing).toBe("item") + }) +}) + +/** + * This test suite checks the RESTful responses when using the TinyNode update endpoint incorrectly. + * + * - Incorrect HTTP method + * - Invalid JSON body + * + * Note: /app/update uses the same logic and would be a redundant test. + */ +describe("Check that incorrect TinyNode update route usage results in expected RESTful responses from RERUM. __rest __core", () => { + it("Incorrect '/update' route usage has expected RESTful responses.", async () => { + let response = null + + response = await request(routeTester) + .get("/update") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .post("/update") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .patch("/update") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + response = await request(routeTester) + .delete("/update") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(405) + + //Bad request body + response = await request(routeTester) + .put("/update") + .set("Content-Type", "application/json") + .send("not json") + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + response = await request(routeTester) + .put("/update") + .set("Content-Type", "application/json") + .send({ "no": "@id" }) + .then(resp => resp) + .catch(err => err) + expect(response.statusCode).toBe(400) + + }) +}) + +/** + * Full integration test. Checks the TinyNode app update endpoint functionality and RERUM connection. + * + * Note: /app/update uses the same logic and would be a redundant test. + */ +describe("Check that the properly used update endpoints function and interact with RERUM. __e2e", () => { + it("'/update' route can update an object in RERUM.", async () => { + const response = await request(routeTester) + .put("/update") + .send({ "@id": rerum_tiny_test_obj_id, "testing": "item" }) + .set("Content-Type", "application/json") + .then(resp => resp) + .catch(err => err) + expect(response.header).toHaveProperty("location") + expect(response.header.location).not.toBe(rerum_tiny_test_obj_id) + expect(response.statusCode).toBe(200) + expect(response.body.testing).toBe("item") + }) +}) \ No newline at end of file diff --git a/routes/create.js b/routes/create.js index 6c73aa7..29d327c 100644 --- a/routes/create.js +++ b/routes/create.js @@ -12,7 +12,7 @@ router.post('/', async (req, res, next) => { body, headers: { 'user-agent': 'Tiny-Things/1.0', - 'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`, // not required for query + 'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`, 'Content-Type' : "application/json;charset=utf-8" } } @@ -21,11 +21,10 @@ router.post('/', async (req, res, next) => { .catch(err=>next(err)) res.setHeader("Location", result["@id"]) res.status(201) - res.send(result) + res.json(result) } catch (err) { - console.log(err) - res.status(500).send("Caught Error:" + err) + next(err) } }) @@ -33,4 +32,4 @@ router.all('/', (req, res, next) => { res.status(405).send("Method Not Allowed") }) -export default router +export default router \ No newline at end of file diff --git a/routes/delete.js b/routes/delete.js index 68ac23d..dda7cde 100644 --- a/routes/delete.js +++ b/routes/delete.js @@ -6,6 +6,10 @@ const router = express.Router() /* DELETE a delete to the thing. */ router.delete('/', async (req, res, next) => { try { + // check for @id in body. Any value is valid. Lack of value is a bad request. + if (!req?.body || !(req.body['@id'] ?? req.body.id)) { + res.status(400).send("No record id to delete! (https://store.rerum.io/v1/API.html#delete)") + } const body = JSON.stringify(req.body) const deleteOptions = { body, @@ -16,15 +20,13 @@ router.delete('/', async (req, res, next) => { 'Content-Type' : "application/json; charset=utf-8" } } - console.log(body) const deleteURL = `${process.env.RERUM_API_ADDR}delete` const result = await fetch(deleteURL, deleteOptions).then(res => res.text()) res.status(204) res.send(result) } catch (err) { - console.log(err) - res.status(500).send("Caught Error:" + err) + next(err) } }) @@ -46,8 +48,7 @@ router.delete('/:id', async (req, res, next) => { res.send(result) } catch (err) { - console.log(err) - res.status(500).send("Caught Error:" + err) + next(err) } }) diff --git a/routes/overwrite.js b/routes/overwrite.js index f42617b..784be5d 100644 --- a/routes/overwrite.js +++ b/routes/overwrite.js @@ -6,14 +6,12 @@ import rerumPropertiesWasher from "../preprocessor.js" router.put('/', rerumPropertiesWasher, async (req, res, next) => { try { + // check for @id in body. Any value is valid. Lack of value is a bad request. + if (!req?.body || !(req.body['@id'] ?? req.body.id)) { + res.status(400).send("No record id to overwrite! (https://store.rerum.io/v1/API.html#overwrite)") + } // check body for JSON const body = JSON.stringify(req.body) - - // check for @id; any value is valid - if (!(req.body['@id'])) { - throw Error("No record id to overwrite! (https://centerfordigitalhumanities.github.io/rerum_server/API.html#overwrite)") - } - const overwriteOptions = { method: 'PUT', body, @@ -26,12 +24,12 @@ router.put('/', rerumPropertiesWasher, async (req, res, next) => { const overwriteURL = `${process.env.RERUM_API_ADDR}overwrite` const result = await fetch(overwriteURL, overwriteOptions).then(res=>res.json()) .catch(err=>next(err)) + res.setHeader("Location", result["@id"]) res.status(200) res.send(result) } catch (err) { - console.log(err) - res.status(500).send("Caught Error:" + err) + next(err) } }) diff --git a/routes/query.js b/routes/query.js index 6d392fa..a147cc3 100644 --- a/routes/query.js +++ b/routes/query.js @@ -31,8 +31,7 @@ router.post('/', async (req, res, next) => { res.send(results) } catch (err) { // a dumb catch-all for Tiny Stuff - console.log(err) - res.status(500).send("Caught " + err) + next(err) } }) diff --git a/routes/update.js b/routes/update.js index d56adf5..2d20dce 100644 --- a/routes/update.js +++ b/routes/update.js @@ -6,32 +6,30 @@ import rerumPropertiesWasher from "../preprocessor.js" router.put('/', rerumPropertiesWasher, async (req, res, next) => { try { + // check for @id in body. Any value is valid. Lack of value is a bad request. + if (!req?.body || !(req.body['@id'] ?? req.body.id)) { + res.status(400).send("No record id to update! (https://store.rerum.io/v1/API.html#update)") + } // check body for JSON const body = JSON.stringify(req.body) - - // check for @id; any value is valid - if (!(req.body['@id'])) { - throw Error("No record id to update! (https://centerfordigitalhumanities.github.io/rerum_server/API.html#update)") - } - const updateOptions = { method: 'PUT', body, headers: { 'user-agent': 'Tiny-Things/1.0', - 'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`, // not required for query + 'Authorization': `Bearer ${process.env.ACCESS_TOKEN}`, 'Content-Type' : "application/json;charset=utf-8" } } const updateURL = `${process.env.RERUM_API_ADDR}update` const result = await fetch(updateURL, updateOptions).then(res=>res.json()) .catch(err=>next(err)) + res.setHeader("Location", result["@id"]) res.status(200) res.send(result) } catch (err) { - console.log(err) - res.status(500).send("Caught Error:" + err) + next(err) } }) diff --git a/tokens.js b/tokens.js index 871c245..3d29730 100644 --- a/tokens.js +++ b/tokens.js @@ -44,7 +44,7 @@ async function generateNewAccessToken() { * This does not validate your access token, so you may still be rejected by * your RERUM instance as unauthorized. */ -function updateExpiredToken () { +function updateExpiredToken() { if (isTokenExpired(process.env.ACCESS_TOKEN)) { console.log("TinyNode detected an expired access token. Updating the token now.") generateNewAccessToken()