]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
feat(simulator): add forceTransactionOnInvalidIdToken template flag (#1907)
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Wed, 17 Jun 2026 16:49:22 +0000 (18:49 +0200)
committerGitHub <noreply@github.com>
Wed, 17 Jun 2026 16:49:22 +0000 (18:49 +0200)
* feat(simulator): add forceTransactionOnInvalidIdToken template flag

Allow station-initiated transactions to continue even when CSMS replies
with a non-Accepted IdToken status (OCPP 1.6 idTagInfo.status, OCPP 2.0.1
idTokenInfo.status). Default false preserves spec-compliant behavior.

The flag is non-spec-compliant by design (violates OCPP 2.0.1 E05.FR.09,
E05.FR.10, E06.FR.04 when enabled) and is intended for testing edge-case
charging station implementations that ignore CSMS rejection. JSDoc on the
field warns explicitly; README entry flags it as a non-spec-compliant test
override.

Scope in OCPP 2.0.1 is limited to TransactionEvent eventType=Started;
mid-transaction revocation (Updated/Ended event types) still triggers
deauthorization regardless of the flag. The OCPP 2.0.1 \`StopTxOnInvalidId\`
device-model variable is left untouched — the template flag short-circuits
ahead of the variable's accounting branch.

Tests: 16 new test cases across both OCPP namespaces covering force-on/off,
mid-tx revocation preservation, override marker log presence, auth cache
non-relaxation, pre-Start guard preservation, and full status-enum parity
(8 OCPP 2.0.1 statuses).

Closes #1826

* fix(types): restore fixedName and pin endedConnector non-null

The forceTransactionOnInvalidIdToken JSDoc rewrite in 73e3358a accidentally
deleted the adjacent fixedName?: boolean field on ChargingStationTemplate,
breaking 30+ TS errors in CI (ChargingStationNameTemplate Pick keyed on
fixedName). Local pnpm test does not run tsc --noEmit, so the regression
was invisible until CI.

Also fix a CI-only error in OCPP20ResponseService-ForceTxOnInvalid.test.ts:
endedConnector is typed as ConnectorStatus | undefined; replace the
optional-chain assertions with an explicit if/fail guard so TS narrows
the type for the subsequent strict-equal assertions.

* test(2.0): document deferred MV-pump fence and ConcurrentTx scope

Phase-4 review-C MINOR-1: the 2.0-T2 test asserts that
\`startUpdatedMeterValues\` is called but stubs the helper, so a wire-level
regression where the interval binds to the wrong connector would not be
caught. Phase 6 golden-set with the live mock CSMS will close that gap;
add a TODO comment to make the deferral explicit.

Phase-4 review-C NIT-1: the T7 enum-parity loop omits ConcurrentTx; this
is correct (ConcurrentTx is not an IdToken rejection in OCPP 2.0.1) but
deserves an inline comment to prevent a future contributor from adding it
naively.

* test(ocpp): tighten forceTransactionOnInvalidIdToken coverage and clarify docs

Follow-up to #1907 applying post-review fixes from a 3-angle audit
(production / types-schema-readme / tests-adversarial).

Production (OCPP 2.0):
- Simplify defensive `else if (overrideRejection && status !== Accepted)`
  to `else if (overrideRejection)` in handleResponseTransactionEvent;
  the second predicate is entailed by the enclosing if/else.
- Trim duplicated 5-line comment block down to a 2-line pointer to the
  canonical JSDoc on ChargingStationTemplate.forceTransactionOnInvalidIdToken.

Documentation:
- ChargingStationTemplate JSDoc: disambiguate from the OCPP variables
  StopTransactionOnInvalidId (1.6) and StopTxOnInvalidId (2.0.1) which
  control mid-transaction stop on revocation and have inverse polarity;
  state independence from ocppStrictCompliance.
- README row: same disambiguation appended to the cell.

Tests (OCPP 1.6 ForceTxOnInvalid):
- T5 (pre-Start guard): change response status from Accepted to Invalid
  so the test exercises the flag-vs-guard interaction it claims to lock.
- T6 (new): status-enum parity loop via Object.values(OCPP16AuthorizationStatus)
  excluding Accepted and ConcurrentTx (3 instances: Blocked, Expired, Invalid).
- T2: replicate the Phase-6 fake-timer-fence TODO from the sibling 2.0 file
  (MV pump observability disclosure).
- Rename test titles to should [verb] per tests/TEST_STYLE_GUIDE.md \xa71.

Tests (OCPP 2.0 ForceTxOnInvalid):
- buildTransactionEventRequest: add optional idToken parameter to exercise
  the auth-cache update path at handleResponseTransactionEvent.
- T8 (new): C10.FR.01/04/05 auth-cache invariant test, parametric over
  [flag=true, flag=false] (2 instances) asserting updateAuthorizationCache
  is called with the supplied idToken and the CSMS-replied idTokenInfo.
- T4 (Ended): add deauth call-count assertion (=== 0); locks the
  cleanup-runs-before-deauth-gate ordering invariant. The deauth path is
  reached but no-ops because cleanupEndedTransaction clears transactionId
  first, so getConnectorIdByTransactionId returns null. A regression that
  reorders cleanup after the gate (or preserves transactionId on Ended)
  flips this to 1.
- T7 (parity): replace static 8-element array with
  Object.values(OCPP20AuthorizationStatusEnumType).filter(...). Same 8
  instances today; future enum additions are auto-covered.
- T6: split into 2 it() blocks (flag-on / flag-off) eliminating the
  mid-test standardCleanup+remock pattern. Each block additionally asserts
  that the override-marker warn-log is NOT emitted on null idTokenInfo,
  locking the invariant against an A6-style regression where the
  override-marker else-if is hoisted outside the outer null guard.
- Rename test titles to should [verb].

Verification:
- pnpm typecheck exit 0
- pnpm lint exit 0
- pnpm test: 0 fail across the full suite
- 23/23 GREEN on the two ForceTxOnInvalid files (was 17/17; +6 new)
- Three mutation experiments confirm regression-detection strength:
  * Drop `|| forceTransactionOnInvalidIdToken` from OCPP16 gate (line 427):
    T2 + 3 parity tests fail with `false !== true` on transactionStarted.
  * Replace `if (requestPayload.idToken != null)` with `if (false)` in
    OCPP20 cache update (line 519): T8 (both flag states) fails `0 !== 1`.
  * Bypass the OCPP16 unauthorized-remote-start guard (lines 315-329):
    T5 only fails (regression-localized to that scenario).

* docs(ocpp): note ocppStrictCompliance independence and clarify Started log

Follow-up to review v2 of #1907 closing the two actionable findings
(B4 Low, N1 nit). Two remaining v2 nits (B5 multi-line JSDoc, R3 test
cast) are intentionally deferred — rationale in
/tmp/pr-1907-review-v2/fixes-design.md.

B4 — ChargingStationTemplate.forceTransactionOnInvalidIdToken JSDoc and
the matching README row both gain a one-clause disambiguation: "Independent
of `ocppStrictCompliance` (operates on response handling, not schema
validation)". Mirrors the pattern used by the sibling `outOfOrderEndMeterValues`
row (which cites ITS `ocppStrictCompliance` coupling), preventing readers
from inferring a non-existent coupling for the new flag.

N1 — OCPP20ResponseService.handleResponseTransactionEvent override warn
log now reads "...on eventType=Started despite..." (was "...on Started
despite..."). The `eventType=` prefix removes ambiguity with OCPP
connector state names (Charging, Available) and aligns the log vocabulary
with the JSDoc, the inline reference comment, and the test fixture
(`requestPayload.eventType === OCPP20TransactionEventEnumType.Started`).
The override-marker substring `forceTransactionOnInvalidIdToken=true` is
preserved verbatim, so the four test assertions that grep for it remain
unaffected.

Verification:
- pnpm typecheck exit 0
- pnpm lint exit 0
- pnpm test: 0 fail across the full suite
- 23/23 GREEN on the two ForceTxOnInvalid files (count unchanged; doc-only changes)
- README table re-aligned by prettier on save (column padding shifted
  421 -> 460 chars to fit the longer description); diff is mechanical
  whitespace, content change is one clause.

README.md
src/charging-station/TemplateSchema.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/charging-station/ocpp/2.0/OCPP20ResponseService.ts
src/types/ChargingStationTemplate.ts
src/utils/Constants.ts
tests/charging-station/ocpp/1.6/OCPP16ResponseService-ForceTxOnInvalid.test.ts [new file with mode: 0644]
tests/charging-station/ocpp/2.0/OCPP20ResponseService-ForceTxOnInvalid.test.ts [new file with mode: 0644]

index ef5858c2b9df03eaf7ae5f02f17af2ea56cb7d15..495532a478bf4813c9e4227642101c55fe67465f 100644 (file)
--- a/README.md
+++ b/README.md
@@ -200,69 +200,70 @@ But the modifications to test have to be done to the files in the build target d
 
 **src/assets/station-templates/\<name\>.json**:
 
-| Key                                                  | Value(s)      | Default Value                                                                                                                      | Value type                                                                                                                                                                    | Description                                                                                                                                                                                                                                       |
-| ---------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| supervisionUrls                                      |               | []                                                                                                                                 | string \| string[]                                                                                                                                                            | string or strings array containing connection URIs to OCPP-J servers                                                                                                                                                                              |
-| supervisionUser                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication user to OCPP-J server                                                                                                                                                                                                   |
-| supervisionPassword                                  |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication password to OCPP-J server                                                                                                                                                                                               |
-| supervisionUrlOcppConfiguration                      | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable supervision URL configuration via a vendor OCPP parameter key                                                                                                                                                                              |
-| supervisionUrlOcppKey                                |               | 'ConnectionUrl'                                                                                                                    | string                                                                                                                                                                        | the vendor string that will be used as a vendor OCPP parameter key to set the supervision URL                                                                                                                                                     |
-| autoStart                                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable automatic start of added charging station from template                                                                                                                                                                                    |
-| ocppVersion                                          | 1.6/2.0/2.0.1 | 1.6                                                                                                                                | string                                                                                                                                                                        | OCPP version                                                                                                                                                                                                                                      |
-| ocppProtocol                                         | json          | json                                                                                                                               | string                                                                                                                                                                        | OCPP protocol                                                                                                                                                                                                                                     |
-| ocppStrictCompliance                                 | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas                                                                         |
-| ocppPersistentConfiguration                          | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent OCPP parameters storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                                    |
-| stationInfoPersistentConfiguration                   | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent station information and specifications storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)             |
-| automaticTransactionGeneratorPersistentConfiguration | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent automatic transaction generator configuration storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)      |
-| wsOptions                                            |               | {}                                                                                                                                 | ClientOptions & ClientRequestArgs                                                                                                                                             | [ws](https://github.com/websockets/ws) and node.js [http](https://nodejs.org/api/http.html) clients options intersection                                                                                                                          |
-| idTagsFile                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | RFID tags list file relative to [src/assets](./src/assets) path                                                                                                                                                                                   |
-| iccid                                                |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card ICCID                                                                                                                                                                                                                                    |
-| imsi                                                 |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card IMSI                                                                                                                                                                                                                                     |
-| baseName                                             |               | undefined                                                                                                                          | string                                                                                                                                                                        | base name to build charging stations id                                                                                                                                                                                                           |
-| nameSuffix                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | name suffix to build charging stations id                                                                                                                                                                                                         |
-| fixedName                                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | use the 'baseName' as the charging stations unique name                                                                                                                                                                                           |
-| chargePointModel                                     |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations model                                                                                                                                                                                                                           |
-| chargePointVendor                                    |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations vendor                                                                                                                                                                                                                          |
-| chargePointSerialNumberPrefix                        |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge point serial number prefix                                                                                                                                                                                                                 |
-| chargeBoxSerialNumberPrefix                          |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge box serial number prefix (deprecated since OCPP 1.6)                                                                                                                                                                                       |
-| meterSerialNumberPrefix                              |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter serial number prefix                                                                                                                                                                                                                        |
-| meterType                                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter type                                                                                                                                                                                                                                        |
-| firmwareVersionPattern                               |               | Semantic versioning regular expression: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string | string                                                                                                                                                                        | charging stations firmware version pattern                                                                                                                                                                                                        |
-| firmwareVersion                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations firmware version                                                                                                                                                                                                                |
-| power                                                |               |                                                                                                                                    | float \| float[]                                                                                                                                                              | charging stations maximum power value(s)                                                                                                                                                                                                          |
-| powerSharedByConnectors                              | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | charging stations power shared by its connectors. When true, any single connector can draw up to the full station power; when false, each connector is allocated an equal share                                                                   |
-| powerUnit                                            | W/kW          | W                                                                                                                                  | string                                                                                                                                                                        | charging stations power unit                                                                                                                                                                                                                      |
-| currentOutType                                       | AC/DC         | AC                                                                                                                                 | string                                                                                                                                                                        | charging stations current out type                                                                                                                                                                                                                |
-| voltageOut                                           |               | AC:230/DC:400                                                                                                                      | integer                                                                                                                                                                       | charging stations voltage out                                                                                                                                                                                                                     |
-| numberOfPhases                                       | 0/1/3         | AC:3/DC:0                                                                                                                          | integer                                                                                                                                                                       | charging stations number of phase(s)                                                                                                                                                                                                              |
-| numberOfConnectors                                   |               |                                                                                                                                    | integer \| integer[]                                                                                                                                                          | charging stations number of connector(s)                                                                                                                                                                                                          |
-| useConnectorId0                                      | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | use connector id 0 definition from the charging station configuration template                                                                                                                                                                    |
-| randomConnectors                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | randomize runtime connector id affectation from the connector id definition in charging station configuration template                                                                                                                            |
-| resetTime                                            |               | 60                                                                                                                                 | integer                                                                                                                                                                       | seconds to wait before the charging stations come back at reset                                                                                                                                                                                   |
-| autoRegister                                         | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | set charging stations as registered at boot notification for testing purpose                                                                                                                                                                      |
-| autoReconnectMaxRetries                              |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | connection retries to the OCPP-J server                                                                                                                                                                                                           |
-| reconnectExponentialDelay                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | connection delay retry to the OCPP-J server                                                                                                                                                                                                       |
-| registrationMaxRetries                               |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | charging stations boot notification retries                                                                                                                                                                                                       |
-| amperageLimitationOcppKey                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations OCPP parameter key used to set the amperage limit, per phase for each connector on AC and global for DC                                                                                                                         |
-| amperageLimitationUnit                               | A/cA/dA/mA    | A                                                                                                                                  | string                                                                                                                                                                        | charging stations amperage limit unit                                                                                                                                                                                                             |
-| enableStatistics                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable charging stations statistics                                                                                                                                                                                                               |
-| remoteAuthorization                                  | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable RFID tags remote authorization                                                                                                                                                                                                             |
-| beginEndMeterValues                                  | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable Transaction.{Begin,End} MeterValues                                                                                                                                                                                                        |
-| outOfOrderEndMeterValues                             | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)                                                                                                             |
-| meteringPerTransaction                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable metering history on a per transaction basis                                                                                                                                                                                                |
-| transactionDataMeterValues                           | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable transaction data MeterValues at stop transaction                                                                                                                                                                                           |
-| stopTransactionsOnStopped                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable stop transactions on charging station stop                                                                                                                                                                                                 |
-| postTransactionDelay                                 | ≥ 0           | 0                                                                                                                                  | integer                                                                                                                                                                       | seconds to wait after transaction stop before transitioning connector to Available. Simulates cable-unplug delay. In OCPP 1.6 the connector stays in Finishing state; in OCPP 2.0.x it stays Occupied. 0 = immediate Available (default behavior) |
-| mainVoltageMeterValues                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | include charging stations main voltage MeterValues on three phased charging stations                                                                                                                                                              |
-| phaseLineToLineVoltageMeterValues                    | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | include charging stations line to line voltage MeterValues on three phased charging stations                                                                                                                                                      |
-| customValueLimitationMeterValues                     | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable limitation on custom fluctuated value in MeterValues                                                                                                                                                                                       |
-| firmwareUpgrade                                      |               | {<br />"versionUpgrade": {<br />"step": 1<br />},<br />"reset": true<br />}                                                        | {<br />versionUpgrade?: {<br />patternGroup?: number;<br />step?: number;<br />};<br />reset?: boolean;<br />failureStatus?: 'DownloadFailed' \| 'InstallationFailed';<br />} | Configuration section for simulating firmware upgrade support.                                                                                                                                                                                    |
-| commandsSupport                                      |               | {<br />"incomingCommands": {},<br />"outgoingCommands": {}<br />}                                                                  | {<br /> incomingCommands: Record<IncomingRequestCommand, boolean>;<br />outgoingCommands?: Record<RequestCommand, boolean>;<br />}                                            | Configuration section for OCPP commands support. Empty section or subsections means all implemented OCPP commands are supported                                                                                                                   |
-| messageTriggerSupport                                |               | {}                                                                                                                                 | Record<MessageTrigger, boolean>                                                                                                                                               | Configuration section for OCPP commands trigger support. Empty section means all implemented OCPP trigger commands are supported                                                                                                                  |
-| Configuration                                        |               |                                                                                                                                    | ChargingStationOcppConfiguration                                                                                                                                              | charging stations OCPP parameters configuration section                                                                                                                                                                                           |
-| AutomaticTransactionGenerator                        |               |                                                                                                                                    | AutomaticTransactionGeneratorConfiguration                                                                                                                                    | charging stations ATG configuration section                                                                                                                                                                                                       |
-| Connectors                                           |               |                                                                                                                                    | Record<string, ConnectorStatus>                                                                                                                                               | charging stations connectors configuration section                                                                                                                                                                                                |
-| Evses                                                |               |                                                                                                                                    | Record<string, EvseTemplate>                                                                                                                                                  | charging stations EVSEs configuration section                                                                                                                                                                                                     |
+| Key                                                  | Value(s)      | Default Value                                                                                                                      | Value type                                                                                                                                                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
+| ---------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| supervisionUrls                                      |               | []                                                                                                                                 | string \| string[]                                                                                                                                                            | string or strings array containing connection URIs to OCPP-J servers                                                                                                                                                                                                                                                                                                                                                                                                       |
+| supervisionUser                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication user to OCPP-J server                                                                                                                                                                                                                                                                                                                                                                                                                            |
+| supervisionPassword                                  |               | undefined                                                                                                                          | string                                                                                                                                                                        | basic HTTP authentication password to OCPP-J server                                                                                                                                                                                                                                                                                                                                                                                                                        |
+| supervisionUrlOcppConfiguration                      | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable supervision URL configuration via a vendor OCPP parameter key                                                                                                                                                                                                                                                                                                                                                                                                       |
+| supervisionUrlOcppKey                                |               | 'ConnectionUrl'                                                                                                                    | string                                                                                                                                                                        | the vendor string that will be used as a vendor OCPP parameter key to set the supervision URL                                                                                                                                                                                                                                                                                                                                                                              |
+| autoStart                                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable automatic start of added charging station from template                                                                                                                                                                                                                                                                                                                                                                                                             |
+| ocppVersion                                          | 1.6/2.0/2.0.1 | 1.6                                                                                                                                | string                                                                                                                                                                        | OCPP version                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
+| ocppProtocol                                         | json          | json                                                                                                                               | string                                                                                                                                                                        | OCPP protocol                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
+| ocppStrictCompliance                                 | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable strict adherence to the OCPP version and protocol specifications with OCPP commands PDU validation against [OCA](https://www.openchargealliance.org/) JSON schemas                                                                                                                                                                                                                                                                                                  |
+| ocppPersistentConfiguration                          | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent OCPP parameters storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                                                                                                                                                                                                                                                             |
+| stationInfoPersistentConfiguration                   | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent station information and specifications storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                                                                                                                                                                                                                                      |
+| automaticTransactionGeneratorPersistentConfiguration | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable persistent automatic transaction generator configuration storage by charging stations 'hashId'. The persistency is ensured by the charging stations configuration files in [dist/assets/configurations](./dist/assets/configurations)                                                                                                                                                                                                                               |
+| wsOptions                                            |               | {}                                                                                                                                 | ClientOptions & ClientRequestArgs                                                                                                                                             | [ws](https://github.com/websockets/ws) and node.js [http](https://nodejs.org/api/http.html) clients options intersection                                                                                                                                                                                                                                                                                                                                                   |
+| idTagsFile                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | RFID tags list file relative to [src/assets](./src/assets) path                                                                                                                                                                                                                                                                                                                                                                                                            |
+| iccid                                                |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card ICCID                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
+| imsi                                                 |               | undefined                                                                                                                          | string                                                                                                                                                                        | SIM card IMSI                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
+| baseName                                             |               | undefined                                                                                                                          | string                                                                                                                                                                        | base name to build charging stations id                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| nameSuffix                                           |               | undefined                                                                                                                          | string                                                                                                                                                                        | name suffix to build charging stations id                                                                                                                                                                                                                                                                                                                                                                                                                                  |
+| fixedName                                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | use the 'baseName' as the charging stations unique name                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| chargePointModel                                     |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations model                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| chargePointVendor                                    |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations vendor                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
+| chargePointSerialNumberPrefix                        |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge point serial number prefix                                                                                                                                                                                                                                                                                                                                                                                                                                          |
+| chargeBoxSerialNumberPrefix                          |               | undefined                                                                                                                          | string                                                                                                                                                                        | charge box serial number prefix (deprecated since OCPP 1.6)                                                                                                                                                                                                                                                                                                                                                                                                                |
+| meterSerialNumberPrefix                              |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter serial number prefix                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+| meterType                                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | meter type                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+| firmwareVersionPattern                               |               | Semantic versioning regular expression: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string | string                                                                                                                                                                        | charging stations firmware version pattern                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+| firmwareVersion                                      |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations firmware version                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| power                                                |               |                                                                                                                                    | float \| float[]                                                                                                                                                              | charging stations maximum power value(s)                                                                                                                                                                                                                                                                                                                                                                                                                                   |
+| powerSharedByConnectors                              | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | charging stations power shared by its connectors. When true, any single connector can draw up to the full station power; when false, each connector is allocated an equal share                                                                                                                                                                                                                                                                                            |
+| powerUnit                                            | W/kW          | W                                                                                                                                  | string                                                                                                                                                                        | charging stations power unit                                                                                                                                                                                                                                                                                                                                                                                                                                               |
+| currentOutType                                       | AC/DC         | AC                                                                                                                                 | string                                                                                                                                                                        | charging stations current out type                                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| voltageOut                                           |               | AC:230/DC:400                                                                                                                      | integer                                                                                                                                                                       | charging stations voltage out                                                                                                                                                                                                                                                                                                                                                                                                                                              |
+| numberOfPhases                                       | 0/1/3         | AC:3/DC:0                                                                                                                          | integer                                                                                                                                                                       | charging stations number of phase(s)                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+| numberOfConnectors                                   |               |                                                                                                                                    | integer \| integer[]                                                                                                                                                          | charging stations number of connector(s)                                                                                                                                                                                                                                                                                                                                                                                                                                   |
+| useConnectorId0                                      | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | use connector id 0 definition from the charging station configuration template                                                                                                                                                                                                                                                                                                                                                                                             |
+| randomConnectors                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | randomize runtime connector id affectation from the connector id definition in charging station configuration template                                                                                                                                                                                                                                                                                                                                                     |
+| resetTime                                            |               | 60                                                                                                                                 | integer                                                                                                                                                                       | seconds to wait before the charging stations come back at reset                                                                                                                                                                                                                                                                                                                                                                                                            |
+| autoRegister                                         | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | set charging stations as registered at boot notification for testing purpose                                                                                                                                                                                                                                                                                                                                                                                               |
+| autoReconnectMaxRetries                              |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | connection retries to the OCPP-J server                                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| reconnectExponentialDelay                            | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | connection delay retry to the OCPP-J server                                                                                                                                                                                                                                                                                                                                                                                                                                |
+| registrationMaxRetries                               |               | -1 (unlimited)                                                                                                                     | integer                                                                                                                                                                       | charging stations boot notification retries                                                                                                                                                                                                                                                                                                                                                                                                                                |
+| amperageLimitationOcppKey                            |               | undefined                                                                                                                          | string                                                                                                                                                                        | charging stations OCPP parameter key used to set the amperage limit, per phase for each connector on AC and global for DC                                                                                                                                                                                                                                                                                                                                                  |
+| amperageLimitationUnit                               | A/cA/dA/mA    | A                                                                                                                                  | string                                                                                                                                                                        | charging stations amperage limit unit                                                                                                                                                                                                                                                                                                                                                                                                                                      |
+| enableStatistics                                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable charging stations statistics                                                                                                                                                                                                                                                                                                                                                                                                                                        |
+| remoteAuthorization                                  | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable RFID tags remote authorization                                                                                                                                                                                                                                                                                                                                                                                                                                      |
+| forceTransactionOnInvalidIdToken                     | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | continue station-initiated transactions when CSMS rejects the IdToken (`idTagInfo.status` ≠ Accepted in 1.6; `idTokenInfo.status` ≠ Accepted on `eventType=Started` in 2.0.1; mid-tx revocation on `Updated`/`Ended` still tears down). Non-spec-compliant when true (violates OCPP 2.0.1 E05.FR.09 / E05.FR.10 / E06.FR.04); independent of `ocppStrictCompliance`; distinct from OCPP variables `StopTransactionOnInvalidId` / `StopTxOnInvalidId` (mid-tx stop control) |
+| beginEndMeterValues                                  | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable Transaction.{Begin,End} MeterValues                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+| outOfOrderEndMeterValues                             | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | send Transaction.End MeterValues out of order. Need to relax OCPP specifications strict compliance ('ocppStrictCompliance' parameter)                                                                                                                                                                                                                                                                                                                                      |
+| meteringPerTransaction                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable metering history on a per transaction basis                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| transactionDataMeterValues                           | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | enable transaction data MeterValues at stop transaction                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| stopTransactionsOnStopped                            | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable stop transactions on charging station stop                                                                                                                                                                                                                                                                                                                                                                                                                          |
+| postTransactionDelay                                 | ≥ 0           | 0                                                                                                                                  | integer                                                                                                                                                                       | seconds to wait after transaction stop before transitioning connector to Available. Simulates cable-unplug delay. In OCPP 1.6 the connector stays in Finishing state; in OCPP 2.0.x it stays Occupied. 0 = immediate Available (default behavior)                                                                                                                                                                                                                          |
+| mainVoltageMeterValues                               | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | include charging stations main voltage MeterValues on three phased charging stations                                                                                                                                                                                                                                                                                                                                                                                       |
+| phaseLineToLineVoltageMeterValues                    | true/false    | false                                                                                                                              | boolean                                                                                                                                                                       | include charging stations line to line voltage MeterValues on three phased charging stations                                                                                                                                                                                                                                                                                                                                                                               |
+| customValueLimitationMeterValues                     | true/false    | true                                                                                                                               | boolean                                                                                                                                                                       | enable limitation on custom fluctuated value in MeterValues                                                                                                                                                                                                                                                                                                                                                                                                                |
+| firmwareUpgrade                                      |               | {<br />"versionUpgrade": {<br />"step": 1<br />},<br />"reset": true<br />}                                                        | {<br />versionUpgrade?: {<br />patternGroup?: number;<br />step?: number;<br />};<br />reset?: boolean;<br />failureStatus?: 'DownloadFailed' \| 'InstallationFailed';<br />} | Configuration section for simulating firmware upgrade support.                                                                                                                                                                                                                                                                                                                                                                                                             |
+| commandsSupport                                      |               | {<br />"incomingCommands": {},<br />"outgoingCommands": {}<br />}                                                                  | {<br /> incomingCommands: Record<IncomingRequestCommand, boolean>;<br />outgoingCommands?: Record<RequestCommand, boolean>;<br />}                                            | Configuration section for OCPP commands support. Empty section or subsections means all implemented OCPP commands are supported                                                                                                                                                                                                                                                                                                                                            |
+| messageTriggerSupport                                |               | {}                                                                                                                                 | Record<MessageTrigger, boolean>                                                                                                                                               | Configuration section for OCPP commands trigger support. Empty section means all implemented OCPP trigger commands are supported                                                                                                                                                                                                                                                                                                                                           |
+| Configuration                                        |               |                                                                                                                                    | ChargingStationOcppConfiguration                                                                                                                                              | charging stations OCPP parameters configuration section                                                                                                                                                                                                                                                                                                                                                                                                                    |
+| AutomaticTransactionGenerator                        |               |                                                                                                                                    | AutomaticTransactionGeneratorConfiguration                                                                                                                                    | charging stations ATG configuration section                                                                                                                                                                                                                                                                                                                                                                                                                                |
+| Connectors                                           |               |                                                                                                                                    | Record<string, ConnectorStatus>                                                                                                                                               | charging stations connectors configuration section                                                                                                                                                                                                                                                                                                                                                                                                                         |
+| Evses                                                |               |                                                                                                                                    | Record<string, EvseTemplate>                                                                                                                                                  | charging stations EVSEs configuration section                                                                                                                                                                                                                                                                                                                                                                                                                              |
 
 #### Configuration section syntax example
 
index ea85e50ea20e5824a3d3bbc6413d29e64ff55add..094336b6465c10145cda488a00f052a82d172272 100644 (file)
@@ -190,6 +190,7 @@ const BaseTemplateSchema = z.looseObject({
   firmwareVersion: z.string().optional(),
   firmwareVersionPattern: z.string().optional(),
   fixedName: z.boolean().optional(),
+  forceTransactionOnInvalidIdToken: z.boolean().optional(),
   iccid: z.string().optional(),
   idTagsFile: z.string().optional(),
   imsi: z.string().optional(),
index c046a819ed92fad56588eab769d9354a59d90fa0..e8a301d60cd2c45e8e822c82890bd8b58bed0ddb 100644 (file)
@@ -401,7 +401,30 @@ export class OCPP16ResponseService extends OCPPResponseService {
       payload.transactionId = convertToInt(payload.transactionId)
     }
 
-    if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
+    const idTokenAccepted = payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
+    const forceTransactionOnInvalidIdToken =
+      chargingStation.stationInfo?.forceTransactionOnInvalidIdToken === true
+    if (!idTokenAccepted) {
+      logger.warn(
+        `${chargingStation.logPrefix()} ${moduleName}.handleResponseStartTransaction: Starting transaction with id ${payload.transactionId.toString()} REJECTED on ${
+          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+          chargingStation.stationInfo?.chargingStationId
+        }#${connectorId.toString()} with status '${payload.idTagInfo.status}', idTag '${truncateId(
+          requestPayload.idTag
+        )}'${
+          OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
+            ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+              `, reservationId '${requestPayload.reservationId?.toString()}'`
+            : ''
+        }`
+      )
+      if (forceTransactionOnInvalidIdToken) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.handleResponseStartTransaction: Forcing transaction id ${payload.transactionId.toString()} on connector id ${connectorId.toString()} despite idTagInfo status '${payload.idTagInfo.status}' per forceTransactionOnInvalidIdToken=true`
+        )
+      }
+    }
+    if (idTokenAccepted || forceTransactionOnInvalidIdToken) {
       connectorStatus.transactionStarted = true
       connectorStatus.transactionStart = requestPayload.timestamp
       connectorStatus.transactionId = payload.transactionId
@@ -482,19 +505,6 @@ export class OCPP16ResponseService extends OCPPResponseService {
           : Constants.DEFAULT_METER_VALUES_INTERVAL_MS
       )
     } else {
-      logger.warn(
-        `${chargingStation.logPrefix()} ${moduleName}.handleResponseStartTransaction: Starting transaction with id ${payload.transactionId.toString()} REJECTED on ${
-          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-          chargingStation.stationInfo?.chargingStationId
-        }#${connectorId.toString()} with status '${payload.idTagInfo.status}', idTag '${truncateId(
-          requestPayload.idTag
-        )}'${
-          OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
-            ? // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-              `, reservationId '${requestPayload.reservationId?.toString()}'`
-            : ''
-        }`
-      )
       await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
     }
     OCPP16ServiceUtils.updateAuthorizationCache(
index fb230a4762965a4f05cfd72cc269345f6594bb94..901001f1bb8c95689c247f1d3af89abdef0d3884 100644 (file)
@@ -424,7 +424,8 @@ export class OCPP20ResponseService extends OCPPResponseService {
           connectorStatus.transactionEnergyActiveImportRegisterValue ??= 0
           const isIdTokenAccepted =
             payload.idTokenInfo == null ||
-            payload.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted
+            payload.idTokenInfo.status === OCPP20AuthorizationStatusEnumType.Accepted ||
+            chargingStation.stationInfo?.forceTransactionOnInvalidIdToken === true
           if (isIdTokenAccepted) {
             connectorStatus.locked = true
           }
@@ -467,8 +468,17 @@ export class OCPP20ResponseService extends OCPPResponseService {
       logger.info(
         `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: IdToken info status: ${payload.idTokenInfo.status}`
       )
-      // E05.FR.09/FR.10 + E06.FR.04: Deauthorize transaction when idToken is not accepted by CSMS
-      if (payload.idTokenInfo.status !== OCPP20AuthorizationStatusEnumType.Accepted) {
+      // E05.FR.09/FR.10 + E06.FR.04: Deauthorize when idToken not Accepted by CSMS.
+      // Override gate documented on `ChargingStationTemplate.forceTransactionOnInvalidIdToken`.
+      const forceTransactionOnInvalidIdToken =
+        chargingStation.stationInfo?.forceTransactionOnInvalidIdToken === true
+      const overrideRejection =
+        forceTransactionOnInvalidIdToken &&
+        requestPayload.eventType === OCPP20TransactionEventEnumType.Started
+      if (
+        payload.idTokenInfo.status !== OCPP20AuthorizationStatusEnumType.Accepted &&
+        !overrideRejection
+      ) {
         logger.warn(
           `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: IdToken authorization rejected with status '${payload.idTokenInfo.status}', de-authorizing transaction per E05.FR.09/E05.FR.10/E06.FR.04`
         )
@@ -494,6 +504,10 @@ export class OCPP20ResponseService extends OCPPResponseService {
             `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Could not find connector for transaction ${requestPayload.transactionInfo.transactionId}, cannot de-authorize`
           )
         }
+      } else if (overrideRejection) {
+        logger.warn(
+          `${chargingStation.logPrefix()} ${moduleName}.handleResponseTransactionEvent: Forcing transaction ${requestPayload.transactionInfo.transactionId} on eventType=Started despite idTokenInfo status '${payload.idTokenInfo.status}' per forceTransactionOnInvalidIdToken=true`
+        )
       }
       // C10.FR.01/04/05: Update auth cache with idTokenInfo from response
       if (requestPayload.idToken != null) {
index f88f24da36d288bd5f32fdf7e5dc03ad8d232e99..10a34e4f8b7e118cfaf511e1a60f894acded14ac 100644 (file)
@@ -73,6 +73,17 @@ export interface ChargingStationTemplate {
   firmwareVersion?: string
   firmwareVersionPattern?: string
   fixedName?: boolean
+  /**
+   * Continue station-initiated transactions when CSMS rejects the IdToken
+   * (`idTagInfo.status` != Accepted in 1.6; `idTokenInfo.status` != Accepted
+   * on `eventType=Started` in 2.0.1; mid-tx revocation on `Updated`/`Ended`
+   * still tears down). Default `false`; when `true`, violates OCPP 2.0.1
+   * E05.FR.09 / E05.FR.10 / E06.FR.04. Independent of `ocppStrictCompliance`
+   * (operates on response handling, not schema validation). Distinct from
+   * OCPP variables `StopTransactionOnInvalidId` / `StopTxOnInvalidId`
+   * (mid-tx stop control); this flag overrides the start-time gate only.
+   */
+  forceTransactionOnInvalidIdToken?: boolean
   iccid?: string
   idTagsFile?: string
   imsi?: string
index 65fcfad680185dd9a0cb4582369e2718821d9911..a0622fb429ae39ede0bee5b64d6624be055748ea 100644 (file)
@@ -73,6 +73,7 @@ export class Constants {
     // See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
     firmwareVersionPattern:
       '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$',
+    forceTransactionOnInvalidIdToken: false,
     mainVoltageMeterValues: true,
     meteringPerTransaction: true,
     ocppPersistentConfiguration: true,
diff --git a/tests/charging-station/ocpp/1.6/OCPP16ResponseService-ForceTxOnInvalid.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-ForceTxOnInvalid.test.ts
new file mode 100644 (file)
index 0000000..d6895c2
--- /dev/null
@@ -0,0 +1,283 @@
+/**
+ * @file Tests for OCPP16ResponseService — `forceTransactionOnInvalidIdToken`
+ *   template flag (issue #1826).
+ * @description Asserts that, when the station-template flag
+ *   `forceTransactionOnInvalidIdToken` is `true`, a StartTransaction response
+ *   carrying `idTagInfo.status === Invalid` does NOT abort: the connector
+ *   adopts the transaction (transactionStarted, transactionId, transactionIdTag,
+ *   locked) as if Accepted, the MeterValues sample timer is initialized, and a
+ *   warn-log entry containing the literal `forceTransactionOnInvalidIdToken=true`
+ *   is emitted. The authorization cache update is NOT relaxed (it always
+ *   reflects what CSMS replied). Pre-Start local-state guards (e.g.
+ *   remote-start with un-authorized idTag) are NOT relaxed by the flag.
+ *
+ *   Default-off regression bound is covered by the sibling test in
+ *   `OCPP16ResponseService-Transactions.test.ts:212` and is intentionally not
+ *   duplicated here.
+ *
+ *   Test runner: node:test (`pnpm test`). No Jest, no Vitest.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import type {
+  OCPP16StartTransactionRequest,
+  OCPP16StartTransactionResponse,
+} from '../../../../src/types/index.js'
+
+import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js'
+import {
+  OCPP16AuthorizationStatus,
+  OCPP16MeterValueUnit,
+  OCPP16RequestCommand,
+} from '../../../../src/types/index.js'
+import { logger } from '../../../../src/utils/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import { TEST_ID_TAG } from '../../ChargingStationTestConstants.js'
+import { createOCPP16ResponseTestContext, setMockRequestHandler } from './OCPP16TestUtils.js'
+
+await describe('OCPP16ResponseService — forceTransactionOnInvalidIdToken (issue #1826)', async () => {
+  let station: ChargingStation
+  let responseService: OCPP16ResponseService
+
+  beforeEach(() => {
+    const ctx = createOCPP16ResponseTestContext({
+      stationInfo: { forceTransactionOnInvalidIdToken: true },
+    })
+    station = ctx.station
+    responseService = ctx.responseService
+    setMockRequestHandler(station, async () => Promise.resolve({}))
+    mock.method(OCPP16ServiceUtils, 'startUpdatedMeterValues', () => {
+      /* noop */
+    })
+    mock.method(OCPP16ServiceUtils, 'stopUpdatedMeterValues', () => {
+      /* noop */
+    })
+    for (const { connectorId } of station.iterateConnectors(true)) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus != null) {
+        connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+      }
+    }
+  })
+
+  afterEach(() => {
+    standardCleanup()
+  })
+
+  // 1.6-T2 — Force-tx on Invalid: transaction continues as if Accepted.
+  // TODO Phase 6 (golden set): add a fake-timer fence to verify the MV pump
+  // actually emits a MeterValues request over the wire — the helper-call
+  // assertion below stubs the pump and therefore does not catch a wire-level
+  // regression where startUpdatedMeterValues is invoked but the interval is
+  // bound to the wrong connector. Phase 6 closes this gap.
+  await it('should continue the transaction when CSMS replies Invalid and the flag is true', async () => {
+    // Arrange
+    const connectorId = 1
+    const transactionId = 4242
+    const startUpdatedMeterValuesMock = mock.method(
+      OCPP16ServiceUtils,
+      'startUpdatedMeterValues',
+      () => {
+        /* noop */
+      }
+    )
+    const requestPayload: OCPP16StartTransactionRequest = {
+      connectorId,
+      idTag: TEST_ID_TAG,
+      meterStart: 0,
+      timestamp: new Date('2025-01-01T12:00:00Z'),
+    }
+    const responsePayload: OCPP16StartTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.INVALID },
+      transactionId,
+    }
+
+    // Act — public dispatcher routes to private handleResponseStartTransaction.
+    await responseService.responseHandler(
+      station,
+      OCPP16RequestCommand.START_TRANSACTION,
+      responsePayload,
+      requestPayload
+    )
+
+    // Assert — connector adopted the transaction.
+    const connectorStatus = station.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
+      assert.fail('Expected connector to be defined')
+    }
+    assert.strictEqual(connectorStatus.transactionStarted, true)
+    assert.strictEqual(connectorStatus.transactionId, transactionId)
+    assert.strictEqual(connectorStatus.transactionIdTag, TEST_ID_TAG)
+    assert.strictEqual(connectorStatus.transactionEnergyActiveImportRegisterValue, 0)
+    assert.strictEqual(connectorStatus.locked, true)
+    assert.deepStrictEqual(connectorStatus.transactionStart, requestPayload.timestamp)
+    // MV pump initialized so the simulator actually meters the override session.
+    assert.strictEqual(startUpdatedMeterValuesMock.mock.calls.length, 1)
+  })
+
+  // 1.6-T3 — Override marker present in warn-level log.
+  await it('should emit a warn log line containing the override marker', async () => {
+    // Arrange
+    const warnMock = mock.method(logger, 'warn', () => undefined)
+    const connectorId = 1
+    const requestPayload: OCPP16StartTransactionRequest = {
+      connectorId,
+      idTag: TEST_ID_TAG,
+      meterStart: 0,
+      timestamp: new Date(),
+    }
+    const responsePayload: OCPP16StartTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.INVALID },
+      transactionId: 7,
+    }
+
+    // Act
+    await responseService.responseHandler(
+      station,
+      OCPP16RequestCommand.START_TRANSACTION,
+      responsePayload,
+      requestPayload
+    )
+
+    // Assert — at least one warn call carries the literal override marker.
+    const overrideMarkerCalls = warnMock.mock.calls.filter(call => {
+      const firstArg: unknown = call.arguments[0]
+      return (
+        typeof firstArg === 'string' && firstArg.includes('forceTransactionOnInvalidIdToken=true')
+      )
+    })
+    assert.strictEqual(overrideMarkerCalls.length, 1)
+  })
+
+  // 1.6-T4 — Authorization cache update is NOT relaxed by the flag.
+  await it('should still update the authorization cache with the CSMS-supplied idTagInfo', async () => {
+    // Arrange
+    const updateAuthMock = mock.method(
+      OCPP16ServiceUtils,
+      'updateAuthorizationCache',
+      () => undefined
+    )
+    const connectorId = 1
+    const requestPayload: OCPP16StartTransactionRequest = {
+      connectorId,
+      idTag: TEST_ID_TAG,
+      meterStart: 0,
+      timestamp: new Date(),
+    }
+    const responsePayload: OCPP16StartTransactionResponse = {
+      idTagInfo: { status: OCPP16AuthorizationStatus.INVALID },
+      transactionId: 11,
+    }
+
+    // Act
+    await responseService.responseHandler(
+      station,
+      OCPP16RequestCommand.START_TRANSACTION,
+      responsePayload,
+      requestPayload
+    )
+
+    // Assert — exactly one cache update with the Invalid idTagInfo.
+    assert.strictEqual(updateAuthMock.mock.calls.length, 1)
+    assert.strictEqual(updateAuthMock.mock.calls[0].arguments[1], TEST_ID_TAG)
+    assert.deepStrictEqual(updateAuthMock.mock.calls[0].arguments[2], {
+      status: OCPP16AuthorizationStatus.INVALID,
+    })
+  })
+
+  // 1.6-T5 — Pre-Start local-state guards are NOT relaxed.
+  // The remote-start guard at :315-329 must still abort even when the flag is true.
+  await it('should still abort on the pre-Start unauthorized-remote-start guard regardless of the flag', async () => {
+    // Arrange — drive the guard at OCPP16ResponseService.ts:315-329:
+    // transactionRemoteStarted=true, AuthorizeRemoteTxRequests=true,
+    // remoteAuthorization=true, idTagAuthorized=false, idTagLocalAuthorized=false.
+    const connectorId = 1
+    const connectorStatus = station.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
+      assert.fail('Expected connector to be defined')
+    }
+    connectorStatus.transactionRemoteStarted = true
+    connectorStatus.idTagAuthorized = false
+    connectorStatus.idTagLocalAuthorized = false
+    connectorStatus.authorizeIdTag = TEST_ID_TAG
+    ;(
+      station as unknown as { getAuthorizeRemoteTxRequests: () => boolean }
+    ).getAuthorizeRemoteTxRequests = () => true
+    const stationInfo = station.stationInfo
+    if (stationInfo != null) {
+      stationInfo.remoteAuthorization = true
+    }
+
+    const requestPayload: OCPP16StartTransactionRequest = {
+      connectorId,
+      idTag: TEST_ID_TAG,
+      meterStart: 0,
+      timestamp: new Date(),
+    }
+    const responsePayload: OCPP16StartTransactionResponse = {
+      // INVALID + flag=true exercises the regression: without the pre-Start
+      // guard the override would skip the abort path. The guard MUST still
+      // win. ACCEPTED would not exercise the flag-vs-guard interaction.
+      idTagInfo: { status: OCPP16AuthorizationStatus.INVALID },
+      transactionId: 22,
+    }
+
+    // Act
+    await responseService.responseHandler(
+      station,
+      OCPP16RequestCommand.START_TRANSACTION,
+      responsePayload,
+      requestPayload
+    )
+
+    // Assert — guard fires: connector reset, no transaction adopted.
+    assert.strictEqual(connectorStatus.transactionStarted, false)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
+    assert.strictEqual(connectorStatus.transactionIdTag, undefined)
+  })
+
+  // 1.6-T6 — Status-enum parity: every non-Accepted, non-ConcurrentTx status
+  // follows the override path on StartTransaction when the flag is true.
+  // ConcurrentTx is excluded because OCPP 1.6 routes it through a different
+  // code path (concurrent transaction detection), outside this issue's scope.
+  for (const status of Object.values(OCPP16AuthorizationStatus).filter(
+    s => s !== OCPP16AuthorizationStatus.ACCEPTED && s !== OCPP16AuthorizationStatus.CONCURRENT_TX
+  )) {
+    await it(`should continue the transaction for status=${status} when the flag is true`, async () => {
+      const startUpdatedMeterValuesMock = mock.method(
+        OCPP16ServiceUtils,
+        'startUpdatedMeterValues',
+        () => undefined
+      )
+      const connectorId = 1
+      const requestPayload: OCPP16StartTransactionRequest = {
+        connectorId,
+        idTag: TEST_ID_TAG,
+        meterStart: 0,
+        timestamp: new Date(),
+      }
+      const responsePayload: OCPP16StartTransactionResponse = {
+        idTagInfo: { status },
+        transactionId: 4242,
+      }
+
+      await responseService.responseHandler(
+        station,
+        OCPP16RequestCommand.START_TRANSACTION,
+        responsePayload,
+        requestPayload
+      )
+
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus == null) {
+        assert.fail('Expected connector to be defined')
+      }
+      assert.strictEqual(connectorStatus.transactionStarted, true)
+      assert.strictEqual(startUpdatedMeterValuesMock.mock.calls.length, 1)
+    })
+  }
+})
diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-ForceTxOnInvalid.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-ForceTxOnInvalid.test.ts
new file mode 100644 (file)
index 0000000..f65c35e
--- /dev/null
@@ -0,0 +1,472 @@
+/**
+ * @file Tests for OCPP20ResponseService — `forceTransactionOnInvalidIdToken`
+ *   template flag (issue #1826).
+ * @description Asserts that, when the station-template flag
+ *   `forceTransactionOnInvalidIdToken` is `true`, an OCPP 2.0.1
+ *   `TransactionEvent(Started)` response carrying a non-Accepted
+ *   `idTokenInfo.status` does NOT abort: `requestDeauthorizeTransaction` is
+ *   not called, the connector is locked, the MeterValues update/ended pumps
+ *   are started, and a warn-level log line containing the literal
+ *   `forceTransactionOnInvalidIdToken=true` is emitted. Mid-transaction
+ *   revocation (`Updated` / `Ended` event types with non-Accepted status)
+ *   STILL aborts via `requestDeauthorizeTransaction` regardless of the flag,
+ *   preserving OCPP 2.0.1 E05.FR.09 / E05.FR.10 / E06.FR.04 mid-tx semantics.
+ *
+ *   Default-off regression bound is covered by the sibling tests in
+ *   `OCPP20ResponseService-TransactionEvent.test.ts` and not duplicated here.
+ *
+ *   Test runner: node:test (`pnpm test`). No Jest, no Vitest.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type {
+  OCPP20TransactionEventRequest,
+  OCPP20TransactionEventResponse,
+  UUIDv4,
+} from '../../../../src/types/index.js'
+
+import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
+import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
+import {
+  OCPP20AuthorizationStatusEnumType,
+  OCPP20IdTokenEnumType,
+  OCPP20TransactionEventEnumType,
+  OCPP20TriggerReasonEnumType,
+  OCPPVersion,
+} from '../../../../src/types/index.js'
+import { Constants, logger } from '../../../../src/utils/index.js'
+import {
+  setupConnectorWithTransaction,
+  standardCleanup,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import {
+  TEST_CHARGING_STATION_BASE_NAME,
+  TEST_ID_TAG,
+  TEST_TRANSACTION_UUID,
+} from '../../ChargingStationTestConstants.js'
+import { createMockChargingStation } from '../../helpers/StationHelpers.js'
+
+interface TestableOCPP20ResponseService {
+  handleResponseTransactionEvent: (
+    chargingStation: ChargingStation,
+    payload: OCPP20TransactionEventResponse,
+    requestPayload: OCPP20TransactionEventRequest
+  ) => Promise<void>
+}
+
+/**
+ * Builds a minimal OCPP20TransactionEventRequest with the given event type and
+ * transaction id. Used as the request-payload twin in handler dispatch.
+ * @param transactionId - The transaction UUID embedded in transactionInfo
+ * @param eventType - The TransactionEvent type (Started/Updated/Ended)
+ * @param idToken - Optional idToken to attach; required to exercise the auth-cache
+ *   update path at OCPP20ResponseService.ts (C10.FR.01/04/05)
+ * @param idToken.idToken
+ * @param idToken.type
+ * @returns A minimal OCPP20TransactionEventRequest
+ */
+function buildTransactionEventRequest (
+  transactionId: UUIDv4,
+  eventType: OCPP20TransactionEventEnumType,
+  idToken?: { idToken: string; type: OCPP20IdTokenEnumType }
+): OCPP20TransactionEventRequest {
+  return {
+    eventType,
+    ...(idToken != null ? { idToken } : {}),
+    meterValue: [],
+    seqNo: 0,
+    timestamp: new Date(),
+    transactionInfo: {
+      transactionId,
+    },
+    triggerReason: OCPP20TriggerReasonEnumType.Authorized,
+  }
+}
+
+/**
+ * Wraps an OCPP20ResponseService instance so its private
+ * `handleResponseTransactionEvent` is reachable by tests via a typed cast.
+ * Mirrors the helper in `OCPP20ResponseService-TransactionEvent.test.ts`.
+ * @param service - The OCPP20ResponseService instance to wrap
+ * @returns A typed interface exposing the private handler
+ */
+function createTestableResponseService (
+  service: OCPP20ResponseService
+): TestableOCPP20ResponseService {
+  const serviceImpl = service as unknown as TestableOCPP20ResponseService
+  return {
+    handleResponseTransactionEvent: serviceImpl.handleResponseTransactionEvent.bind(service),
+  }
+}
+
+await describe('OCPP20ResponseService — forceTransactionOnInvalidIdToken (issue #1826)', async () => {
+  let station: ChargingStation
+  let testable: TestableOCPP20ResponseService
+
+  beforeEach(() => {
+    const { station: mockStation } = createMockChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 1,
+      evseConfiguration: { evsesCount: 1 },
+      stationInfo: {
+        forceTransactionOnInvalidIdToken: true,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WS_PING_INTERVAL_SECONDS,
+    })
+    station = mockStation
+    setupConnectorWithTransaction(station, 1, { transactionId: 100 })
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus != null) {
+      connectorStatus.transactionId = TEST_TRANSACTION_UUID
+    }
+    const responseService = new OCPP20ResponseService()
+    testable = createTestableResponseService(responseService)
+  })
+
+  afterEach(() => {
+    standardCleanup()
+  })
+
+  // 2.0-T2 — Force-tx on Invalid `Started`: deauth NOT called, connector
+  // locked, MV update + ended pumps started.
+  // TODO Phase 6 (golden set): add a fake-timer fence to verify the MV pump
+  // actually emits a TransactionEvent(Updated) over the wire — the helper-
+  // call assertion below stubs the pump and therefore does not catch a
+  // wire-level regression where startUpdatedMeterValues is invoked but the
+  // interval is bound to the wrong connector. Phase 6 closes this gap.
+  await it('should not deauthorize on Invalid Started when the flag is true', async () => {
+    // Arrange
+    const mockDeauthTransaction = mock.method(
+      OCPP20ServiceUtils,
+      'requestDeauthorizeTransaction',
+      async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    const mockStartUpdated = mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => {
+      /* noop */
+    })
+    const mockStartEnded = mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => {
+      /* noop */
+    })
+    const payload: OCPP20TransactionEventResponse = {
+      idTokenInfo: {
+        status: OCPP20AuthorizationStatusEnumType.Invalid,
+      },
+    }
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Started
+    )
+
+    // Act
+    await testable.handleResponseTransactionEvent(station, payload, requestPayload)
+
+    // Assert — deauth NOT called; locked + MV pumps started.
+    assert.strictEqual(mockDeauthTransaction.mock.calls.length, 0)
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus == null) {
+      assert.fail('Expected connector to be defined')
+    }
+    assert.strictEqual(connectorStatus.locked, true)
+    assert.strictEqual(mockStartUpdated.mock.calls.length, 1)
+    assert.strictEqual(mockStartEnded.mock.calls.length, 1)
+  })
+
+  // 2.0-T3 — Mid-transaction revocation (Updated) STILL aborts.
+  await it('should still de-authorize on Invalid Updated when the flag is true', async () => {
+    // Arrange
+    const mockDeauthTransaction = mock.method(
+      OCPP20ServiceUtils,
+      'requestDeauthorizeTransaction',
+      async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    const payload: OCPP20TransactionEventResponse = {
+      idTokenInfo: {
+        status: OCPP20AuthorizationStatusEnumType.Invalid,
+      },
+    }
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Updated
+    )
+
+    // Act
+    await testable.handleResponseTransactionEvent(station, payload, requestPayload)
+
+    // Assert
+    assert.strictEqual(mockDeauthTransaction.mock.calls.length, 1)
+  })
+
+  // 2.0-T4 — Mid-transaction revocation (Ended) STILL tears down (regardless of flag).
+  await it('should clean up on Invalid Ended even when the flag is true (mid-tx tear-down preserved)', async () => {
+    // Arrange — In OCPP 2.0.1, the Ended branch runs cleanupEndedTransaction BEFORE the
+    // deauth gate. With the flag on, we cannot bypass mid-transaction tear-down: the
+    // case Ended in the switch always cleans up. Asserting on connector cleanup is the
+    // accurate way to pin the "mid-tx revocation always aborts" invariant for Ended.
+    const { station: endedStation } = createMockChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 1,
+      evseConfiguration: { evsesCount: 1 },
+      ocppRequestService: {
+        requestHandler: async () => Promise.resolve({}),
+      },
+      stationInfo: {
+        forceTransactionOnInvalidIdToken: true,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WS_PING_INTERVAL_SECONDS,
+    })
+    setupConnectorWithTransaction(endedStation, 1, { transactionId: 100 })
+    const endedConnector = endedStation.getConnectorStatus(1)
+    if (endedConnector != null) {
+      endedConnector.transactionId = TEST_TRANSACTION_UUID
+    }
+    const endedTestable = createTestableResponseService(new OCPP20ResponseService())
+    const mockDeauthEnded = mock.method(
+      OCPP20ServiceUtils,
+      'requestDeauthorizeTransaction',
+      async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    const payload: OCPP20TransactionEventResponse = {
+      idTokenInfo: {
+        status: OCPP20AuthorizationStatusEnumType.Invalid,
+      },
+    }
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Ended
+    )
+
+    // Act
+    await endedTestable.handleResponseTransactionEvent(endedStation, payload, requestPayload)
+
+    // Asserts cleanup ran AND deauth was a no-op (cleanupEndedTransaction
+    // clears transactionId before the gate, so the connector lookup fails).
+    // `=== 0` locks the cleanup-then-gate ordering: any regression that
+    // reorders or preserves transactionId on Ended flips this to 1.
+    if (endedConnector == null) {
+      assert.fail('endedConnector should be defined after setupConnectorWithTransaction')
+    }
+    assert.strictEqual(endedConnector.transactionStarted, false)
+    assert.strictEqual(endedConnector.locked, false)
+    assert.strictEqual(mockDeauthEnded.mock.calls.length, 0)
+  })
+
+  // 2.0-T5 — Override marker present in warn-level log on Started override path.
+  await it('should emit a warn log line containing the override marker', async () => {
+    // Arrange
+    mock.method(OCPP20ServiceUtils, 'requestDeauthorizeTransaction', async () =>
+      Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => {
+      /* noop */
+    })
+    mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => {
+      /* noop */
+    })
+    const warnMock = mock.method(logger, 'warn', () => undefined)
+    const payload: OCPP20TransactionEventResponse = {
+      idTokenInfo: {
+        status: OCPP20AuthorizationStatusEnumType.Invalid,
+      },
+    }
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Started
+    )
+
+    // Act
+    await testable.handleResponseTransactionEvent(station, payload, requestPayload)
+
+    // Assert
+    const overrideMarkerCalls = warnMock.mock.calls.filter(call => {
+      const firstArg: unknown = call.arguments[0]
+      return (
+        typeof firstArg === 'string' && firstArg.includes('forceTransactionOnInvalidIdToken=true')
+      )
+    })
+    assert.strictEqual(overrideMarkerCalls.length, 1)
+  })
+
+  // 2.0-T6 — `idTokenInfo == null` is treated as Accepted under both flag values.
+  // Split into two `it()` blocks (flag-on / flag-off) so each runs against a
+  // freshly-mocked station; avoids the mid-test cleanup+remock pattern.
+  // Both branches additionally assert that the override-marker warn-log is
+  // NOT emitted on null payload (locks the invariant against the A6 regression
+  // where someone "fixes" the override-marker `else if` to also fire on null).
+  await it('should treat null idTokenInfo as Accepted when the flag is true', async () => {
+    const mockDeauthOn = mock.method(
+      OCPP20ServiceUtils,
+      'requestDeauthorizeTransaction',
+      async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => undefined)
+    mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => undefined)
+    const warnMockOn = mock.method(logger, 'warn', () => undefined)
+
+    const payload: OCPP20TransactionEventResponse = {}
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Started
+    )
+
+    await testable.handleResponseTransactionEvent(station, payload, requestPayload)
+
+    assert.strictEqual(mockDeauthOn.mock.calls.length, 0)
+    const overrideMarkerCallsOn = warnMockOn.mock.calls.filter(call => {
+      const firstArg: unknown = call.arguments[0]
+      return (
+        typeof firstArg === 'string' && firstArg.includes('forceTransactionOnInvalidIdToken=true')
+      )
+    })
+    assert.strictEqual(overrideMarkerCallsOn.length, 0)
+  })
+
+  await it('should treat null idTokenInfo as Accepted when the flag is false', async () => {
+    const { station: flagOffStation } = createMockChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 1,
+      evseConfiguration: { evsesCount: 1 },
+      stationInfo: {
+        forceTransactionOnInvalidIdToken: false,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WS_PING_INTERVAL_SECONDS,
+    })
+    setupConnectorWithTransaction(flagOffStation, 1, { transactionId: 100 })
+    const flagOffConnector = flagOffStation.getConnectorStatus(1)
+    if (flagOffConnector != null) {
+      flagOffConnector.transactionId = TEST_TRANSACTION_UUID
+    }
+    const mockDeauthOff = mock.method(
+      OCPP20ServiceUtils,
+      'requestDeauthorizeTransaction',
+      async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+    )
+    mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => undefined)
+    mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => undefined)
+    const warnMockOff = mock.method(logger, 'warn', () => undefined)
+    const flagOffTestable = createTestableResponseService(new OCPP20ResponseService())
+
+    const payload: OCPP20TransactionEventResponse = {}
+    const requestPayload = buildTransactionEventRequest(
+      TEST_TRANSACTION_UUID,
+      OCPP20TransactionEventEnumType.Started
+    )
+
+    await flagOffTestable.handleResponseTransactionEvent(flagOffStation, payload, requestPayload)
+
+    assert.strictEqual(mockDeauthOff.mock.calls.length, 0)
+    const overrideMarkerCallsOff = warnMockOff.mock.calls.filter(call => {
+      const firstArg: unknown = call.arguments[0]
+      return (
+        typeof firstArg === 'string' && firstArg.includes('forceTransactionOnInvalidIdToken=true')
+      )
+    })
+    assert.strictEqual(overrideMarkerCallsOff.length, 0)
+  })
+
+  // 2.0-T7 — Status-enum parity: every non-Accepted status follows the override
+  // path on Started when the flag is true (deauth NOT called, MV pumps run).
+  // ConcurrentTx is omitted: per OCPP 2.0.1 it is not a rejection of the IdToken
+  // itself but a signal that another transaction is already running, handled in
+  // a different code path that is outside this issue's scope.
+  // `Object.values(enum)` derivation makes future enum additions auto-covered.
+  for (const status of Object.values(OCPP20AuthorizationStatusEnumType).filter(
+    s =>
+      s !== OCPP20AuthorizationStatusEnumType.Accepted &&
+      s !== OCPP20AuthorizationStatusEnumType.ConcurrentTx
+  )) {
+    await it(`should override Started for status=${status} when the flag is true`, async () => {
+      // Arrange
+      const mockDeauthTransaction = mock.method(
+        OCPP20ServiceUtils,
+        'requestDeauthorizeTransaction',
+        async () => Promise.resolve({} as OCPP20TransactionEventResponse)
+      )
+      const mockStartUpdated = mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => {
+        /* noop */
+      })
+      const mockStartEnded = mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => {
+        /* noop */
+      })
+      const payload: OCPP20TransactionEventResponse = {
+        idTokenInfo: {
+          status,
+        },
+      }
+      const requestPayload = buildTransactionEventRequest(
+        TEST_TRANSACTION_UUID,
+        OCPP20TransactionEventEnumType.Started
+      )
+
+      // Act
+      await testable.handleResponseTransactionEvent(station, payload, requestPayload)
+
+      // Assert
+      assert.strictEqual(mockDeauthTransaction.mock.calls.length, 0)
+      assert.strictEqual(mockStartUpdated.mock.calls.length, 1)
+      assert.strictEqual(mockStartEnded.mock.calls.length, 1)
+    })
+  }
+
+  // 2.0-T8 — Auth-cache update invariant (C10.FR.01/04/05): the cache is
+  // written with the CSMS-supplied idTokenInfo regardless of whether the
+  // override path was taken. Mocks `OCPP20ServiceUtils.updateAuthorizationCache`
+  // and asserts call-count = 1 with the right idToken + idTokenInfo for both
+  // flag states.
+  for (const flagState of [true, false] as const) {
+    await it(`should update the authorization cache regardless of the flag (flag=${String(flagState)})`, async () => {
+      const { station: cacheStation } = createMockChargingStation({
+        baseName: TEST_CHARGING_STATION_BASE_NAME,
+        connectorsCount: 1,
+        evseConfiguration: { evsesCount: 1 },
+        stationInfo: {
+          forceTransactionOnInvalidIdToken: flagState,
+          ocppStrictCompliance: false,
+          ocppVersion: OCPPVersion.VERSION_201,
+        },
+        websocketPingInterval: Constants.DEFAULT_WS_PING_INTERVAL_SECONDS,
+      })
+      setupConnectorWithTransaction(cacheStation, 1, { transactionId: 100 })
+      const cacheConnector = cacheStation.getConnectorStatus(1)
+      if (cacheConnector != null) {
+        cacheConnector.transactionId = TEST_TRANSACTION_UUID
+      }
+      const cacheTestable = createTestableResponseService(new OCPP20ResponseService())
+      mock.method(OCPP20ServiceUtils, 'requestDeauthorizeTransaction', async () =>
+        Promise.resolve({} as OCPP20TransactionEventResponse)
+      )
+      mock.method(OCPP20ServiceUtils, 'startUpdatedMeterValues', () => undefined)
+      mock.method(OCPP20ServiceUtils, 'startEndedMeterValues', () => undefined)
+      const updateAuthMock = mock.method(
+        OCPP20ServiceUtils,
+        'updateAuthorizationCache',
+        () => undefined
+      )
+      const idToken = { idToken: TEST_ID_TAG, type: OCPP20IdTokenEnumType.ISO14443 }
+      const payload: OCPP20TransactionEventResponse = {
+        idTokenInfo: { status: OCPP20AuthorizationStatusEnumType.Invalid },
+      }
+      const requestPayload = buildTransactionEventRequest(
+        TEST_TRANSACTION_UUID,
+        OCPP20TransactionEventEnumType.Started,
+        idToken
+      )
+
+      await cacheTestable.handleResponseTransactionEvent(cacheStation, payload, requestPayload)
+
+      assert.strictEqual(updateAuthMock.mock.calls.length, 1)
+      assert.deepStrictEqual(updateAuthMock.mock.calls[0].arguments[1], idToken)
+      assert.deepStrictEqual(updateAuthMock.mock.calls[0].arguments[2], {
+        status: OCPP20AuthorizationStatusEnumType.Invalid,
+      })
+    })
+  }
+})