feat(simulator): add forceTransactionOnInvalidIdToken template flag (#1907)
* 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.