From: Jérôme Benoit Date: Mon, 30 Mar 2026 20:02:25 +0000 (+0200) Subject: refactor(ocpp): integrate unified auth as sole authorization mechanism (#1764) X-Git-Tag: ocpp-server@v4.1.0~19 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=cebc17583b0d5ec09c5e3d41a6c204a84afe134e;p=e-mobility-charging-stations-simulator.git refactor(ocpp): integrate unified auth as sole authorization mechanism (#1764) * refactor(ocpp): route v1.6 auth through unified system, remove legacy functions * refactor(ocpp): replace OCPP 2.0 inline auth with unified auth service * test(ocpp): update OCPP 1.6 authorization tests for unified auth path * test(ocpp): add OCPP 2.0 unified auth integration tests * chore: pass all quality gates * [autofix.ci] apply automated fixes * refactor(ocpp): pass correct AuthContext to isIdTagAuthorized callers REMOTE_START for RemoteStartTransaction, RESERVATION for ReserveNow. ATG keeps the default TRANSACTION_START. * refactor(ocpp): use auth barrel index instead of deep imports * refactor(ocpp): use auth barrel for all external imports * refactor(ocpp): remove redundant 'unified' naming from auth system * refactor(ocpp): extract authorizeToken helper and fix rejection test assertions * test(ocpp): add coverage for auth error and context parameter paths * test(ocpp): remove redundant tests and unnecessary JSDoc from authorization tests * refactor(ocpp): address review feedback — explicit switch cases, parameterize context, merge imports * fix(ocpp): preserve OCPP 2.0 token type in authorizeToken per C01.FR.25 authorizeToken was hardcoding IdentifierType.ID_TAG, causing all Authorize.req to carry type 'Local' regardless of the actual token type (ISO14443, eMAID, etc.). Pass the OCPP 2.0 token type through and use mapOCPP20TokenType for proper mapping. Also fix log message attribution and deduplicate OCPPAuthServiceFactory from the dynamic import (already available via static import). * refactor(ocpp): eliminate unnecessary dynamic imports and synchronize auth factory Extract isIdTagAuthorized to IdTagAuthorization.ts to break the OCPPServiceUtils ESM evaluation cycle, enabling static imports from the auth barrel. Convert AuthComponentFactory, OCPPAuthServiceFactory, and OCPPAuthServiceImpl to fully synchronous APIs. Remove the globalThis Symbol hack that was needed for cross-module-instance sharing. Move getMessageTypeString to utils/Utils.ts where it belongs. - 9 dynamic imports eliminated (20 → 11, remaining are inheritance cycles) - Auth factory chain fully sync (getInstance, createAdapter, initialize) - IdTagAuthorization.ts imports auth/index.js barrel statically - Tests relocated to follow moved code - All barrel exports consolidated between components * build: add skott for type-aware circular dependency detection Add skott as devDependency with a circular-deps script that scans the entire src/ directory using --no-trackTypeOnlyDependencies to ignore import type (unlike madge which reports false positives). Exit code 0 for now since known cycles exist — switch to 1 when integrated in CI. * build: override effect>=3.20.0 to fix CVE-2026-32887 (skott transitive dep) * refactor: break UI server circular dependency via IBootstrap DIP Introduce IBootstrap interface to decouple AbstractUIService from the Bootstrap singleton import. Bootstrap implements IBootstrap and is injected through the UIServerFactory → AbstractUIServer → UIService chain. This structurally eliminates the cycle: AbstractUIServer → UIServiceFactory → AbstractUIService → Bootstrap → UIServerFactory → UIHttpServer → extends AbstractUIServer. Also removes the cross-component re-export workaround in charging-station/index.ts (getMessageTypeString from utils/) that was only needed to preserve ESM evaluation order around this cycle. - Circular dependency paths: 177 → 39 (type-aware, skott) - Short cycles (depth ≤ 4): 7 → 4 - ErrorUtils now imports getMessageTypeString from its own component * test: fix mock isolation and type safety in UI server tests Move mockBootstrap to beforeEach for per-test isolation. Remove as-never cast by providing all SimulatorState fields. Remove unnecessary JSDoc on self-documenting mock factory. * fix: align auth barrel section headers with exports, use barrel for CertificateAuthStrategy import * refactor(auth): eliminate code duplication, dead code, and type casts - DRY: Replace duplicated mappers in OCPP20AuthAdapter with shared mapToOCPP20TokenType/mapOCPP20TokenType from AuthTypes - DRY: Extract duplicated enhanceResult from Local/Remote strategies into shared enhanceAuthResult in AuthTypes - Dead code: Remove unused parseIntegerVariable from OCPP20AuthAdapter - YAGNI: Remove unused AuthHelpers from barrel export - Type safety: Add getAuthCache() to AuthStrategy interface, eliminating 3 concrete as-LocalAuthStrategy casts in OCPPAuthServiceImpl * fix(auth): preserve OCPP 2.0 Central/Local token type in adapter mapping Remove post-processing that collapsed Central/Local to ID_TAG in OCPP20AuthAdapter.convertToIdentifier. The shared mapOCPP20TokenType correctly returns CENTRAL/LOCAL, which downstream code uses for spec compliance (C03.FR.01 skip Authorize.req for Central tokens, C11 cache exclusion for Central identifiers). --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- diff --git a/package.json b/package.json index 9c980616..01642ed0 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "clean:dist": "pnpm exec rimraf dist", "clean:node_modules": "pnpm exec rimraf node_modules", "typecheck": "tsc --noEmit --skipLibCheck", + "circular-deps": "skott --no-trackTypeOnlyDependencies --showCircularDependencies --exitCodeOnCircularDependencies 0 --displayMode=raw --fileExtensions=.ts --cwd=src", "lint": "cross-env TIMING=1 eslint --cache src tests scripts ./*.js ./*.ts", "lint:fix": "cross-env TIMING=1 eslint --cache --fix src tests scripts ./*.js ./*.ts", "format": "prettier --cache --write .; eslint --cache --fix src tests scripts ./*.js ./*.ts", @@ -124,6 +125,7 @@ "prettier": "^3.8.1", "rimraf": "^6.1.3", "semver": "^7.7.4", + "skott": "^0.35.8", "ts-node": "^10.9.2", "tsx": "^4.21.0", "typescript": "~6.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bebe160..c0a357c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,10 +8,12 @@ overrides: bn.js@<5.2.3: '>=5.2.3' brace-expansion@<1.1.13: '>=1.1.13' d3-color@<3.1.0: '>=3.1.0' + effect@<3.20.0: '>=3.20.0' form-data@<2.5.4: '>=2.5.4' got@<11.8.5: '>=11.8.5' minimatch@>=10.0.0 <10.2.3: '>=10.2.3' minimatch@>=9.0.0 <9.0.7: '>=9.0.7' + path-to-regexp@>=8.0.0 <8.4.0: '>=8.4.0' picomatch@<2.3.2: '>=2.3.2' qs@<6.14.1: '>=6.14.1' smol-toml@<1.6.1: '>=1.6.1' @@ -154,6 +156,9 @@ importers: semver: specifier: ^7.7.4 version: 7.7.4 + skott: + specifier: ^0.35.8 + version: 0.35.8 ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@24.12.0)(typescript@6.0.2) @@ -248,6 +253,10 @@ packages: engines: {node: '>=8.5.0'} hasBin: true + '@arr/every@1.0.1': + resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} + engines: {node: '>=4'} + '@asamuzakjp/css-color@5.0.1': resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1136,6 +1145,94 @@ packages: '@oxc-project/types@0.122.0': resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@parcel/watcher-android-arm64@2.5.4': + resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.4': + resolution: {integrity: sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.4': + resolution: {integrity: sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.4': + resolution: {integrity: sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.4': + resolution: {integrity: sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.4': + resolution: {integrity: sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.4': + resolution: {integrity: sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.4': + resolution: {integrity: sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.4': + resolution: {integrity: sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.4': + resolution: {integrity: sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.4': + resolution: {integrity: sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.4': + resolution: {integrity: sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.4': + resolution: {integrity: sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.4': + resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==} + engines: {node: '>= 10.0.0'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1144,6 +1241,12 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@0.5.0': + resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rolldown/binding-android-arm64@1.0.0-rc.12': resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1334,12 +1437,18 @@ packages: '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + '@types/node@24.12.0': resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} '@types/offscreencanvas@2019.3.0': resolution: {integrity: sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/seedrandom@2.4.34': resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==} @@ -1379,6 +1488,12 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.53.0': + resolution: {integrity: sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.57.1': resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1389,6 +1504,12 @@ packages: resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.53.0': + resolution: {integrity: sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.57.1': resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1402,10 +1523,20 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@8.53.0': + resolution: {integrity: sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.57.1': resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.53.0': + resolution: {integrity: sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.57.1': resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1419,6 +1550,10 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@8.53.0': + resolution: {integrity: sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.57.1': resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1699,6 +1834,9 @@ packages: arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1706,6 +1844,10 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-differ@3.0.0: + resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} + engines: {node: '>=8'} + array-flatten@3.0.0: resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} @@ -1746,6 +1888,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + arrify@2.0.1: + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} + asn1.js@4.10.1: resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} @@ -1817,6 +1963,9 @@ packages: react-native-b4a: optional: true + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -1872,6 +2021,9 @@ packages: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} + brace-expansion@2.0.3: + resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} @@ -1985,6 +2137,9 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsite@1.0.0: + resolution: {integrity: sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -2106,6 +2261,9 @@ packages: cliui@5.0.0: resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2171,6 +2329,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} @@ -2192,6 +2354,14 @@ packages: compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + concat-stream@1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} @@ -2282,6 +2452,10 @@ packages: cosmiconfig: '>=9' typescript: '>=5' + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} @@ -2480,6 +2654,14 @@ packages: debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -2546,10 +2728,18 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + depcheck@1.4.7: + resolution: {integrity: sha512-1lklS/bV5chOxwNKA/2XUUk/hPORp8zihZsXflr8x0kLwmcZ9Y9BsS6Hs3ssvA+2wUVbG0U2Ciqvm1SokNjPkA==} + engines: {node: '>=10'} + hasBin: true + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + deps-regex@0.2.0: + resolution: {integrity: sha512-PwuBojGMQAYbWkMXOY9Pd/NWCDNHVH12pnS7WHqZkTSeMESe4hwnKKRp0yR87g37113x4JPbo/oIvXY+s/f56Q==} + deps-sort@2.0.1: resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==} hasBin: true @@ -2557,6 +2747,10 @@ packages: des.js@1.1.0: resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + detect-file@1.0.0: + resolution: {integrity: sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==} + engines: {node: '>=0.10.0'} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2573,6 +2767,10 @@ packages: diffie-hellman@5.0.3: resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} + digraph-js@2.2.4: + resolution: {integrity: sha512-K42PmnGfSO73gOUDwq/kM7DLZaCQJpEKImEuz/aiSbmzEwoEUWSfkDNGfYcIFVZuNgn3tCkyEvzXJOoluP3bSQ==} + engines: {node: '>=16.0.0'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -2627,6 +2825,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.21.0: + resolution: {integrity: sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==} + electron-to-chromium@1.5.321: resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} @@ -2964,6 +3165,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expand-tilde@2.0.2: + resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} + engines: {node: '>=0.10.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -2995,6 +3200,10 @@ packages: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3072,6 +3281,10 @@ packages: resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==} engines: {node: '>=20'} + findup-sync@5.0.0: + resolution: {integrity: sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==} + engines: {node: '>= 10.13.0'} + flame-gradient@1.0.0: resolution: {integrity: sha512-9ejk16/DqvQJ4dHsh68W/4N0zmVQ60zukyUuEHrTbf5pJvP4JqlIdke86Z9174PZokRCXAntY5+H1txSyC7mUA==} @@ -3111,6 +3324,9 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + fp-ts@2.5.0: + resolution: {integrity: sha512-xkC9ZKl/i2cU+8FAsdyLcTvPRXphp42FcK5WmZpB47VXb4gggC3DHlVDKNLdbC+U8zz6yp1b0bj0mZg0axmZYQ==} + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -3136,6 +3352,9 @@ packages: resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} engines: {node: '>=14.14'} + fs-tree-structure@0.0.5: + resolution: {integrity: sha512-827ACYnAMC1DQRvhLUzZH0fCPhBJLo9P7WfxxwP4cibIzlrSzbD+Fh9W4FxFtSU+p9GlX0BoQUWLJ2LFJuoKuQ==} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -3257,6 +3476,14 @@ packages: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} + global-modules@1.0.0: + resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} + engines: {node: '>=0.10.0'} + + global-prefix@1.0.2: + resolution: {integrity: sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==} + engines: {node: '>=0.10.0'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -3375,6 +3602,10 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + hono@4.12.8: resolution: {integrity: sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A==} engines: {node: '>=16.9.0'} @@ -3456,6 +3687,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@6.0.5: + resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3534,6 +3769,11 @@ packages: resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} engines: {node: '>= 0.10'} + io-ts@2.2.22: + resolution: {integrity: sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA==} + peerDependencies: + fp-ts: ^2.5.0 + iota-array@1.0.0: resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} @@ -3610,6 +3850,11 @@ packages: engines: {node: '>=8'} hasBin: true + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3642,6 +3887,11 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} @@ -3750,6 +4000,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@1.1.0: resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} engines: {node: '>=4'} @@ -3758,6 +4012,10 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + is-yarn-global@0.3.0: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} @@ -3814,6 +4072,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -3897,6 +4159,10 @@ packages: keyv@5.6.0: resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + knex@3.1.0: resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} engines: {node: '>=16'} @@ -4049,6 +4315,9 @@ packages: resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} engines: {node: '>=20'} + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -4082,6 +4351,9 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash.uniqwith@4.5.0: + resolution: {integrity: sha512-7lYL8bLopMoy4CTICbxygAUq6CdRJ36vFc80DucPueUee+d5NBRxz3FdT9Pes/HEx5mPoT9jwnsEJWz1N7uq7Q==} + lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} @@ -4162,6 +4434,10 @@ packages: resolution: {integrity: sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==} engines: {node: '>= 14'} + matchit@1.1.0: + resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==} + engines: {node: '>=6'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4197,6 +4473,10 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + meriyah@4.5.0: + resolution: {integrity: sha512-Rbiu0QPIxTXgOXwiIpRVJfZRQ2FWyfzYrOGBs9SN5RbaXg1CN5ELn/plodwWwluX93yzc4qO/bNIen1ThGFCxw==} + engines: {node: '>=10.4.0'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -4266,6 +4546,10 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@7.4.9: + resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -4360,6 +4644,13 @@ packages: morphdom@2.7.8: resolution: {integrity: sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -4369,6 +4660,10 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + multimatch@5.0.0: + resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} + engines: {node: '>=10'} + multistream@2.1.1: resolution: {integrity: sha512-xasv76hl6nr1dEy3lPvy7Ej7K/Lx3O/FCvwge8PeVJpciPPoNCbaANcNiBug3IpdvTveZUcAV0DJzdnUDMesNQ==} @@ -4393,6 +4688,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -4430,6 +4728,10 @@ packages: ndarray@1.0.19: resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} @@ -4451,6 +4753,9 @@ packages: resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==} engines: {node: '>=10'} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-exports-info@1.6.0: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} @@ -4547,6 +4852,10 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + on-net-listen@1.1.2: resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} engines: {node: '>=9.4.0 || ^8.9.4'} @@ -4665,6 +4974,10 @@ packages: resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==} engines: {node: '>= 0.10'} + parse-gitignore@2.0.0: + resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} + engines: {node: '>=14'} + parse-imports-exports@0.2.4: resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==} @@ -4672,6 +4985,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} @@ -4719,8 +5036,8 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@8.3.0: - resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-to-regexp@8.4.0: + resolution: {integrity: sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -4771,6 +5088,12 @@ packages: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + please-upgrade-node@3.2.0: + resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==} + + polka@0.5.2: + resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==} + poolifier@5.3.2: resolution: {integrity: sha512-5Cu+3i+m5s56mHYPS7OXmsl5Eqs8aoTALPTrhGKcRVoHQgQrySukBHoDmHx3yH6f29tS8JfSse/58Tl987ztow==} engines: {node: '>=20.11.0', pnpm: '>=9.0.0'} @@ -4874,6 +5197,9 @@ packages: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.15.0: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} @@ -4986,6 +5312,9 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + require-package-name@2.0.1: + resolution: {integrity: sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==} + reserved-identifiers@1.2.0: resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==} engines: {node: '>=18'} @@ -4993,6 +5322,10 @@ packages: resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-dir@1.0.1: + resolution: {integrity: sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -5112,6 +5445,9 @@ packages: seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver-diff@3.1.1: resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} engines: {node: '>=8'} @@ -5220,6 +5556,17 @@ packages: sinusoidal-decimal@1.0.0: resolution: {integrity: sha512-KPUi1ZqLocV64n0AuV+g4VDjAM+tEEY66nUd+rYaVBHIfeheGGUvIxe9bf7Mpc1PonDTVW2uRr9nigQa9odvuA==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + + skott-webapp@2.3.0: + resolution: {integrity: sha512-nmt+ilxGOqX5zN2WDKv1Y5gLfxy/lceHgbB8HM/ym/Cm8572ypD1s2S+pcN+jOw13xqoavHJPonX1WT2QvkpDg==} + + skott@0.35.8: + resolution: {integrity: sha512-PRh9cIAZfWn1ry0mXDXJW5xqCCI4ly/EwsLFF/+/RLf5qT+QdrEhAbBHeSgjtds/cqkyUSsYkiAvo0EGIvHd0w==} + hasBin: true + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -5274,6 +5621,9 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sqlstring-sqlite@0.1.1: resolution: {integrity: sha512-9CAYUJ0lEUPYJrswqiqdINNSfq3jqWo/bFJ7tufdoNeSK0Fy+d1kFTxjqO9PIqza0Kri+ZtYMfPVf1aZaFOvrQ==} engines: {node: '>= 0.6'} @@ -5559,6 +5909,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -5581,6 +5935,10 @@ packages: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} + trouter@2.0.1: + resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==} + engines: {node: '>=6'} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -5697,6 +6055,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + typescript@6.0.2: resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} engines: {node: '>=14.17'} @@ -6003,6 +6366,10 @@ packages: resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6110,6 +6477,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + yaml@2.8.3: resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} engines: {node: '>= 14.6'} @@ -6118,6 +6489,10 @@ packages: yargs-parser@15.0.3: resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==} + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -6125,6 +6500,10 @@ packages: yargs@14.2.3: resolution: {integrity: sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==} + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -6184,6 +6563,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@arr/every@1.0.1': {} + '@asamuzakjp/css-color@5.0.1': dependencies: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) @@ -7238,11 +7619,75 @@ snapshots: '@oxc-project/types@0.122.0': {} + '@parcel/watcher-android-arm64@2.5.4': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.4': + optional: true + + '@parcel/watcher-darwin-x64@2.5.4': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.4': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.4': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.4': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.4': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.4': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.4': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.4': + optional: true + + '@parcel/watcher-win32-arm64@2.5.4': + optional: true + + '@parcel/watcher-win32-ia32@2.5.4': + optional: true + + '@parcel/watcher-win32-x64@2.5.4': + optional: true + + '@parcel/watcher@2.5.4': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.4 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.4 + '@parcel/watcher-darwin-arm64': 2.5.4 + '@parcel/watcher-darwin-x64': 2.5.4 + '@parcel/watcher-freebsd-x64': 2.5.4 + '@parcel/watcher-linux-arm-glibc': 2.5.4 + '@parcel/watcher-linux-arm-musl': 2.5.4 + '@parcel/watcher-linux-arm64-glibc': 2.5.4 + '@parcel/watcher-linux-arm64-musl': 2.5.4 + '@parcel/watcher-linux-x64-glibc': 2.5.4 + '@parcel/watcher-linux-x64-musl': 2.5.4 + '@parcel/watcher-win32-arm64': 2.5.4 + '@parcel/watcher-win32-ia32': 2.5.4 + '@parcel/watcher-win32-x64': 2.5.4 + '@pkgjs/parseargs@0.11.0': optional: true '@pkgr/core@0.2.9': {} + '@polka/url@0.5.0': {} + + '@polka/url@1.0.0-next.29': {} + '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true @@ -7391,12 +7836,16 @@ snapshots: '@types/long@4.0.2': {} + '@types/minimatch@3.0.5': {} + '@types/node@24.12.0': dependencies: undici-types: 7.16.0 '@types/offscreencanvas@2019.3.0': {} + '@types/parse-json@4.0.2': {} + '@types/seedrandom@2.4.34': {} '@types/semver@7.7.1': {} @@ -7445,6 +7894,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.53.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) + '@typescript-eslint/types': 8.57.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.57.1(typescript@6.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@6.0.2) @@ -7459,6 +7917,14 @@ snapshots: '@typescript-eslint/types': 8.57.1 '@typescript-eslint/visitor-keys': 8.57.1 + '@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.57.1(typescript@6.0.2)': dependencies: typescript: 6.0.2 @@ -7475,8 +7941,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/types@8.53.0': {} + '@typescript-eslint/types@8.57.1': {} + '@typescript-eslint/typescript-estree@8.53.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.53.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.53.0(typescript@5.9.3) + '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/visitor-keys': 8.53.0 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@8.57.1(typescript@6.0.2)': dependencies: '@typescript-eslint/project-service': 8.57.1(typescript@6.0.2) @@ -7503,6 +7986,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/visitor-keys@8.53.0': + dependencies: + '@typescript-eslint/types': 8.53.0 + eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.57.1': dependencies: '@typescript-eslint/types': 8.57.1 @@ -7829,6 +8317,10 @@ snapshots: arg@4.1.3: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -7836,6 +8328,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-differ@3.0.0: {} + array-flatten@3.0.0: {} array-from@2.1.1: {} @@ -7898,6 +8392,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + arrify@2.0.1: {} + asn1.js@4.10.1: dependencies: bn.js: 5.2.3 @@ -7983,6 +8479,8 @@ snapshots: b4a@1.8.0: {} + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} base64-js@1.5.1: {} @@ -8049,6 +8547,10 @@ snapshots: widest-line: 3.1.0 wrap-ansi: 7.0.0 + brace-expansion@2.0.3: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -8244,6 +8746,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsite@1.0.0: {} + callsites@3.1.0: {} camel-case@3.0.0: @@ -8387,6 +8891,12 @@ snapshots: strip-ansi: 5.2.0 wrap-ansi: 5.1.0 + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -8445,6 +8955,8 @@ snapshots: commander@10.0.1: {} + commander@11.1.0: {} + commander@14.0.3: {} commander@2.20.3: {} @@ -8466,6 +8978,22 @@ snapshots: array-ify: 1.0.0 dot-prop: 5.3.0 + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + concat-stream@1.6.2: dependencies: buffer-from: 1.1.2 @@ -8562,6 +9090,14 @@ snapshots: jiti: 2.6.1 typescript: 6.0.2 + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.3 + cosmiconfig@9.0.1(typescript@6.0.2): dependencies: env-paths: 2.2.1 @@ -8833,6 +9369,10 @@ snapshots: debounce@1.2.1: {} + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@4.3.4: dependencies: ms: 2.1.2 @@ -8890,8 +9430,38 @@ snapshots: denque@2.1.0: {} + depcheck@1.4.7: + dependencies: + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 + '@vue/compiler-sfc': 3.5.31 + callsite: 1.0.0 + camelcase: 6.3.0 + cosmiconfig: 7.1.0 + debug: 4.4.3 + deps-regex: 0.2.0 + findup-sync: 5.0.0 + ignore: 5.3.2 + is-core-module: 2.16.1 + js-yaml: 3.14.2 + json5: 2.2.3 + lodash: 4.17.23 + minimatch: 7.4.9 + multimatch: 5.0.0 + please-upgrade-node: 3.2.0 + readdirp: 3.6.0 + require-package-name: 2.0.1 + resolve: 1.22.11 + resolve-from: 5.0.0 + semver: 7.7.4 + yargs: 16.2.0 + transitivePeerDependencies: + - supports-color + depd@2.0.0: {} + deps-regex@0.2.0: {} + deps-sort@2.0.1: dependencies: JSONStream: 1.3.5 @@ -8904,6 +9474,8 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 + detect-file@1.0.0: {} + detect-libc@2.1.2: {} detective@5.2.1: @@ -8920,6 +9492,10 @@ snapshots: miller-rabin: 4.0.1 randombytes: 2.1.0 + digraph-js@2.2.4: + dependencies: + lodash.uniqwith: 4.5.0 + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -8979,6 +9555,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.21.0: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.321: {} elliptic@6.6.1: @@ -9489,6 +10070,10 @@ snapshots: expand-template@2.0.3: {} + expand-tilde@2.0.2: + dependencies: + homedir-polyfill: 1.0.3 + expect-type@1.3.0: {} express-rate-limit@8.3.1(express@5.2.1): @@ -9545,6 +10130,10 @@ snapshots: extsprintf@1.3.0: {} + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-equals@6.0.0: {} @@ -9622,6 +10211,13 @@ snapshots: locate-path: 8.0.0 unicorn-magic: 0.3.0 + findup-sync@5.0.0: + dependencies: + detect-file: 1.0.0 + is-glob: 4.0.3 + micromatch: 4.0.8 + resolve-dir: 1.0.1 + flame-gradient@1.0.0: dependencies: sinusoidal-decimal: 1.0.0 @@ -9660,6 +10256,8 @@ snapshots: forwarded@0.2.0: {} + fp-ts@2.5.0: {} + fresh@2.0.0: {} from2-string@1.1.0: @@ -9691,6 +10289,10 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 + fs-tree-structure@0.0.5: + dependencies: + lodash-es: 4.17.23 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -9828,6 +10430,20 @@ snapshots: dependencies: ini: 2.0.0 + global-modules@1.0.0: + dependencies: + global-prefix: 1.0.2 + is-windows: 1.0.2 + resolve-dir: 1.0.1 + + global-prefix@1.0.2: + dependencies: + expand-tilde: 2.0.2 + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 1.0.2 + which: 1.3.1 + globals@14.0.0: {} globals@15.15.0: {} @@ -9954,6 +10570,10 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + hono@4.12.8: {} hookable@5.5.3: {} @@ -10029,6 +10649,10 @@ snapshots: ieee754@1.2.1: {} + ignore-walk@6.0.5: + dependencies: + minimatch: 10.2.4 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -10118,6 +10742,10 @@ snapshots: interpret@2.2.0: {} + io-ts@2.2.22(fp-ts@2.5.0): + dependencies: + fp-ts: 2.5.0 + iota-array@1.0.0: {} ip-address@10.1.0: {} @@ -10189,6 +10817,8 @@ snapshots: is-docker@2.2.1: {} + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -10219,6 +10849,10 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-installed-globally@0.4.0: dependencies: global-dirs: 3.0.1 @@ -10302,12 +10936,18 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-windows@1.0.2: {} + is-wsl@1.1.0: {} is-wsl@2.2.0: dependencies: is-docker: 2.2.1 + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + is-yarn-global@0.3.0: {} isarray@1.0.0: {} @@ -10364,6 +11004,11 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -10456,6 +11101,8 @@ snapshots: dependencies: '@keyv/serialize': 1.1.1 + kleur@4.1.5: {} + knex@3.1.0(better-sqlite3@11.10.0): dependencies: colorette: 2.0.19 @@ -10588,6 +11235,8 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash-es@4.17.23: {} + lodash.camelcase@4.3.0: {} lodash.chunk@4.2.0: {} @@ -10610,6 +11259,8 @@ snapshots: lodash.startcase@4.4.0: {} + lodash.uniqwith@4.5.0: {} + lodash.upperfirst@4.3.1: {} lodash@4.17.23: {} @@ -10698,6 +11349,10 @@ snapshots: iconv-lite: 0.6.3 lru-cache: 10.4.3 + matchit@1.1.0: + dependencies: + '@arr/every': 1.0.1 + math-intrinsics@1.1.0: {} md5.js@1.3.5: @@ -10724,6 +11379,8 @@ snapshots: merge2@1.4.1: {} + meriyah@4.5.0: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -10781,6 +11438,10 @@ snapshots: dependencies: brace-expansion: 5.0.5 + minimatch@7.4.9: + dependencies: + brace-expansion: 2.0.3 + minimist@1.2.8: {} minipass@7.1.3: {} @@ -10875,12 +11536,24 @@ snapshots: morphdom@2.7.8: {} + mrmime@2.0.1: {} + + ms@2.0.0: {} + ms@2.1.2: {} ms@2.1.3: {} muggle-string@0.4.1: {} + multimatch@5.0.0: + dependencies: + '@types/minimatch': 3.0.5 + array-differ: 3.0.0 + array-union: 2.1.0 + arrify: 2.0.1 + minimatch: 3.1.5 + multistream@2.1.1: dependencies: inherits: 2.0.4 @@ -10917,6 +11590,10 @@ snapshots: nanoid@3.3.11: {} + nanospinner@1.2.2: + dependencies: + picocolors: 1.1.1 + napi-build-utils@2.0.0: {} natural-compare@1.4.0: {} @@ -10964,6 +11641,8 @@ snapshots: iota-array: 1.0.0 is-buffer: 1.1.6 + negotiator@0.6.4: {} + negotiator@1.0.0: {} neostandard@0.13.0(eslint@9.39.4(jiti@2.6.1))(typescript@6.0.2): @@ -10992,6 +11671,8 @@ snapshots: dependencies: semver: 7.7.4 + node-addon-api@7.1.1: {} + node-exports-info@1.6.0: dependencies: array.prototype.flatmap: 1.3.3 @@ -11078,6 +11759,8 @@ snapshots: dependencies: ee-first: 1.1.1 + on-headers@1.1.0: {} + on-net-listen@1.1.2: {} once@1.4.0: @@ -11217,6 +11900,8 @@ snapshots: pbkdf2: 3.1.5 safe-buffer: 5.2.1 + parse-gitignore@2.0.0: {} + parse-imports-exports@0.2.4: dependencies: parse-statements: 1.0.11 @@ -11228,6 +11913,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-passwd@1.0.0: {} + parse-statements@1.0.11: {} parse5@7.3.0: @@ -11264,7 +11951,7 @@ snapshots: lru-cache: 11.2.7 minipass: 7.1.3 - path-to-regexp@8.3.0: {} + path-to-regexp@8.4.0: {} path-type@4.0.0: {} @@ -11311,6 +11998,15 @@ snapshots: dependencies: find-up: 3.0.0 + please-upgrade-node@3.2.0: + dependencies: + semver-compare: 1.0.0 + + polka@0.5.2: + dependencies: + '@polka/url': 0.5.0 + trouter: 2.0.1 + poolifier@5.3.2: {} possible-typed-array-names@1.1.0: {} @@ -11428,6 +12124,8 @@ snapshots: dependencies: escape-goat: 2.1.1 + pure-rand@6.1.0: {} + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -11572,10 +12270,17 @@ snapshots: require-main-filename@2.0.0: {} + require-package-name@2.0.1: {} + reserved-identifiers@1.2.0: {} resolve-alpn@1.2.1: {} + resolve-dir@1.0.1: + dependencies: + expand-tilde: 2.0.2 + global-modules: 1.0.0 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -11663,7 +12368,7 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.0 transitivePeerDependencies: - supports-color @@ -11722,6 +12427,8 @@ snapshots: seedrandom@3.0.5: {} + semver-compare@1.0.0: {} + semver-diff@3.1.1: dependencies: semver: 6.3.1 @@ -11857,6 +12564,45 @@ snapshots: sinusoidal-decimal@1.0.0: {} + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + + skott-webapp@2.3.0: + dependencies: + digraph-js: 2.2.4 + + skott@0.35.8: + dependencies: + '@parcel/watcher': 2.5.4 + '@typescript-eslint/typescript-estree': 8.53.0(typescript@5.9.3) + commander: 11.1.0 + compression: 1.8.1 + depcheck: 1.4.7 + digraph-js: 2.2.4 + effect: 3.21.0 + estree-walker: 3.0.3 + fp-ts: 2.5.0 + fs-tree-structure: 0.0.5 + ignore-walk: 6.0.5 + io-ts: 2.2.22(fp-ts@2.5.0) + is-wsl: 3.1.1 + json5: 2.2.3 + kleur: 4.1.5 + lodash-es: 4.17.23 + meriyah: 4.5.0 + minimatch: 10.2.4 + nanospinner: 1.2.2 + parse-gitignore: 2.0.0 + polka: 0.5.2 + sirv: 2.0.4 + skott-webapp: 2.3.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + slash@3.0.0: {} slice-ansi@7.1.2: @@ -11904,6 +12650,8 @@ snapshots: split2@4.2.0: {} + sprintf-js@1.0.3: {} + sqlstring-sqlite@0.1.1: {} sqlstring@2.3.3: {} @@ -12242,6 +12990,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + tough-cookie@6.0.1: dependencies: tldts: 7.0.27 @@ -12268,6 +13018,14 @@ snapshots: triple-beam@1.4.1: {} + trouter@2.0.1: + dependencies: + matchit: 1.1.0 + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-api-utils@2.5.0(typescript@6.0.2): dependencies: typescript: 6.0.2 @@ -12412,6 +13170,8 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.9.3: {} + typescript@6.0.2: {} ufo@1.6.3: {} @@ -12722,6 +13482,10 @@ snapshots: gopd: 1.2.0 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -12827,6 +13591,8 @@ snapshots: yallist@5.0.0: {} + yaml@1.10.3: {} + yaml@2.8.3: {} yargs-parser@15.0.3: @@ -12834,6 +13600,8 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} yargs@14.2.3: @@ -12850,6 +13618,16 @@ snapshots: y18n: 4.0.3 yargs-parser: 15.0.3 + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yargs@17.7.2: dependencies: cliui: 8.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e2b42a07..0d1a4107 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,10 +6,12 @@ overrides: bn.js@<5.2.3: '>=5.2.3' brace-expansion@<1.1.13: '>=1.1.13' d3-color@<3.1.0: '>=3.1.0' + effect@<3.20.0: '>=3.20.0' form-data@<2.5.4: '>=2.5.4' got@<11.8.5: '>=11.8.5' minimatch@>=10.0.0 <10.2.3: '>=10.2.3' minimatch@>=9.0.0 <9.0.7: '>=9.0.7' + path-to-regexp@>=8.0.0 <8.4.0: '>=8.4.0' picomatch@<2.3.2: '>=2.3.2' qs@<6.14.1: '>=6.14.1' smol-toml@<1.6.1: '>=1.6.1' diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index ed0817a2..acc6388d 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -10,6 +10,7 @@ import { fileURLToPath } from 'node:url' import { isMainThread } from 'node:worker_threads' import { availableParallelism, type MessageHandler } from 'poolifier' +import type { IBootstrap } from './IBootstrap.js' import type { AbstractUIServer } from './ui-server/AbstractUIServer.js' import packageJson from '../../package.json' with { type: 'json' } @@ -64,7 +65,7 @@ enum exitCodes { gracefulShutdownError = 4, } -export class Bootstrap extends EventEmitter { +export class Bootstrap extends EventEmitter implements IBootstrap { private static instance: Bootstrap | null = null public get numberOfChargingStationTemplates (): number { return this.templateStatistics.size @@ -122,7 +123,8 @@ export class Bootstrap extends EventEmitter { this.uiServerStarted = false this.templateStatistics = new Map() this.uiServer = UIServerFactory.getUIServerImplementation( - Configuration.getConfigurationSection(ConfigurationSection.uiServer) + Configuration.getConfigurationSection(ConfigurationSection.uiServer), + this ) this.initializeCounters() this.initializeWorkerImplementation( diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index a14addfb..8f96d7b3 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -88,6 +88,7 @@ import { formatDurationMilliSeconds, formatDurationSeconds, getErrorMessage, + getMessageTypeString, getWebSocketCloseEventStatusString, handleFileException, isEmpty, @@ -132,7 +133,6 @@ import { getHashId, getIdTagsFile, getMaxNumberOfEvses, - getMessageTypeString, getPhaseRotationValue, hasFeatureProfile, hasReservationExpired, diff --git a/src/charging-station/Helpers.ts b/src/charging-station/Helpers.ts index f162d7c4..ae29991e 100644 --- a/src/charging-station/Helpers.ts +++ b/src/charging-station/Helpers.ts @@ -45,7 +45,6 @@ import { ConnectorStatusEnum, CurrentType, type EvseTemplate, - MessageType, OCPPVersion, RecurrencyKindType, type Reservation, @@ -1480,16 +1479,3 @@ const getRandomSerialNumberSuffix = (params?: { } return randomSerialNumberSuffix } - -export const getMessageTypeString = (messageType: MessageType | undefined): string => { - switch (messageType) { - case MessageType.CALL_ERROR_MESSAGE: - return 'error' - case MessageType.CALL_MESSAGE: - return 'request' - case MessageType.CALL_RESULT_MESSAGE: - return 'response' - default: - return 'unknown' - } -} diff --git a/src/charging-station/IBootstrap.ts b/src/charging-station/IBootstrap.ts new file mode 100644 index 00000000..b12cbcbe --- /dev/null +++ b/src/charging-station/IBootstrap.ts @@ -0,0 +1,19 @@ +import type { + ChargingStationInfo, + ChargingStationOptions, + SimulatorState, + Statistics, +} from '../types/index.js' + +export interface IBootstrap { + addChargingStation( + index: number, + templateFile: string, + options?: ChargingStationOptions + ): Promise + getLastContiguousIndex(templateName: string): number + getPerformanceStatistics(): IterableIterator | undefined + getState(): SimulatorState + start(): Promise + stop(): Promise +} diff --git a/src/charging-station/index.ts b/src/charging-station/index.ts index 0d25e222..c1eb1836 100644 --- a/src/charging-station/index.ts +++ b/src/charging-station/index.ts @@ -11,7 +11,6 @@ export { checkChargingStationState, getConnectorChargingProfiles, getIdTagsFile, - getMessageTypeString, hasFeatureProfile, hasPendingReservation, hasPendingReservations, @@ -21,3 +20,4 @@ export { resetAuthorizeConnectorStatus, resetConnectorStatus, } from './Helpers.js' +export type { IBootstrap } from './IBootstrap.js' diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 33b802c2..a71f4f1c 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -115,9 +115,11 @@ import { sleep, truncateId, } from '../../../utils/index.js' +import { AuthContext } from '../auth/index.js' +import { isIdTagAuthorized } from '../IdTagAuthorization.js' import { OCPPConstants } from '../OCPPConstants.js' import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' -import { buildMeterValue, OCPPServiceUtils } from '../OCPPServiceUtils.js' +import { buildMeterValue } from '../OCPPServiceUtils.js' import { OCPP16Constants } from './OCPP16Constants.js' import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js' @@ -1244,10 +1246,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { // idTag authorization check required if ( chargingStation.getAuthorizeRemoteTxRequests() && - !(await OCPPServiceUtils.isIdTagAuthorizedUnified( + !(await isIdTagAuthorized( chargingStation, transactionConnectorId, - idTag + idTag, + AuthContext.REMOTE_START )) ) { return this.notifyRemoteStartTransactionRejected( @@ -1323,7 +1326,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService { if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) { return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } - if (!(await OCPPServiceUtils.isIdTagAuthorizedUnified(chargingStation, connectorId, idTag))) { + if (!(await isIdTagAuthorized(chargingStation, connectorId, idTag, AuthContext.RESERVATION))) { return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED } const connectorStatus = chargingStation.getConnectorStatus(connectorId) diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index 90fa1331..fe02790e 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -3,6 +3,7 @@ import type { ValidateFunction } from 'ajv' import type { ChargingStation } from '../../../charging-station/index.js' +import type { OCPP20IdTokenEnumType } from '../../../types/index.js' import { OCPPError } from '../../../exception/index.js' import { @@ -136,12 +137,16 @@ import { } from '../../../utils/index.js' import { buildConfigKey, getConfigurationKey } from '../../ConfigurationKeyUtils.js' import { - getIdTagsFile, hasPendingReservation, hasPendingReservations, resetConnectorStatus, } from '../../Helpers.js' -import { OCPPAuthServiceFactory } from '../auth/index.js' +import { + AuthContext, + AuthorizationStatus, + mapOCPP20TokenType, + OCPPAuthServiceFactory, +} from '../auth/index.js' import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js' import { buildMeterValue, @@ -729,11 +734,9 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { * @param chargingStation - The charging station instance * @returns Promise resolving to ClearCacheResponse */ - protected async handleRequestClearCache ( - chargingStation: ChargingStation - ): Promise { + protected handleRequestClearCache (chargingStation: ChargingStation): OCPP20ClearCacheResponse { try { - const authService = await OCPPAuthServiceFactory.getInstance(chargingStation) + const authService = OCPPAuthServiceFactory.getInstance(chargingStation) // C11.FR.04: IF AuthCacheEnabled is false, CS SHALL send ClearCacheResponse with status Rejected const config = authService.getConfiguration() if (!config.authorizationCacheEnabled) { @@ -766,6 +769,35 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { ) } + private async authorizeToken ( + chargingStation: ChargingStation, + connectorId: number, + tokenValue: string, + tokenLabel: string, + ocpp20TokenType: OCPP20IdTokenEnumType, + context?: AuthContext + ): Promise { + const authService = OCPPAuthServiceFactory.getInstance(chargingStation) + const authResult = await authService.authorize({ + allowOffline: false, + connectorId, + context: context ?? AuthContext.REMOTE_START, + identifier: { + type: mapOCPP20TokenType(ocpp20TokenType), + value: tokenValue, + }, + timestamp: new Date(), + }) + + if (authResult.status !== AuthorizationStatus.ACCEPTED) { + logger.warn( + `${chargingStation.logPrefix()} ${moduleName}.authorizeToken: ${tokenLabel} ${truncateId(tokenValue)} is not authorized` + ) + } + + return authResult.status === AuthorizationStatus.ACCEPTED + } + private buildReportData ( chargingStation: ChargingStation, reportBase: ReportBaseEnumType @@ -2332,7 +2364,13 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { } try { - isAuthorized = this.isIdTokenAuthorized(chargingStation, idToken) + isAuthorized = await this.authorizeToken( + chargingStation, + connectorId, + idToken.idToken, + 'IdToken', + idToken.type + ) } catch (error) { logger.error( `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Authorization error for ${truncateId(idToken.idToken)}:`, @@ -2354,9 +2392,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { } if (!isAuthorized) { - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: IdToken ${truncateId(idToken.idToken)} is not authorized` - ) return { status: RequestStartStopStatusEnumType.Rejected, statusInfo: { @@ -2370,7 +2405,13 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { if (groupIdToken != null) { let isGroupAuthorized = false try { - isGroupAuthorized = this.isIdTokenAuthorized(chargingStation, groupIdToken) + isGroupAuthorized = await this.authorizeToken( + chargingStation, + connectorId, + groupIdToken.idToken, + 'GroupIdToken', + groupIdToken.type + ) } catch (error) { logger.error( `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: Group authorization error for ${truncateId(groupIdToken.idToken)}:`, @@ -2385,11 +2426,7 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { transactionId: generateUUID(), } } - if (!isGroupAuthorized) { - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.handleRequestStartTransaction: GroupIdToken ${truncateId(groupIdToken.idToken)} is not authorized` - ) return { status: RequestStartStopStatusEnumType.Rejected, statusInfo: { @@ -2938,97 +2975,6 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { ) } - private isIdTokenAuthorized ( - chargingStation: ChargingStation, - idToken: OCPP20IdTokenType - ): boolean { - /** - * OCPP 2.0 Authorization Logic Implementation - * - * OCPP 2.0 handles authorization differently from 1.6: - * 1. Check if authorization is required (LocalAuthorizationOffline, AuthorizeRemoteStart variables) - * 2. Local authorization list validation if enabled - * 3. For OCPP 2.0, there's no explicit AuthorizeRequest - authorization is validated - * through configuration variables and local auth lists - * 4. Remote validation through TransactionEvent if needed - */ - - logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Validating idToken ${truncateId(idToken.idToken)} of type ${idToken.type}` - ) - - try { - const localAuthListEnabled = chargingStation.getLocalAuthListEnabled() - const remoteAuthorizationEnabled = chargingStation.stationInfo?.remoteAuthorization ?? true - - if (!localAuthListEnabled && !remoteAuthorizationEnabled) { - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Both local and remote authorization are disabled. Allowing access but this may indicate misconfiguration.` - ) - return true - } - - if (localAuthListEnabled) { - const isLocalAuthorized = this.isIdTokenLocalAuthorized(chargingStation, idToken.idToken) - if (isLocalAuthorized) { - logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} authorized via local auth list` - ) - return true - } - logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} not found in local auth list` - ) - } - - // In OCPP 2.0, remote authorization happens during TransactionEvent processing - if (remoteAuthorizationEnabled) { - logger.debug( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Remote authorization enabled but no explicit remote auth mechanism in OCPP 2.0 - deferring to transaction event validation` - ) - return true - } - - logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: IdToken ${truncateId(idToken.idToken)} authorization failed - not found in local list and remote auth not configured` - ) - return false - } catch (error) { - logger.error( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenAuthorized: Error during authorization validation for ${truncateId(idToken.idToken)}:`, - error - ) - return false - } - } - - /** - * Check if idToken is authorized in local authorization list - * @param chargingStation - The charging station instance - * @param idTokenString - The ID token string to validate - * @returns true if authorized locally, false otherwise - */ - private isIdTokenLocalAuthorized ( - chargingStation: ChargingStation, - idTokenString: string - ): boolean { - try { - const idTagsFile = - chargingStation.stationInfo != null ? getIdTagsFile(chargingStation.stationInfo) : undefined - return ( - chargingStation.hasIdTags() && - idTagsFile != null && - chargingStation.idTagsCache.getIdTags(idTagsFile)?.includes(idTokenString) === true - ) - } catch (error) { - logger.error( - `${chargingStation.logPrefix()} ${moduleName}.isIdTokenLocalAuthorized: Error checking local authorization for ${idTokenString}:`, - error - ) - return false - } - } - private isValidFirmwareLocation (location: string): boolean { try { const url = new URL(location) diff --git a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts index d76b6288..48f6f8df 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts @@ -476,17 +476,15 @@ export class OCPP20ResponseService extends OCPPResponseService { const idTokenValue = requestPayload.idToken.idToken const idTokenInfo = payload.idTokenInfo const identifierType = mapOCPP20TokenType(requestPayload.idToken.type) - OCPPAuthServiceFactory.getInstance(chargingStation) - .then(authService => { - authService.updateCacheEntry(idTokenValue, idTokenInfo, identifierType) - return undefined - }) - .catch((error: unknown) => { - logger.error( - `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Error updating auth cache:`, - error - ) - }) + try { + const authService = OCPPAuthServiceFactory.getInstance(chargingStation) + authService.updateCacheEntry(idTokenValue, idTokenInfo, identifierType) + } catch (error: unknown) { + logger.error( + `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Error updating auth cache:`, + error + ) + } } } if (payload.updatedPersonalMessage != null) { diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts index 6ff837a6..b8109c5b 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts @@ -753,7 +753,7 @@ export const VARIABLE_REGISTRY: Record = { component: OCPP20ComponentName.DeviceDataCtrlr, dataType: DataEnumType.integer, defaultValue: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString(), - description: 'Unified maximum size for any stored or reported value.', + description: 'Maximum size for any stored or reported value.', max: Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH, maxLength: 5, min: 1, diff --git a/src/charging-station/ocpp/IdTagAuthorization.ts b/src/charging-station/ocpp/IdTagAuthorization.ts new file mode 100644 index 00000000..0412fdd4 --- /dev/null +++ b/src/charging-station/ocpp/IdTagAuthorization.ts @@ -0,0 +1,64 @@ +import type { ChargingStation } from '../../charging-station/index.js' + +import { logger } from '../../utils/index.js' +import { + AuthContext, + AuthenticationMethod, + AuthorizationStatus, + IdentifierType, + OCPPAuthServiceFactory, +} from './auth/index.js' + +export const isIdTagAuthorized = async ( + chargingStation: ChargingStation, + connectorId: number, + idTag: string, + context?: AuthContext +): Promise => { + try { + logger.debug( + `${chargingStation.logPrefix()} Authorizing idTag '${idTag}' on connector ${connectorId.toString()}` + ) + + const authService = OCPPAuthServiceFactory.getInstance(chargingStation) + + const authResult = await authService.authorize({ + allowOffline: false, + connectorId, + context: context ?? AuthContext.TRANSACTION_START, + identifier: { + type: IdentifierType.ID_TAG, + value: idTag, + }, + timestamp: new Date(), + }) + + logger.debug( + `${chargingStation.logPrefix()} Authorization result for idTag '${idTag}': ${authResult.status} using ${authResult.method} method` + ) + + if (authResult.status === AuthorizationStatus.ACCEPTED) { + const connectorStatus = chargingStation.getConnectorStatus(connectorId) + if (connectorStatus != null) { + switch (authResult.method) { + case AuthenticationMethod.CACHE: + case AuthenticationMethod.LOCAL_LIST: + case AuthenticationMethod.OFFLINE_FALLBACK: + connectorStatus.localAuthorizeIdTag = idTag + connectorStatus.idTagLocalAuthorized = true + break + case AuthenticationMethod.CERTIFICATE_BASED: + case AuthenticationMethod.NONE: + case AuthenticationMethod.REMOTE_AUTHORIZATION: + break + } + } + return true + } + + return false + } catch (error) { + logger.error(`${chargingStation.logPrefix()} Authorization failed`, error) + return false + } +} diff --git a/src/charging-station/ocpp/OCPPRequestService.ts b/src/charging-station/ocpp/OCPPRequestService.ts index f433945a..c4cab42d 100644 --- a/src/charging-station/ocpp/OCPPRequestService.ts +++ b/src/charging-station/ocpp/OCPPRequestService.ts @@ -27,10 +27,10 @@ import { ensureError, formatDurationMilliSeconds, getErrorMessage, + getMessageTypeString, handleSendMessageError, logger, } from '../../utils/index.js' -import { getMessageTypeString } from '../index.js' import { OCPPConstants } from './OCPPConstants.js' import { ajvErrorsToErrorType, convertDateToISOString } from './OCPPServiceUtils.js' diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index e915b320..5ed47166 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -7,15 +7,10 @@ import { fileURLToPath } from 'node:url' import type { StopTransactionReason } from '../../types/index.js' -import { - type ChargingStation, - getConfigurationKey, - getIdTagsFile, -} from '../../charging-station/index.js' +import { type ChargingStation, getConfigurationKey } from '../../charging-station/index.js' import { BaseError, OCPPError } from '../../exception/index.js' import { AuthorizationStatus, - type AuthorizeRequest, ChargePointErrorCode, ChargingStationEvents, type ConfigurationKeyType, @@ -35,13 +30,11 @@ import { MeterValueMeasurand, MeterValuePhase, MeterValueUnit, - type OCPP16AuthorizeResponse, type OCPP16MeterValue, type OCPP16SampledValue, type OCPP16StatusNotificationRequest, OCPP16StopTransactionReason, OCPP20AuthorizationStatusEnumType, - type OCPP20AuthorizeResponse, type OCPP20ConnectorStatusEnumType, OCPP20IdTokenEnumType, type OCPP20MeterValue, @@ -78,6 +71,8 @@ import { min, roundTo, } from '../../utils/index.js' +import { OCPP16Constants } from './1.6/OCPP16Constants.js' +import { OCPP20Constants } from './2.0/OCPP20Constants.js' import { OCPPConstants } from './OCPPConstants.js' const moduleName = 'OCPPServiceUtils' @@ -141,162 +136,6 @@ export const buildStatusNotificationRequest = ( } } -/** - * Unified authorization function that uses the new OCPP authentication system - * when enabled, with automatic fallback to legacy system - * @param chargingStation - The charging station instance - * @param connectorId - The connector ID for authorization context - * @param idTag - The identifier to authorize - * @returns Promise resolving to authorization result - */ -export const isIdTagAuthorizedUnified = async ( - chargingStation: ChargingStation, - connectorId: number, - idTag: string -): Promise => { - switch (chargingStation.stationInfo?.ocppVersion) { - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - try { - logger.debug( - `${chargingStation.logPrefix()} Using unified auth system for idTag '${idTag}' on connector ${connectorId.toString()}` - ) - - // Dynamic import to avoid circular dependencies - const { OCPPAuthServiceFactory } = await import('./auth/services/OCPPAuthServiceFactory.js') - const { - AuthContext, - AuthorizationStatus: UnifiedAuthorizationStatus, - IdentifierType, - } = await import('./auth/types/AuthTypes.js') - - // Get unified auth service - const authService = await OCPPAuthServiceFactory.getInstance(chargingStation) - - // Create auth request with unified types - const authResult = await authService.authorize({ - allowOffline: false, - connectorId, - context: AuthContext.TRANSACTION_START, - identifier: { - type: IdentifierType.ID_TAG, - value: idTag, - }, - timestamp: new Date(), - }) - - logger.debug( - `${chargingStation.logPrefix()} Unified auth result for idTag '${idTag}': ${authResult.status} using ${authResult.method} method` - ) - - return authResult.status === UnifiedAuthorizationStatus.ACCEPTED - } catch (error) { - logger.error(`${chargingStation.logPrefix()} Unified auth failed for OCPP 2.0`, error) - return false - } - case OCPPVersion.VERSION_16: - logger.debug( - `${chargingStation.logPrefix()} Using legacy auth system for idTag '${idTag}' on connector ${connectorId.toString()}` - ) - return isIdTagAuthorized(chargingStation, connectorId, idTag) - default: - return false - } -} - -/** - * Legacy authorization function - used for OCPP 1.6 only - * OCPP 2.0+ always uses the unified system via isIdTagAuthorizedUnified - * @param chargingStation - The charging station instance - * @param connectorId - The connector ID for authorization context - * @param idTag - The identifier to authorize - * @returns Promise resolving to authorization result - */ -export const isIdTagAuthorized = async ( - chargingStation: ChargingStation, - connectorId: number, - idTag: string -): Promise => { - switch (chargingStation.stationInfo?.ocppVersion) { - case OCPPVersion.VERSION_16: { - if ( - !chargingStation.getLocalAuthListEnabled() && - chargingStation.stationInfo.remoteAuthorization === false - ) { - logger.warn( - `${chargingStation.logPrefix()} The charging station expects to authorize RFID tags but nor local authorization nor remote authorization are enabled. Misbehavior may occur` - ) - } - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - if ( - connectorStatus != null && - chargingStation.getLocalAuthListEnabled() && - isIdTagLocalAuthorized(chargingStation, idTag) - ) { - connectorStatus.localAuthorizeIdTag = idTag - connectorStatus.idTagLocalAuthorized = true - return true - } else if (chargingStation.stationInfo.remoteAuthorization === true) { - return await isIdTagRemoteAuthorized(chargingStation, connectorId, idTag) - } - return false - } - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - return isIdTagAuthorizedUnified(chargingStation, connectorId, idTag) - default: - return false - } -} - -const isIdTagLocalAuthorized = (chargingStation: ChargingStation, idTag: string): boolean => { - const idTagsFile = - chargingStation.stationInfo != null ? getIdTagsFile(chargingStation.stationInfo) : undefined - return ( - chargingStation.hasIdTags() && - idTagsFile != null && - isNotEmptyString(chargingStation.idTagsCache.getIdTags(idTagsFile)?.find(tag => tag === idTag)) - ) -} - -const isIdTagRemoteAuthorized = async ( - chargingStation: ChargingStation, - connectorId: number, - idTag: string -): Promise => { - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - if (connectorStatus != null) { - connectorStatus.authorizeIdTag = idTag - } - switch (chargingStation.stationInfo?.ocppVersion) { - case OCPPVersion.VERSION_16: - return ( - ( - await chargingStation.ocppRequestService.requestHandler< - AuthorizeRequest, - OCPP16AuthorizeResponse - >(chargingStation, RequestCommand.AUTHORIZE, { - idTag, - }) - ).idTagInfo.status === AuthorizationStatus.ACCEPTED - ) - case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: - return ( - ( - await chargingStation.ocppRequestService.requestHandler< - AuthorizeRequest, - OCPP20AuthorizeResponse - >(chargingStation, RequestCommand.AUTHORIZE, { - idToken: { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 }, - }) - ).idTokenInfo.status === AuthorizationStatus.Accepted - ) - default: - return false - } -} - export const sendAndSetConnectorStatus = async ( chargingStation: ChargingStation, commandParams: StatusNotificationRequest, @@ -311,7 +150,7 @@ export const sendAndSetConnectorStatus = async ( return } if (options.send) { - await checkConnectorStatusTransition(chargingStation, connectorId, status) + checkConnectorStatusTransition(chargingStation, connectorId, status) await chargingStation.ocppRequestService.requestHandler< StatusNotificationRequest, StatusNotificationResponse @@ -623,16 +462,15 @@ export const flushQueuedTransactionMessages = async ( } } -const checkConnectorStatusTransition = async ( +const checkConnectorStatusTransition = ( chargingStation: ChargingStation, connectorId: number, status: ConnectorStatusEnum -): Promise => { +): boolean => { const fromStatus = chargingStation.getConnectorStatus(connectorId)?.status let transitionAllowed = false switch (chargingStation.stationInfo?.ocppVersion) { case OCPPVersion.VERSION_16: { - const { OCPP16Constants } = await import('./1.6/OCPP16Constants.js') if ( (connectorId === 0 && OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex( @@ -649,7 +487,6 @@ const checkConnectorStatusTransition = async ( } case OCPPVersion.VERSION_20: case OCPPVersion.VERSION_201: { - const { OCPP20Constants } = await import('./2.0/OCPP20Constants.js') if ( (connectorId === 0 && OCPP20Constants.ChargingStationStatusTransitions.findIndex( @@ -2401,8 +2238,6 @@ const getMeasurandDefaultUnit = ( // eslint-disable-next-line @typescript-eslint/no-extraneous-class export class OCPPServiceUtils { public static readonly buildTransactionEndMeterValue = buildTransactionEndMeterValue - public static readonly isIdTagAuthorized = isIdTagAuthorized - public static readonly isIdTagAuthorizedUnified = isIdTagAuthorizedUnified public static readonly mapStopReasonToOCPP20 = mapStopReasonToOCPP20 public static readonly restoreConnectorStatus = restoreConnectorStatus public static readonly sendAndSetConnectorStatus = sendAndSetConnectorStatus diff --git a/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts b/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts index abbb05e7..9ed202d6 100644 --- a/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts +++ b/src/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.ts @@ -5,7 +5,7 @@ import type { AuthConfiguration, AuthorizationResult, AuthRequest, - UnifiedIdentifier, + Identifier, } from '../types/AuthTypes.js' import { getConfigurationKey } from '../../../../charging-station/ConfigurationKeyUtils.js' @@ -33,7 +33,7 @@ const moduleName = 'OCPP16AuthAdapter' * OCPP 1.6 Authentication Adapter * * Handles authentication for OCPP 1.6 charging stations by translating - * between unified auth types and OCPP 1.6 specific types and protocols. + * between auth types and OCPP 1.6 specific types and protocols. */ export class OCPP16AuthAdapter implements OCPPAuthAdapter { readonly ocppVersion = OCPPVersion.VERSION_16 @@ -42,13 +42,13 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { /** * Perform remote authorization using OCPP 1.6 Authorize message - * @param identifier - Unified identifier containing the idTag to authorize + * @param identifier - Identifier containing the idTag to authorize * @param connectorId - Connector ID where authorization is requested * @param transactionId - Active transaction ID if authorizing during a transaction - * @returns Authorization result with OCPP 1.6 status mapped to unified format + * @returns Authorization result with OCPP 1.6 status mapped to auth format */ async authorizeRemote ( - identifier: UnifiedIdentifier, + identifier: Identifier, connectorId?: number, transactionId?: number | string ): Promise { @@ -75,7 +75,7 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { idTag: identifier.value, }) - // Convert response to unified format + // Convert response to auth format const result: AuthorizationResult = { additionalInfo: { connectorId, @@ -117,40 +117,22 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { } /** - * Convert unified identifier to OCPP 1.6 idTag string - * @param identifier - Unified identifier to convert + * Convert identifier to OCPP 1.6 idTag string + * @param identifier - Identifier to convert * @returns OCPP 1.6 idTag string value */ - convertFromUnifiedIdentifier (identifier: UnifiedIdentifier): string { + convertFromIdentifier (identifier: Identifier): string { // For OCPP 1.6, we always return the string value return identifier.value } /** - * Convert unified authorization result to OCPP 1.6 response format - * @param result - Unified authorization result to convert - * @returns OCPP 1.6 AuthorizeResponse with idTagInfo structure - */ - convertToOCPP16Response (result: AuthorizationResult): OCPP16AuthorizeResponse { - return { - idTagInfo: { - expiryDate: result.expiryDate, - parentIdTag: result.parentId, - status: mapToOCPP16Status(result.status), - }, - } - } - - /** - * Convert OCPP 1.6 idTag to unified identifier + * Convert OCPP 1.6 idTag to identifier * @param identifier - OCPP 1.6 idTag string to convert - * @param additionalData - Optional metadata to include in unified identifier - * @returns Unified identifier with ID_TAG type + * @param additionalData - Optional metadata to include in identifier + * @returns Identifier with ID_TAG type */ - convertToUnifiedIdentifier ( - identifier: string, - additionalData?: Record - ): UnifiedIdentifier { + convertToIdentifier (identifier: string, additionalData?: Record): Identifier { return { additionalInfo: additionalData ? Object.fromEntries(Object.entries(additionalData).map(([k, v]) => [k, String(v)])) @@ -161,13 +143,28 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { } } + /** + * Convert authorization result to OCPP 1.6 response format + * @param result - Authorization result to convert + * @returns OCPP 1.6 AuthorizeResponse with idTagInfo structure + */ + convertToOCPP16Response (result: AuthorizationResult): OCPP16AuthorizeResponse { + return { + idTagInfo: { + expiryDate: result.expiryDate, + parentIdTag: result.parentId, + status: mapToOCPP16Status(result.status), + }, + } + } + /** * Create authorization request from OCPP 1.6 context * @param idTag - OCPP 1.6 idTag string for authorization * @param connectorId - Connector where authorization is requested * @param transactionId - Transaction ID if in transaction context * @param context - Authorization context string (e.g., 'start', 'stop', 'remote_start') - * @returns Unified auth request with identifier and context information + * @returns Auth request with identifier and context information */ createAuthRequest ( idTag: string, @@ -175,7 +172,7 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { transactionId?: number, context?: string ): AuthRequest { - const identifier = this.convertToUnifiedIdentifier(idTag) + const identifier = this.convertToIdentifier(idTag) // Map context string to AuthContext enum let authContext: AuthContext @@ -293,10 +290,10 @@ export class OCPP16AuthAdapter implements OCPPAuthAdapter { /** * Check if identifier is valid for OCPP 1.6 - * @param identifier - Unified identifier to validate + * @param identifier - Identifier to validate * @returns True if identifier has valid ID_TAG type and length within OCPP 1.6 limits */ - isValidIdentifier (identifier: UnifiedIdentifier): boolean { + isValidIdentifier (identifier: Identifier): boolean { // OCPP 1.6 idTag validation if (!identifier.value || typeof identifier.value !== 'string') { return false diff --git a/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts b/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts index 78ba035d..514bc9b7 100644 --- a/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts +++ b/src/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.ts @@ -11,7 +11,7 @@ import type { AuthConfiguration, AuthorizationResult, AuthRequest, - UnifiedIdentifier, + Identifier, } from '../types/AuthTypes.js' import { OCPP20VariableManager } from '../../2.0/OCPP20VariableManager.js' @@ -31,7 +31,9 @@ import { AuthorizationStatus, IdentifierType, mapOCPP20AuthorizationStatus, + mapOCPP20TokenType, mapToOCPP20Status, + mapToOCPP20TokenType, } from '../types/AuthTypes.js' const moduleName = 'OCPP20AuthAdapter' @@ -40,7 +42,7 @@ const moduleName = 'OCPP20AuthAdapter' * OCPP 2.0 Authentication Adapter * * Handles authentication for OCPP 2.0/2.1 charging stations by translating - * between unified auth types and OCPP 2.0 specific types and protocols. + * between auth types and OCPP 2.0 specific types and protocols. */ export class OCPP20AuthAdapter implements OCPPAuthAdapter { readonly ocppVersion = OCPPVersion.VERSION_20 @@ -49,13 +51,13 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { /** * Perform remote authorization using OCPP 2.0 Authorize request. - * @param identifier - Unified identifier containing the IdToken to authorize + * @param identifier - Identifier containing the IdToken to authorize * @param connectorId - EVSE/connector ID for the authorization context * @param transactionId - Optional existing transaction ID for ongoing transactions * @returns Authorization result with status, method, and OCPP 2.0 specific metadata */ async authorizeRemote ( - identifier: UnifiedIdentifier, + identifier: Identifier, connectorId?: number, transactionId?: number | string ): Promise { @@ -83,7 +85,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { } try { - const idToken = this.convertFromUnifiedIdentifier(identifier) + const idToken = this.convertFromIdentifier(identifier) // Validate token format const isValidToken = this.isValidIdentifier(identifier) @@ -117,11 +119,11 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { const authStatus = response.idTokenInfo.status const cacheExpiryDateTime = response.idTokenInfo.cacheExpiryDateTime - // Map OCPP 2.0 authorization status to unified status - const unifiedStatus = mapOCPP20AuthorizationStatus(authStatus) + // Map OCPP 2.0 authorization status + const mappedStatus = mapOCPP20AuthorizationStatus(authStatus) logger.debug( - `${this.chargingStation.logPrefix()} ${moduleName}.${methodName}: Authorization result for ${idToken.idToken}: ${authStatus} (unified: ${unifiedStatus})` + `${this.chargingStation.logPrefix()} ${moduleName}.${methodName}: Authorization result for ${idToken.idToken}: ${authStatus} (mapped: ${mappedStatus})` ) return { @@ -135,7 +137,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { }, isOffline: false, method: AuthenticationMethod.REMOTE_AUTHORIZATION, - status: unifiedStatus, + status: mappedStatus, timestamp: new Date(), } } catch (error) { @@ -177,15 +179,15 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { } /** - * Convert unified identifier to OCPP 2.0 IdToken - * @param identifier - Unified identifier to convert to OCPP 2.0 format + * Convert identifier to OCPP 2.0 IdToken + * @param identifier - Identifier to convert to OCPP 2.0 format * @returns OCPP 2.0 IdTokenType with mapped type and additionalInfo */ - convertFromUnifiedIdentifier (identifier: UnifiedIdentifier): OCPP20IdTokenType { - // Map unified type back to OCPP 2.0 type - const ocpp20Type = this.mapFromUnifiedIdentifierType(identifier.type) + convertFromIdentifier (identifier: Identifier): OCPP20IdTokenType { + // Map type back to OCPP 2.0 type + const ocpp20Type = mapToOCPP20TokenType(identifier.type) - // Convert unified additionalInfo back to OCPP 2.0 format + // Convert additionalInfo back to OCPP 2.0 format const additionalInfo: AdditionalInfoType[] | undefined = identifier.additionalInfo ? Object.entries(identifier.additionalInfo) .filter(([key]) => key.startsWith('info_')) @@ -210,24 +212,15 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { } /** - * Convert unified authorization result to OCPP 2.0 response format - * @param result - Unified authorization result to convert - * @returns OCPP 2.0 RequestStartStopStatusEnumType for transaction responses - */ - convertToOCPP20Response (result: AuthorizationResult): RequestStartStopStatusEnumType { - return mapToOCPP20Status(result.status) - } - - /** - * Convert OCPP 2.0 IdToken to unified identifier + * Convert OCPP 2.0 IdToken to identifier * @param identifier - OCPP 2.0 IdToken or raw string identifier - * @param additionalData - Optional metadata to include in the unified identifier - * @returns Unified identifier with normalized type and metadata + * @param additionalData - Optional metadata to include in the identifier + * @returns Identifier with normalized type and metadata */ - convertToUnifiedIdentifier ( + convertToIdentifier ( identifier: OCPP20IdTokenType | string, additionalData?: Record - ): UnifiedIdentifier { + ): Identifier { let idToken: OCPP20IdTokenType // Handle both string and object formats @@ -241,8 +234,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { idToken = identifier } - // Map OCPP 2.0 IdToken type to unified type - const unifiedType = this.mapToUnifiedIdentifierType(idToken.type) + const identifierType = mapOCPP20TokenType(idToken.type) return { additionalInfo: { @@ -260,18 +252,27 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { : {}), }, parentId: additionalData?.parentId as string | undefined, - type: unifiedType, + type: identifierType, value: idToken.idToken, } } + /** + * Convert authorization result to OCPP 2.0 response format + * @param result - Authorization result to convert + * @returns OCPP 2.0 RequestStartStopStatusEnumType for transaction responses + */ + convertToOCPP20Response (result: AuthorizationResult): RequestStartStopStatusEnumType { + return mapToOCPP20Status(result.status) + } + /** * Create authorization request from OCPP 2.0 context * @param idTokenOrString - OCPP 2.0 IdToken or raw string identifier * @param connectorId - Optional EVSE/connector ID for the request * @param transactionId - Optional transaction ID for ongoing transactions * @param context - Optional context string (e.g., 'start', 'stop', 'remote_start') - * @returns AuthRequest with unified identifier, context, and station metadata + * @returns AuthRequest with identifier, context, and station metadata */ createAuthRequest ( idTokenOrString: OCPP20IdTokenType | string, @@ -279,7 +280,7 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { transactionId?: string, context?: string ): AuthRequest { - const identifier = this.convertToUnifiedIdentifier(idTokenOrString) + const identifier = this.convertToIdentifier(idTokenOrString) // Map context string to AuthContext enum let authContext: AuthContext @@ -415,10 +416,10 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { /** * Check if identifier is valid for OCPP 2.0 - * @param identifier - Unified identifier to validate against OCPP 2.0 rules + * @param identifier - Identifier to validate against OCPP 2.0 rules * @returns True if identifier meets OCPP 2.0 format requirements (max 36 chars, valid type) */ - isValidIdentifier (identifier: UnifiedIdentifier): boolean { + isValidIdentifier (identifier: Identifier): boolean { // OCPP 2.0 idToken validation if (!identifier.value || typeof identifier.value !== 'string') { return false @@ -599,63 +600,6 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { } } - /** - * Map unified identifier type to OCPP 2.0 IdToken type - * @param unifiedType - Unified identifier type to convert - * @returns Corresponding OCPP 2.0 IdTokenEnumType value - */ - private mapFromUnifiedIdentifierType (unifiedType: IdentifierType): OCPP20IdTokenEnumType { - switch (unifiedType) { - case IdentifierType.CENTRAL: - return OCPP20IdTokenEnumType.Central - case IdentifierType.E_MAID: - return OCPP20IdTokenEnumType.eMAID - case IdentifierType.ID_TAG: - return OCPP20IdTokenEnumType.Local - case IdentifierType.ISO14443: - return OCPP20IdTokenEnumType.ISO14443 - case IdentifierType.ISO15693: - return OCPP20IdTokenEnumType.ISO15693 - case IdentifierType.KEY_CODE: - return OCPP20IdTokenEnumType.KeyCode - case IdentifierType.LOCAL: - return OCPP20IdTokenEnumType.Local - case IdentifierType.MAC_ADDRESS: - return OCPP20IdTokenEnumType.MacAddress - case IdentifierType.NO_AUTHORIZATION: - return OCPP20IdTokenEnumType.NoAuthorization - default: - return OCPP20IdTokenEnumType.Central - } - } - - /** - * Map OCPP 2.0 IdToken type to unified identifier type - * @param ocpp20Type - OCPP 2.0 IdTokenEnumType to convert - * @returns Corresponding unified IdentifierType value - */ - private mapToUnifiedIdentifierType (ocpp20Type: OCPP20IdTokenEnumType): IdentifierType { - switch (ocpp20Type) { - case OCPP20IdTokenEnumType.Central: - case OCPP20IdTokenEnumType.Local: - return IdentifierType.ID_TAG - case OCPP20IdTokenEnumType.eMAID: - return IdentifierType.E_MAID - case OCPP20IdTokenEnumType.ISO14443: - return IdentifierType.ISO14443 - case OCPP20IdTokenEnumType.ISO15693: - return IdentifierType.ISO15693 - case OCPP20IdTokenEnumType.KeyCode: - return IdentifierType.KEY_CODE - case OCPP20IdTokenEnumType.MacAddress: - return IdentifierType.MAC_ADDRESS - case OCPP20IdTokenEnumType.NoAuthorization: - return IdentifierType.NO_AUTHORIZATION - default: - return IdentifierType.ID_TAG - } - } - /** * Parse and validate a boolean variable value * @param value - String value to parse ('true', 'false', '1', '0') @@ -682,49 +626,4 @@ export class OCPP20AuthAdapter implements OCPPAuthAdapter { ) return defaultValue } - - /** - * Parse and validate an integer variable value - * @param value - String value to parse as integer - * @param defaultValue - Fallback value when parsing fails or value is undefined - * @param min - Optional minimum allowed value (clamped if exceeded) - * @param max - Optional maximum allowed value (clamped if exceeded) - * @returns Parsed integer value clamped to min/max bounds, or defaultValue if parsing fails - */ - private parseIntegerVariable ( - value: string | undefined, - defaultValue: number, - min?: number, - max?: number - ): number { - if (value == null) { - return defaultValue - } - - const parsed = parseInt(value, 10) - - if (Number.isNaN(parsed)) { - logger.warn( - `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Invalid integer value '${value}', using default: ${defaultValue.toString()}` - ) - return defaultValue - } - - // Validate range - if (min != null && parsed < min) { - logger.warn( - `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Integer value ${parsed.toString()} below minimum ${min.toString()}, using minimum` - ) - return min - } - - if (max != null && parsed > max) { - logger.warn( - `${this.chargingStation.logPrefix()} ${moduleName}.parseIntegerVariable: Integer value ${parsed.toString()} above maximum ${max.toString()}, using maximum` - ) - return max - } - - return parsed - } } diff --git a/src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts b/src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts index ff1cdb69..a9242151 100644 --- a/src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts +++ b/src/charging-station/ocpp/auth/factories/AuthComponentFactory.ts @@ -1,4 +1,4 @@ -import type { ChargingStation } from '../../../ChargingStation.js' +import type { ChargingStation } from '../../../index.js' import type { AuthCache, AuthStrategy, @@ -9,7 +9,12 @@ import type { AuthConfiguration } from '../types/AuthTypes.js' import { OCPPError } from '../../../../exception/index.js' import { ErrorType, OCPPVersion } from '../../../../types/index.js' +import { OCPP16AuthAdapter } from '../adapters/OCPP16AuthAdapter.js' +import { OCPP20AuthAdapter } from '../adapters/OCPP20AuthAdapter.js' import { InMemoryAuthCache } from '../cache/InMemoryAuthCache.js' +import { CertificateAuthStrategy } from '../strategies/CertificateAuthStrategy.js' +import { LocalAuthStrategy } from '../strategies/LocalAuthStrategy.js' +import { RemoteAuthStrategy } from '../strategies/RemoteAuthStrategy.js' import { AuthConfigValidator } from '../utils/ConfigValidator.js' /** @@ -36,7 +41,7 @@ export class AuthComponentFactory { * @returns Single version-specific adapter (OCPP 1.6 or 2.0.x) * @throws {Error} When OCPP version is not found or unsupported */ - static async createAdapter (chargingStation: ChargingStation): Promise { + static createAdapter (chargingStation: ChargingStation): OCPPAuthAdapter { const ocppVersion = chargingStation.stationInfo?.ocppVersion if (!ocppVersion) { @@ -44,17 +49,11 @@ export class AuthComponentFactory { } switch (ocppVersion) { - case OCPPVersion.VERSION_16: { - // Use static import - circular dependency is acceptable here - const { OCPP16AuthAdapter } = await import('../adapters/OCPP16AuthAdapter.js') + case OCPPVersion.VERSION_16: return new OCPP16AuthAdapter(chargingStation) - } case OCPPVersion.VERSION_20: - case OCPPVersion.VERSION_201: { - // Use static import - circular dependency is acceptable here - const { OCPP20AuthAdapter } = await import('../adapters/OCPP20AuthAdapter.js') + case OCPPVersion.VERSION_201: return new OCPP20AuthAdapter(chargingStation) - } default: throw new OCPPError( ErrorType.INTERNAL_ERROR, @@ -82,13 +81,11 @@ export class AuthComponentFactory { * @param config - Authentication configuration with certificate settings * @returns Initialized certificate-based authentication strategy */ - static async createCertificateStrategy ( + static createCertificateStrategy ( chargingStation: ChargingStation, adapter: OCPPAuthAdapter, config: AuthConfiguration - ): Promise { - // Use static import - circular dependency is acceptable here - const { CertificateAuthStrategy } = await import('../strategies/CertificateAuthStrategy.js') + ): AuthStrategy { const strategy = new CertificateAuthStrategy(chargingStation, adapter) strategy.initialize(config) return strategy @@ -121,11 +118,11 @@ export class AuthComponentFactory { * @param config - Authentication configuration controlling local auth behavior * @returns Local strategy instance or undefined if local auth disabled */ - static async createLocalStrategy ( + static createLocalStrategy ( manager: LocalAuthListManager | undefined, cache: AuthCache | undefined, config: AuthConfiguration - ): Promise { + ): AuthStrategy | undefined { if ( !config.localAuthListEnabled && !config.authorizationCacheEnabled && @@ -134,8 +131,6 @@ export class AuthComponentFactory { return undefined } - // Use static import - circular dependency is acceptable here - const { LocalAuthStrategy } = await import('../strategies/LocalAuthStrategy.js') const strategy = new LocalAuthStrategy(manager, cache) strategy.initialize(config) return strategy @@ -149,18 +144,16 @@ export class AuthComponentFactory { * @param localAuthListManager - Optional local auth list manager for C13.FR.01 cache exclusion * @returns Remote strategy instance or undefined if remote auth disabled */ - static async createRemoteStrategy ( + static createRemoteStrategy ( adapter: OCPPAuthAdapter, cache: AuthCache | undefined, config: AuthConfiguration, localAuthListManager?: LocalAuthListManager - ): Promise { + ): AuthStrategy | undefined { if (!config.remoteAuthorization) { return undefined } - // Use static import - circular dependency is acceptable here - const { RemoteAuthStrategy } = await import('../strategies/RemoteAuthStrategy.js') const strategy = new RemoteAuthStrategy(adapter, cache, localAuthListManager) strategy.initialize(config) return strategy @@ -175,29 +168,29 @@ export class AuthComponentFactory { * @param config - Authentication configuration controlling strategy creation * @returns Array of initialized strategies sorted by priority (lowest first) */ - static async createStrategies ( + static createStrategies ( chargingStation: ChargingStation, adapter: OCPPAuthAdapter, manager: LocalAuthListManager | undefined, cache: AuthCache | undefined, config: AuthConfiguration - ): Promise { + ): AuthStrategy[] { const strategies: AuthStrategy[] = [] // Add local strategy if enabled - const localStrategy = await this.createLocalStrategy(manager, cache, config) + const localStrategy = this.createLocalStrategy(manager, cache, config) if (localStrategy) { strategies.push(localStrategy) } // Add remote strategy if enabled - const remoteStrategy = await this.createRemoteStrategy(adapter, cache, config, manager) + const remoteStrategy = this.createRemoteStrategy(adapter, cache, config, manager) if (remoteStrategy) { strategies.push(remoteStrategy) } // Always add certificate strategy - const certStrategy = await this.createCertificateStrategy(chargingStation, adapter, config) + const certStrategy = this.createCertificateStrategy(chargingStation, adapter, config) strategies.push(certStrategy) // Sort by priority diff --git a/src/charging-station/ocpp/auth/index.ts b/src/charging-station/ocpp/auth/index.ts index 57a79faa..b6aa93ee 100644 --- a/src/charging-station/ocpp/auth/index.ts +++ b/src/charging-station/ocpp/auth/index.ts @@ -1,7 +1,7 @@ /** * OCPP Authentication System * - * Unified authentication layer for OCPP 1.6 and 2.0 protocols. + * Authentication layer for OCPP 1.6 and 2.0 protocols. * This module provides a consistent API for handling authentication * across different OCPP versions, with support for multiple authentication * strategies including local lists, remote authorization, and certificate-based auth. @@ -21,7 +21,7 @@ export { OCPP16AuthAdapter } from './adapters/OCPP16AuthAdapter.js' export { OCPP20AuthAdapter } from './adapters/OCPP20AuthAdapter.js' // ============================================================================ -// Type Guards & Mappers (Pure Functions) +// Adapters // ============================================================================ export type { @@ -37,26 +37,21 @@ export type { OCPPAuthAdapter, OCPPAuthService, } from './interfaces/OCPPAuthService.js' - -// ============================================================================ -// Adapters -// ============================================================================ - export { OCPPAuthServiceFactory } from './services/OCPPAuthServiceFactory.js' -export { OCPPAuthServiceImpl } from './services/OCPPAuthServiceImpl.js' // ============================================================================ // Strategies // ============================================================================ +export { OCPPAuthServiceImpl } from './services/OCPPAuthServiceImpl.js' export { CertificateAuthStrategy } from './strategies/CertificateAuthStrategy.js' export { LocalAuthStrategy } from './strategies/LocalAuthStrategy.js' -export { RemoteAuthStrategy } from './strategies/RemoteAuthStrategy.js' // ============================================================================ // Services // ============================================================================ +export { RemoteAuthStrategy } from './strategies/RemoteAuthStrategy.js' export { type AuthConfiguration, AuthContext, @@ -67,6 +62,7 @@ export { AuthorizationStatus, type AuthRequest, type CertificateHashData, + type Identifier, IdentifierType, isCertificateBased, isOCPP16Type, @@ -78,7 +74,6 @@ export { mapToOCPP20Status, mapToOCPP20TokenType, requiresAdditionalInfo, - type UnifiedIdentifier, } from './types/AuthTypes.js' // ============================================================================ diff --git a/src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts b/src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts index b6e5bc98..35536482 100644 --- a/src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts +++ b/src/charging-station/ocpp/auth/interfaces/OCPPAuthService.ts @@ -8,7 +8,7 @@ import type { AuthConfiguration, AuthorizationResult, AuthRequest, - UnifiedIdentifier, + Identifier, } from '../types/AuthTypes.js' import type { IdentifierType } from '../types/AuthTypes.js' @@ -156,6 +156,12 @@ export interface AuthStrategy { */ configure?(config: Partial): Promise + /** + * Get the authorization cache if available + * @returns The authorization cache, or undefined if not available + */ + getAuthCache?(): AuthCache | undefined + /** * Get strategy-specific statistics */ @@ -165,7 +171,7 @@ export interface AuthStrategy { * Initialize the strategy with configuration * @param config - Authentication configuration */ - initialize(config: AuthConfiguration): Promise | void + initialize(config: AuthConfiguration): void /** * Strategy name for identification @@ -324,40 +330,37 @@ export interface LocalAuthListManager { /** * OCPP version-specific adapter interface * - * Adapters handle the translation between unified auth types + * Adapters handle the translation between auth types * and version-specific OCPP types and protocols. */ export interface OCPPAuthAdapter { /** * Perform remote authorization using version-specific protocol - * @param identifier - Unified identifier to authorize + * @param identifier - Identifier to authorize * @param connectorId - Optional connector ID * @param transactionId - Optional transaction ID for stop auth * @returns Promise resolving to authorization result */ authorizeRemote( - identifier: UnifiedIdentifier, + identifier: Identifier, connectorId?: number, transactionId?: number | string ): Promise /** - * Convert unified identifier to version-specific format - * @param identifier - Unified identifier + * Convert identifier to version-specific format + * @param identifier - Identifier * @returns Version-specific identifier */ - convertFromUnifiedIdentifier(identifier: UnifiedIdentifier): TVersionId + convertFromIdentifier(identifier: Identifier): TVersionId /** - * Convert a version-specific identifier to unified format + * Convert a version-specific identifier to common format * @param identifier - Version-specific identifier * @param additionalData - Optional additional context data - * @returns Unified identifier + * @returns Identifier */ - convertToUnifiedIdentifier( - identifier: TVersionId, - additionalData?: Record - ): UnifiedIdentifier + convertToIdentifier(identifier: TVersionId, additionalData?: Record): Identifier /** * Get adapter-specific configuration requirements @@ -383,7 +386,7 @@ export interface OCPPAuthAdapter { /** * Main OCPP Authentication Service interface * - * This is the primary interface that provides unified authentication + * This is the primary interface that provides authentication * capabilities across different OCPP versions and strategies. */ export interface OCPPAuthService { @@ -413,7 +416,7 @@ export interface OCPPAuthService { * Invalidate cached authorization for an identifier * @param identifier - Identifier to invalidate */ - invalidateCache(identifier: UnifiedIdentifier): void + invalidateCache(identifier: Identifier): void /** * Check if an identifier is locally authorized (cache/local list) @@ -422,7 +425,7 @@ export interface OCPPAuthService { * @returns Promise resolving to local authorization result, undefined if not found */ isLocallyAuthorized( - identifier: UnifiedIdentifier, + identifier: Identifier, connectorId?: number ): Promise diff --git a/src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.ts b/src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.ts index f3a9272f..4cde25b2 100644 --- a/src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.ts +++ b/src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.ts @@ -1,4 +1,4 @@ -import type { ChargingStation } from '../../../ChargingStation.js' +import type { ChargingStation } from '../../../index.js' import type { OCPPAuthService } from '../interfaces/OCPPAuthService.js' import { OCPPError } from '../../../../exception/index.js' @@ -8,26 +8,6 @@ import { OCPPAuthServiceImpl } from './OCPPAuthServiceImpl.js' const moduleName = 'OCPPAuthServiceFactory' -/** - * Global symbol key for sharing auth service instances across module boundaries. - * This is required because dynamic imports (used in OCPPServiceUtils) create - * separate module instances, breaking test mock injection. - * Using globalThis ensures the same Map is shared regardless of import method. - */ -const INSTANCES_KEY = Symbol.for('OCPPAuthServiceFactory.instances') - -/** - * Get or create the shared instances Map. - * Uses globalThis to ensure the same Map is used across all module instances, - * which is critical for test mock injection to work with dynamic imports. - * @returns The shared instances Map for OCPPAuthService - */ -const getSharedInstances = (): Map => { - const globalAny = globalThis as Record | undefined> - globalAny[INSTANCES_KEY] ??= new Map() - return globalAny[INSTANCES_KEY] -} - /** * Factory for creating OCPP Authentication Services with proper adapter configuration * @@ -37,9 +17,7 @@ const getSharedInstances = (): Map => { */ // eslint-disable-next-line @typescript-eslint/no-extraneous-class export class OCPPAuthServiceFactory { - private static get instances (): Map { - return getSharedInstances() - } + private static readonly instances = new Map() /** * Clear all cached instances @@ -72,13 +50,13 @@ export class OCPPAuthServiceFactory { * @param chargingStation - The charging station to create the service for * @returns New OCPPAuthService instance (initialized) */ - static async createInstance (chargingStation: ChargingStation): Promise { + static createInstance (chargingStation: ChargingStation): OCPPAuthService { logger.debug( `${chargingStation.logPrefix()} ${moduleName}.createInstance: Creating new uncached auth service` ) const authService = new OCPPAuthServiceImpl(chargingStation) - await authService.initialize() + authService.initialize() return authService } @@ -96,7 +74,7 @@ export class OCPPAuthServiceFactory { * @param chargingStation - The charging station to create the service for * @returns Configured OCPPAuthService instance (initialized) */ - static async getInstance (chargingStation: ChargingStation): Promise { + static getInstance (chargingStation: ChargingStation): OCPPAuthService { const stationId = chargingStation.stationInfo?.chargingStationId ?? 'unknown' // Return existing instance if available @@ -120,7 +98,7 @@ export class OCPPAuthServiceFactory { ) const authService = new OCPPAuthServiceImpl(chargingStation) - await authService.initialize() + authService.initialize() // Cache the instance this.instances.set(stationId, authService) diff --git a/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts b/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts index 58e87650..26672e0e 100644 --- a/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts +++ b/src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.ts @@ -1,6 +1,5 @@ import type { OCPP20IdTokenInfoType } from '../../../../types/index.js' import type { OCPPAuthAdapter } from '../interfaces/OCPPAuthService.js' -import type { LocalAuthStrategy } from '../strategies/LocalAuthStrategy.js' import { OCPPError } from '../../../../exception/index.js' import { ErrorType, OCPPVersion } from '../../../../types/index.js' @@ -11,7 +10,7 @@ import { logger, truncateId, } from '../../../../utils/index.js' -import { type ChargingStation } from '../../../ChargingStation.js' +import { type ChargingStation } from '../../../index.js' import { AuthComponentFactory } from '../factories/AuthComponentFactory.js' import { type AuthStats, @@ -25,9 +24,9 @@ import { type AuthorizationResult, AuthorizationStatus, type AuthRequest, + type Identifier, IdentifierType, mapOCPP20AuthorizationStatus, - type UnifiedIdentifier, } from '../types/AuthTypes.js' import { AuthConfigValidator } from '../utils/ConfigValidator.js' @@ -280,8 +279,8 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { ) // Clear cache in local strategy - const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined - const localAuthCache = localStrategy?.getAuthCache() + const localStrategy = this.strategies.get('local') + const localAuthCache = localStrategy?.getAuthCache?.() if (localAuthCache) { localAuthCache.clear() logger.info( @@ -308,7 +307,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { const supportedTypes = new Set() // Test common identifier types - const testIdentifiers: UnifiedIdentifier[] = [ + const testIdentifiers: Identifier[] = [ { type: IdentifierType.ISO14443, value: 'test' }, { type: IdentifierType.ISO15693, value: 'test' }, { type: IdentifierType.KEY_CODE, value: 'test' }, @@ -413,24 +412,25 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { * Async initialization of adapters and strategies * Must be called after construction */ - public async initialize (): Promise { - await this.initializeAdapter() - await this.initializeStrategies() + public initialize (): void { + this.initializeAdapter() + this.initializeStrategies() } /** * Invalidate cached authorization for an identifier - * @param identifier - Unified identifier whose cached authorization should be invalidated + * @param identifier - Identifier whose cached authorization should be invalidated */ - public invalidateCache (identifier: UnifiedIdentifier): void { + public invalidateCache (identifier: Identifier): void { logger.debug( `${this.chargingStation.logPrefix()} ${moduleName}.invalidateCache: Invalidating cache for identifier: ${truncateId(identifier.value)}` ) // Invalidate in local strategy - const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined - if (localStrategy) { - localStrategy.invalidateCache(identifier.value) + const localStrategy = this.strategies.get('local') + const localAuthCache = localStrategy?.getAuthCache?.() + if (localAuthCache) { + localAuthCache.remove(identifier.value) logger.info( `${this.chargingStation.logPrefix()} ${moduleName}.invalidateCache: Cache invalidated for identifier: ${truncateId(identifier.value)}` ) @@ -443,12 +443,12 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { /** * Check if an identifier is locally authorized (cache/local list) - * @param identifier - Unified identifier to check for local authorization + * @param identifier - Identifier to check for local authorization * @param connectorId - Optional connector ID for context-specific authorization * @returns Promise resolving to the authorization result if locally authorized, or undefined if not found */ public async isLocallyAuthorized ( - identifier: UnifiedIdentifier, + identifier: Identifier, connectorId?: number ): Promise { // Try local strategy first for quick cache/list lookup @@ -480,10 +480,10 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { /** * Check if authentication is supported for given identifier type - * @param identifier - Unified identifier to check for support + * @param identifier - Identifier to check for support * @returns True if at least one strategy can handle the identifier type, false otherwise */ - public isSupported (identifier: UnifiedIdentifier): boolean { + public isSupported (identifier: Identifier): boolean { // Create a minimal request to check applicability const testRequest: AuthRequest = { allowOffline: false, @@ -542,8 +542,8 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { return } - const localStrategy = this.strategies.get('local') as LocalAuthStrategy | undefined - const authCache = localStrategy?.getAuthCache() + const localStrategy = this.strategies.get('local') + const authCache = localStrategy?.getAuthCache?.() if (authCache == null) { logger.debug( `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: No auth cache available` @@ -551,12 +551,12 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { return } - const unifiedStatus = mapOCPP20AuthorizationStatus(idTokenInfo.status) + const mappedStatus = mapOCPP20AuthorizationStatus(idTokenInfo.status) const result: AuthorizationResult = { isOffline: false, method: AuthenticationMethod.REMOTE_AUTHORIZATION, - status: unifiedStatus, + status: mappedStatus, timestamp: new Date(), } @@ -576,7 +576,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { authCache.set(identifier, result, ttl) logger.debug( - `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: Updated cache for ${truncateId(identifier)} status=${unifiedStatus}, ttl=${ttl != null ? ttl.toString() : 'default'}s` + `${this.chargingStation.logPrefix()} ${moduleName}.updateCacheEntry: Updated cache for ${truncateId(identifier)} status=${mappedStatus}, ttl=${ttl != null ? ttl.toString() : 'default'}s` ) } @@ -665,14 +665,14 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { /** * Initialize OCPP adapter using AuthComponentFactory */ - private async initializeAdapter (): Promise { - this.adapter = await AuthComponentFactory.createAdapter(this.chargingStation) + private initializeAdapter (): void { + this.adapter = AuthComponentFactory.createAdapter(this.chargingStation) } /** * Initialize all authentication strategies using AuthComponentFactory */ - private async initializeStrategies (): Promise { + private initializeStrategies (): void { const ocppVersion = this.chargingStation.stationInfo?.ocppVersion if (this.adapter == null) { @@ -683,7 +683,7 @@ export class OCPPAuthServiceImpl implements OCPPAuthService { const authCache = AuthComponentFactory.createAuthCache(this.config) // Create strategies using factory - const strategies = await AuthComponentFactory.createStrategies( + const strategies = AuthComponentFactory.createStrategies( this.chargingStation, this.adapter, undefined, // manager - delegated to OCPPAuthServiceImpl diff --git a/src/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.ts b/src/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.ts index a8482a94..7452a93f 100644 --- a/src/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.ts +++ b/src/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.ts @@ -1,11 +1,11 @@ import type { JsonObject } from '../../../../types/index.js' -import type { ChargingStation } from '../../../ChargingStation.js' +import type { ChargingStation } from '../../../index.js' import type { AuthStrategy, OCPPAuthAdapter } from '../interfaces/OCPPAuthService.js' import type { AuthConfiguration, AuthorizationResult, AuthRequest, - UnifiedIdentifier, + Identifier, } from '../types/AuthTypes.js' import { OCPPVersion } from '../../../../types/index.js' @@ -151,10 +151,10 @@ export class CertificateAuthStrategy implements AuthStrategy { /** * Calculate certificate expiry information - * @param identifier - Unified identifier containing certificate hash data + * @param identifier - Identifier containing certificate hash data * @returns Expiry date extracted from certificate, or undefined if not determinable */ - private calculateCertificateExpiry (identifier: UnifiedIdentifier): Date | undefined { + private calculateCertificateExpiry (identifier: Identifier): Date | undefined { // In a real implementation, this would parse the actual certificate // and extract the notAfter field. For simulation, we'll use a placeholder. @@ -175,14 +175,14 @@ export class CertificateAuthStrategy implements AuthStrategy { * Create a failure result with consistent format * @param status - Authorization status indicating the failure type * @param reason - Human-readable description of why authorization failed - * @param identifier - Unified identifier from the original request + * @param identifier - Identifier from the original request * @param startTime - Request start timestamp for response time calculation * @returns Authorization result with failure status and diagnostic information */ private createFailureResult ( status: AuthorizationStatus, reason: string, - identifier: UnifiedIdentifier, + identifier: Identifier, startTime: number ): AuthorizationResult { const result: AuthorizationResult = { @@ -203,10 +203,10 @@ export class CertificateAuthStrategy implements AuthStrategy { /** * Check if the identifier contains certificate data - * @param identifier - Unified identifier to check for certificate hash data + * @param identifier - Identifier to check for certificate hash data * @returns True if all required certificate hash fields are present and non-empty */ - private hasCertificateData (identifier: UnifiedIdentifier): boolean { + private hasCertificateData (identifier: Identifier): boolean { const certData = identifier.certificateHashData if (!certData) return false @@ -289,10 +289,10 @@ export class CertificateAuthStrategy implements AuthStrategy { /** * Validate certificate data structure and content - * @param identifier - Unified identifier containing certificate hash data to validate + * @param identifier - Identifier containing certificate hash data to validate * @returns Validation result with isValid flag and optional reason on failure */ - private validateCertificateData (identifier: UnifiedIdentifier): { + private validateCertificateData (identifier: Identifier): { isValid: boolean reason?: string } { diff --git a/src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.ts b/src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.ts index 3f0b7bca..b7f071f5 100644 --- a/src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.ts +++ b/src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.ts @@ -13,6 +13,7 @@ import { AuthenticationMethod, AuthErrorCode, AuthorizationStatus, + enhanceAuthResult, } from '../types/AuthTypes.js' const moduleName = 'LocalAuthStrategy' @@ -87,7 +88,12 @@ export class LocalAuthStrategy implements AuthStrategy { ) return undefined } - return this.enhanceResult(localResult, AuthenticationMethod.LOCAL_LIST, startTime) + return enhanceAuthResult( + localResult, + AuthenticationMethod.LOCAL_LIST, + this.name, + startTime + ) } } @@ -104,7 +110,7 @@ export class LocalAuthStrategy implements AuthStrategy { ) return undefined } - return this.enhanceResult(cacheResult, AuthenticationMethod.CACHE, startTime) + return enhanceAuthResult(cacheResult, AuthenticationMethod.CACHE, this.name, startTime) } } @@ -114,7 +120,12 @@ export class LocalAuthStrategy implements AuthStrategy { if (offlineResult) { logger.debug(`${moduleName}: Offline fallback: ${offlineResult.status}`) this.stats.offlineDecisions++ - return this.enhanceResult(offlineResult, AuthenticationMethod.OFFLINE_FALLBACK, startTime) + return enhanceAuthResult( + offlineResult, + AuthenticationMethod.OFFLINE_FALLBACK, + this.name, + startTime + ) } } @@ -415,32 +426,6 @@ export class LocalAuthStrategy implements AuthStrategy { } } - /** - * Enhance authorization result with method and timing info - * @param result - Original authorization result to enhance - * @param method - Authentication method used to obtain the result - * @param startTime - Request start timestamp for response time calculation - * @returns Enhanced authorization result with strategy metadata and timing - */ - private enhanceResult ( - result: AuthorizationResult, - method: AuthenticationMethod, - startTime: number - ): AuthorizationResult { - const responseTime = Date.now() - startTime - - return { - ...result, - additionalInfo: { - ...result.additionalInfo, - responseTimeMs: responseTime, - strategy: this.name, - }, - method, - timestamp: new Date(), - } - } - /** * Handle offline fallback behavior when remote services unavailable * @param request - Authorization request with context information @@ -490,9 +475,9 @@ export class LocalAuthStrategy implements AuthStrategy { } /** - * Map local auth list entry status to unified authorization status + * Map local auth list entry status to authorization status * @param status - Status string from local auth list entry - * @returns Unified authorization status corresponding to the entry status + * @returns Authorization status corresponding to the entry status */ private mapEntryStatus (status: string): AuthorizationStatus { switch (status.toLowerCase()) { diff --git a/src/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.ts b/src/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.ts index f716257b..7a073503 100644 --- a/src/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.ts +++ b/src/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.ts @@ -18,6 +18,7 @@ import { AuthenticationError, AuthenticationMethod, AuthErrorCode, + enhanceAuthResult, IdentifierType, } from '../types/AuthTypes.js' @@ -137,7 +138,12 @@ export class RemoteAuthStrategy implements AuthStrategy { ) } - return this.enhanceResult(result, startTime) + return enhanceAuthResult( + result, + AuthenticationMethod.REMOTE_AUTHORIZATION, + this.name, + startTime + ) } logger.debug( @@ -406,27 +412,6 @@ export class RemoteAuthStrategy implements AuthStrategy { } } - /** - * Enhance authorization result with method and timing info - * @param result - Original authorization result from remote service - * @param startTime - Request start timestamp for response time calculation - * @returns Enhanced authorization result with strategy metadata and timing - */ - private enhanceResult (result: AuthorizationResult, startTime: number): AuthorizationResult { - const responseTime = Date.now() - startTime - - return { - ...result, - additionalInfo: { - ...result.additionalInfo, - responseTimeMs: responseTime, - strategy: this.name, - }, - method: AuthenticationMethod.REMOTE_AUTHORIZATION, - timestamp: new Date(), - } - } - /** * Perform the actual remote authorization with timeout handling * @param request - Authorization request with identifier and context diff --git a/src/charging-station/ocpp/auth/types/AuthTypes.ts b/src/charging-station/ocpp/auth/types/AuthTypes.ts index a96625d5..1e1c1878 100644 --- a/src/charging-station/ocpp/auth/types/AuthTypes.ts +++ b/src/charging-station/ocpp/auth/types/AuthTypes.ts @@ -49,7 +49,7 @@ export enum AuthErrorCode { } /** - * Unified authorization status combining OCPP 1.6 and 2.0 statuses + * Authorization status combining OCPP 1.6 and 2.0 statuses */ export enum AuthorizationStatus { // Common statuses across versions @@ -67,13 +67,13 @@ export enum AuthorizationStatus { NOT_ALLOWED_TYPE_EVSE = 'NotAllowedTypeEVSE', NOT_AT_THIS_LOCATION = 'NotAtThisLocation', NOT_AT_THIS_TIME = 'NotAtThisTime', - // Internal statuses for unified handling + // Internal statuses PENDING = 'Pending', UNKNOWN = 'Unknown', } /** - * Unified identifier types combining OCPP 1.6 and 2.0 token types + * Identifier types combining OCPP 1.6 and 2.0 token types */ export enum IdentifierType { BIOMETRIC = 'Biometric', @@ -210,7 +210,7 @@ export interface AuthRequest { readonly evseId?: number /** Identifier to authenticate */ - readonly identifier: UnifiedIdentifier + readonly identifier: Identifier /** Additional context data */ readonly metadata?: Record @@ -246,9 +246,9 @@ export interface CertificateHashData { } /** - * Unified identifier that works across OCPP versions + * Identifier that works across OCPP versions */ -export interface UnifiedIdentifier { +export interface Identifier { /** Additional info for OCPP 2.0 tokens */ readonly additionalInfo?: Record @@ -345,7 +345,7 @@ export const requiresAdditionalInfo = (type: IdentifierType): boolean => { /** * Type mappers for OCPP version compatibility * - * Provides bidirectional mapping between OCPP version-specific types and unified types. + * Provides bidirectional mapping between OCPP version-specific types and auth types. * This allows the authentication system to work seamlessly across OCPP 1.6 and 2.0. * @remarks * **Edge cases and limitations:** @@ -353,16 +353,16 @@ export const requiresAdditionalInfo = (type: IdentifierType): boolean => { * map to INVALID when converting to OCPP 1.6 * - OCPP 2.0 IdToken types have more granularity than OCPP 1.6 IdTag * - Certificate-based auth (IdentifierType.CERTIFICATE) is only available in OCPP 2.0+ - * - When mapping from unified to OCPP 2.0, unsupported types default to Local + * - When mapping to OCPP 2.0, unsupported types default to Local */ /** - * Maps OCPP 1.6 authorization status to unified status + * Maps OCPP 1.6 authorization status to authorization status * @param status - OCPP 1.6 authorization status - * @returns Unified authorization status + * @returns Authorization status * @example * ```typescript - * const unifiedStatus = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED) + * const status = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED) * // Returns: AuthorizationStatus.ACCEPTED * ``` */ @@ -384,12 +384,12 @@ export const mapOCPP16Status = (status: OCPP16AuthorizationStatus): Authorizatio } /** - * Maps OCPP 2.0 authorization status enum to unified authorization status + * Maps OCPP 2.0 authorization status enum to authorization status * @param status - OCPP 2.0 authorization status - * @returns Unified authorization status + * @returns Authorization status * @example * ```typescript - * const unifiedStatus = mapOCPP20AuthorizationStatus(OCPP20AuthorizationStatusEnumType.Accepted) + * const status = mapOCPP20AuthorizationStatus(OCPP20AuthorizationStatusEnumType.Accepted) * // Returns: AuthorizationStatus.ACCEPTED * ``` */ @@ -423,12 +423,12 @@ export const mapOCPP20AuthorizationStatus = ( } /** - * Maps OCPP 2.0 token type to unified identifier type + * Maps OCPP 2.0 token type to identifier type * @param type - OCPP 2.0 token type - * @returns Unified identifier type + * @returns Identifier type * @example * ```typescript - * const unifiedType = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443) + * const identifierType = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443) * // Returns: IdentifierType.ISO14443 * ``` */ @@ -456,8 +456,8 @@ export const mapOCPP20TokenType = (type: OCPP20IdTokenEnumType): IdentifierType } /** - * Maps unified authorization status to OCPP 1.6 status - * @param status - Unified authorization status + * Maps authorization status to OCPP 1.6 status + * @param status - Authorization status * @returns OCPP 1.6 authorization status * @example * ```typescript @@ -486,8 +486,8 @@ export const mapToOCPP16Status = (status: AuthorizationStatus): OCPP16Authorizat } /** - * Maps unified authorization status to OCPP 2.0 RequestStartStopStatus - * @param status - Unified authorization status + * Maps authorization status to OCPP 2.0 RequestStartStopStatus + * @param status - Authorization status * @returns OCPP 2.0 RequestStartStopStatus * @example * ```typescript @@ -513,8 +513,8 @@ export const mapToOCPP20Status = (status: AuthorizationStatus): RequestStartStop } /** - * Maps unified identifier type to OCPP 2.0 token type - * @param type - Unified identifier type + * Maps identifier type to OCPP 2.0 token type + * @param type - Identifier type * @returns OCPP 2.0 token type * @example * ```typescript @@ -545,3 +545,19 @@ export const mapToOCPP20TokenType = (type: IdentifierType): OCPP20IdTokenEnumTyp return OCPP20IdTokenEnumType.Local } } + +export const enhanceAuthResult = ( + result: AuthorizationResult, + method: AuthenticationMethod, + strategyName: string, + startTime: number +): AuthorizationResult => ({ + ...result, + additionalInfo: { + ...result.additionalInfo, + responseTimeMs: Date.now() - startTime, + strategy: strategyName, + }, + method, + timestamp: new Date(), +}) diff --git a/src/charging-station/ocpp/auth/utils/AuthHelpers.ts b/src/charging-station/ocpp/auth/utils/AuthHelpers.ts index 3358c720..de0a5435 100644 --- a/src/charging-station/ocpp/auth/utils/AuthHelpers.ts +++ b/src/charging-station/ocpp/auth/utils/AuthHelpers.ts @@ -3,7 +3,7 @@ import type { AuthenticationMethod, AuthorizationResult, AuthRequest, - UnifiedIdentifier, + Identifier, } from '../types/AuthTypes.js' import { truncateId } from '../../../../utils/index.js' @@ -30,14 +30,14 @@ function calculateTTL (expiryDate?: Date): number | undefined { /** * Build an AuthRequest with sensible defaults. - * @param identifier - Unified identifier for the request + * @param identifier - Identifier for the request * @param context - Authentication context * @param connectorId - Optional connector ID * @param metadata - Optional additional metadata * @returns Fully populated AuthRequest */ function createAuthRequest ( - identifier: UnifiedIdentifier, + identifier: Identifier, context: AuthContext, connectorId?: number, metadata?: Record @@ -79,7 +79,7 @@ function createRejectedResult ( * @param identifier - Identifier involved in the failed auth attempt * @returns Formatted error string with truncated identifier */ -function formatAuthError (error: Error, identifier: UnifiedIdentifier): string { +function formatAuthError (error: Error, identifier: Identifier): string { return `Authentication failed for identifier ${truncateId(identifier.value)} (${identifier.type}): ${error.message}` } diff --git a/src/charging-station/ocpp/auth/utils/AuthValidators.ts b/src/charging-station/ocpp/auth/utils/AuthValidators.ts index 250f1a17..f3b238a5 100644 --- a/src/charging-station/ocpp/auth/utils/AuthValidators.ts +++ b/src/charging-station/ocpp/auth/utils/AuthValidators.ts @@ -1,4 +1,4 @@ -import type { AuthConfiguration, UnifiedIdentifier } from '../types/AuthTypes.js' +import type { AuthConfiguration, Identifier } from '../types/AuthTypes.js' import { IdentifierType } from '../types/AuthTypes.js' @@ -146,8 +146,8 @@ function validateAuthConfiguration (config: unknown): boolean { } /** - * Validate unified identifier format and constraints - * @param identifier - Unified identifier object to validate (may be any type) + * Validate identifier format and constraints + * @param identifier - Identifier object to validate (may be any type) * @returns True if the identifier has a valid type and value within OCPP length constraints, false otherwise */ function validateIdentifier (identifier: unknown): boolean { @@ -156,14 +156,14 @@ function validateIdentifier (identifier: unknown): boolean { return false } - const unifiedIdentifier = identifier as UnifiedIdentifier + const typedIdentifier = identifier as Identifier - if (!unifiedIdentifier.value) { + if (!typedIdentifier.value) { return false } // Check length constraints based on identifier type - switch (unifiedIdentifier.type) { + switch (typedIdentifier.type) { case IdentifierType.BIOMETRIC: // Fallthrough intentional: all these OCPP 2.0 types share the same validation case IdentifierType.CENTRAL: @@ -177,13 +177,9 @@ function validateIdentifier (identifier: unknown): boolean { case IdentifierType.MOBILE_APP: case IdentifierType.NO_AUTHORIZATION: // OCPP 2.0 types - use IdToken max length - return ( - unifiedIdentifier.value.length > 0 && unifiedIdentifier.value.length <= MAX_IDTOKEN_LENGTH - ) + return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTOKEN_LENGTH case IdentifierType.ID_TAG: - return ( - unifiedIdentifier.value.length > 0 && unifiedIdentifier.value.length <= MAX_IDTAG_LENGTH - ) + return typedIdentifier.value.length > 0 && typedIdentifier.value.length <= MAX_IDTAG_LENGTH default: return false diff --git a/src/charging-station/ocpp/auth/utils/index.ts b/src/charging-station/ocpp/auth/utils/index.ts index 86d47678..5af98ac2 100644 --- a/src/charging-station/ocpp/auth/utils/index.ts +++ b/src/charging-station/ocpp/auth/utils/index.ts @@ -4,6 +4,5 @@ * Provides validation and helper functions for authentication operations */ -export { AuthHelpers } from './AuthHelpers.js' export { AuthValidators } from './AuthValidators.js' export { AuthConfigValidator } from './ConfigValidator.js' diff --git a/src/charging-station/ocpp/index.ts b/src/charging-station/ocpp/index.ts index bfd92858..c1d71766 100644 --- a/src/charging-station/ocpp/index.ts +++ b/src/charging-station/ocpp/index.ts @@ -7,13 +7,13 @@ export { OCPP20RequestService } from './2.0/OCPP20RequestService.js' export { OCPP20ResponseService } from './2.0/OCPP20ResponseService.js' export { buildTransactionEvent, OCPP20ServiceUtils } from './2.0/OCPP20ServiceUtils.js' export { OCPP20VariableManager } from './2.0/OCPP20VariableManager.js' -export { OCPPAuthServiceFactory } from './auth/services/OCPPAuthServiceFactory.js' +export { OCPPAuthServiceFactory } from './auth/index.js' +export { isIdTagAuthorized } from './IdTagAuthorization.js' export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js' export { OCPPRequestService } from './OCPPRequestService.js' export { buildMeterValue, buildStatusNotificationRequest, buildTransactionEndMeterValue, - isIdTagAuthorized, sendAndSetConnectorStatus, } from './OCPPServiceUtils.js' diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index bfc8d03a..4e1c418e 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -3,6 +3,7 @@ import type { WebSocket } from 'ws' import { type IncomingMessage, Server, type ServerResponse } from 'node:http' import { createServer, type Http2Server } from 'node:http2' +import type { IBootstrap } from '../IBootstrap.js' import type { AbstractUIService } from './ui-services/AbstractUIService.js' import { BaseError } from '../../exception/index.js' @@ -35,11 +36,16 @@ export abstract class AbstractUIServer { protected readonly uiServices: Map + private readonly bootstrap: IBootstrap private readonly chargingStations: Map private readonly chargingStationTemplates: Set private clientNotificationDebounceTimer: ReturnType | undefined - public constructor (protected readonly uiServerConfiguration: UIServerConfiguration) { + public constructor ( + protected readonly uiServerConfiguration: UIServerConfiguration, + bootstrap: IBootstrap + ) { + this.bootstrap = bootstrap this.chargingStations = new Map() this.chargingStationTemplates = new Set() switch (this.uiServerConfiguration.version) { @@ -80,6 +86,10 @@ export abstract class AbstractUIServer { return this.chargingStations.delete(hashId) } + public getBootstrap (): IBootstrap { + return this.bootstrap + } + public getChargingStationData (hashId: string): ChargingStationData | undefined { return this.chargingStations.get(hashId) } diff --git a/src/charging-station/ui-server/UIHttpServer.ts b/src/charging-station/ui-server/UIHttpServer.ts index 463613b8..a07f51d3 100644 --- a/src/charging-station/ui-server/UIHttpServer.ts +++ b/src/charging-station/ui-server/UIHttpServer.ts @@ -3,6 +3,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http' import { StatusCodes } from 'http-status-codes' import { createGzip } from 'node:zlib' +import type { IBootstrap } from '../IBootstrap.js' + import { BaseError } from '../../exception/index.js' import { ApplicationProtocolVersion, @@ -41,8 +43,11 @@ export class UIHttpServer extends AbstractUIServer { private readonly acceptsGzip: Map - public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) { - super(uiServerConfiguration) + public constructor ( + protected override readonly uiServerConfiguration: UIServerConfiguration, + bootstrap: IBootstrap + ) { + super(uiServerConfiguration, bootstrap) this.acceptsGzip = new Map() } diff --git a/src/charging-station/ui-server/UIMCPServer.ts b/src/charging-station/ui-server/UIMCPServer.ts index 2d4934f4..03f6381e 100644 --- a/src/charging-station/ui-server/UIMCPServer.ts +++ b/src/charging-station/ui-server/UIMCPServer.ts @@ -7,6 +7,7 @@ import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' +import type { IBootstrap } from '../IBootstrap.js' import type { AbstractUIService } from './ui-services/AbstractUIService.js' import { BaseError } from '../../exception/index.js' @@ -60,8 +61,11 @@ export class UIMCPServer extends AbstractUIServer { private service: AbstractUIService | undefined - public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) { - super(uiServerConfiguration) + public constructor ( + protected override readonly uiServerConfiguration: UIServerConfiguration, + bootstrap: IBootstrap + ) { + super(uiServerConfiguration, bootstrap) this.pendingMcpRequests = new Map() this.ocppSchemaCache = new Map() } diff --git a/src/charging-station/ui-server/UIServerFactory.ts b/src/charging-station/ui-server/UIServerFactory.ts index e7af6f4d..d5f17a13 100644 --- a/src/charging-station/ui-server/UIServerFactory.ts +++ b/src/charging-station/ui-server/UIServerFactory.ts @@ -1,3 +1,4 @@ +import type { IBootstrap } from '../IBootstrap.js' import type { AbstractUIServer } from './AbstractUIServer.js' import { BaseError } from '../../exception/index.js' @@ -21,7 +22,8 @@ export class UIServerFactory { } public static getUIServerImplementation ( - uiServerConfiguration: UIServerConfiguration + uiServerConfiguration: UIServerConfiguration, + bootstrap: IBootstrap ): AbstractUIServer { if ( uiServerConfiguration.authentication?.enabled === true && @@ -70,10 +72,10 @@ export class UIServerFactory { const logMsg = `Application protocol type '${uiServerConfiguration.type}' is deprecated in '${ConfigurationSection.uiServer}' configuration section. Use '${ApplicationProtocol.MCP}' instead` logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`) // eslint-disable-next-line @typescript-eslint/no-deprecated - return new UIHttpServer(uiServerConfiguration) + return new UIHttpServer(uiServerConfiguration, bootstrap) } case ApplicationProtocol.MCP: - return new UIMCPServer(uiServerConfiguration) + return new UIMCPServer(uiServerConfiguration, bootstrap) case ApplicationProtocol.WS: default: if ( @@ -90,7 +92,7 @@ export class UIServerFactory { }'` logger.warn(`${UIServerFactory.logPrefix()} ${logMsg}`) } - return new UIWebSocketServer(uiServerConfiguration) + return new UIWebSocketServer(uiServerConfiguration, bootstrap) } } diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index ee85a2e3..51b39b70 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -4,6 +4,8 @@ import type { Duplex } from 'node:stream' import { StatusCodes } from 'http-status-codes' import { type RawData, WebSocket, WebSocketServer } from 'ws' +import type { IBootstrap } from '../IBootstrap.js' + import { MapStringifyFormat, type ProtocolNotification, @@ -34,8 +36,11 @@ export class UIWebSocketServer extends AbstractUIServer { private readonly webSocketServer: WebSocketServer - public constructor (protected override readonly uiServerConfiguration: UIServerConfiguration) { - super(uiServerConfiguration) + public constructor ( + protected override readonly uiServerConfiguration: UIServerConfiguration, + bootstrap: IBootstrap + ) { + super(uiServerConfiguration, bootstrap) this.webSocketServer = new WebSocketServer({ handleProtocols, maxPayload: DEFAULT_MAX_PAYLOAD_SIZE, diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index 03b57805..58c5a358 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -28,7 +28,6 @@ import { isNotEmptyArray, logger, } from '../../../utils/index.js' -import { Bootstrap } from '../../Bootstrap.js' import { UIServiceWorkerBroadcastChannel } from '../../broadcast-channel/UIServiceWorkerBroadcastChannel.js' import { DEFAULT_MAX_STATIONS, isValidNumberOfStations } from '../UIServerSecurity.js' @@ -240,7 +239,7 @@ export abstract class AbstractUIService { ): Promise { const { numberOfStations, options, template } = requestPayload as AddChargingStationsRequestPayload - if (!Bootstrap.getInstance().getState().started) { + if (!this.uiServer.getBootstrap().getState().started) { return { errorMessage: 'Cannot add charging station(s) while the charging stations simulator is not started', @@ -276,11 +275,13 @@ export abstract class AbstractUIService { for (let i = 0; i < numberOfStations; i++) { let stationInfo: ChargingStationInfo | undefined try { - stationInfo = await Bootstrap.getInstance().addChargingStation( - Bootstrap.getInstance().getLastContiguousIndex(template) + 1, - `${template}.json`, - options - ) + stationInfo = await this.uiServer + .getBootstrap() + .addChargingStation( + this.uiServer.getBootstrap().getLastContiguousIndex(template) + 1, + `${template}.json`, + options + ) if (stationInfo != null) { succeededStationInfos.push(stationInfo) } @@ -331,7 +332,7 @@ export abstract class AbstractUIService { try { return { performanceStatistics: [ - ...(Bootstrap.getInstance().getPerformanceStatistics() ?? []), + ...(this.uiServer.getBootstrap().getPerformanceStatistics() ?? []), ] as JsonType[], status: ResponseStatus.SUCCESS, } satisfies ResponsePayload @@ -347,7 +348,7 @@ export abstract class AbstractUIService { private handleSimulatorState (): ResponsePayload { try { return { - state: Bootstrap.getInstance().getState() as unknown as JsonObject, + state: this.uiServer.getBootstrap().getState() as unknown as JsonObject, status: ResponseStatus.SUCCESS, } satisfies ResponsePayload } catch (error) { @@ -361,7 +362,7 @@ export abstract class AbstractUIService { private async handleStartSimulator (): Promise { try { - await Bootstrap.getInstance().start() + await this.uiServer.getBootstrap().start() return { status: ResponseStatus.SUCCESS } } catch (error) { return { @@ -374,7 +375,7 @@ export abstract class AbstractUIService { private async handleStopSimulator (): Promise { try { - await Bootstrap.getInstance().stop() + await this.uiServer.getBootstrap().stop() return { status: ResponseStatus.SUCCESS } } catch (error) { return { diff --git a/src/utils/ErrorUtils.ts b/src/utils/ErrorUtils.ts index 0bea6739..dbcfb6d5 100644 --- a/src/utils/ErrorUtils.ts +++ b/src/utils/ErrorUtils.ts @@ -12,9 +12,8 @@ import type { RequestCommand, } from '../types/index.js' -import { getMessageTypeString } from '../charging-station/index.js' import { logger } from './Logger.js' -import { isNotEmptyString } from './Utils.js' +import { getMessageTypeString, isNotEmptyString } from './Utils.js' const moduleName = 'ErrorUtils' diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 6a33795e..a635a59a 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -17,6 +17,7 @@ import { env } from 'node:process' import { type JsonType, MapStringifyFormat, + MessageType, type TimestampedData, type UUIDv4, WebSocketCloseEventStatusString, @@ -514,3 +515,16 @@ export const truncateId = (identifier: string, maxLen = 8): string => { } return `${identifier.slice(0, maxLen)}...` } + +export const getMessageTypeString = (messageType: MessageType | undefined): string => { + switch (messageType) { + case MessageType.CALL_ERROR_MESSAGE: + return 'error' + case MessageType.CALL_MESSAGE: + return 'request' + case MessageType.CALL_RESULT_MESSAGE: + return 'response' + default: + return 'unknown' + } +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 84bcf617..f1c900aa 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -43,6 +43,7 @@ export { formatDurationMilliSeconds, formatDurationSeconds, generateUUID, + getMessageTypeString, getRandomFloatFluctuatedRounded, getRandomFloatRounded, getWebSocketCloseEventStatusString, diff --git a/tests/TEST_STYLE_GUIDE.md b/tests/TEST_STYLE_GUIDE.md index 8760f05c..66818a0c 100644 --- a/tests/TEST_STYLE_GUIDE.md +++ b/tests/TEST_STYLE_GUIDE.md @@ -341,7 +341,7 @@ assert.strictEqual(mocks.webSocket.sentMessages.length, 1) | Utility | Purpose | | --------------------------------- | --------------------------- | -| `createMockIdentifier()` | UnifiedIdentifier factory | +| `createMockIdentifier()` | Identifier factory | | `createMockAuthRequest()` | AuthRequest factory | | `createMockAuthorizationResult()` | AuthorizationResult factory | diff --git a/tests/charging-station/Helpers.test.ts b/tests/charging-station/Helpers.test.ts index 9ea30724..d407f541 100644 --- a/tests/charging-station/Helpers.test.ts +++ b/tests/charging-station/Helpers.test.ts @@ -15,7 +15,6 @@ import { getChargingStationId, getHashId, getMaxNumberOfEvses, - getMessageTypeString, getPhaseRotationValue, hasPendingReservation, hasPendingReservations, @@ -32,7 +31,6 @@ import { type ChargingStationTemplate, type ConnectorStatus, ConnectorStatusEnum, - MessageType, type MeterValue, OCPPVersion, type Reservation, @@ -879,22 +877,4 @@ await describe('Helpers', async () => { assert.strictEqual(connectorStatus.MeterValues.length, 1) }) }) - - await describe('getMessageTypeString', async () => { - await it('should return "request" for MessageType.CALL_MESSAGE', () => { - assert.strictEqual(getMessageTypeString(MessageType.CALL_MESSAGE), 'request') - }) - - await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => { - assert.strictEqual(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE), 'response') - }) - - await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => { - assert.strictEqual(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE), 'error') - }) - - await it('should return "unknown" for undefined', () => { - assert.strictEqual(getMessageTypeString(undefined), 'unknown') - }) - }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts index 8883b7c2..5a30056d 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts @@ -10,7 +10,7 @@ import type { ChargingStation } from '../../../../src/charging-station/index.js' import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { GenericStatus, OCPPVersion } from '../../../../src/types/index.js' import { Constants } from '../../../../src/utils/index.js' import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js' @@ -81,7 +81,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock the factory to return our mock auth service const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => Promise.resolve(mockAuthService), + getInstance: (): typeof mockAuthService => mockAuthService, }) try { @@ -132,7 +132,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock the factory to return our mock auth service const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => Promise.resolve(mockAuthService), + getInstance: (): typeof mockAuthService => mockAuthService, }) try { @@ -159,7 +159,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock the factory to return our mock auth service const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => Promise.resolve(mockAuthService), + getInstance: (): typeof mockAuthService => mockAuthService, }) try { @@ -186,7 +186,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock the factory to return our mock auth service const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => Promise.resolve(mockAuthService), + getInstance: (): typeof mockAuthService => mockAuthService, }) try { @@ -213,7 +213,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock the factory to return our mock auth service const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => Promise.resolve(mockAuthService), + getInstance: (): typeof mockAuthService => mockAuthService, }) try { @@ -234,8 +234,9 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () // Mock factory to throw error (simulates no Authorization Cache support) const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory) Object.assign(OCPPAuthServiceFactory, { - getInstance: (): Promise => - Promise.reject(new Error('Authorization Cache not supported')), + getInstance: (): never => { + throw new Error('Authorization Cache not supported') + }, }) try { diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GroupIdStop.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GroupIdStop.test.ts index db4cd183..621790b6 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GroupIdStop.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GroupIdStop.test.ts @@ -11,7 +11,7 @@ import type { OCPP20RequestStartTransactionRequest } from '../../../../src/types import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { OCPP20IdTokenEnumType, RequestStartStopStatusEnumType, diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-MasterPass.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-MasterPass.test.ts index be9c1d9b..170873d5 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-MasterPass.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-MasterPass.test.ts @@ -14,7 +14,7 @@ import type { import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { GetVariableStatusEnumType, OCPP20IdTokenEnumType, diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts index 058ecff9..0b9326b6 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts @@ -19,13 +19,21 @@ import type { import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js' import { + AuthenticationMethod, + AuthorizationStatus, + OCPPAuthServiceFactory, +} from '../../../../src/charging-station/ocpp/auth/index.js' +import { + AttributeEnumType, OCPP20ChargingProfileKindEnumType, OCPP20ChargingProfilePurposeEnumType, + OCPP20ComponentName, OCPP20IdTokenEnumType, OCPP20IncomingRequestCommand, OCPP20RequestCommand, + OCPP20RequiredVariableName, OCPP20TransactionEventEnumType, OCPP20TriggerReasonEnumType, OCPPVersion, @@ -35,7 +43,10 @@ import { Constants } from '../../../../src/utils/index.js' import { flushMicrotasks, standardCleanup } from '../../../helpers/TestLifecycleHelpers.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' -import { createMockAuthService } from '../auth/helpers/MockFactories.js' +import { + createMockAuthorizationResult, + createMockAuthService, +} from '../auth/helpers/MockFactories.js' import { createOCPP20ListenerStation, resetConnectorTransactionState, @@ -96,6 +107,87 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { assert.strictEqual(typeof response.transactionId, 'string') }) + // G03.FR.03 — Reject when auth returns non-ACCEPTED status + await it('should reject RequestStartTransaction when auth returns INVALID status', async () => { + // Arrange: Override mock auth service to return INVALID + const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown' + const rejectingAuthService = createMockAuthService({ + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.INVALID, + }) + ), + }) + OCPPAuthServiceFactory.setInstanceForTesting(stationId, rejectingAuthService) + + const request: OCPP20RequestStartTransactionRequest = { + evseId: 1, + idToken: { + idToken: 'INVALID_TOKEN_001', + type: OCPP20IdTokenEnumType.ISO14443, + }, + remoteStartId: 50, + } + + // Act + const response = await testableService.handleRequestStartTransaction(mockStation, request) + + // Assert + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected) + }) + + // G03.FR.03 — Reject when auth returns BLOCKED for groupIdToken + await it('should reject RequestStartTransaction when groupIdToken auth returns BLOCKED', async () => { + // Arrange: Primary token accepted, group token blocked + let callCount = 0 + const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown' + const mixedAuthService = createMockAuthService({ + authorize: () => { + callCount++ + if (callCount === 1) { + // First call: idToken → accepted + return Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.ACCEPTED, + }) + ) + } + // Second call: groupIdToken → blocked + return Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.BLOCKED, + }) + ) + }, + }) + OCPPAuthServiceFactory.setInstanceForTesting(stationId, mixedAuthService) + + const request: OCPP20RequestStartTransactionRequest = { + evseId: 2, + groupIdToken: { + idToken: 'BLOCKED_GROUP_TOKEN', + type: OCPP20IdTokenEnumType.Central, + }, + idToken: { + idToken: 'VALID_TOKEN_002', + type: OCPP20IdTokenEnumType.ISO14443, + }, + remoteStartId: 51, + } + + // Act + const response = await testableService.handleRequestStartTransaction(mockStation, request) + + // Assert + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected) + }) + // FR: F01.FR.17, F02.FR.05 - Verify remoteStartId and idToken are stored for later TransactionEvent await it('should store remoteStartId and idToken in connector status for TransactionEvent', async () => { const { station: spyChargingStation } = createMockChargingStation({ @@ -343,6 +435,61 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { assert.notStrictEqual(response.transactionId, undefined) }) + await it('should reject RequestStartTransaction when authorization throws an error', async () => { + // Arrange + const stationId = mockStation.stationInfo?.chargingStationId ?? 'unknown' + const throwingAuthService = createMockAuthService({ + authorize: () => Promise.reject(new Error('Auth service unavailable')), + }) + OCPPAuthServiceFactory.setInstanceForTesting(stationId, throwingAuthService) + + const request: OCPP20RequestStartTransactionRequest = { + evseId: 1, + idToken: { + idToken: 'ERROR_TOKEN', + type: OCPP20IdTokenEnumType.ISO14443, + }, + remoteStartId: 99, + } + + // Act + const response = await testableService.handleRequestStartTransaction(mockStation, request) + + // Assert + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected) + }) + + await it('should accept RequestStartTransaction when AuthorizeRemoteStart is false', async () => { + // Arrange + const variableManager = OCPP20VariableManager.getInstance() + variableManager.setVariables(mockStation, [ + { + attributeType: AttributeEnumType.Actual, + attributeValue: 'false', + component: { name: OCPP20ComponentName.AuthCtrlr }, + variable: { name: OCPP20RequiredVariableName.AuthorizeRemoteStart }, + }, + ]) + + const request: OCPP20RequestStartTransactionRequest = { + evseId: 1, + idToken: { + idToken: 'SKIP_AUTH_TOKEN', + type: OCPP20IdTokenEnumType.ISO14443, + }, + remoteStartId: 77, + } + + // Act + const response = await testableService.handleRequestStartTransaction(mockStation, request) + + // Assert + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted) + assert.notStrictEqual(response.transactionId, undefined) + }) + // FR: F02.FR.01 await it('should return proper response structure', async () => { const validRequest: OCPP20RequestStartTransactionRequest = { diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts index 507ab408..d2029e2d 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts @@ -19,7 +19,7 @@ import type { import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { OCPP20IdTokenEnumType, OCPP20IncomingRequestCommand, diff --git a/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts index 20734005..97d9d318 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts @@ -15,7 +15,7 @@ import type { import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { GetCertificateIdUseEnumType, HashAlgorithmEnumType, diff --git a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts index 470373dd..5c71ab6c 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts @@ -16,7 +16,7 @@ import type { import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' +import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/index.js' import { AttributeEnumType, GetVariableStatusEnumType, diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-CacheUpdate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-CacheUpdate.test.ts index 91ff9057..8df8cc9c 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-CacheUpdate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-CacheUpdate.test.ts @@ -8,15 +8,15 @@ import assert from 'node:assert/strict' import { afterEach, beforeEach, describe, it } from 'node:test' import type { ChargingStation } from '../../../../src/charging-station/index.js' +import type { LocalAuthStrategy } from '../../../../src/charging-station/ocpp/auth/index.js' import type { AuthCache } from '../../../../src/charging-station/ocpp/auth/interfaces/OCPPAuthService.js' -import type { LocalAuthStrategy } from '../../../../src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.js' -import { OCPPAuthServiceFactory } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.js' -import { OCPPAuthServiceImpl } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.js' import { AuthorizationStatus, IdentifierType, -} from '../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' + OCPPAuthServiceFactory, + OCPPAuthServiceImpl, +} from '../../../../src/charging-station/ocpp/auth/index.js' import { OCPP20AuthorizationStatusEnumType, OCPPVersion } from '../../../../src/types/index.js' import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' @@ -29,7 +29,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => { let authService: OCPPAuthServiceImpl let authCache: AuthCache - beforeEach(async () => { + beforeEach(() => { const { station: mockStation } = createMockChargingStation({ baseName: TEST_STATION_ID, connectorsCount: 1, @@ -41,7 +41,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => { station = mockStation authService = new OCPPAuthServiceImpl(station) - await authService.initialize() + authService.initialize() const localStrategy = authService.getStrategy('local') as LocalAuthStrategy | undefined const cache = localStrategy?.getAuthCache() @@ -152,7 +152,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => { assert.strictEqual(cachedExpired.status, AuthorizationStatus.EXPIRED) }) - await it('should not update cache when authorizationCacheEnabled is false', async () => { + await it('should not update cache when authorizationCacheEnabled is false', () => { // Arrange — create service with cache disabled const { station: disabledStation } = createMockChargingStation({ baseName: 'CS_CACHE_DISABLED', @@ -163,7 +163,7 @@ await describe('C10 - TransactionEventResponse Cache Update', async () => { }, }) const disabledService = new OCPPAuthServiceImpl(disabledStation) - await disabledService.initialize() + disabledService.initialize() disabledService.updateConfiguration({ authorizationCacheEnabled: false }) const idTokenInfo = { diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts index 5ca8924f..e5f95c39 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts @@ -2009,7 +2009,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { }) await describe('startUpdatedMeterValues', async () => { - await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via unified dispatch', async t => { + await it('should not start OCPP 2.0 timer for OCPP 1.6 stations via dispatch', async t => { await withMockTimers(t, ['setInterval'], async () => { const { station: ocpp16Station } = createMockChargingStation({ baseName: TEST_CHARGING_STATION_BASE_NAME, diff --git a/tests/charging-station/ocpp/IdTagAuthorization.test.ts b/tests/charging-station/ocpp/IdTagAuthorization.test.ts new file mode 100644 index 00000000..7e2c408b --- /dev/null +++ b/tests/charging-station/ocpp/IdTagAuthorization.test.ts @@ -0,0 +1,298 @@ +/** + * @file Tests for IdTagAuthorization + * @description Verifies isIdTagAuthorized authorization function + * + * Covers: + * - isIdTagAuthorized — auth system for all OCPP versions + * - Connector state management based on authentication method + * + * Note: The auth subsystem (OCPPAuthService, strategies, adapters) has its own + * dedicated test suite in tests/charging-station/ocpp/auth/. These tests verify the + * wrapper/dispatch layer only — no overlap. + */ + +import assert from 'node:assert/strict' +import { afterEach, describe, it } from 'node:test' + +import { + AuthContext, + AuthenticationMethod, + AuthorizationStatus, + OCPPAuthServiceFactory, +} from '../../../src/charging-station/ocpp/auth/index.js' +import { isIdTagAuthorized } from '../../../src/charging-station/ocpp/IdTagAuthorization.js' +import { OCPPVersion } from '../../../src/types/index.js' +import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' +import { createMockChargingStation } from '../ChargingStationTestUtils.js' +import { + createMockAuthorizationResult, + createMockAuthService, +} from './auth/helpers/MockFactories.js' + +/** + * Registers a mock auth service for the given station in OCPPAuthServiceFactory. + * @param station - Mock charging station instance + * @param overrides - Partial overrides for the mock auth service methods + * @returns The created mock auth service + */ +function injectMockAuthService ( + station: ReturnType['station'], + overrides?: Parameters[0] +): ReturnType { + const stationId = station.stationInfo?.chargingStationId ?? 'unknown' + const mockService = createMockAuthService(overrides) + OCPPAuthServiceFactory.setInstanceForTesting(stationId, mockService) + return mockService +} + +await describe('IdTagAuthorization', async () => { + afterEach(() => { + OCPPAuthServiceFactory.clearAllInstances() + standardCleanup() + }) + + await describe('isIdTagAuthorized', async () => { + await it('should return false when auth service rejects the tag', async () => { + // Arrange + const { station } = createMockChargingStation({ + stationInfo: { remoteAuthorization: false }, + }) + injectMockAuthService(station, { + authorize: () => + Promise.resolve(createMockAuthorizationResult({ status: AuthorizationStatus.INVALID })), + }) + + // Act + const result = await isIdTagAuthorized(station, 1, 'TAG-001') + + // Assert + assert.strictEqual(result, false) + }) + + await it('should return true when auth service returns LOCAL_LIST accepted', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.LOCAL_LIST, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + const result = await isIdTagAuthorized(station, 1, 'TAG-001') + + // Assert + assert.strictEqual(result, true) + }) + + await it('should set localAuthorizeIdTag when auth returns LOCAL_LIST method', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.LOCAL_LIST, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + await isIdTagAuthorized(station, 1, 'TAG-001') + + // Assert + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-001') + assert.strictEqual(connectorStatus.idTagLocalAuthorized, true) + }) + + await it('should set idTagLocalAuthorized when auth returns CACHE method', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.CACHE, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + await isIdTagAuthorized(station, 1, 'TAG-CACHED') + + // Assert + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-CACHED') + assert.strictEqual(connectorStatus.idTagLocalAuthorized, true) + }) + + await it('should authorize remotely when auth service returns REMOTE_AUTHORIZATION accepted', async () => { + // Arrange + const { station } = createMockChargingStation({ + stationInfo: { remoteAuthorization: true }, + }) + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + const result = await isIdTagAuthorized(station, 1, 'TAG-001') + + // Assert + assert.strictEqual(result, true) + }) + + await it('should not set localAuthorizeIdTag when REMOTE_AUTHORIZATION method', async () => { + // Arrange + const { station } = createMockChargingStation({ + stationInfo: { remoteAuthorization: true }, + }) + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + await isIdTagAuthorized(station, 1, 'TAG-001') + + // Assert + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + assert.strictEqual(connectorStatus.localAuthorizeIdTag, undefined) + assert.notStrictEqual(connectorStatus.idTagLocalAuthorized, true) + }) + + await it('should return false when remote authorization rejects the tag', async () => { + // Arrange + const { station } = createMockChargingStation({ + stationInfo: { remoteAuthorization: true }, + }) + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.REMOTE_AUTHORIZATION, + status: AuthorizationStatus.BLOCKED, + }) + ), + }) + + // Act + const result = await isIdTagAuthorized(station, 1, 'TAG-999') + + // Assert + assert.strictEqual(result, false) + }) + + await it('should return true but not set connector state for non-existent connector', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.LOCAL_LIST, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + const result = await isIdTagAuthorized(station, 99, 'TAG-001') + + // Assert + assert.strictEqual(result, true) + const connectorStatus = station.getConnectorStatus(99) + assert.strictEqual(connectorStatus, undefined) + }) + + await it('should set localAuthorizeIdTag when auth returns OFFLINE_FALLBACK method', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => + Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.OFFLINE_FALLBACK, + status: AuthorizationStatus.ACCEPTED, + }) + ), + }) + + // Act + await isIdTagAuthorized(station, 1, 'TAG-OFFLINE') + + // Assert + const connectorStatus = station.getConnectorStatus(1) + assert.ok(connectorStatus != null) + assert.strictEqual(connectorStatus.localAuthorizeIdTag, 'TAG-OFFLINE') + assert.strictEqual(connectorStatus.idTagLocalAuthorized, true) + }) + + await it('should return false when auth service throws an error', async () => { + // Arrange + const { station } = createMockChargingStation() + injectMockAuthService(station, { + authorize: () => Promise.reject(new Error('Test auth service error')), + }) + + // Act + const result = await isIdTagAuthorized(station, 1, 'TAG-ERROR') + + // Assert + assert.strictEqual(result, false) + }) + + await it('should accept explicit auth context parameter', async () => { + // Arrange + const { station } = createMockChargingStation() + let capturedContext: string | undefined + injectMockAuthService(station, { + authorize: (request: { context?: string }) => { + capturedContext = request.context + return Promise.resolve( + createMockAuthorizationResult({ + method: AuthenticationMethod.LOCAL_LIST, + status: AuthorizationStatus.ACCEPTED, + }) + ) + }, + }) + + // Act + await isIdTagAuthorized(station, 1, 'TAG-001', AuthContext.REMOTE_START) + + // Assert + assert.strictEqual(capturedContext, 'RemoteStart') + }) + + await it('should return false when no auth service is registered for station', async () => { + const { station } = createMockChargingStation({ + ocppVersion: OCPPVersion.VERSION_20, + }) + + const result = await isIdTagAuthorized(station, 1, 'TAG-001') + assert.strictEqual(result, false) + }) + }) +}) diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts deleted file mode 100644 index bf99cb91..00000000 --- a/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * @file Tests for OCPPServiceUtils authorization wrapper functions - * @description Verifies isIdTagAuthorized and isIdTagAuthorizedUnified functions - * - * Covers: - * - isIdTagAuthorized — OCPP 1.6 legacy authorization (local auth list + remote authorization) - * - isIdTagAuthorizedUnified — OCPP 2.0+ unified auth system with fallback - * - * Note: The unified auth subsystem (OCPPAuthService, strategies, adapters) has its own - * dedicated test suite in tests/charging-station/ocpp/auth/. These tests verify the - * wrapper/dispatch layer only — no overlap. - */ - -import assert from 'node:assert/strict' -import { afterEach, describe, it } from 'node:test' - -import { getIdTagsFile } from '../../../src/charging-station/Helpers.js' -import { - isIdTagAuthorized, - isIdTagAuthorizedUnified, -} from '../../../src/charging-station/ocpp/OCPPServiceUtils.js' -import { AuthorizationStatus, OCPPVersion } from '../../../src/types/index.js' -import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' -import { createMockChargingStation } from '../ChargingStationTestUtils.js' - -interface StationLocalAuthOverrides { - getLocalAuthListEnabled: () => boolean - hasIdTags: () => boolean -} - -/** - * Configures local authorization on a mock station with the given id tags. - * @param station - The mock station to configure - * @param mocks - The mock infrastructure (for idTagsCache injection) - * @param tags - Array of id tags to register in the local auth list - */ -function setupLocalAuth ( - station: ReturnType['station'], - mocks: ReturnType['mocks'], - tags: string[] -): void { - const stationOverrides = station as unknown as StationLocalAuthOverrides - stationOverrides.getLocalAuthListEnabled = () => true - stationOverrides.hasIdTags = () => true - const stationInfo = station.stationInfo - if (stationInfo != null) { - const resolvedPath = getIdTagsFile(stationInfo) - if (resolvedPath != null) { - mocks.idTagsCache.setIdTags(resolvedPath, tags) - } - } -} - -await describe('OCPPServiceUtils — authorization wrappers', async () => { - afterEach(() => { - standardCleanup() - }) - - await describe('isIdTagAuthorized (OCPP 1.6 legacy)', async () => { - await it('should return false when local and remote auth are both disabled', async () => { - const { station } = createMockChargingStation({ - stationInfo: { remoteAuthorization: false }, - }) - const result = await isIdTagAuthorized(station, 1, 'TAG-001') - assert.strictEqual(result, false) - }) - - await it('should authorize locally when tag is in local auth list', async () => { - const { mocks, station } = createMockChargingStation({ - stationInfo: { idTagsFile: 'test-idtags.json' }, - }) - setupLocalAuth(station, mocks, ['TAG-001', 'TAG-002']) - - const result = await isIdTagAuthorized(station, 1, 'TAG-001') - assert.strictEqual(result, true) - }) - - await it('should set localAuthorizeIdTag and idTagLocalAuthorized on local auth success', async () => { - const { mocks, station } = createMockChargingStation({ - stationInfo: { idTagsFile: 'test-idtags.json' }, - }) - setupLocalAuth(station, mocks, ['TAG-001']) - - await isIdTagAuthorized(station, 1, 'TAG-001') - - const connectorStatus = station.getConnectorStatus(1) - assert.strictEqual(connectorStatus?.localAuthorizeIdTag, 'TAG-001') - assert.strictEqual(connectorStatus.idTagLocalAuthorized, true) - }) - - await it('should authorize remotely when local auth is disabled and remote returns accepted', async () => { - const { station } = createMockChargingStation({ - ocppRequestService: { - requestHandler: () => - Promise.resolve({ - idTagInfo: { status: AuthorizationStatus.ACCEPTED }, - }), - }, - stationInfo: { remoteAuthorization: true }, - }) - - const result = await isIdTagAuthorized(station, 1, 'TAG-001') - assert.strictEqual(result, true) - }) - - await it('should return false when remote authorization rejects the tag', async () => { - const { station } = createMockChargingStation({ - ocppRequestService: { - requestHandler: () => - Promise.resolve({ - idTagInfo: { status: AuthorizationStatus.BLOCKED }, - }), - }, - stationInfo: { remoteAuthorization: true }, - }) - - const result = await isIdTagAuthorized(station, 1, 'TAG-999') - assert.strictEqual(result, false) - }) - - await it('should return false for non-existent connector even with local auth enabled', async () => { - const { mocks, station } = createMockChargingStation({ - stationInfo: { idTagsFile: 'test-idtags.json', remoteAuthorization: false }, - }) - setupLocalAuth(station, mocks, ['TAG-001']) - - const result = await isIdTagAuthorized(station, 99, 'TAG-001') - assert.strictEqual(result, false) - }) - }) - - await describe('isIdTagAuthorizedUnified', async () => { - await it('should fall back to legacy auth for OCPP 1.6 station', async () => { - const { mocks, station } = createMockChargingStation({ - stationInfo: { idTagsFile: 'test-idtags.json' }, - }) - setupLocalAuth(station, mocks, ['TAG-001']) - - const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001') - assert.strictEqual(result, true) - }) - - await it('should return false on auth error for OCPP 2.0 station', async () => { - const { station } = createMockChargingStation({ - ocppVersion: OCPPVersion.VERSION_20, - }) - - const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001') - assert.strictEqual(result, false) - }) - - await it('should attempt unified auth service for OCPP 2.0.1 station', async () => { - const { station } = createMockChargingStation({ - ocppVersion: OCPPVersion.VERSION_201, - }) - - const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001') - assert.strictEqual(result, false) - }) - }) -}) diff --git a/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts b/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts index 4e9f8d66..39ea0e71 100644 --- a/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts +++ b/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts @@ -220,7 +220,7 @@ await describe('OCPP Authentication', async () => { await describe('Cache Spec Compliance Integration', async () => { // C10.INT.01 - Cache wiring regression - await it('C10.INT.01: OCPPAuthServiceImpl wires auth cache into local strategy', async () => { + await it('C10.INT.01: OCPPAuthServiceImpl wires auth cache into local strategy', () => { const result16 = createMockChargingStation({ baseName: 'TEST_CACHE_WIRING', connectorsCount: 1, @@ -231,7 +231,7 @@ await describe('OCPP Authentication', async () => { }, }) const service = new OCPPAuthServiceImpl(result16.station) - await service.initialize() + service.initialize() const localStrategy = service.getStrategy('local') as LocalAuthStrategy | undefined assert.notStrictEqual(localStrategy, undefined) diff --git a/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts b/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts index 52a3fcbb..5340bde9 100644 --- a/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts +++ b/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts @@ -64,20 +64,20 @@ await describe('OCPP16AuthAdapter', async () => { }) }) - await describe('convertToUnifiedIdentifier', async () => { - await it('should convert OCPP 1.6 idTag to unified identifier', () => { + await describe('convertToIdentifier', async () => { + await it('should convert OCPP 1.6 idTag to identifier', () => { const idTag = 'TEST_ID_TAG' - const result = adapter.convertToUnifiedIdentifier(idTag) + const result = adapter.convertToIdentifier(idTag) const expected = createMockIdentifier(idTag) assert.strictEqual(result.value, expected.value) assert.strictEqual(result.type, expected.type) }) - await it('should include additional data in unified identifier', () => { + await it('should include additional data in identifier', () => { const idTag = 'TEST_ID_TAG' const additionalData = { customField: 'customValue', parentId: 'PARENT_TAG' } - const result = adapter.convertToUnifiedIdentifier(idTag, additionalData) + const result = adapter.convertToIdentifier(idTag, additionalData) assert.strictEqual(result.value, idTag) assert.strictEqual(result.parentId, 'PARENT_TAG') @@ -85,11 +85,11 @@ await describe('OCPP16AuthAdapter', async () => { }) }) - await describe('convertFromUnifiedIdentifier', async () => { - await it('should convert unified identifier to OCPP 1.6 idTag', () => { + await describe('convertFromIdentifier', async () => { + await it('should convert identifier to OCPP 1.6 idTag', () => { const identifier = createMockIdentifier('TEST_ID_TAG') - const result = adapter.convertFromUnifiedIdentifier(identifier) + const result = adapter.convertFromIdentifier(identifier) assert.strictEqual(result, 'TEST_ID_TAG') }) }) @@ -276,7 +276,7 @@ await describe('OCPP16AuthAdapter', async () => { }) await describe('convertToOCPP16Response', async () => { - await it('should convert unified result to OCPP 1.6 response', () => { + await it('should convert result to OCPP 1.6 response', () => { const expiryDate = new Date() const result = createMockAuthorizationResult({ expiryDate, diff --git a/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts b/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts index a9e29510..88c6ab03 100644 --- a/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts +++ b/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts @@ -50,27 +50,26 @@ await describe('OCPP20AuthAdapter', async () => { }) }) - await describe('convertToUnifiedIdentifier', async () => { - await it('should convert OCPP 2.0 IdToken object to unified identifier', () => { + await describe('convertToIdentifier', async () => { + await it('should convert OCPP 2.0 IdToken object to identifier', () => { const idToken = { idToken: 'TEST_TOKEN', type: OCPP20IdTokenEnumType.Central, } - const result = adapter.convertToUnifiedIdentifier(idToken) + const result = adapter.convertToIdentifier(idToken) const expected = createMockIdentifier('TEST_TOKEN') assert.strictEqual(result.value, expected.value) - assert.strictEqual(result.type, IdentifierType.ID_TAG) + assert.strictEqual(result.type, IdentifierType.CENTRAL) assert.strictEqual(result.additionalInfo?.ocpp20Type, OCPP20IdTokenEnumType.Central) }) - await it('should convert string to unified identifier', () => { - const result = adapter.convertToUnifiedIdentifier('STRING_TOKEN') - const expected = createMockIdentifier('STRING_TOKEN') + await it('should convert string to identifier', () => { + const result = adapter.convertToIdentifier('STRING_TOKEN') - assert.strictEqual(result.value, expected.value) - assert.strictEqual(result.type, expected.type) + assert.strictEqual(result.value, 'STRING_TOKEN') + assert.strictEqual(result.type, IdentifierType.CENTRAL) }) await it('should handle eMAID type correctly', () => { @@ -79,7 +78,7 @@ await describe('OCPP20AuthAdapter', async () => { type: OCPP20IdTokenEnumType.eMAID, } - const result = adapter.convertToUnifiedIdentifier(idToken) + const result = adapter.convertToIdentifier(idToken) assert.strictEqual(result.value, 'EMAID123') assert.strictEqual(result.type, IdentifierType.E_MAID) @@ -95,7 +94,7 @@ await describe('OCPP20AuthAdapter', async () => { type: OCPP20IdTokenEnumType.Local, } - const result = adapter.convertToUnifiedIdentifier(idToken) + const result = adapter.convertToIdentifier(idToken) assert.notStrictEqual(result.additionalInfo, undefined) assert.notStrictEqual(result.additionalInfo?.info_0, undefined) @@ -103,11 +102,11 @@ await describe('OCPP20AuthAdapter', async () => { }) }) - await describe('convertFromUnifiedIdentifier', async () => { - await it('should convert unified identifier to OCPP 2.0 IdToken', () => { + await describe('convertFromIdentifier', async () => { + await it('should convert identifier to OCPP 2.0 IdToken', () => { const identifier = createMockIdentifier('CENTRAL_TOKEN', IdentifierType.CENTRAL) - const result = adapter.convertFromUnifiedIdentifier(identifier) + const result = adapter.convertFromIdentifier(identifier) assert.strictEqual(result.idToken, 'CENTRAL_TOKEN') assert.strictEqual(result.type, OCPP20IdTokenEnumType.Central) @@ -116,7 +115,7 @@ await describe('OCPP20AuthAdapter', async () => { await it('should map E_MAID type correctly', () => { const identifier = createMockIdentifier('EMAID_TOKEN', IdentifierType.E_MAID) - const result = adapter.convertFromUnifiedIdentifier(identifier) + const result = adapter.convertFromIdentifier(identifier) assert.strictEqual(result.idToken, 'EMAID_TOKEN') assert.strictEqual(result.type, OCPP20IdTokenEnumType.eMAID) @@ -125,7 +124,7 @@ await describe('OCPP20AuthAdapter', async () => { await it('should handle ID_TAG to Local mapping', () => { const identifier = createMockIdentifier('LOCAL_TAG') - const result = adapter.convertFromUnifiedIdentifier(identifier) + const result = adapter.convertFromIdentifier(identifier) assert.strictEqual(result.type, OCPP20IdTokenEnumType.Local) }) @@ -341,7 +340,7 @@ await describe('OCPP20AuthAdapter', async () => { }) await describe('convertToOCPP20Response', async () => { - await it('should convert unified ACCEPTED status to OCPP 2.0 Accepted', () => { + await it('should convert ACCEPTED status to OCPP 2.0 Accepted', () => { const result = createMockAuthorizationResult({ method: AuthenticationMethod.REMOTE_AUTHORIZATION, }) @@ -350,7 +349,7 @@ await describe('OCPP20AuthAdapter', async () => { assert.strictEqual(response, RequestStartStopStatusEnumType.Accepted) }) - await it('should convert unified rejection statuses to OCPP 2.0 Rejected', () => { + await it('should convert rejection statuses to OCPP 2.0 Rejected', () => { const statuses = [ AuthorizationStatus.BLOCKED, AuthorizationStatus.INVALID, diff --git a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts index 0fc91b86..07b1a7d5 100644 --- a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts +++ b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts @@ -20,51 +20,51 @@ await describe('AuthComponentFactory', async () => { }) await describe('createAdapter', async () => { - await it('should create OCPP 1.6 adapter', async () => { + await it('should create OCPP 1.6 adapter', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) assert.notStrictEqual(adapter, undefined) assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_16) }) - await it('should create OCPP 2.0 adapter', async () => { + await it('should create OCPP 2.0 adapter', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_20 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) assert.notStrictEqual(adapter, undefined) assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_20) }) - await it('should create OCPP 2.0.1 adapter', async () => { + await it('should create OCPP 2.0.1 adapter', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) assert.notStrictEqual(adapter, undefined) assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_20) }) - await it('should throw error for unsupported version', async () => { + await it('should throw error for unsupported version', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: 'VERSION_15' as OCPPVersion }, }) - await assert.rejects(AuthComponentFactory.createAdapter(chargingStation), { + assert.throws(() => AuthComponentFactory.createAdapter(chargingStation), { message: /Unsupported OCPP version/, }) }) - await it('should throw error when no OCPP version', async () => { + await it('should throw error when no OCPP version', () => { const { station: chargingStation } = createMockChargingStation() chargingStation.stationInfo = undefined - await assert.rejects(AuthComponentFactory.createAdapter(chargingStation), { + assert.throws(() => AuthComponentFactory.createAdapter(chargingStation), { message: /OCPP version not found/, }) }) @@ -112,7 +112,7 @@ await describe('AuthComponentFactory', async () => { }) await describe('createLocalStrategy', async () => { - await it('should return undefined when local auth list disabled', async () => { + await it('should return undefined when local auth list disabled', () => { const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -123,12 +123,12 @@ await describe('AuthComponentFactory', async () => { offlineAuthorizationEnabled: false, } - const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config) + const result = AuthComponentFactory.createLocalStrategy(undefined, undefined, config) assert.strictEqual(result, undefined) }) - await it('should create local strategy when enabled', async () => { + await it('should create local strategy when enabled', () => { const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -139,7 +139,7 @@ await describe('AuthComponentFactory', async () => { offlineAuthorizationEnabled: false, } - const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config) + const result = AuthComponentFactory.createLocalStrategy(undefined, undefined, config) assert.notStrictEqual(result, undefined) if (result) { @@ -149,11 +149,11 @@ await describe('AuthComponentFactory', async () => { }) await describe('createRemoteStrategy', async () => { - await it('should return undefined when remote auth disabled', async () => { + await it('should return undefined when remote auth disabled', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -165,16 +165,16 @@ await describe('AuthComponentFactory', async () => { remoteAuthorization: false, } - const result = await AuthComponentFactory.createRemoteStrategy(adapter, undefined, config) + const result = AuthComponentFactory.createRemoteStrategy(adapter, undefined, config) assert.strictEqual(result, undefined) }) - await it('should create remote strategy when enabled', async () => { + await it('should create remote strategy when enabled', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -186,7 +186,7 @@ await describe('AuthComponentFactory', async () => { remoteAuthorization: true, } - const result = await AuthComponentFactory.createRemoteStrategy(adapter, undefined, config) + const result = AuthComponentFactory.createRemoteStrategy(adapter, undefined, config) assert.notStrictEqual(result, undefined) if (result) { @@ -196,11 +196,11 @@ await describe('AuthComponentFactory', async () => { }) await describe('createCertificateStrategy', async () => { - await it('should create certificate strategy', async () => { + await it('should create certificate strategy', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -211,7 +211,7 @@ await describe('AuthComponentFactory', async () => { offlineAuthorizationEnabled: false, } - const result = await AuthComponentFactory.createCertificateStrategy( + const result = AuthComponentFactory.createCertificateStrategy( chargingStation, adapter, config @@ -223,11 +223,11 @@ await describe('AuthComponentFactory', async () => { }) await describe('createStrategies', async () => { - await it('should create only certificate strategy by default', async () => { + await it('should create only certificate strategy by default', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -238,7 +238,7 @@ await describe('AuthComponentFactory', async () => { offlineAuthorizationEnabled: false, } - const result = await AuthComponentFactory.createStrategies( + const result = AuthComponentFactory.createStrategies( chargingStation, adapter, undefined, @@ -250,11 +250,11 @@ await describe('AuthComponentFactory', async () => { assert.strictEqual(result[0].priority, 3) }) - await it('should create and sort all strategies when enabled', async () => { + await it('should create and sort all strategies when enabled', () => { const { station: chargingStation } = createMockChargingStation({ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, }) - const adapter = await AuthComponentFactory.createAdapter(chargingStation) + const adapter = AuthComponentFactory.createAdapter(chargingStation) const config: AuthConfiguration = { allowOfflineTxForUnknownId: false, authorizationCacheEnabled: false, @@ -266,7 +266,7 @@ await describe('AuthComponentFactory', async () => { remoteAuthorization: true, } - const result = await AuthComponentFactory.createStrategies( + const result = AuthComponentFactory.createStrategies( chargingStation, adapter, undefined, diff --git a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts index edd7447c..cfc863a2 100644 --- a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts +++ b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts @@ -20,8 +20,8 @@ import { type AuthorizationResult, AuthorizationStatus, type AuthRequest, + type Identifier, IdentifierType, - type UnifiedIdentifier, } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' import { OCPPVersion } from '../../../../../src/types/index.js' import { OCPP20IdTokenEnumType, type OCPP20IdTokenType } from '../../../../../src/types/index.js' @@ -32,15 +32,15 @@ import { OCPP20IdTokenEnumType, type OCPP20IdTokenType } from '../../../../../sr */ /** - * Create a mock UnifiedIdentifier for any OCPP version. + * Create a mock Identifier for any OCPP version. * @param value - Identifier token value (defaults to 'TEST-TAG-001') * @param type - Identifier type enum value (defaults to ID_TAG) - * @returns Mock UnifiedIdentifier configured for testing + * @returns Mock Identifier configured for testing */ export const createMockIdentifier = ( value = 'TEST-TAG-001', type: IdentifierType = IdentifierType.ID_TAG -): UnifiedIdentifier => ({ +): Identifier => ({ type, value, }) @@ -137,7 +137,7 @@ export const createMockAuthService = (overrides?: Partial): OCP invalidateCache: () => { /* empty */ }, - isLocallyAuthorized: (_identifier: UnifiedIdentifier, _connectorId?: number) => + isLocallyAuthorized: (_identifier: Identifier, _connectorId?: number) => new Promise(resolve => { resolve(undefined) }), @@ -194,7 +194,7 @@ export const createMockOCPPAdapter = ( ocppVersion: OCPPVersion, overrides?: Partial ): OCPPAuthAdapter => ({ - authorizeRemote: (_identifier: UnifiedIdentifier) => + authorizeRemote: (_identifier: Identifier) => new Promise(resolve => { resolve( createMockAuthorizationResult({ @@ -202,11 +202,11 @@ export const createMockOCPPAdapter = ( }) ) }), - convertFromUnifiedIdentifier: (identifier: UnifiedIdentifier) => + convertFromIdentifier: (identifier: Identifier) => ocppVersion === OCPPVersion.VERSION_16 ? identifier.value : { idToken: identifier.value, type: OCPP20IdTokenEnumType.Central }, - convertToUnifiedIdentifier: (identifier: OCPP20IdTokenType | string) => ({ + convertToIdentifier: (identifier: OCPP20IdTokenType | string) => ({ type: IdentifierType.ID_TAG, value: typeof identifier === 'string' ? identifier : identifier.idToken, }), diff --git a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts index 93ca23e7..83c58926 100644 --- a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts +++ b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts @@ -28,41 +28,44 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('getInstance-20', OCPPVersion.VERSION_20) }) - await it('should create a new instance for a charging station', async () => { - const authService = await OCPPAuthServiceFactory.getInstance(mockStation16) + await it('should create a new instance for a charging station', () => { + const authService = OCPPAuthServiceFactory.getInstance(mockStation16) assert.notStrictEqual(authService, undefined) assert.strictEqual(typeof authService.authorize, 'function') assert.strictEqual(typeof authService.getConfiguration, 'function') }) - await it('should return cached instance for same charging station', async () => { - const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation20) - const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20) + await it('should return cached instance for same charging station', () => { + const authService1 = OCPPAuthServiceFactory.getInstance(mockStation20) + const authService2 = OCPPAuthServiceFactory.getInstance(mockStation20) assert.strictEqual(authService1, authService2) }) - await it('should create different instances for different charging stations', async () => { - const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation16) - const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20) + await it('should create different instances for different charging stations', () => { + const authService1 = OCPPAuthServiceFactory.getInstance(mockStation16) + const authService2 = OCPPAuthServiceFactory.getInstance(mockStation20) assert.notStrictEqual(authService1, authService2) }) - await it('should throw error for charging station without stationInfo', async () => { + await it('should throw error for charging station without stationInfo', () => { const mockStation = { logPrefix: () => '[TEST-CS-UNKNOWN]', stationInfo: undefined, } as unknown as ChargingStation - try { - await OCPPAuthServiceFactory.getInstance(mockStation) - assert.fail('Expected getInstance to throw for station without stationInfo') - } catch (error) { - assert.ok(error instanceof Error) - assert.ok(error.message.includes('OCPP version not found in charging station')) - } + assert.throws( + () => { + OCPPAuthServiceFactory.getInstance(mockStation) + }, + (error: unknown) => { + assert.ok(error instanceof Error) + assert.ok(error.message.includes('OCPP version not found in charging station')) + return true + } + ) }) }) @@ -75,18 +78,18 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('createInstance-20', OCPPVersion.VERSION_20) }) - await it('should create a new uncached instance', async () => { - const authService1 = await OCPPAuthServiceFactory.createInstance(mockStation16) - const authService2 = await OCPPAuthServiceFactory.createInstance(mockStation16) + await it('should create a new uncached instance', () => { + const authService1 = OCPPAuthServiceFactory.createInstance(mockStation16) + const authService2 = OCPPAuthServiceFactory.createInstance(mockStation16) assert.notStrictEqual(authService1, undefined) assert.notStrictEqual(authService2, undefined) assert.notStrictEqual(authService1, authService2) }) - await it('should not cache created instances', async () => { + await it('should not cache created instances', () => { const initialCount = OCPPAuthServiceFactory.getCachedInstanceCount() - await OCPPAuthServiceFactory.createInstance(mockStation20) + OCPPAuthServiceFactory.createInstance(mockStation20) const finalCount = OCPPAuthServiceFactory.getCachedInstanceCount() assert.strictEqual(finalCount, initialCount) @@ -102,15 +105,15 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('clearInstance-20', OCPPVersion.VERSION_20) }) - await it('should clear cached instance for a charging station', async () => { + await it('should clear cached instance for a charging station', () => { // Create and cache instance - const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation16) + const authService1 = OCPPAuthServiceFactory.getInstance(mockStation16) // Clear the cache OCPPAuthServiceFactory.clearInstance(mockStation16) // Get instance again - should be a new instance - const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation16) + const authService2 = OCPPAuthServiceFactory.getInstance(mockStation16) assert.notStrictEqual(authService1, authService2) }) @@ -131,10 +134,10 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('clearAll-20', OCPPVersion.VERSION_20) }) - await it('should clear all cached instances', async () => { + await it('should clear all cached instances', () => { // Create multiple instances - await OCPPAuthServiceFactory.getInstance(mockStation16) - await OCPPAuthServiceFactory.getInstance(mockStation20) + OCPPAuthServiceFactory.getInstance(mockStation16) + OCPPAuthServiceFactory.getInstance(mockStation20) // Clear all OCPPAuthServiceFactory.clearAllInstances() @@ -155,17 +158,17 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('count-20', OCPPVersion.VERSION_20) }) - await it('should return the number of cached instances', async () => { + await it('should return the number of cached instances', () => { assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 0) - await OCPPAuthServiceFactory.getInstance(mockStation16) + OCPPAuthServiceFactory.getInstance(mockStation16) assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 1) - await OCPPAuthServiceFactory.getInstance(mockStation20) + OCPPAuthServiceFactory.getInstance(mockStation20) assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2) // Getting same instance should not increase count - await OCPPAuthServiceFactory.getInstance(mockStation16) + OCPPAuthServiceFactory.getInstance(mockStation16) assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2) }) }) @@ -180,9 +183,9 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('stats-20', OCPPVersion.VERSION_20) }) - await it('should return factory statistics', async () => { - await OCPPAuthServiceFactory.getInstance(mockStation16) - await OCPPAuthServiceFactory.getInstance(mockStation20) + await it('should return factory statistics', () => { + OCPPAuthServiceFactory.getInstance(mockStation16) + OCPPAuthServiceFactory.getInstance(mockStation20) const stats = OCPPAuthServiceFactory.getStatistics() @@ -212,16 +215,16 @@ await describe('OCPPAuthServiceFactory', async () => { mockStation20 = createMockAuthServiceTestStation('version-20', OCPPVersion.VERSION_20) }) - await it('should create service for OCPP 1.6 station', async () => { - const authService = await OCPPAuthServiceFactory.getInstance(mockStation16) + await it('should create service for OCPP 1.6 station', () => { + const authService = OCPPAuthServiceFactory.getInstance(mockStation16) assert.notStrictEqual(authService, undefined) assert.strictEqual(typeof authService.authorize, 'function') assert.strictEqual(typeof authService.getConfiguration, 'function') }) - await it('should create service for OCPP 2.0 station', async () => { - const authService = await OCPPAuthServiceFactory.getInstance(mockStation20) + await it('should create service for OCPP 2.0 station', () => { + const authService = OCPPAuthServiceFactory.getInstance(mockStation20) assert.notStrictEqual(authService, undefined) assert.strictEqual(typeof authService.authorize, 'function') @@ -242,10 +245,10 @@ await describe('OCPPAuthServiceFactory', async () => { ) }) - await it('should properly manage instance lifecycle', async () => { + await it('should properly manage instance lifecycle', () => { // Create instances for (const station of mockStations) { - await OCPPAuthServiceFactory.getInstance(station) + OCPPAuthServiceFactory.getInstance(station) } assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 5) diff --git a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts index 21a44f42..919ce6cd 100644 --- a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts +++ b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts @@ -14,8 +14,8 @@ import { AuthContext, AuthenticationMethod, AuthorizationStatus, + type Identifier, IdentifierType, - type UnifiedIdentifier, } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' import { OCPPVersion } from '../../../../../src/types/index.js' import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js' @@ -98,11 +98,11 @@ await describe('OCPPAuthServiceImpl', async () => { mockStation20 = createMockAuthServiceTestStation('isSupported-20', OCPPVersion.VERSION_20) }) - await it('should check if identifier type is supported for OCPP 1.6', async () => { + await it('should check if identifier type is supported for OCPP 1.6', () => { const authService = new OCPPAuthServiceImpl(mockStation16) - await authService.initialize() + authService.initialize() - const idTagIdentifier: UnifiedIdentifier = { + const idTagIdentifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VALID_ID_TAG', } @@ -110,11 +110,11 @@ await describe('OCPPAuthServiceImpl', async () => { assert.strictEqual(authService.isSupported(idTagIdentifier), true) }) - await it('should check if identifier type is supported for OCPP 2.0', async () => { + await it('should check if identifier type is supported for OCPP 2.0', () => { const authService = new OCPPAuthServiceImpl(mockStation20) - await authService.initialize() + authService.initialize() - const centralIdentifier: UnifiedIdentifier = { + const centralIdentifier: Identifier = { type: IdentifierType.CENTRAL, value: 'CENTRAL_ID', } @@ -164,7 +164,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should invalidate cache for specific identifier', () => { const authService = new OCPPAuthServiceImpl(mockStation) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'TAG_TO_INVALIDATE', } @@ -204,7 +204,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should authorize identifier using strategy chain', async () => { const authService = new OCPPAuthServiceImpl(mockStation) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VALID_TAG', } @@ -225,7 +225,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should return INVALID status when all strategies fail', async () => { const authService = new OCPPAuthServiceImpl(mockStation) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'UNKNOWN_TAG', } @@ -253,7 +253,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should check local authorization', async () => { const authService = new OCPPAuthServiceImpl(mockStation) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'LOCAL_TAG', } @@ -277,7 +277,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle OCPP 1.6 specific identifiers', async () => { const authService = new OCPPAuthServiceImpl(mockStation16) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'OCPP16_TAG', } @@ -296,7 +296,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle OCPP 2.0 specific identifiers', async () => { const authService = new OCPPAuthServiceImpl(mockStation20) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.E_MAID, value: 'EMAID123456', } @@ -323,7 +323,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle invalid identifier gracefully', async () => { const authService = new OCPPAuthServiceImpl(mockStation) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: '', } @@ -352,7 +352,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle TRANSACTION_START context', async () => { const authService = new OCPPAuthServiceImpl(mockStation16) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'START_TAG', } @@ -372,7 +372,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle TRANSACTION_STOP context', async () => { const authService = new OCPPAuthServiceImpl(mockStation16) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'STOP_TAG', } @@ -392,7 +392,7 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should handle REMOTE_START context', async () => { const authService = new OCPPAuthServiceImpl(mockStation20) - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.CENTRAL, value: 'REMOTE_ID', } @@ -416,9 +416,9 @@ await describe('OCPPAuthServiceImpl', async () => { mockStation = createMockAuthServiceTestStation('cache-wiring') }) - await it('should have a defined authCache after initialization', async () => { + await it('should have a defined authCache after initialization', () => { const authService = new OCPPAuthServiceImpl(mockStation) - await authService.initialize() + authService.initialize() const localStrategy = authService.getStrategy('local') assert.notStrictEqual(localStrategy, undefined) diff --git a/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts b/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts index 321a5320..19d9a79a 100644 --- a/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts +++ b/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts @@ -47,7 +47,7 @@ await describe('CertificateAuthStrategy', async () => { }) ) }), - convertToUnifiedIdentifier: identifier => ({ + convertToIdentifier: identifier => ({ type: IdentifierType.CERTIFICATE, value: typeof identifier === 'string' ? identifier : JSON.stringify(identifier), }), diff --git a/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts b/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts index 361c9384..5564dc15 100644 --- a/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts +++ b/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts @@ -10,6 +10,7 @@ import { AuthenticationError, AuthErrorCode, AuthorizationStatus, + type Identifier, IdentifierType, isCertificateBased, isOCPP16Type, @@ -20,7 +21,6 @@ import { mapToOCPP20Status, mapToOCPP20TokenType, requiresAdditionalInfo, - type UnifiedIdentifier, } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' import { OCPP16AuthorizationStatus, @@ -66,71 +66,71 @@ await describe('AuthTypes', async () => { await describe('TypeMappers', async () => { await describe('OCPP 1.6 Status Mapping', async () => { - await it('should map OCPP 1.6 ACCEPTED to unified ACCEPTED', () => { + await it('should map OCPP 1.6 ACCEPTED to ACCEPTED', () => { const result = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED) assert.strictEqual(result, AuthorizationStatus.ACCEPTED) }) - await it('should map OCPP 1.6 BLOCKED to unified BLOCKED', () => { + await it('should map OCPP 1.6 BLOCKED to BLOCKED', () => { const result = mapOCPP16Status(OCPP16AuthorizationStatus.BLOCKED) assert.strictEqual(result, AuthorizationStatus.BLOCKED) }) - await it('should map OCPP 1.6 EXPIRED to unified EXPIRED', () => { + await it('should map OCPP 1.6 EXPIRED to EXPIRED', () => { const result = mapOCPP16Status(OCPP16AuthorizationStatus.EXPIRED) assert.strictEqual(result, AuthorizationStatus.EXPIRED) }) - await it('should map OCPP 1.6 INVALID to unified INVALID', () => { + await it('should map OCPP 1.6 INVALID to INVALID', () => { const result = mapOCPP16Status(OCPP16AuthorizationStatus.INVALID) assert.strictEqual(result, AuthorizationStatus.INVALID) }) - await it('should map OCPP 1.6 CONCURRENT_TX to unified CONCURRENT_TX', () => { + await it('should map OCPP 1.6 CONCURRENT_TX to CONCURRENT_TX', () => { const result = mapOCPP16Status(OCPP16AuthorizationStatus.CONCURRENT_TX) assert.strictEqual(result, AuthorizationStatus.CONCURRENT_TX) }) }) await describe('OCPP 2.0 Token Type Mapping', async () => { - await it('should map OCPP 2.0 Central to unified CENTRAL', () => { + await it('should map OCPP 2.0 Central to CENTRAL', () => { const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Central) assert.strictEqual(result, IdentifierType.CENTRAL) }) - await it('should map OCPP 2.0 Local to unified LOCAL', () => { + await it('should map OCPP 2.0 Local to LOCAL', () => { const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Local) assert.strictEqual(result, IdentifierType.LOCAL) }) - await it('should map OCPP 2.0 eMAID to unified E_MAID', () => { + await it('should map OCPP 2.0 eMAID to E_MAID', () => { const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.eMAID) assert.strictEqual(result, IdentifierType.E_MAID) }) - await it('should map OCPP 2.0 ISO14443 to unified ISO14443', () => { + await it('should map OCPP 2.0 ISO14443 to ISO14443', () => { const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443) assert.strictEqual(result, IdentifierType.ISO14443) }) - await it('should map OCPP 2.0 KeyCode to unified KEY_CODE', () => { + await it('should map OCPP 2.0 KeyCode to KEY_CODE', () => { const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.KeyCode) assert.strictEqual(result, IdentifierType.KEY_CODE) }) }) - await describe('Unified to OCPP 1.6 Status Mapping', async () => { - await it('should map unified ACCEPTED to OCPP 1.6 ACCEPTED', () => { + await describe('Auth to OCPP 1.6 Status Mapping', async () => { + await it('should map ACCEPTED to OCPP 1.6 ACCEPTED', () => { const result = mapToOCPP16Status(AuthorizationStatus.ACCEPTED) assert.strictEqual(result, OCPP16AuthorizationStatus.ACCEPTED) }) - await it('should map unified BLOCKED to OCPP 1.6 BLOCKED', () => { + await it('should map BLOCKED to OCPP 1.6 BLOCKED', () => { const result = mapToOCPP16Status(AuthorizationStatus.BLOCKED) assert.strictEqual(result, OCPP16AuthorizationStatus.BLOCKED) }) - await it('should map unified EXPIRED to OCPP 1.6 EXPIRED', () => { + await it('should map EXPIRED to OCPP 1.6 EXPIRED', () => { const result = mapToOCPP16Status(AuthorizationStatus.EXPIRED) assert.strictEqual(result, OCPP16AuthorizationStatus.EXPIRED) }) @@ -151,8 +151,8 @@ await describe('AuthTypes', async () => { }) }) - await describe('Unified to OCPP 2.0 Status Mapping', async () => { - await it('should map unified ACCEPTED to OCPP 2.0 Accepted', () => { + await describe('Auth to OCPP 2.0 Status Mapping', async () => { + await it('should map ACCEPTED to OCPP 2.0 Accepted', () => { const result = mapToOCPP20Status(AuthorizationStatus.ACCEPTED) assert.strictEqual(result, RequestStartStopStatusEnumType.Accepted) }) @@ -173,23 +173,23 @@ await describe('AuthTypes', async () => { }) }) - await describe('Unified to OCPP 2.0 Token Type Mapping', async () => { - await it('should map unified CENTRAL to OCPP 2.0 Central', () => { + await describe('Auth to OCPP 2.0 Token Type Mapping', async () => { + await it('should map CENTRAL to OCPP 2.0 Central', () => { const result = mapToOCPP20TokenType(IdentifierType.CENTRAL) assert.strictEqual(result, OCPP20IdTokenEnumType.Central) }) - await it('should map unified E_MAID to OCPP 2.0 eMAID', () => { + await it('should map E_MAID to OCPP 2.0 eMAID', () => { const result = mapToOCPP20TokenType(IdentifierType.E_MAID) assert.strictEqual(result, OCPP20IdTokenEnumType.eMAID) }) - await it('should map unified ID_TAG to OCPP 2.0 Local', () => { + await it('should map ID_TAG to OCPP 2.0 Local', () => { const result = mapToOCPP20TokenType(IdentifierType.ID_TAG) assert.strictEqual(result, OCPP20IdTokenEnumType.Local) }) - await it('should map unified LOCAL to OCPP 2.0 Local', () => { + await it('should map LOCAL to OCPP 2.0 Local', () => { const result = mapToOCPP20TokenType(IdentifierType.LOCAL) assert.strictEqual(result, OCPP20IdTokenEnumType.Local) }) @@ -249,9 +249,9 @@ await describe('AuthTypes', async () => { }) }) - await describe('UnifiedIdentifier', async () => { + await describe('Identifier', async () => { await it('should create valid OCPP 1.6 identifier', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VALID_ID_TAG', } @@ -261,7 +261,7 @@ await describe('AuthTypes', async () => { }) await it('should create valid OCPP 2.0 identifier with additional info', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { additionalInfo: { contractId: 'CONTRACT123', issuer: 'EMSProvider', @@ -277,7 +277,7 @@ await describe('AuthTypes', async () => { }) await it('should support certificate-based identifier', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { certificateHashData: { hashAlgorithm: 'SHA256', issuerKeyHash: 'KEY_HASH', diff --git a/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts b/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts index ba032bfe..0499605e 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts @@ -10,8 +10,8 @@ import { AuthenticationMethod, type AuthorizationResult, AuthorizationStatus, + type Identifier, IdentifierType, - type UnifiedIdentifier, } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' import { AuthHelpers } from '../../../../../src/charging-station/ocpp/auth/utils/AuthHelpers.js' import { OCPP20MessageFormatEnumType } from '../../../../../src/types/index.js' @@ -52,7 +52,7 @@ await describe('AuthHelpers', async () => { await describe('createAuthRequest', async () => { await it('should create basic auth request with minimal parameters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'TEST123', } @@ -69,7 +69,7 @@ await describe('AuthHelpers', async () => { }) await it('should create auth request with connector ID', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.LOCAL, value: 'LOCAL001', } @@ -82,7 +82,7 @@ await describe('AuthHelpers', async () => { }) await it('should create auth request with metadata', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.CENTRAL, value: 'CENTRAL001', } @@ -125,7 +125,7 @@ await describe('AuthHelpers', async () => { await describe('formatAuthError', async () => { await it('should format error message with truncated identifier', () => { const error = new Error('Connection timeout') - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VERY_LONG_IDENTIFIER_VALUE_12345', } @@ -139,7 +139,7 @@ await describe('AuthHelpers', async () => { await it('should handle short identifiers correctly', () => { const error = new Error('Invalid format') - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.LOCAL, value: 'SHORT', } diff --git a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts index 5396361e..80745b20 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts @@ -7,8 +7,8 @@ import { afterEach, describe, it } from 'node:test' import { type AuthConfiguration, + type Identifier, IdentifierType, - type UnifiedIdentifier, } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js' import { AuthValidators } from '../../../../../src/charging-station/ocpp/auth/utils/AuthValidators.js' import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js' @@ -273,7 +273,7 @@ await describe('AuthValidators', async () => { }) await it('should return false for empty value', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: '', } @@ -282,7 +282,7 @@ await describe('AuthValidators', async () => { }) await it('should return false for ID_TAG exceeding 20 characters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VERY_LONG_IDENTIFIER_VALUE_123456789', } @@ -291,7 +291,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for valid ID_TAG within 20 characters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ID_TAG, value: 'VALID_ID_TAG', } @@ -300,7 +300,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for OCPP 2.0 LOCAL type within 36 characters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.LOCAL, value: 'LOCAL_TOKEN_123', } @@ -309,7 +309,7 @@ await describe('AuthValidators', async () => { }) await it('should return false for OCPP 2.0 type exceeding 36 characters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.CENTRAL, value: 'VERY_LONG_CENTRAL_IDENTIFIER_VALUE_1234567890123456789', } @@ -318,7 +318,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for CENTRAL type within 36 characters', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.CENTRAL, value: 'CENTRAL_TOKEN', } @@ -327,7 +327,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for E_MAID type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.E_MAID, value: 'DE-ABC-123456', } @@ -336,7 +336,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for ISO14443 type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ISO14443, value: '04A2B3C4D5E6F7', } @@ -345,7 +345,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for KEY_CODE type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.KEY_CODE, value: '1234', } @@ -354,7 +354,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for MAC_ADDRESS type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.MAC_ADDRESS, value: '00:11:22:33:44:55', } @@ -363,7 +363,7 @@ await describe('AuthValidators', async () => { }) await it('should return true for NO_AUTHORIZATION type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.NO_AUTHORIZATION, value: 'NO_AUTH', } @@ -372,7 +372,7 @@ await describe('AuthValidators', async () => { }) await it('should return false for unsupported type', () => { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { // @ts-expect-error: Testing invalid type type: 'UNSUPPORTED_TYPE', value: 'VALUE', diff --git a/tests/charging-station/ui-server/UIHttpServer.test.ts b/tests/charging-station/ui-server/UIHttpServer.test.ts index 89d2f54c..906681c7 100644 --- a/tests/charging-station/ui-server/UIHttpServer.test.ts +++ b/tests/charging-station/ui-server/UIHttpServer.test.ts @@ -7,7 +7,7 @@ import assert from 'node:assert/strict' import { afterEach, beforeEach, describe, it } from 'node:test' import { gunzipSync } from 'node:zlib' -import type { UUIDv4 } from '../../../src/types/index.js' +import type { UIServerConfiguration, UUIDv4 } from '../../../src/types/index.js' import { UIHttpServer } from '../../../src/charging-station/ui-server/UIHttpServer.js' import { DEFAULT_COMPRESSION_THRESHOLD } from '../../../src/charging-station/ui-server/UIServerSecurity.js' @@ -15,6 +15,7 @@ import { ApplicationProtocol, ResponseStatus } from '../../../src/types/index.js import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' import { GZIP_STREAM_FLUSH_DELAY_MS, TEST_UUID } from './UIServerTestConstants.js' import { + createMockBootstrap, createMockUIServerConfiguration, MockServerResponse, waitForStreamFlush, @@ -22,6 +23,11 @@ import { // eslint-disable-next-line @typescript-eslint/no-deprecated class TestableUIHttpServer extends UIHttpServer { + public constructor (config: UIServerConfiguration) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + super(config, createMockBootstrap()) + } + public addResponseHandler (uuid: UUIDv4, res: MockServerResponse): void { this.responseHandlers.set(uuid, res as never) } @@ -182,7 +188,8 @@ await describe('UIHttpServer', async () => { port: 9090, }, type: ApplicationProtocol.HTTP, - }) + }), + createMockBootstrap() ) assert.notStrictEqual(serverCustom, undefined) diff --git a/tests/charging-station/ui-server/UIMCPServer.integration.test.ts b/tests/charging-station/ui-server/UIMCPServer.integration.test.ts index a32e0dc2..63493472 100644 --- a/tests/charging-station/ui-server/UIMCPServer.integration.test.ts +++ b/tests/charging-station/ui-server/UIMCPServer.integration.test.ts @@ -17,7 +17,7 @@ import { HttpMethod } from '../../../src/charging-station/ui-server/UIServerUtil import { ApplicationProtocol, ConfigurationSection } from '../../../src/types/index.js' import { Configuration } from '../../../src/utils/Configuration.js' import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' -import { createMockUIServerConfiguration } from './UIServerTestUtils.js' +import { createMockBootstrap, createMockUIServerConfiguration } from './UIServerTestUtils.js' /** * Parse SSE events from raw response body. @@ -118,7 +118,8 @@ await describe('UIMCPServer HTTP Integration', async () => { createMockUIServerConfiguration({ options: { host: 'localhost', port: 0 }, type: ApplicationProtocol.MCP, - }) + }), + createMockBootstrap() ) server.start() const httpServer = Reflect.get(server, 'httpServer') as Server diff --git a/tests/charging-station/ui-server/UIMCPServer.test.ts b/tests/charging-station/ui-server/UIMCPServer.test.ts index 1145c86f..8bae9e8a 100644 --- a/tests/charging-station/ui-server/UIMCPServer.test.ts +++ b/tests/charging-station/ui-server/UIMCPServer.test.ts @@ -12,7 +12,12 @@ import { Readable } from 'node:stream' import { afterEach, beforeEach, describe, it } from 'node:test' import { fileURLToPath } from 'node:url' -import type { ProtocolResponse, RequestPayload, ResponsePayload } from '../../../src/types/index.js' +import type { + ProtocolResponse, + RequestPayload, + ResponsePayload, + UIServerConfiguration, +} from '../../../src/types/index.js' import { mcpToolSchemas, @@ -36,11 +41,16 @@ import { } from '../../helpers/TestLifecycleHelpers.js' import { TEST_HASH_ID, TEST_HASH_ID_2, TEST_UUID, TEST_UUID_2 } from './UIServerTestConstants.js' import { + createMockBootstrap, createMockChargingStationDataWithVersion, createMockUIServerConfiguration, } from './UIServerTestUtils.js' class TestableUIMCPServer extends UIMCPServer { + public constructor (config: UIServerConfiguration) { + super(config, createMockBootstrap()) + } + public callCheckVersionCompatibility ( hashIds: string[] | undefined, ocpp16Payload: Record | undefined, diff --git a/tests/charging-station/ui-server/UIServerFactory.test.ts b/tests/charging-station/ui-server/UIServerFactory.test.ts index 1916a1a6..4f729439 100644 --- a/tests/charging-station/ui-server/UIServerFactory.test.ts +++ b/tests/charging-station/ui-server/UIServerFactory.test.ts @@ -4,7 +4,7 @@ */ import assert from 'node:assert/strict' -import { afterEach, describe, it } from 'node:test' +import { afterEach, beforeEach, describe, it } from 'node:test' import { UIHttpServer } from '../../../src/charging-station/ui-server/UIHttpServer.js' import { UIMCPServer } from '../../../src/charging-station/ui-server/UIMCPServer.js' @@ -12,16 +12,22 @@ import { UIServerFactory } from '../../../src/charging-station/ui-server/UIServe import { UIWebSocketServer } from '../../../src/charging-station/ui-server/UIWebSocketServer.js' import { ApplicationProtocol, ApplicationProtocolVersion } from '../../../src/types/index.js' import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' -import { createMockUIServerConfiguration } from './UIServerTestUtils.js' +import { createMockBootstrap, createMockUIServerConfiguration } from './UIServerTestUtils.js' await describe('UIServerFactory', async () => { + let mockBootstrap: ReturnType + + beforeEach(() => { + mockBootstrap = createMockBootstrap() + }) + afterEach(() => { standardCleanup() }) await it('should create UIHttpServer for HTTP protocol', () => { const config = createMockUIServerConfiguration({ type: ApplicationProtocol.HTTP }) - const server = UIServerFactory.getUIServerImplementation(config) + const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap) // eslint-disable-next-line @typescript-eslint/no-deprecated assert.ok(server instanceof UIHttpServer) server.stop() @@ -29,14 +35,14 @@ await describe('UIServerFactory', async () => { await it('should create UIWebSocketServer for WS protocol', () => { const config = createMockUIServerConfiguration({ type: ApplicationProtocol.WS }) - const server = UIServerFactory.getUIServerImplementation(config) + const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap) assert.ok(server instanceof UIWebSocketServer) server.stop() }) await it('should create UIMCPServer for MCP protocol', () => { const config = createMockUIServerConfiguration({ type: ApplicationProtocol.MCP }) - const server = UIServerFactory.getUIServerImplementation(config) + const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap) assert.ok(server instanceof UIMCPServer) server.stop() }) @@ -46,7 +52,7 @@ await describe('UIServerFactory', async () => { type: ApplicationProtocol.MCP, version: ApplicationProtocolVersion.VERSION_20, }) - const server = UIServerFactory.getUIServerImplementation(config) + const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap) assert.strictEqual(config.version, ApplicationProtocolVersion.VERSION_11) server.stop() }) @@ -56,7 +62,7 @@ await describe('UIServerFactory', async () => { type: ApplicationProtocol.WS, version: ApplicationProtocolVersion.VERSION_20, }) - const server = UIServerFactory.getUIServerImplementation(config) + const server = UIServerFactory.getUIServerImplementation(config, mockBootstrap) assert.strictEqual(config.version, ApplicationProtocolVersion.VERSION_11) server.stop() }) diff --git a/tests/charging-station/ui-server/UIServerTestUtils.ts b/tests/charging-station/ui-server/UIServerTestUtils.ts index c162f0b8..48f99a30 100644 --- a/tests/charging-station/ui-server/UIServerTestUtils.ts +++ b/tests/charging-station/ui-server/UIServerTestUtils.ts @@ -6,6 +6,7 @@ import type { IncomingMessage } from 'node:http' import { EventEmitter } from 'node:events' +import type { IBootstrap } from '../../../src/charging-station/IBootstrap.js' import type { ChargingStationData, ProcedureName, @@ -28,10 +29,28 @@ import { } from '../../../src/types/index.js' import { MockWebSocket } from '../mocks/MockWebSocket.js' +export const createMockBootstrap = (): IBootstrap => ({ + addChargingStation: () => Promise.resolve(undefined), + getLastContiguousIndex: () => 0, + getPerformanceStatistics: () => undefined, + getState: () => ({ + configuration: undefined, + started: false, + templateStatistics: new Map(), + version: '0.0.0', + }), + start: () => Promise.resolve(), + stop: () => Promise.resolve(), +}) + /** * Testable UIWebSocketServer that exposes protected members for testing. */ export class TestableUIWebSocketServer extends UIWebSocketServer { + public constructor (config: UIServerConfiguration, bootstrap: IBootstrap = createMockBootstrap()) { + super(config, bootstrap) + } + /** * Add a response handler for testing. * @param uuid - Unique identifier for the response handler diff --git a/tests/helpers/OCPPAuthIntegrationTest.ts b/tests/helpers/OCPPAuthIntegrationTest.ts index d873e234..499d7337 100644 --- a/tests/helpers/OCPPAuthIntegrationTest.ts +++ b/tests/helpers/OCPPAuthIntegrationTest.ts @@ -3,16 +3,16 @@ import type { AuthConfiguration, AuthorizationResult, AuthRequest, - UnifiedIdentifier, -} from '../../src/charging-station/ocpp/auth/types/AuthTypes.js' + Identifier, +} from '../../src/charging-station/ocpp/auth/index.js' -import { OCPPAuthServiceImpl } from '../../src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.js' import { AuthContext, AuthenticationMethod, AuthorizationStatus, IdentifierType, -} from '../../src/charging-station/ocpp/auth/types/AuthTypes.js' + OCPPAuthServiceImpl, +} from '../../src/charging-station/ocpp/auth/index.js' import { logger } from '../../src/utils/index.js' export class OCPPAuthIntegrationTest { @@ -113,7 +113,7 @@ export class OCPPAuthIntegrationTest { } private async testCacheOperations (): Promise { - const testIdentifier: UnifiedIdentifier = { + const testIdentifier: Identifier = { type: IdentifierType.LOCAL, value: 'CACHE_TEST_ID', } @@ -156,7 +156,7 @@ export class OCPPAuthIntegrationTest { } private async testErrorHandling (): Promise { - const invalidIdentifier: UnifiedIdentifier = { + const invalidIdentifier: Identifier = { type: IdentifierType.ISO14443, value: '', } @@ -188,7 +188,7 @@ export class OCPPAuthIntegrationTest { } private async testOCPP16AuthFlow (): Promise { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ISO14443, value: 'VALID_ID_123', } @@ -216,7 +216,7 @@ export class OCPPAuthIntegrationTest { } private async testOCPP20AuthFlow (): Promise { - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ISO15693, value: 'VALID_ID_456', } @@ -261,7 +261,7 @@ export class OCPPAuthIntegrationTest { throw new Error('Invalid authentication statistics') } - const identifier: UnifiedIdentifier = { + const identifier: Identifier = { type: IdentifierType.ISO14443, value: 'PERF_TEST_ID', } @@ -327,7 +327,7 @@ export class OCPPAuthIntegrationTest { } } - const testIdentifier: UnifiedIdentifier = { + const testIdentifier: Identifier = { type: IdentifierType.ISO14443, value: 'TEST123', } diff --git a/tests/utils/Utils.test.ts b/tests/utils/Utils.test.ts index cf87126f..0930d08d 100644 --- a/tests/utils/Utils.test.ts +++ b/tests/utils/Utils.test.ts @@ -14,7 +14,7 @@ import { satisfies } from 'semver' import type { TimestampedData } from '../../src/types/index.js' import { JSRuntime, runtime } from '../../scripts/runtime.js' -import { MapStringifyFormat } from '../../src/types/index.js' +import { MapStringifyFormat, MessageType } from '../../src/types/index.js' import { Constants } from '../../src/utils/index.js' import { clampToSafeTimerValue, @@ -29,6 +29,7 @@ import { formatDurationMilliSeconds, formatDurationSeconds, generateUUID, + getMessageTypeString, getRandomFloat, getRandomFloatFluctuatedRounded, getRandomFloatRounded, @@ -886,4 +887,22 @@ await describe('Utils', async () => { ) }) }) + + await describe('getMessageTypeString', async () => { + await it('should return "request" for MessageType.CALL_MESSAGE', () => { + assert.strictEqual(getMessageTypeString(MessageType.CALL_MESSAGE), 'request') + }) + + await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => { + assert.strictEqual(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE), 'response') + }) + + await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => { + assert.strictEqual(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE), 'error') + }) + + await it('should return "unknown" for undefined', () => { + assert.strictEqual(getMessageTypeString(undefined), 'unknown') + }) + }) })