cleanup test structure
Some checks failed
Sonarqube Scanner / Build and analyze (push) Failing after 1m29s
Some checks failed
Sonarqube Scanner / Build and analyze (push) Failing after 1m29s
This commit is contained in:
parent
b9f3417aa8
commit
2442f8d458
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,5 @@ storage/trash/routes/*
|
||||
npm-debug.log*
|
||||
|
||||
coverage/
|
||||
|
||||
.gitea/workflows/
|
||||
699
package-lock.json
generated
699
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wegwichtel-next",
|
||||
"version": "0.12.3",
|
||||
"version": "0.12.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wegwichtel-next",
|
||||
"version": "0.12.3",
|
||||
"version": "0.12.5",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@file-type/av": "0.2.0",
|
||||
@ -17,10 +17,23 @@
|
||||
"jquery-ui": "1.14.2",
|
||||
"multer": "2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@bcoe/v8-coverage": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
|
||||
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@borewit/text-codec": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
|
||||
@ -40,6 +53,44 @@
|
||||
"music-metadata": "^11.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@istanbuljs/schema": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
|
||||
"integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodable/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
|
||||
@ -75,6 +126,13 @@
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/istanbul-lib-coverage": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||
@ -88,6 +146,32 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anynum": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz",
|
||||
@ -106,6 +190,16 @@
|
||||
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
|
||||
@ -143,6 +237,18 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
@ -169,6 +275,40 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/c8": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz",
|
||||
"integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@bcoe/v8-coverage": "^1.0.1",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
"find-up": "^5.0.0",
|
||||
"foreground-child": "^3.1.1",
|
||||
"istanbul-lib-coverage": "^3.2.0",
|
||||
"istanbul-lib-report": "^3.0.1",
|
||||
"istanbul-reports": "^3.1.6",
|
||||
"test-exclude": "^8.0.0",
|
||||
"v8-to-istanbul": "^9.0.0",
|
||||
"yargs": "^17.7.2",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"c8": "bin/c8.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"monocart-coverage-reports": "^2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"monocart-coverage-reports": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@ -198,6 +338,41 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concat-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
|
||||
@ -235,6 +410,13 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/convert-source-map": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
@ -253,6 +435,21 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
@ -299,6 +496,13 @@
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@ -337,6 +541,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@ -471,6 +685,39 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^6.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -498,6 +745,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@ -535,6 +792,24 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "13.0.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^10.2.2",
|
||||
"minipass": "^7.1.3",
|
||||
"path-scurry": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
@ -547,6 +822,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@ -571,6 +856,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
@ -642,6 +933,16 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
@ -660,6 +961,50 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
|
||||
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-report": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
|
||||
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"istanbul-lib-coverage": "^3.0.0",
|
||||
"make-dir": "^4.0.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-reports": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
|
||||
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"html-escaper": "^2.0.0",
|
||||
"istanbul-lib-report": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-4.0.0.tgz",
|
||||
@ -675,6 +1020,47 @@
|
||||
"jquery": ">=1.12.0 <5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "11.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
@ -730,6 +1116,31 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
|
||||
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@ -913,6 +1324,38 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
||||
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@ -922,6 +1365,16 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
|
||||
@ -937,6 +1390,33 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||
@ -1012,6 +1492,16 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
@ -1053,6 +1543,19 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
|
||||
"integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
|
||||
@ -1104,6 +1607,29 @@
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||
@ -1174,6 +1700,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
@ -1200,6 +1739,34 @@
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz",
|
||||
@ -1231,6 +1798,34 @@
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/test-exclude": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz",
|
||||
"integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@istanbuljs/schema": "^0.1.2",
|
||||
"glob": "^13.0.6",
|
||||
"minimatch": "^10.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
@ -1322,6 +1917,20 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.12",
|
||||
"@types/istanbul-lib-coverage": "^2.0.1",
|
||||
"convert-source-map": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@ -1331,12 +1940,46 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/win-guid": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz",
|
||||
"integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@ -1357,6 +2000,58 @@
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wegwichtel-next",
|
||||
"version": "0.12.3",
|
||||
"version": "0.12.5",
|
||||
"private": true,
|
||||
"description": "GPS-gestützte Lern- und Wanderwege mit GPX, POIs, Bildern und Audioansagen.",
|
||||
"type": "module",
|
||||
@ -15,13 +15,7 @@
|
||||
"init-db": "node --env-file-if-exists=.env scripts/init-db.js",
|
||||
"check": "node --check server.js && node --test",
|
||||
"test": "node --test",
|
||||
"test:coverage": "c8 --reporter=lcov --reports-dir=coverage/js node --test"
|
||||
},
|
||||
"c8": {
|
||||
"exclude": [
|
||||
"test/**",
|
||||
"test-support/**"
|
||||
]
|
||||
"test:coverage": "c8 --exclude='test/**' --exclude='test-support/**' --reporter=text-summary --reporter=lcov --reports-dir=coverage/js node --test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@file-type/av": "0.2.0",
|
||||
@ -31,5 +25,8 @@
|
||||
"jquery": "4.0.0",
|
||||
"jquery-ui": "1.14.2",
|
||||
"multer": "2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"c8": "11.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,12 +57,11 @@ export function getRouteGpxFile(db, id) {
|
||||
|
||||
export function createRoute(db, fields, gpxFile) {
|
||||
if (!gpxFile) throw new HttpError(400, 'Eine GPX-Datei ist erforderlich.');
|
||||
const parsed = parseGpx(fs.readFileSync(gpxFile.path, 'utf8'));
|
||||
const name = String(fields.name || parsed.name || '').trim();
|
||||
if (!name) throw new HttpError(400, 'Ein Streckenname ist erforderlich.');
|
||||
let routeId;
|
||||
let finalGpx;
|
||||
try {
|
||||
const parsed = parseGpx(fs.readFileSync(gpxFile.path, 'utf8'));
|
||||
const name = String(fields.name || parsed.name || '').trim();
|
||||
if (!name) throw new HttpError(400, 'Ein Streckenname ist erforderlich.');
|
||||
routeId = transaction(db, () => {
|
||||
let slug = slugify(fields.slug || name);
|
||||
let suffix = 2;
|
||||
@ -79,7 +78,7 @@ export function createRoute(db, fields, gpxFile) {
|
||||
return id;
|
||||
});
|
||||
ensureRouteDirectories(routeId);
|
||||
finalGpx = path.join(routeDirectory(routeId), 'route.gpx');
|
||||
const finalGpx = path.join(routeDirectory(routeId), 'route.gpx');
|
||||
const gpxPath = moveUploadedFile(gpxFile, finalGpx);
|
||||
db.prepare("UPDATE routes SET gpx_path = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(gpxPath, routeId);
|
||||
return getRoute(db, routeId);
|
||||
|
||||
491
test/backend-services.test.js
Normal file
491
test/backend-services.test.js
Normal file
@ -0,0 +1,491 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import fsp from 'node:fs/promises';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
const runtime = await fsp.mkdtemp(path.join(os.tmpdir(), 'wegwichtel-services-'));
|
||||
process.env.DATA_DIR = path.join(runtime, 'data');
|
||||
process.env.STORAGE_DIR = path.join(runtime, 'storage');
|
||||
|
||||
const { config, ensureRuntimeDirectories } = await import('../src/config.js');
|
||||
const { openDatabase, transaction } = await import('../src/database.js');
|
||||
const { HttpError, notFoundHandler, errorHandler } = await import('../src/middleware/errors.js');
|
||||
const express = (await import('express')).default;
|
||||
const { createApiRouter } = await import('../src/routes/api.js');
|
||||
const { inspectUpload } = await import('../src/middleware/upload.js');
|
||||
const routes = await import('../src/services/routes-service.js');
|
||||
const media = await import('../src/services/media-service.js');
|
||||
const storage = await import('../src/services/storage.js');
|
||||
|
||||
ensureRuntimeDirectories();
|
||||
|
||||
const PNG_FILE = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9ZQmcAAAAASUVORK5CYII=',
|
||||
'base64'
|
||||
);
|
||||
const MP3_FILE = Buffer.concat([
|
||||
Buffer.from('49443304000000000000', 'hex'),
|
||||
Buffer.from('fffb9064', 'hex'),
|
||||
Buffer.alloc(512)
|
||||
]);
|
||||
const GPX = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="test"><metadata><name>Teststrecke</name></metadata><trk><trkseg>
|
||||
<trkpt lat="50.0000" lon="10.0000"><ele>100</ele></trkpt>
|
||||
<trkpt lat="50.0010" lon="10.0010"><ele>110</ele></trkpt>
|
||||
</trkseg></trk></gpx>`;
|
||||
const GPX_APPEND = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="test"><trk><trkseg>
|
||||
<trkpt lat="50.0020" lon="10.0020"><ele>105</ele></trkpt>
|
||||
</trkseg></trk></gpx>`;
|
||||
|
||||
let uploadCounter = 0;
|
||||
function uploadedFile(name, contents, extra = {}) {
|
||||
const filePath = path.join(config.storageDir, 'uploads', `${Date.now()}-${uploadCounter += 1}-${name}`);
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, contents);
|
||||
return {
|
||||
fieldname: extra.fieldname || 'file',
|
||||
originalname: name,
|
||||
encoding: '7bit',
|
||||
mimetype: extra.mimetype || 'application/octet-stream',
|
||||
destination: path.dirname(filePath),
|
||||
filename: path.basename(filePath),
|
||||
path: filePath,
|
||||
size: Buffer.byteLength(contents),
|
||||
...extra
|
||||
};
|
||||
}
|
||||
|
||||
function expectHttpError(callback, status) {
|
||||
assert.throws(callback, error => error instanceof HttpError && error.status === status);
|
||||
}
|
||||
|
||||
async function invokeUpload(middleware, req) {
|
||||
return new Promise(resolve => {
|
||||
middleware(req, {}, error => resolve(error));
|
||||
});
|
||||
}
|
||||
|
||||
test.after(async () => {
|
||||
await fsp.rm(runtime, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('route, POI, media and storage services cover successful and rejected lifecycle paths', async () => {
|
||||
const db = openDatabase(path.join(config.dataDir, 'services.sqlite'));
|
||||
try {
|
||||
assert.equal(transaction(db, () => 42), 42);
|
||||
assert.throws(() => transaction(db, () => {
|
||||
throw new Error('rollback');
|
||||
}), /rollback/);
|
||||
|
||||
expectHttpError(() => routes.createRoute(db, {}, null), 400);
|
||||
|
||||
const invalidGpx = uploadedFile('invalid.gpx', '<xml/>');
|
||||
expectHttpError(() => routes.createRoute(db, { name: 'Ungültig' }, invalidGpx), 400);
|
||||
assert.equal(fs.existsSync(invalidGpx.path), false);
|
||||
|
||||
const route = routes.createRoute(db, {
|
||||
name: 'Äußerst schöne Strecke',
|
||||
slug: ' gleiche route ',
|
||||
description: 'Beschreibung',
|
||||
schoolName: 'Schule'
|
||||
}, uploadedFile('route.gpx', GPX));
|
||||
assert.equal(route.name, 'Äußerst schöne Strecke');
|
||||
assert.equal(route.pointCount, 2);
|
||||
assert.ok(fs.existsSync(routes.getRouteGpxFile(db, route.id).absolutePath));
|
||||
|
||||
const duplicate = routes.createRoute(db, {
|
||||
name: 'Zweite Strecke',
|
||||
slug: ' gleiche route '
|
||||
}, uploadedFile('route-2.gpx', GPX));
|
||||
assert.notEqual(duplicate.slug, route.slug);
|
||||
|
||||
assert.deepEqual(routes.listRoutes(db, {}).map(item => item.id), [route.id, duplicate.id]);
|
||||
assert.equal(routes.listRoutes(db, { lat: 90, lon: 90, radiusKm: 0.001 }).length, 0);
|
||||
assert.ok(routes.listRoutes(db, {
|
||||
lat: route.start.lat,
|
||||
lon: route.start.lon,
|
||||
radiusKm: 1
|
||||
})[0].proximityM <= 1);
|
||||
expectHttpError(() => routes.getRoute(db, 999999), 404);
|
||||
expectHttpError(() => routes.getRouteGpxFile(db, 999999), 404);
|
||||
expectHttpError(() => routes.updateRoute(db, 999999, {}, null), 404);
|
||||
|
||||
const renamed = routes.updateRoute(db, route.id, {
|
||||
name: 'Umbenannt',
|
||||
description: 'Neu',
|
||||
schoolName: 'Neue Schule'
|
||||
}, null);
|
||||
assert.equal(renamed.name, 'Umbenannt');
|
||||
|
||||
const replaced = routes.updateRoute(db, route.id, {}, uploadedFile('replacement.gpx', GPX_APPEND));
|
||||
assert.equal(replaced.pointCount, 1);
|
||||
|
||||
expectHttpError(() => routes.appendRoute(db, route.id, null), 400);
|
||||
expectHttpError(() => routes.appendRoute(db, 999999, uploadedFile('missing.gpx', GPX)), 404);
|
||||
const appended = routes.appendRoute(db, route.id, uploadedFile('append.gpx', GPX));
|
||||
assert.equal(appended.pointCount, 3);
|
||||
|
||||
expectHttpError(() => routes.createPoi(db, 999999, { lat: 1, lon: 2 }), 404);
|
||||
expectHttpError(() => routes.createPoi(db, route.id, { lat: 'x', lon: 2 }), 400);
|
||||
const poi = routes.createPoi(db, route.id, {
|
||||
title: '',
|
||||
description: 'POI',
|
||||
lat: route.start.lat,
|
||||
lon: route.start.lon,
|
||||
triggerRadiusM: '55',
|
||||
sequence: '1'
|
||||
});
|
||||
assert.equal(poi.title, 'Unbenannter POI');
|
||||
const secondPoi = routes.createPoi(db, route.id, {
|
||||
title: 'Zweiter POI',
|
||||
lat: route.start.lat,
|
||||
lon: route.start.lon,
|
||||
sequence: '2'
|
||||
});
|
||||
|
||||
expectHttpError(() => routes.getPoi(db, route.id, 999999), 404);
|
||||
expectHttpError(() => routes.updatePoi(db, route.id, 999999, {}), 404);
|
||||
expectHttpError(() => routes.updatePoi(db, route.id, poi.id, { sequence: -1 }), 400);
|
||||
const updatedPoi = routes.updatePoi(db, route.id, poi.id, {
|
||||
title: 'Aktualisiert',
|
||||
lat: route.start.lat + 0.001,
|
||||
lon: route.start.lon + 0.001,
|
||||
triggerRadiusM: 60,
|
||||
sequence: 0
|
||||
});
|
||||
assert.equal(updatedPoi.title, 'Aktualisiert');
|
||||
assert.equal(routes.listPois(db, route.id)[0].id, poi.id);
|
||||
|
||||
expectHttpError(() => media.listPictures(db, route.id, 999999), 404);
|
||||
expectHttpError(() => media.createPicture(db, route.id, poi.id, {}, null), 400);
|
||||
const invalidPicture = uploadedFile('invalid-picture.bin', 'not image', {
|
||||
detectedMime: 'text/plain',
|
||||
detectedExtension: 'txt'
|
||||
});
|
||||
expectHttpError(() => media.createPicture(db, route.id, poi.id, {}, invalidPicture), 415);
|
||||
assert.equal(fs.existsSync(invalidPicture.path), false);
|
||||
|
||||
const picture = media.createPicture(db, route.id, poi.id, { caption: 'Bild' }, uploadedFile('picture.png', PNG_FILE, {
|
||||
detectedMime: 'image/png',
|
||||
detectedExtension: 'png'
|
||||
}));
|
||||
assert.equal(picture.sequence, 0);
|
||||
const secondPicture = media.createPicture(db, route.id, poi.id, { caption: 'Bild 2', sequence: 2 }, uploadedFile('picture-2.png', PNG_FILE, {
|
||||
detectedMime: 'image/png',
|
||||
detectedExtension: 'png'
|
||||
}));
|
||||
assert.equal(media.listPictures(db, route.id, poi.id).length, 2);
|
||||
assert.ok(fs.existsSync(media.getPictureFile(db, route.id, poi.id, picture.id).absolutePath));
|
||||
expectHttpError(() => media.getPicture(db, route.id, poi.id, 999999), 404);
|
||||
expectHttpError(() => media.updatePicture(db, route.id, poi.id, picture.id, { sequence: -1 }, null), 400);
|
||||
|
||||
const metadataUpdate = media.updatePicture(db, route.id, poi.id, picture.id, {
|
||||
caption: 'Neue Bildbeschreibung',
|
||||
sequence: 1
|
||||
}, null);
|
||||
assert.equal(metadataUpdate.caption, 'Neue Bildbeschreibung');
|
||||
|
||||
const replacement = uploadedFile('replacement.webp', PNG_FILE, {
|
||||
detectedMime: 'image/png',
|
||||
detectedExtension: 'png'
|
||||
});
|
||||
const replacedPicture = media.updatePicture(db, route.id, poi.id, picture.id, {}, replacement);
|
||||
assert.equal(replacedPicture.id, picture.id);
|
||||
|
||||
expectHttpError(() => media.getAudio(db, route.id, poi.id), 404);
|
||||
expectHttpError(() => media.createAudio(db, route.id, poi.id, null), 400);
|
||||
const invalidAudio = uploadedFile('invalid-audio.bin', 'bad', {
|
||||
detectedMime: 'text/plain',
|
||||
detectedExtension: 'txt'
|
||||
});
|
||||
expectHttpError(() => media.createAudio(db, route.id, poi.id, invalidAudio), 415);
|
||||
assert.equal(fs.existsSync(invalidAudio.path), false);
|
||||
|
||||
const audio = media.createAudio(db, route.id, poi.id, uploadedFile('audio.mp3', MP3_FILE, {
|
||||
detectedMime: 'audio/mpeg',
|
||||
detectedExtension: 'mp3'
|
||||
}));
|
||||
assert.equal(audio.poiId, poi.id);
|
||||
assert.ok(fs.existsSync(media.getAudioFile(db, route.id, poi.id).absolutePath));
|
||||
expectHttpError(() => media.createAudio(db, route.id, poi.id, uploadedFile('duplicate.mp3', MP3_FILE, {
|
||||
detectedMime: 'audio/mpeg',
|
||||
detectedExtension: 'mp3'
|
||||
})), 409);
|
||||
expectHttpError(() => media.updateAudio(db, route.id, secondPoi.id, uploadedFile('missing.mp3', MP3_FILE, {
|
||||
detectedMime: 'audio/mpeg',
|
||||
detectedExtension: 'mp3'
|
||||
})), 404);
|
||||
|
||||
const replacedAudio = media.updateAudio(db, route.id, poi.id, uploadedFile('replacement.mp3', MP3_FILE, {
|
||||
detectedMime: 'audio/mpeg',
|
||||
detectedExtension: 'mp3'
|
||||
}));
|
||||
assert.equal(replacedAudio.poiId, poi.id);
|
||||
assert.equal(media.deleteAudio(db, route.id, poi.id).deleted, true);
|
||||
expectHttpError(() => media.deleteAudio(db, route.id, poi.id), 404);
|
||||
|
||||
assert.equal(media.deletePicture(db, route.id, poi.id, secondPicture.id).deleted, true);
|
||||
expectHttpError(() => media.deletePicture(db, route.id, poi.id, secondPicture.id), 404);
|
||||
|
||||
const poiPicture = media.createPicture(db, route.id, secondPoi.id, {}, uploadedFile('poi-picture.png', PNG_FILE, {
|
||||
detectedMime: 'image/png',
|
||||
detectedExtension: 'png'
|
||||
}));
|
||||
media.createAudio(db, route.id, secondPoi.id, uploadedFile('poi-audio.mp3', MP3_FILE, {
|
||||
detectedMime: 'audio/mpeg',
|
||||
detectedExtension: 'mp3'
|
||||
}));
|
||||
assert.equal(routes.deletePoi(db, route.id, secondPoi.id).deleted, true);
|
||||
expectHttpError(() => routes.deletePoi(db, route.id, secondPoi.id), 404);
|
||||
expectHttpError(() => media.getPicture(db, route.id, secondPoi.id, poiPicture.id), 404);
|
||||
|
||||
const deleted = routes.softDeleteRoute(db, route.id);
|
||||
assert.equal(deleted.status, 'deleted');
|
||||
assert.equal(routes.listRoutes(db, {}).some(item => item.id === route.id), false);
|
||||
assert.equal(routes.listRoutes(db, { includeDeleted: true }).some(item => item.id === route.id), true);
|
||||
expectHttpError(() => routes.getRoute(db, route.id), 404);
|
||||
expectHttpError(() => routes.softDeleteRoute(db, route.id), 404);
|
||||
const restored = routes.restoreRoute(db, route.id);
|
||||
assert.equal(restored.status, 'active');
|
||||
expectHttpError(() => routes.restoreRoute(db, route.id), 404);
|
||||
|
||||
const missingRouteDir = routes.createRoute(db, { name: 'Ohne Verzeichnis' }, uploadedFile('missing-dir.gpx', GPX));
|
||||
fs.rmSync(storage.routeDirectory(missingRouteDir.id), { recursive: true, force: true });
|
||||
expectHttpError(() => routes.softDeleteRoute(db, missingRouteDir.id), 409);
|
||||
|
||||
assert.equal(storage.removeStoredFile(null), false);
|
||||
assert.equal(storage.removeStoredFile('active/routes/not-there.bin'), false);
|
||||
expectHttpError(() => storage.removeStoredFile('../outside'), 500);
|
||||
expectHttpError(() => storage.resolveStoredFile('trash/routes/file'), 404);
|
||||
expectHttpError(() => storage.resolveStoredFile('active/../../outside'), 500);
|
||||
expectHttpError(() => storage.resolveStoredFile('active/routes/no-file'), 404);
|
||||
|
||||
const orphanUpload = uploadedFile('orphan.bin', 'x');
|
||||
storage.removeUpload(orphanUpload);
|
||||
assert.equal(fs.existsSync(orphanUpload.path), false);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('upload inspection handles JSON, GPX, binary validation and cleanup paths', async () => {
|
||||
const noFileReq = { body: {} };
|
||||
assert.equal(await invokeUpload(inspectUpload('picture', 'picture'), noFileReq), undefined);
|
||||
|
||||
const jsonPictureReq = {
|
||||
body: {
|
||||
picture: {
|
||||
filename: ' strange name.png ',
|
||||
dataUrl: `data:image/png;base64,${PNG_FILE.toString('base64')}`
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.equal(await invokeUpload(inspectUpload('picture', 'picture', { allowJson: true }), jsonPictureReq), undefined);
|
||||
assert.equal(jsonPictureReq.file.detectedMime, 'image/png');
|
||||
assert.equal(jsonPictureReq.file.detectedExtension, 'png');
|
||||
assert.equal(jsonPictureReq.body.picture, undefined);
|
||||
storage.removeUpload(jsonPictureReq.file);
|
||||
|
||||
const jsonAudioReq = {
|
||||
body: {
|
||||
audio: {
|
||||
filename: 'audio.bin',
|
||||
base64: MP3_FILE.toString('base64').replace(/(.{40})/g, '$1\n')
|
||||
}
|
||||
}
|
||||
};
|
||||
assert.equal(await invokeUpload(inspectUpload('audio', 'audio', { allowJson: true }), jsonAudioReq), undefined);
|
||||
assert.equal(jsonAudioReq.file.detectedMime, 'audio/mpeg');
|
||||
storage.removeUpload(jsonAudioReq.file);
|
||||
|
||||
for (const descriptor of [
|
||||
'not-object',
|
||||
{ base64: '' },
|
||||
{ base64: '!!!' },
|
||||
{ dataUrl: 12 },
|
||||
{ dataUrl: 'not-a-data-url' }
|
||||
]) {
|
||||
const req = { body: { picture: descriptor } };
|
||||
const error = await invokeUpload(inspectUpload('picture', 'picture', { allowJson: true }), req);
|
||||
assert.equal(error.status, 400);
|
||||
assert.equal(req.file, undefined);
|
||||
}
|
||||
|
||||
const unsupportedReq = {
|
||||
body: { picture: { filename: 'text.txt', base64: Buffer.from('plain text').toString('base64') } }
|
||||
};
|
||||
const unsupported = await invokeUpload(inspectUpload('picture', 'picture', { allowJson: true }), unsupportedReq);
|
||||
assert.equal(unsupported.status, 415);
|
||||
assert.equal(unsupportedReq.file, undefined);
|
||||
|
||||
const validGpxReq = { body: {}, file: uploadedFile('route.xml', `\uFEFF${GPX}`) };
|
||||
assert.equal(await invokeUpload(inspectUpload('gpx', 'gpx'), validGpxReq), undefined);
|
||||
assert.equal(validGpxReq.file.detectedExtension, 'gpx');
|
||||
storage.removeUpload(validGpxReq.file);
|
||||
|
||||
const invalidGpxReq = { body: {}, file: uploadedFile('route.xml', '<xml/>') };
|
||||
const invalidGpx = await invokeUpload(inspectUpload('gpx', 'gpx'), invalidGpxReq);
|
||||
assert.equal(invalidGpx.status, 415);
|
||||
assert.equal(invalidGpxReq.file, undefined);
|
||||
|
||||
const invalidUtf8Req = { body: {}, file: uploadedFile('route.gpx', Buffer.from([0xff, 0xfe, 0xfd])) };
|
||||
const invalidUtf8 = await invokeUpload(inspectUpload('gpx', 'gpx'), invalidUtf8Req);
|
||||
assert.equal(invalidUtf8.status, 415);
|
||||
assert.equal(invalidUtf8Req.file, undefined);
|
||||
});
|
||||
|
||||
test('HTTP error middleware preserves details, maps upload errors and delegates sent headers', () => {
|
||||
const notFound = [];
|
||||
notFoundHandler({}, {}, error => notFound.push(error));
|
||||
assert.equal(notFound[0].status, 404);
|
||||
|
||||
const sent = [];
|
||||
const response = {
|
||||
headersSent: false,
|
||||
statusCode: null,
|
||||
status(value) {
|
||||
this.statusCode = value;
|
||||
return this;
|
||||
},
|
||||
json(value) {
|
||||
sent.push(value);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
errorHandler(new HttpError(422, 'Ungültig', { field: 'name' }), {}, response, () => {});
|
||||
assert.equal(response.statusCode, 422);
|
||||
assert.deepEqual(sent[0].details, { field: 'name' });
|
||||
|
||||
errorHandler(Object.assign(new Error('large'), { code: 'LIMIT_FILE_SIZE' }), {}, response, () => {});
|
||||
assert.equal(response.statusCode, 413);
|
||||
errorHandler(Object.assign(new Error('unexpected'), { code: 'LIMIT_UNEXPECTED_FILE' }), {}, response, () => {});
|
||||
assert.equal(response.statusCode, 400);
|
||||
|
||||
const delegated = [];
|
||||
errorHandler(new Error('already sent'), {}, { headersSent: true }, error => delegated.push(error));
|
||||
assert.equal(delegated[0].message, 'already sent');
|
||||
});
|
||||
|
||||
|
||||
test('Express API router executes route, POI, file and restore handlers in-process', async t => {
|
||||
const db = openDatabase(path.join(config.dataDir, 'router.sqlite'));
|
||||
const app = express();
|
||||
app.use(express.json({
|
||||
limit: config.maxJsonBodyBytes,
|
||||
type: ['application/json', 'application/*+json', 'text/json']
|
||||
}));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use('/api', createApiRouter(db));
|
||||
app.use(notFoundHandler);
|
||||
app.use(errorHandler);
|
||||
|
||||
const server = await new Promise((resolve, reject) => {
|
||||
const listener = app.listen(0, '127.0.0.1', () => resolve(listener));
|
||||
listener.once('error', reject);
|
||||
});
|
||||
const address = server.address();
|
||||
const baseUrl = `http://127.0.0.1:${address.port}`;
|
||||
|
||||
t.after(async () => {
|
||||
await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
|
||||
db.close();
|
||||
});
|
||||
|
||||
async function json(url, options = {}, status = 200) {
|
||||
const response = await fetch(`${baseUrl}${url}`, options);
|
||||
const body = await response.json();
|
||||
assert.equal(response.status, status, JSON.stringify(body));
|
||||
return { response, body };
|
||||
}
|
||||
|
||||
const health = await json('/api/health');
|
||||
assert.equal(health.body.ok, true);
|
||||
assert.match(health.body.socket, /:/);
|
||||
|
||||
const createForm = new FormData();
|
||||
createForm.append('name', 'Router-Test');
|
||||
createForm.append('description', 'API-Abdeckung');
|
||||
createForm.append('gpx', new Blob([GPX], { type: 'text/plain' }), 'route.txt');
|
||||
const created = await json('/api/routes', { method: 'POST', body: createForm }, 201);
|
||||
const routeId = created.body.id;
|
||||
assert.equal(created.response.headers.get('location'), `/api/routes/${routeId}`);
|
||||
|
||||
assert.equal((await json('/api/routes?lat=invalid&lon=&radiusKm=invalid')).body.routes.length, 1);
|
||||
assert.equal((await json(`/api/routes/${routeId}`)).body.id, routeId);
|
||||
assert.equal((await fetch(`${baseUrl}/api/routes/${routeId}/gpx`)).status, 200);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois`)).body.pois.length, 0);
|
||||
|
||||
const updateForm = new FormData();
|
||||
updateForm.append('name', 'Router-Test aktualisiert');
|
||||
const updated = await json(`/api/routes/${routeId}`, { method: 'PUT', body: updateForm });
|
||||
assert.equal(updated.body.name, 'Router-Test aktualisiert');
|
||||
|
||||
const appendForm = new FormData();
|
||||
appendForm.append('gpx', new Blob([GPX_APPEND], { type: 'application/octet-stream' }), 'append.bin');
|
||||
assert.equal((await json(`/api/routes/${routeId}/append`, { method: 'POST', body: appendForm })).body.pointCount, 3);
|
||||
|
||||
const poiCreated = await json(`/api/routes/${routeId}/pois`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Router-POI', lat: 50, lon: 10, triggerRadiusM: 40, sequence: 0 })
|
||||
}, 201);
|
||||
const poiId = poiCreated.body.id;
|
||||
assert.equal(poiCreated.response.headers.get('location'), `/api/routes/${routeId}/pois/${poiId}`);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}`)).body.title, 'Router-POI');
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Router-POI neu' })
|
||||
})).body.title, 'Router-POI neu');
|
||||
|
||||
const pictureForm = new FormData();
|
||||
pictureForm.append('caption', 'Router-Bild');
|
||||
pictureForm.append('picture', new Blob([PNG_FILE], { type: 'text/plain' }), 'picture.txt');
|
||||
const pictureCreated = await json(`/api/routes/${routeId}/pois/${poiId}/pictures`, {
|
||||
method: 'POST', body: pictureForm
|
||||
}, 201);
|
||||
const pictureId = pictureCreated.body.id;
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/pictures`)).body.pictures.length, 1);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}?metadata=true`)).body.id, pictureId);
|
||||
assert.equal((await fetch(`${baseUrl}/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}`)).status, 200);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ caption: 'Router-Bild neu' })
|
||||
})).body.caption, 'Router-Bild neu');
|
||||
|
||||
const audioForm = new FormData();
|
||||
audioForm.append('audio', new Blob([MP3_FILE], { type: 'text/plain' }), 'audio.txt');
|
||||
const audioCreated = await json(`/api/routes/${routeId}/pois/${poiId}/audio`, {
|
||||
method: 'POST', body: audioForm
|
||||
}, 201);
|
||||
assert.equal(audioCreated.response.headers.get('location'), `/api/routes/${routeId}/pois/${poiId}/audio`);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/audio?metadata=true`)).body.poiId, poiId);
|
||||
assert.equal((await fetch(`${baseUrl}/api/routes/${routeId}/pois/${poiId}/audio`)).status, 200);
|
||||
|
||||
const replaceAudio = {
|
||||
audio: {
|
||||
filename: 'replacement.mp3',
|
||||
base64: MP3_FILE.toString('base64')
|
||||
}
|
||||
};
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/audio`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(replaceAudio)
|
||||
})).body.poiId, poiId);
|
||||
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/audio`, { method: 'DELETE' })).body.deleted, true);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}/pictures/${pictureId}`, { method: 'DELETE' })).body.deleted, true);
|
||||
assert.equal((await json(`/api/routes/${routeId}/pois/${poiId}`, { method: 'DELETE' })).body.deleted, true);
|
||||
|
||||
const deleted = await json(`/api/routes/${routeId}`, { method: 'DELETE' });
|
||||
assert.equal(deleted.body.softDeleted, true);
|
||||
const restored = await json(`/api/routes/${routeId}/restore`, { method: 'POST' });
|
||||
assert.equal(restored.body.restored, true);
|
||||
|
||||
await json('/api/not-found', {}, 404);
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user