]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor: align variable naming and remove non-null assertions in tests and UI
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Mar 2026 20:15:44 +0000 (21:15 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Mar 2026 20:15:44 +0000 (21:15 +0100)
Apply the same naming conventions established in src/ to tests/ and ui/:
- connector → connectorStatus for ConnectorStatus values (48 instances)
- Stats → Statistics: heartbeatStats, authStats, rateLimitStats (3)
- Config → Configuration: atgConfig, originalConfig, updatedConfig,
  singleServerConfig, multiServerConfig (7)
- Remove 11 eslint-disable no-non-null-assertion suppressions in
  tests/ and ui/web/src/ with proper null guards

19 files changed:
tests/charging-station/ChargingStation-Connectors.test.ts
tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts
tests/charging-station/ocpp/1.6/OCPP16Integration-Reservations.test.ts
tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts
tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts
tests/charging-station/ocpp/2.0/OCPP20ResponseService-TransactionEvent.test.ts
tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts
tests/charging-station/ocpp/OCPPServiceUtils-StopTransaction.test.ts
tests/charging-station/ocpp/OCPPServiceUtils-connectorStatus.test.ts
tests/helpers/OCPPAuthIntegrationTest.ts
tests/helpers/TestLifecycleHelpers.ts
tests/ocpp2-e2e-test-plan.md [new file with mode: 0644]
tests/utils/ChargingStationConfigurationUtils.test.ts
tests/utils/MessageChannelUtils.test.ts
ui/web/src/composables/UIClient.ts
ui/web/tests/unit/CSConnector.test.ts
ui/web/tests/unit/ChargingStationsView.test.ts

index e90c2cc36e8fb4c9d5c1e787095b20d54d6e6ab3..98e65e56c31f7880949639b57dd91b8b0d9c3367 100644 (file)
@@ -372,10 +372,11 @@ await describe('ChargingStation Connector and EVSE State', async () => {
       assert.strictEqual(station.inPendingState(), true)
 
       // Act - transition from PENDING to ACCEPTED
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      station.bootNotificationResponse!.status = RegistrationStatusEnumType.ACCEPTED
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      station.bootNotificationResponse!.currentTime = new Date()
+      if (station.bootNotificationResponse == null) {
+        throw new Error('Expected bootNotificationResponse to be defined')
+      }
+      station.bootNotificationResponse.status = RegistrationStatusEnumType.ACCEPTED
+      station.bootNotificationResponse.currentTime = new Date()
 
       // Assert
       assert.strictEqual(station.inAcceptedState(), true)
@@ -391,10 +392,11 @@ await describe('ChargingStation Connector and EVSE State', async () => {
       assert.strictEqual(station.inPendingState(), true)
 
       // Act - transition from PENDING to REJECTED
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      station.bootNotificationResponse!.status = RegistrationStatusEnumType.REJECTED
-      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-      station.bootNotificationResponse!.currentTime = new Date()
+      if (station.bootNotificationResponse == null) {
+        throw new Error('Expected bootNotificationResponse to be defined')
+      }
+      station.bootNotificationResponse.status = RegistrationStatusEnumType.REJECTED
+      station.bootNotificationResponse.currentTime = new Date()
 
       // Assert
       assert.strictEqual(station.inRejectedState(), true)
index 77f752146e301a6f8cb341f4280dd558a79c2af9..191983aa4a340165ca32a3a78435ace00a681ed8 100644 (file)
@@ -70,10 +70,10 @@ await describe('OCPP16IncomingRequestService — Reset', async () => {
     // Arrange
     const { testableService } = testContext
     const station = ResetFixtures.createStandardStation(1)
-    const connector = station.getConnectorStatus(1)
-    if (connector != null) {
-      connector.transactionStarted = true
-      connector.transactionId = 1
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus != null) {
+      connectorStatus.transactionStarted = true
+      connectorStatus.transactionId = 1
     }
 
     const resetRequest: ResetRequest = {
@@ -95,10 +95,10 @@ await describe('OCPP16IncomingRequestService — Reset', async () => {
     // Arrange
     const { testableService } = testContext
     const station = ResetFixtures.createStandardStation(1)
-    const connector = station.getConnectorStatus(1)
-    if (connector != null) {
-      connector.transactionStarted = true
-      connector.transactionId = 1
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus != null) {
+      connectorStatus.transactionStarted = true
+      connectorStatus.transactionId = 1
     }
 
     const resetRequest: ResetRequest = {
index 7f33970f54bcb912e50c9b5f610836e3ea698b4f..455e05f7715cd1dd23b4d931c752277eb9a8049d 100644 (file)
@@ -284,15 +284,15 @@ await describe('OCPP16 Integration — Reservation Flow', async () => {
       assert.strictEqual(cancelResponse.status, GenericStatus.Rejected)
 
       // Assert — original reservation still intact
-      const connector = station.getConnectorStatus(1)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      if (connector.reservation == null) {
+      if (connectorStatus.reservation == null) {
         assert.fail('Expected reservation to be defined')
       }
-      assert.strictEqual(connector.reservation.reservationId, 500)
-      assert.strictEqual(connector.reservation.idTag, 'TAG-KEEP')
+      assert.strictEqual(connectorStatus.reservation.reservationId, 500)
+      assert.strictEqual(connectorStatus.reservation.idTag, 'TAG-KEEP')
     })
   })
 })
index f03c1e2932ba10faca7248016d4401f5cedf3a2e..bac5fe3e91001057798b2ddcae4a8af163701a50 100644 (file)
@@ -93,9 +93,9 @@ function createIntegrationContext (): {
   // Add MeterValues template required by buildTransactionBeginMeterValue
   for (const [connectorId] of station.connectors) {
     if (connectorId > 0) {
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector != null) {
-        connector.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus != null) {
+        connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
       }
     }
   }
@@ -282,12 +282,12 @@ await describe('OCPP16 Integration — Transaction Lifecycle', async () => {
     )
 
     // Assert: connector should be reset, no active transaction
-    const connector = station.getConnectorStatus(connectorId)
-    if (connector == null) {
+    const connectorStatus = station.getConnectorStatus(connectorId)
+    if (connectorStatus == null) {
       assert.fail('Expected connector to be defined')
     }
-    assert.strictEqual(connector.transactionStarted, false)
-    assert.strictEqual(connector.transactionId, undefined)
+    assert.strictEqual(connectorStatus.transactionStarted, false)
+    assert.strictEqual(connectorStatus.transactionId, undefined)
   })
 
   // ─── State consistency ───────────────────────────────────────────────
index ae76e7633aea311c061cf66d57d1b4cd2e7e7f09..61daa7d5c400019a7ff8aa22aa83b47c52089bcb 100644 (file)
@@ -52,9 +52,9 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
     // Add MeterValues template required by buildTransactionBeginMeterValue
     for (const [connectorId] of station.connectors) {
       if (connectorId > 0) {
-        const connector = station.getConnectorStatus(connectorId)
-        if (connector != null) {
-          connector.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+        const connectorStatus = station.getConnectorStatus(connectorId)
+        if (connectorStatus != null) {
+          connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
         }
       }
     }
@@ -92,14 +92,14 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionId, transactionId)
-      assert.strictEqual(connector.transactionStarted, true)
-      assert.strictEqual(connector.transactionIdTag, 'TEST-TAG-001')
-      assert.strictEqual(connector.transactionEnergyActiveImportRegisterValue, 0)
+      assert.strictEqual(connectorStatus.transactionId, transactionId)
+      assert.strictEqual(connectorStatus.transactionStarted, true)
+      assert.strictEqual(connectorStatus.transactionIdTag, 'TEST-TAG-001')
+      assert.strictEqual(connectorStatus.transactionEnergyActiveImportRegisterValue, 0)
     })
 
     // @spec §5.14 — TC_004_CS
@@ -126,12 +126,12 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert — connector should be reset (no transactionId)
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, false)
-      assert.strictEqual(connector.transactionId, undefined)
+      assert.strictEqual(connectorStatus.transactionStarted, false)
+      assert.strictEqual(connectorStatus.transactionId, undefined)
     })
 
     // @spec §5.14 — TC_010_CS
@@ -139,9 +139,9 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       // Arrange
       const connectorId = 1
       const reservationId = 5
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector != null) {
-        connector.reservation = {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus != null) {
+        connectorStatus.reservation = {
           connectorId,
           expiryDate: new Date(Date.now() + 3600000),
           idTag: 'TEST-TAG-001',
@@ -202,12 +202,12 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, true)
-      assert.deepStrictEqual(connector.transactionStart, requestTimestamp)
+      assert.strictEqual(connectorStatus.transactionStarted, true)
+      assert.deepStrictEqual(connectorStatus.transactionStart, requestTimestamp)
     })
 
     await it('should reset connector on rejected with Invalid status', async () => {
@@ -233,13 +233,13 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert — connector should be reset
-      const connector = station.getConnectorStatus(connectorId)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(connectorId)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, false)
-      assert.strictEqual(connector.transactionId, undefined)
-      assert.strictEqual(connector.transactionIdTag, undefined)
+      assert.strictEqual(connectorStatus.transactionStarted, false)
+      assert.strictEqual(connectorStatus.transactionId, undefined)
+      assert.strictEqual(connectorStatus.transactionIdTag, undefined)
     })
   })
 
@@ -268,12 +268,12 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert — connector should be reset after stop
-      const connector = station.getConnectorStatus(1)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, false)
-      assert.strictEqual(connector.transactionId, undefined)
+      assert.strictEqual(connectorStatus.transactionStarted, false)
+      assert.strictEqual(connectorStatus.transactionId, undefined)
     })
 
     // @spec §5.16 — TC_072_CS
@@ -296,12 +296,12 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert — connector should still be reset
-      const connector = station.getConnectorStatus(1)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, false)
-      assert.strictEqual(connector.transactionId, undefined)
+      assert.strictEqual(connectorStatus.transactionStarted, false)
+      assert.strictEqual(connectorStatus.transactionId, undefined)
     })
 
     await it('should clear transactionIdTag and energy register after stop', async () => {
@@ -329,15 +329,15 @@ await describe('OCPP16ResponseService — StartTransaction and StopTransaction',
       )
 
       // Assert
-      const connector = station.getConnectorStatus(1)
-      if (connector == null) {
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus == null) {
         assert.fail('Expected connector to be defined')
       }
-      assert.strictEqual(connector.transactionStarted, false)
-      assert.strictEqual(connector.transactionId, undefined)
-      assert.strictEqual(connector.transactionIdTag, undefined)
-      assert.strictEqual(connector.transactionEnergyActiveImportRegisterValue, 0)
-      assert.strictEqual(connector.transactionRemoteStarted, false)
+      assert.strictEqual(connectorStatus.transactionStarted, false)
+      assert.strictEqual(connectorStatus.transactionId, undefined)
+      assert.strictEqual(connectorStatus.transactionIdTag, undefined)
+      assert.strictEqual(connectorStatus.transactionEnergyActiveImportRegisterValue, 0)
+      assert.strictEqual(connectorStatus.transactionRemoteStarted, false)
     })
 
     await it('should not throw when transactionId does not match any connector', async () => {
index a888f5bd7b7deb12204e86e0e91372e4006fb706..6ce2be69ff5c1578ea17d3732691005c3118e34d 100644 (file)
@@ -306,8 +306,10 @@ await describe('B11 & B12 - Reset', async () => {
         await it('should return Rejected/FwUpdateInProgress when firmware is Downloading', async () => {
           const station = createTestStation()
           // Firmware check runs before OnIdle idle-state logic — always returns Rejected
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Downloading,
           })
 
@@ -328,8 +330,10 @@ await describe('B11 & B12 - Reset', async () => {
         await it('should return Rejected/FwUpdateInProgress when firmware is Downloaded', async () => {
           const station = createTestStation()
           // Firmware check runs before OnIdle idle-state logic — always returns Rejected
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Downloaded,
           })
 
@@ -350,8 +354,10 @@ await describe('B11 & B12 - Reset', async () => {
         await it('should return Rejected/FwUpdateInProgress when firmware is Installing', async () => {
           const station = createTestStation()
           // Firmware check runs before OnIdle idle-state logic — always returns Rejected
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Installing,
           })
 
@@ -372,8 +378,10 @@ await describe('B11 & B12 - Reset', async () => {
         await it('should return Accepted when firmware is Installed (complete)', async () => {
           const station = createTestStation()
           // Firmware status: Installed (complete)
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Installed,
           })
 
@@ -393,8 +401,10 @@ await describe('B11 & B12 - Reset', async () => {
         await it('should return Accepted when firmware status is Idle', async () => {
           const station = createTestStation()
           // Firmware status: Idle
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Idle,
           })
 
@@ -429,9 +439,9 @@ await describe('B11 & B12 - Reset', async () => {
           const evse: EvseStatus | undefined = station.evses.get(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
-            const connector = evse.connectors.get(connectorId)
-            if (connector) {
-              connector.reservation = mockReservation as Reservation
+            const connectorStatus = evse.connectors.get(connectorId)
+            if (connectorStatus) {
+              connectorStatus.reservation = mockReservation as Reservation
             }
           }
 
@@ -462,9 +472,9 @@ await describe('B11 & B12 - Reset', async () => {
           const evse: EvseStatus | undefined = station.evses.get(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
-            const connector = evse.connectors.get(connectorId)
-            if (connector) {
-              connector.reservation = mockReservation as Reservation
+            const connectorStatus = evse.connectors.get(connectorId)
+            if (connectorStatus) {
+              connectorStatus.reservation = mockReservation as Reservation
             }
           }
 
@@ -508,8 +518,10 @@ await describe('B11 & B12 - Reset', async () => {
           // No transactions
           station.getNumberOfRunningTransactions = () => 0
           // No firmware update
-          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-          Object.assign(station.stationInfo!, {
+          if (station.stationInfo == null) {
+            throw new Error('Expected stationInfo to be defined')
+          }
+          Object.assign(station.stationInfo, {
             firmwareStatus: FirmwareStatus.Idle,
           })
           // No reservations (default)
@@ -541,9 +553,9 @@ await describe('B11 & B12 - Reset', async () => {
           const evse: EvseStatus | undefined = station.evses.get(1)
           if (evse) {
             const connectorId = [...evse.connectors.keys()][0]
-            const connector = evse.connectors.get(connectorId)
-            if (connector) {
-              connector.reservation = mockReservation as Reservation
+            const connectorStatus = evse.connectors.get(connectorId)
+            if (connectorStatus) {
+              connectorStatus.reservation = mockReservation as Reservation
             }
           }
 
index af71d2b113ec4ad2c6d71d755f6710835c15a390..ea86a3348c3720990ff28370f9aaa6aaeb1bedf8 100644 (file)
@@ -138,9 +138,9 @@ await describe('F05 - UnlockConnector', async () => {
       const { mockStation } = createUnlockConnectorStation()
 
       const evseStatus = mockStation.evses.get(1)
-      const connector = evseStatus?.connectors.get(1)
-      if (connector != null) {
-        connector.transactionId = 'tx-001'
+      const connectorStatus = evseStatus?.connectors.get(1)
+      if (connectorStatus != null) {
+        connectorStatus.transactionId = 'tx-001'
       }
 
       const request: OCPP20UnlockConnectorRequest = { connectorId: 1, evseId: 1 }
index ee3edecaacd5d5b8fe37168817525629d722c41a..b578be1bcb984bf8eee77122882a6d86a0671268 100644 (file)
@@ -94,9 +94,9 @@ await describe('D01 - TransactionEvent Response', async () => {
     // Set connector transactionId to the UUID string used in request payloads
     setupConnectorWithTransaction(station, 1, { transactionId: 100 })
     // Override with UUID string so getConnectorIdByTransactionId can find it
-    const connector = station.getConnectorStatus(1)
-    if (connector != null) {
-      connector.transactionId = TEST_TRANSACTION_ID
+    const connectorStatus = station.getConnectorStatus(1)
+    if (connectorStatus != null) {
+      connectorStatus.transactionId = TEST_TRANSACTION_ID
     }
     const responseService = new OCPP20ResponseService()
     testable = createTestableResponseService(responseService)
index 26dfae656dbefce3bd4ab698cf202fc4452ea663..c213fe7a13ddff69f9add93ebfba18a5ad3c4144 100644 (file)
@@ -1419,9 +1419,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
     afterEach(() => {
       for (let connectorId = 1; connectorId <= 3; connectorId++) {
-        const connector = mockStation.getConnectorStatus(connectorId)
-        if (connector != null) {
-          connector.transactionEventQueue = undefined
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        if (connectorStatus != null) {
+          connectorStatus.transactionEventQueue = undefined
         }
       }
       standardCleanup()
@@ -1448,11 +1448,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
         assert.strictEqual(response.idTokenInfo, undefined)
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert(connector != null)
-        assert(connector.transactionEventQueue != null)
-        assert.strictEqual(connector.transactionEventQueue.length, 1)
-        assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert(connectorStatus != null)
+        assert(connectorStatus.transactionEventQueue != null)
+        assert.strictEqual(connectorStatus.transactionEventQueue.length, 1)
+        assert.strictEqual(connectorStatus.transactionEventQueue[0].seqNo, 0)
       })
 
       await it('should queue multiple TransactionEvents in order when offline', async () => {
@@ -1487,23 +1487,23 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           transactionId
         )
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.strictEqual(connector?.transactionEventQueue?.length, 3)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.strictEqual(connectorStatus?.transactionEventQueue?.length, 3)
 
-        assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
-        assert.strictEqual(connector.transactionEventQueue[1].seqNo, 1)
-        assert.strictEqual(connector.transactionEventQueue[2].seqNo, 2)
+        assert.strictEqual(connectorStatus.transactionEventQueue[0].seqNo, 0)
+        assert.strictEqual(connectorStatus.transactionEventQueue[1].seqNo, 1)
+        assert.strictEqual(connectorStatus.transactionEventQueue[2].seqNo, 2)
 
         assert.ok(
-          connector.transactionEventQueue[0].request.eventType,
+          connectorStatus.transactionEventQueue[0].request.eventType,
           OCPP20TransactionEventEnumType.Started
         )
         assert.strictEqual(
-          connector.transactionEventQueue[1].request.eventType,
+          connectorStatus.transactionEventQueue[1].request.eventType,
           OCPP20TransactionEventEnumType.Updated
         )
         assert.strictEqual(
-          connector.transactionEventQueue[2].request.eventType,
+          connectorStatus.transactionEventQueue[2].request.eventType,
           OCPP20TransactionEventEnumType.Ended
         )
       })
@@ -1547,11 +1547,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           transactionId
         )
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.strictEqual(connector?.transactionEventQueue?.length, 2)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.strictEqual(connectorStatus?.transactionEventQueue?.length, 2)
         // Online path with mock doesn't call buildTransactionEvent, so seqNo starts from 0
-        assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
-        assert.strictEqual(connector.transactionEventQueue[1].seqNo, 1)
+        assert.strictEqual(connectorStatus.transactionEventQueue[0].seqNo, 0)
+        assert.strictEqual(connectorStatus.transactionEventQueue[1].seqNo, 1)
       })
 
       await it('should include timestamp in queued events', async () => {
@@ -1571,13 +1571,15 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         )
         const afterQueue = new Date()
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.ok(connector?.transactionEventQueue?.[0]?.timestamp instanceof Date)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.ok(connectorStatus?.transactionEventQueue?.[0]?.timestamp instanceof Date)
         assert.strictEqual(
-          connector.transactionEventQueue[0].timestamp.getTime() >= beforeQueue.getTime(),
+          connectorStatus.transactionEventQueue[0].timestamp.getTime() >= beforeQueue.getTime(),
           true
         )
-        assert.ok(connector.transactionEventQueue[0].timestamp.getTime() <= afterQueue.getTime())
+        assert.ok(
+          connectorStatus.transactionEventQueue[0].timestamp.getTime() <= afterQueue.getTime()
+        )
       })
 
       await it('should set offline flag to true when queueing transaction event while station is offline', async () => {
@@ -1595,10 +1597,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           transactionId
         )
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.ok(connector?.transactionEventQueue != null)
-        assert.strictEqual(connector.transactionEventQueue.length, 1)
-        assert.strictEqual(connector.transactionEventQueue[0].request.offline, true)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.ok(connectorStatus?.transactionEventQueue != null)
+        assert.strictEqual(connectorStatus.transactionEventQueue.length, 1)
+        assert.strictEqual(connectorStatus.transactionEventQueue[0].request.offline, true)
       })
     })
 
@@ -1660,20 +1662,20 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
           transactionId
         )
 
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert(connector != null)
-        connector.transactionStarted = true
-        connector.transactionId = transactionId
-        connector.locked = true
-        assert.strictEqual(connector.transactionEventQueue?.length, 2)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert(connectorStatus != null)
+        connectorStatus.transactionStarted = true
+        connectorStatus.transactionId = transactionId
+        connectorStatus.locked = true
+        assert.strictEqual(connectorStatus.transactionEventQueue?.length, 2)
 
         setOnline(true)
         await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
 
-        assert.strictEqual(connector.transactionEventQueue.length, 0)
-        assert.strictEqual(connector.transactionStarted, false)
-        assert.strictEqual(connector.transactionId, undefined)
-        assert.strictEqual(connector.locked, false)
+        assert.strictEqual(connectorStatus.transactionEventQueue.length, 0)
+        assert.strictEqual(connectorStatus.transactionStarted, false)
+        assert.strictEqual(connectorStatus.transactionId, undefined)
+        assert.strictEqual(connectorStatus.locked, false)
       })
 
       await it('should preserve FIFO order when draining queue', async () => {
@@ -1737,9 +1739,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
       await it('should handle null queue gracefully', async () => {
         const connectorId = 1
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert(connector != null)
-        connector.transactionEventQueue = undefined
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert(connectorStatus != null)
+        connectorStatus.transactionEventQueue = undefined
 
         await assert.doesNotReject(
           OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
@@ -1996,10 +1998,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
     afterEach(() => {
       // Clean up any running timers
       for (let connectorId = 1; connectorId <= 3; connectorId++) {
-        const connector = mockStation.getConnectorStatus(connectorId)
-        if (connector?.transactionMeterValuesSetInterval != null) {
-          clearInterval(connector.transactionMeterValuesSetInterval)
-          connector.transactionMeterValuesSetInterval = undefined
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        if (connectorStatus?.transactionMeterValuesSetInterval != null) {
+          clearInterval(connectorStatus.transactionMeterValuesSetInterval)
+          connectorStatus.transactionMeterValuesSetInterval = undefined
         }
       }
       standardCleanup()
@@ -2018,8 +2020,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
 
           await startPeriodicMeterValues(ocpp16Station, 1, 60000)
 
-          const connector = ocpp16Station.getConnectorStatus(1)
-          assert.strictEqual(connector?.transactionMeterValuesSetInterval, undefined)
+          const connectorStatus = ocpp16Station.getConnectorStatus(1)
+          assert.strictEqual(connectorStatus?.transactionMeterValuesSetInterval, undefined)
         })
       })
 
@@ -2027,23 +2029,23 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         const connectorId = 1
 
         // Simulate startTxUpdatedInterval with zero interval
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.notStrictEqual(connector, undefined)
-        assert(connector != null)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.notStrictEqual(connectorStatus, undefined)
+        assert(connectorStatus != null)
 
         // Zero interval should not start timer
         // This is verified by the implementation logging debug message
-        assert.strictEqual(connector.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
       })
 
       await it('should not start timer when interval is negative', () => {
         const connectorId = 1
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.notStrictEqual(connector, undefined)
-        assert(connector != null)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.notStrictEqual(connectorStatus, undefined)
+        assert(connectorStatus != null)
 
         // Negative interval should not start timer
-        assert.strictEqual(connector.transactionMeterValuesSetInterval, undefined)
+        assert.strictEqual(connectorStatus.transactionMeterValuesSetInterval, undefined)
       })
 
       await it('should handle non-existent connector gracefully', () => {
@@ -2117,8 +2119,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => {
         }
 
         // Verify sequence numbers are continuous: 0, 1, 2, 3
-        const connector = mockStation.getConnectorStatus(connectorId)
-        assert.strictEqual(connector?.transactionSeqNo, 3)
+        const connectorStatus = mockStation.getConnectorStatus(connectorId)
+        assert.strictEqual(connectorStatus?.transactionSeqNo, 3)
       })
 
       await it('should maintain correct eventType (Updated) for periodic events', async () => {
index fe3239365d3f30ae2b191fee56c762c90f6645eb..ffa3dc926a8e72035a915a8217c89931bf0b02d8 100644 (file)
@@ -53,14 +53,14 @@ function setupPendingTransaction (
   connectorId: number,
   txId: string
 ): void {
-  const connector = station.getConnectorStatus(connectorId)
-  if (connector == null) {
+  const connectorStatus = station.getConnectorStatus(connectorId)
+  if (connectorStatus == null) {
     throw new Error(`Connector ${String(connectorId)} not found`)
   }
-  connector.transactionPending = true
-  connector.transactionStarted = false
-  connector.transactionId = txId
-  connector.transactionStart = new Date()
+  connectorStatus.transactionPending = true
+  connectorStatus.transactionStarted = false
+  connectorStatus.transactionId = txId
+  connectorStatus.transactionStart = new Date()
 }
 
 /**
@@ -74,15 +74,15 @@ function setupTransaction (
   connectorId: number,
   txId: number | string
 ): void {
-  const connector = station.getConnectorStatus(connectorId)
-  if (connector == null) {
+  const connectorStatus = station.getConnectorStatus(connectorId)
+  if (connectorStatus == null) {
     throw new Error(`Connector ${String(connectorId)} not found`)
   }
-  connector.transactionStarted = true
-  connector.transactionId = txId
-  connector.transactionIdTag = `TAG-${String(txId)}`
-  connector.transactionStart = new Date()
-  connector.idTagAuthorized = true
+  connectorStatus.transactionStarted = true
+  connectorStatus.transactionId = txId
+  connectorStatus.transactionIdTag = `TAG-${String(txId)}`
+  connectorStatus.transactionStart = new Date()
+  connectorStatus.idTagAuthorized = true
 }
 
 await describe('OCPPServiceUtils — stop transaction functions', async () => {
@@ -262,15 +262,15 @@ await describe('OCPPServiceUtils — stop transaction functions', async () => {
       requestHandler.mock.mockImplementation(async (..._args: unknown[]) =>
         Promise.resolve({ idTokenInfo: { status: 'Accepted' } })
       )
-      const connector = station.getConnectorStatus(1)
-      assert.notStrictEqual(connector, undefined)
-      assert(connector != null)
-      delete connector.transactionId
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.notStrictEqual(connectorStatus, undefined)
+      assert(connectorStatus != null)
+      delete connectorStatus.transactionId
 
       await startTransactionOnConnector(station, 1)
 
-      assert.notStrictEqual(connector.transactionId, undefined)
-      assert.strictEqual(typeof connector.transactionId, 'string')
+      assert.notStrictEqual(connectorStatus.transactionId, undefined)
+      assert.strictEqual(typeof connectorStatus.transactionId, 'string')
     })
   })
 
@@ -287,10 +287,10 @@ await describe('OCPPServiceUtils — stop transaction functions', async () => {
         ocppVersion: OCPPVersion.VERSION_20,
       })
       requestHandler.mock.mockImplementation(async (..._args: unknown[]) => Promise.resolve({}))
-      const connector = station.getConnectorStatus(1)
-      assert.notStrictEqual(connector, undefined)
-      assert(connector != null)
-      connector.transactionEventQueue = [
+      const connectorStatus = station.getConnectorStatus(1)
+      assert.notStrictEqual(connectorStatus, undefined)
+      assert(connectorStatus != null)
+      connectorStatus.transactionEventQueue = [
         {
           request: {
             eventType: 'Updated',
@@ -307,7 +307,7 @@ await describe('OCPPServiceUtils — stop transaction functions', async () => {
 
       await flushQueuedTransactionMessages(station)
 
-      assert.strictEqual(connector.transactionEventQueue.length, 0)
+      assert.strictEqual(connectorStatus.transactionEventQueue.length, 0)
     })
   })
 
index 94ab7111dde59e2c6f474e2b5d3c15ccf79a245c..7a6f0802129b3b181c10cb4b6662a56e7df6dc93 100644 (file)
@@ -148,18 +148,18 @@ await describe('OCPPServiceUtils — connector status management', async () => {
     await it('should restore to Reserved when connector has reservation and is not Reserved', async () => {
       const { station } = createStationWithRequestHandler()
 
-      const connector = station.getConnectorStatus(1)
-      if (connector != null) {
-        connector.reservation = {
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus != null) {
+        connectorStatus.reservation = {
           connectorId: 1,
           expiryDate: new Date().toISOString(),
           idTag: 'TEST-TAG',
           reservationId: 1,
         } as unknown as Reservation
-        connector.status = ConnectorStatusEnum.Occupied
+        connectorStatus.status = ConnectorStatusEnum.Occupied
       }
 
-      await restoreConnectorStatus(station, 1, connector)
+      await restoreConnectorStatus(station, 1, connectorStatus)
 
       assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Reserved)
     })
@@ -167,12 +167,12 @@ await describe('OCPPServiceUtils — connector status management', async () => {
     await it('should restore to Available when connector has no reservation and is not Available', async () => {
       const { station } = createStationWithRequestHandler()
 
-      const connector = station.getConnectorStatus(1)
-      if (connector != null) {
-        connector.status = ConnectorStatusEnum.Occupied
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus != null) {
+        connectorStatus.status = ConnectorStatusEnum.Occupied
       }
 
-      await restoreConnectorStatus(station, 1, connector)
+      await restoreConnectorStatus(station, 1, connectorStatus)
 
       assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Available)
     })
@@ -180,12 +180,12 @@ await describe('OCPPServiceUtils — connector status management', async () => {
     await it('should not change status when connector is already Available with no reservation', async () => {
       const { requestHandler, station } = createStationWithRequestHandler()
 
-      const connector = station.getConnectorStatus(1)
-      if (connector != null) {
-        connector.status = ConnectorStatusEnum.Available
+      const connectorStatus = station.getConnectorStatus(1)
+      if (connectorStatus != null) {
+        connectorStatus.status = ConnectorStatusEnum.Available
       }
 
-      await restoreConnectorStatus(station, 1, connector)
+      await restoreConnectorStatus(station, 1, connectorStatus)
 
       assert.strictEqual(requestHandler.mock.calls.length, 0)
       assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Available)
index 47ad76df43fb63300785d6bf918600474f4f6be2..d873e234cd23e2dcb8f52a8677f219537d01907a 100644 (file)
@@ -126,7 +126,7 @@ export class OCPPAuthIntegrationTest {
   }
 
   private testConfigurationManagement (): void {
-    const originalConfig = this.authService.getConfiguration()
+    const originalConfiguration = this.authService.getConfiguration()
 
     const updates: Partial<AuthConfiguration> = {
       authorizationTimeout: 60,
@@ -136,21 +136,21 @@ export class OCPPAuthIntegrationTest {
 
     this.authService.updateConfiguration(updates)
 
-    const updatedConfig = this.authService.getConfiguration()
+    const updatedConfiguration = this.authService.getConfiguration()
 
-    if (updatedConfig.authorizationTimeout !== 60) {
+    if (updatedConfiguration.authorizationTimeout !== 60) {
       throw new Error('Configuration update failed: authorizationTimeout')
     }
 
-    if (updatedConfig.localAuthListEnabled) {
+    if (updatedConfiguration.localAuthListEnabled) {
       throw new Error('Configuration update failed: localAuthListEnabled')
     }
 
-    if (updatedConfig.maxCacheEntries !== 2000) {
+    if (updatedConfiguration.maxCacheEntries !== 2000) {
       throw new Error('Configuration update failed: maxCacheEntries')
     }
 
-    this.authService.updateConfiguration(originalConfig)
+    this.authService.updateConfiguration(originalConfiguration)
 
     logger.debug(`${this.chargingStation.logPrefix()} Configuration management test completed`)
   }
@@ -256,8 +256,8 @@ export class OCPPAuthIntegrationTest {
       throw new Error('Invalid statistics object')
     }
 
-    const authStats = this.authService.getAuthenticationStats()
-    if (!Array.isArray(authStats.availableStrategies)) {
+    const authStatistics = this.authService.getAuthenticationStats()
+    if (!Array.isArray(authStatistics.availableStrategies)) {
       throw new Error('Invalid authentication statistics')
     }
 
index e12c807fb3097f8cb840fa4cbd8e9f5102826d8d..9236282374a41ace4e28455ee866b473f078ddb2 100644 (file)
@@ -98,24 +98,24 @@ interface TimerTestContext {
  * @param connectorId - Connector to clear
  */
 export function clearConnectorTransaction (station: ChargingStation, connectorId: number): void {
-  const connector = station.getConnectorStatus(connectorId)
-  if (connector == null) {
+  const connectorStatus = station.getConnectorStatus(connectorId)
+  if (connectorStatus == null) {
     return
   }
 
-  connector.transactionStarted = false
-  connector.transactionId = undefined
-  connector.transactionIdTag = undefined
-  connector.transactionEnergyActiveImportRegisterValue = 0
-  connector.transactionRemoteStarted = false
-  connector.transactionStart = undefined
-  connector.idTagAuthorized = false
-  connector.idTagLocalAuthorized = false
+  connectorStatus.transactionStarted = false
+  connectorStatus.transactionId = undefined
+  connectorStatus.transactionIdTag = undefined
+  connectorStatus.transactionEnergyActiveImportRegisterValue = 0
+  connectorStatus.transactionRemoteStarted = false
+  connectorStatus.transactionStart = undefined
+  connectorStatus.idTagAuthorized = false
+  connectorStatus.idTagLocalAuthorized = false
 
   // Clear any transaction interval
-  if (connector.transactionMeterValuesSetInterval != null) {
-    clearInterval(connector.transactionMeterValuesSetInterval)
-    connector.transactionMeterValuesSetInterval = undefined
+  if (connectorStatus.transactionMeterValuesSetInterval != null) {
+    clearInterval(connectorStatus.transactionMeterValuesSetInterval)
+    connectorStatus.transactionMeterValuesSetInterval = undefined
   }
 }
 
@@ -267,18 +267,18 @@ export function setupConnectorWithTransaction (
     transactionId: number
   }
 ): void {
-  const connector = station.getConnectorStatus(connectorId)
-  if (connector == null) {
+  const connectorStatus = station.getConnectorStatus(connectorId)
+  if (connectorStatus == null) {
     throw new Error(`Connector ${String(connectorId)} not found`)
   }
 
-  connector.transactionStarted = true
-  connector.transactionId = options.transactionId
-  connector.transactionIdTag = options.idTag ?? `TAG-${String(options.transactionId)}`
-  connector.transactionEnergyActiveImportRegisterValue = options.energyImport ?? 0
-  connector.transactionRemoteStarted = options.remoteStarted ?? false
-  connector.transactionStart = new Date()
-  connector.idTagAuthorized = true
+  connectorStatus.transactionStarted = true
+  connectorStatus.transactionId = options.transactionId
+  connectorStatus.transactionIdTag = options.idTag ?? `TAG-${String(options.transactionId)}`
+  connectorStatus.transactionEnergyActiveImportRegisterValue = options.energyImport ?? 0
+  connectorStatus.transactionRemoteStarted = options.remoteStarted ?? false
+  connectorStatus.transactionStart = new Date()
+  connectorStatus.idTagAuthorized = true
 }
 
 /**
diff --git a/tests/ocpp2-e2e-test-plan.md b/tests/ocpp2-e2e-test-plan.md
new file mode 100644 (file)
index 0000000..220285c
--- /dev/null
@@ -0,0 +1,414 @@
+# OCPP 2.0.1 End-to-End Test Plan
+
+E2E test scenarios for the charging station simulator's OCPP 2.0.1 stack.
+Executed via MCP tools against the mock OCPP server (`tests/ocpp-server/`).
+
+## Conventions
+
+| Item             | Value                                                           |
+| ---------------- | --------------------------------------------------------------- |
+| Mock server      | `cd tests/ocpp-server && poetry run python server.py [OPTIONS]` |
+| Station template | `keba-ocpp2.station-template.json`                              |
+| Station ID       | `CS-KEBA-OCPP2-00001`                                           |
+| EVSE / Connector | 1 / 1                                                           |
+| Supervision URL  | `ws://localhost:9000`                                           |
+
+### Execution Rules
+
+- **Tester manages the mock server only** — start/stop/restart with options.
+- All `--boot-status` and enum CLI values are **Title-Case** (`Accepted`, not `accepted`).
+
+### Reconnection
+
+The station auto-reconnects when the server restarts, with a **fixed 30s delay** (`reconnectExponentialDelay: false`, `ConnectionTimeOut: 30`). This means:
+
+- After a server restart, the station takes ~30s to reconnect (WebSocket close → sleep 30s → reopen).
+- The station does NOT re-send `BootNotification` if it already has `bootNotificationResponse.status = Accepted` in cache. It connects silently.
+- To force a fresh boot (e.g., to clear cached Inoperative state), use `stopChargingStation`/`startChargingStation` as a **setup step**, not as a test step.
+
+**To avoid the 30s reconnect delay between server restarts**, use this pattern:
+
+```
+1. Kill mock server
+2. Start new mock server with new options
+3. MCP: closeConnection (triggers CLOSE_NORMAL → resets retry count to 0)
+4. MCP: openConnection (immediate reconnect, no 30s wait)
+5. Wait ~5s for WebSocket handshake
+6. Proceed with test
+```
+
+### Verification
+
+A test case **passes** when ALL of:
+
+1. MCP tool response: `"status": "success"` (no `responsesFailed`)
+2. `readCombinedLog`: expected OCPP messages in correct order
+3. `listChargingStations`: expected station/connector state
+4. `readErrorLog`: no unexpected errors
+
+### Server Lifecycle
+
+Tests are grouped by server configuration to minimize restarts.
+Within a group, tests execute sequentially without restart.
+Between groups, use the close/open pattern above to avoid cumulative reconnect delays.
+
+---
+
+## A — Security
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case                    | Via                             | Steps                                                       | Expected                    |
+| --- | --------------------------- | ------------------------------- | ----------------------------------------------------------- | --------------------------- |
+| A03 | CS-initiated cert update    | MCP `signCertificate`           | Send CSR with `certificateType: ChargingStationCertificate` | Response `status: Accepted` |
+| A04 | Security event notification | MCP `securityEventNotification` | Send `type: FirmwareUpdated`                                | Response empty (success)    |
+
+### Server: `--boot-status Accepted --command CertificateSigned --delay 5`
+
+| TC  | Use Case                   | Via            | Steps     | Expected                                                                                                              |
+| --- | -------------------------- | -------------- | --------- | --------------------------------------------------------------------------------------------------------------------- |
+| A02 | CSMS-initiated cert update | Server command | Wait ~15s | CertificateSigned received → Rejected (statusInfo.reasonCode: InternalError — no cert manager in keba-ocpp2 template) |
+
+---
+
+## B — Provisioning
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case             | Via                   | Steps                              | Expected                                                                      |
+| --- | -------------------- | --------------------- | ---------------------------------- | ----------------------------------------------------------------------------- |
+| B01 | Cold Boot — Accepted | Auto (server restart) | Restart server, wait for reconnect | BootNotification → Accepted, StatusNotification(Available), Heartbeat started |
+| B04 | Offline reconnection | Server kill/restart   | Kill server, wait 10s, restart     | Station reconnects, re-sends BootNotification, returns to Available           |
+
+### Server: `--boot-status-sequence Pending,Accepted`
+
+| TC  | Use Case                     | Via                   | Steps                                  | Expected                                                                                       |
+| --- | ---------------------------- | --------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------- |
+| B02 | Cold Boot — Pending→Accepted | Auto (server restart) | Restart server, wait for 2 boot cycles | 1st BootNotification → Pending, station retries, 2nd → Accepted, StatusNotification(Available) |
+
+### Server: `--boot-status Rejected`
+
+| TC  | Use Case             | Via                   | Steps                              | Expected                                                                                                                                                                                                                                                 |
+| --- | -------------------- | --------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| B03 | Cold Boot — Rejected | Auto (server restart) | Restart server, wait for reconnect | BootNotification → Rejected → 1 retry (registrationMaxRetries defaults to 0) → Rejected → "Registration failure" log. No StatusNotification sent. Station stays in Rejected state but can still retry BootNotification on next reconnection (B03.FR.06). |
+
+### Server: various `--command X --delay 5`
+
+| TC   | Use Case                     | Server flags                                                                  | Expected                                                                                                        |
+| ---- | ---------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
+| B05  | SetVariables                 | `--command SetVariables --set-variables "OCPPCommCtrlr.HeartbeatInterval=30"` | SetVariablesResponse with result status                                                                         |
+| B06  | GetVariables                 | `--command GetVariables --get-variables "ChargingStation.AvailabilityState"`  | GetVariablesResponse with variable value                                                                        |
+| B07  | GetBaseReport                | `--command GetBaseReport`                                                     | GetBaseReportResponse Accepted + NotifyReport sequence                                                          |
+| B09  | SetNetworkProfile            | `--command SetNetworkProfile`                                                 | Response Rejected (NoSecurityDowngrade per B09.FR.01)                                                           |
+| B11  | Reset (no transaction)       | `--command Reset`                                                             | Reset Accepted → StatusNotification(Unavailable) → close → re-boot → Available                                  |
+| B11b | Reset OnIdle (no active txn) | `--command Reset --reset-type OnIdle`                                         | Reset Accepted → StatusNotification(Unavailable) → re-boot → Available (no transaction active, immediate reset) |
+
+---
+
+## C — Authorization
+
+### Server: `--boot-status Accepted` (normal auth)
+
+| TC  | Use Case           | Via             | Steps                                               | Expected                       |
+| --- | ------------------ | --------------- | --------------------------------------------------- | ------------------------------ |
+| C01 | Authorize — normal | MCP `authorize` | `idToken: {idToken: "any_token", type: "ISO14443"}` | `idTokenInfo.status: Accepted` |
+
+### Server: `--boot-status Accepted --auth-mode whitelist --whitelist valid_token test_token`
+
+| TC            | Use Case                    | Via             | Steps                    | Expected           |
+| ------------- | --------------------------- | --------------- | ------------------------ | ------------------ |
+| C01-WL-OK     | Authorize — whitelisted     | MCP `authorize` | `idToken: test_token`    | `status: Accepted` |
+| C01-WL-REJECT | Authorize — not whitelisted | MCP `authorize` | `idToken: unknown_token` | `status: Blocked`  |
+
+### Server: `--boot-status Accepted --auth-mode blacklist --blacklist blocked_token`
+
+| TC            | Use Case                    | Via             | Steps                    | Expected           |
+| ------------- | --------------------------- | --------------- | ------------------------ | ------------------ |
+| C01-BL-OK     | Authorize — not blacklisted | MCP `authorize` | `idToken: good_token`    | `status: Accepted` |
+| C01-BL-REJECT | Authorize — blacklisted     | MCP `authorize` | `idToken: blocked_token` | `status: Blocked`  |
+
+### Server: `--boot-status Accepted --auth-mode rate_limit`
+
+| TC     | Use Case                 | Via             | Steps     | Expected                |
+| ------ | ------------------------ | --------------- | --------- | ----------------------- |
+| C01-RL | Authorize — rate limited | MCP `authorize` | Any token | `status: NotAtThisTime` |
+
+### Server: `--boot-status Accepted --offline`
+
+| TC          | Use Case                    | Via             | Steps     | Expected                  |
+| ----------- | --------------------------- | --------------- | --------- | ------------------------- |
+| C01-OFFLINE | Authorize — network failure | MCP `authorize` | Any token | InternalError from server |
+
+### Server: `--boot-status Accepted --auth-group-id MyGroup --auth-cache-expiry 3600`
+
+| TC  | Use Case            | Via             | Steps     | Expected                                                                   |
+| --- | ------------------- | --------------- | --------- | -------------------------------------------------------------------------- |
+| C09 | GroupId in response | MCP `authorize` | Any token | `idTokenInfo.groupIdToken.idToken: MyGroup`, `cacheExpiryDateTime` present |
+
+### Server: `--boot-status Accepted --command ClearCache --delay 5`
+
+| TC  | Use Case         | Via            | Steps     | Expected              |
+| --- | ---------------- | -------------- | --------- | --------------------- |
+| C11 | Clear auth cache | Server command | Wait ~15s | ClearCache → Accepted |
+
+---
+
+## E — Transactions
+
+### Server: `--boot-status Accepted`
+
+| TC         | Use Case                    | Via                                                                                       | Steps                                                               | Expected                                                                                                             |
+| ---------- | --------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| E01-ATG    | Transaction lifecycle (ATG) | MCP `startAutomaticTransactionGenerator` → wait 30s → `stopAutomaticTransactionGenerator` | Wait for full cycle                                                 | Authorize → TransactionEvent.Started(seqNo=0) → Updated(seqNo=1+, MeterValues) → Ended(seqNo=N, stoppedReason=Local) |
+| E01-DIRECT | TransactionEvent direct     | MCP `transactionEvent`                                                                    | Send Started → Updated → Ended with `transactionId: "mcp-test-001"` | All 3 accepted, seqNo sequential                                                                                     |
+
+### Server: `--boot-status Accepted --commands "RequestStartTransaction:15,RequestStopTransaction:45"`
+
+| TC      | Use Case                   | Via             | Steps                    | Expected                                                                                                                                                                      |
+| ------- | -------------------------- | --------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| F01+F03 | Remote Start → Remote Stop | Server commands | Wait ~60s for full cycle | RequestStartTransaction → Accepted → TransactionEvent.Started(RemoteStart) → MeterValues → RequestStopTransaction (real txn ID via tracking) → TransactionEvent.Ended(Remote) |
+
+### Server: `--boot-status Accepted --command RequestStopTransaction --delay 5`
+
+| TC   | Use Case                    | Via            | Steps     | Expected                                                                                                               |
+| ---- | --------------------------- | -------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- |
+| F03b | Remote Stop — no active txn | Server command | Wait ~15s | RequestStopTransaction with fallback ID `test_transaction_123` → Rejected (invalid transaction ID format — not a UUID) |
+
+### Server: `--boot-status Accepted --total-cost 25.50`
+
+| TC  | Use Case           | Via                                                 | Steps                                    | Expected                             |
+| --- | ------------------ | --------------------------------------------------- | ---------------------------------------- | ------------------------------------ |
+| I02 | Running total cost | MCP `startAutomaticTransactionGenerator` → wait 30s | Check TransactionEvent.Updated responses | `totalCost: 25.5` in server response |
+
+---
+
+## F — Remote Control
+
+### Server: various `--command X --delay 5`
+
+| TC  | Use Case                             | Server flags                     | Expected                                                        |
+| --- | ------------------------------------ | -------------------------------- | --------------------------------------------------------------- |
+| F05 | Unlock connector                     | `--command UnlockConnector`      | UnlockConnector → Unlocked                                      |
+| E14 | GetTransactionStatus (no active txn) | `--command GetTransactionStatus` | GetTransactionStatus → messagesInQueue: false, uses fallback ID |
+
+### Server: `--boot-status Accepted --commands "RequestStartTransaction:15,GetTransactionStatus:25"`
+
+| TC         | Use Case                             | Via             | Steps     | Expected                                                                                         |
+| ---------- | ------------------------------------ | --------------- | --------- | ------------------------------------------------------------------------------------------------ |
+| E14-ACTIVE | GetTransactionStatus with active txn | Server commands | Wait ~35s | GetTransactionStatus → ongoingIndicator: true, messagesInQueue: false (real txn ID via tracking) |
+
+### Server: `--boot-status Accepted --commands "RequestStartTransaction:15,UnlockConnector:25"`
+
+| TC      | Use Case                          | Via             | Steps     | Expected                                       |
+| ------- | --------------------------------- | --------------- | --------- | ---------------------------------------------- |
+| F05-TXN | UnlockConnector during active txn | Server commands | Wait ~35s | UnlockConnector → OngoingAuthorizedTransaction |
+
+### Server: various `--command X --delay 5` (continued)
+
+| F06-SN | TriggerMessage (StatusNotification) | `--command TriggerMessage` | TriggerMessage → Accepted → StatusNotification sent |
+| F06-BN | TriggerMessage (BootNotification) | `--command TriggerMessage --trigger-message BootNotification` | TriggerMessage → Rejected(NotEnabled, F06.FR.17 — already accepted) |
+| F06-HB | TriggerMessage (Heartbeat) | `--command TriggerMessage --trigger-message Heartbeat` | TriggerMessage → Accepted → Heartbeat sent |
+| F06-MV | TriggerMessage (MeterValues) | `--command TriggerMessage --trigger-message MeterValues` | TriggerMessage → Accepted → MeterValues sent |
+| F06-FW | TriggerMessage (FirmwareStatus) | `--command TriggerMessage --trigger-message FirmwareStatusNotification` | TriggerMessage → Accepted → FirmwareStatusNotification(Idle) sent |
+| F06-LS | TriggerMessage (LogStatus) | `--command TriggerMessage --trigger-message LogStatusNotification` | TriggerMessage → Accepted → LogStatusNotification(Idle) sent |
+
+---
+
+## G — Availability
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case           | Via                      | Steps                                                                     | Expected                       |
+| --- | ------------------ | ------------------------ | ------------------------------------------------------------------------- | ------------------------------ |
+| G01 | StatusNotification | MCP `statusNotification` | Send for each status: Available, Occupied, Faulted, Unavailable, Reserved | All succeed (empty response)   |
+| G02 | Heartbeat          | MCP `heartbeat`          | Send 5x rapid                                                             | All succeed with `currentTime` |
+
+### Server: various `--command ChangeAvailability --delay 5`
+
+| TC  | Use Case                       | Server flags                                                     | Expected                                   |
+| --- | ------------------------------ | ---------------------------------------------------------------- | ------------------------------------------ |
+| G03 | ChangeAvailability Operative   | `--command ChangeAvailability`                                   | Accepted + StatusNotification(Available)   |
+| G04 | ChangeAvailability Inoperative | `--command ChangeAvailability --availability-status Inoperative` | Accepted + StatusNotification(Unavailable) |
+
+---
+
+## J — MeterValues
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case                    | Via                                                    | Steps                         | Expected                                                                                                                                                                                                       |
+| --- | --------------------------- | ------------------------------------------------------ | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| J01 | Non-transaction MeterValues | MCP `meterValues`                                      | Send Voltage=230V on evseId=1 | Response empty (success)                                                                                                                                                                                       |
+| J02 | Transaction MeterValues     | MCP `startAutomaticTransactionGenerator` → wait 60-90s | Check logs                    | TransactionEvent.Updated contains Voltage, Energy, Power, Current with context `Sample.Periodic`. Note: ATG start delay (15-30s) + MeterValueSampleInterval (30s) = first MeterValues ~45-60s after ATG start. |
+
+---
+
+## L — Firmware Management
+
+### Server: `--boot-status Accepted --command UpdateFirmware --delay 5`
+
+| TC  | Use Case               | Via            | Steps     | Expected                                                                                                                                               |
+| --- | ---------------------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| L01 | Secure Firmware Update | Server command | Wait ~40s | UpdateFirmware → Accepted → FirmwareStatusNotification: Downloading → Downloaded → Installing → Installed → SecurityEventNotification(FirmwareUpdated) |
+
+### Server: `--boot-status Accepted`
+
+| TC      | Use Case                   | Via                              | Steps                                  | Expected                 |
+| ------- | -------------------------- | -------------------------------- | -------------------------------------- | ------------------------ |
+| L01-MCP | FirmwareStatusNotification | MCP `firmwareStatusNotification` | Send `status: Installed, requestId: 1` | Response empty (success) |
+
+---
+
+## M — ISO 15118 Certificate Management
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case                 | Via                         | Steps               | Expected                    |
+| --- | ------------------------ | --------------------------- | ------------------- | --------------------------- |
+| M01 | Get 15118 EV Certificate | MCP `get15118EVCertificate` | Send Install action | Response `status: Accepted` |
+| M06 | Get Certificate Status   | MCP `getCertificateStatus`  | Send OCSP data      | Response `status: Accepted` |
+
+### Server: various `--command X --delay 5`
+
+| TC  | Use Case               | Server flags                           | Expected                                |
+| --- | ---------------------- | -------------------------------------- | --------------------------------------- |
+| M03 | Get installed cert IDs | `--command GetInstalledCertificateIds` | Response NotFound (cert manager absent) |
+| M04 | Delete certificate     | `--command DeleteCertificate`          | Response Failed (cert manager absent)   |
+| M05 | Install certificate    | `--command InstallCertificate`         | Response Failed (cert manager absent)   |
+
+---
+
+## N — Diagnostics
+
+### Server: `--boot-status Accepted --command GetLog --delay 5`
+
+| TC  | Use Case     | Via            | Steps     | Expected                                                             |
+| --- | ------------ | -------------- | --------- | -------------------------------------------------------------------- |
+| N01 | Retrieve Log | Server command | Wait ~15s | GetLog(DiagnosticsLog) → Accepted → LogStatusNotification(Uploading) |
+
+### Server: `--boot-status Accepted --command CustomerInformation --delay 5`
+
+| TC  | Use Case             | Via            | Steps     | Expected                                                                                                      |
+| --- | -------------------- | -------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
+| N09 | Customer Information | Server command | Wait ~15s | CustomerInformation(report=true, customerIdentifier=test_customer_001) → Accepted → NotifyCustomerInformation |
+
+### Server: `--boot-status Accepted`
+
+| TC             | Use Case                  | Via                             | Steps                        | Expected                 |
+| -------------- | ------------------------- | ------------------------------- | ---------------------------- | ------------------------ |
+| N01-MCP        | LogStatusNotification     | MCP `logStatusNotification`     | Send `status: Uploaded`      | Response empty (success) |
+| N09-MCP        | NotifyCustomerInformation | MCP `notifyCustomerInformation` | Send data with requestId     | Response empty (success) |
+| N-NOTIF-REPORT | NotifyReport              | MCP `notifyReport`              | Send with requestId, seqNo=0 | Response empty (success) |
+
+---
+
+## P — DataTransfer
+
+### Server: `--boot-status Accepted`
+
+| TC  | Use Case             | Via                | Steps                                           | Expected                    |
+| --- | -------------------- | ------------------ | ----------------------------------------------- | --------------------------- |
+| P02 | DataTransfer CS→CSMS | MCP `dataTransfer` | Send `vendorId: TestVendor, messageId: TestMsg` | Response `status: Accepted` |
+
+### Server: `--boot-status Accepted --command DataTransfer --delay 5`
+
+| TC  | Use Case             | Via            | Steps     | Expected                                                               |
+| --- | -------------------- | -------------- | --------- | ---------------------------------------------------------------------- |
+| P01 | DataTransfer CSMS→CS | Server command | Wait ~15s | DataTransfer received → Response `UnknownVendorId` (no custom handler) |
+
+---
+
+## B12 — Reset With Active Transaction
+
+### Server: `--boot-status Accepted --commands "RequestStartTransaction:15,Reset:30"`
+
+| TC  | Use Case               | Via                              | Steps                    | Expected                                                                                                                                                             |
+| --- | ---------------------- | -------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| B12 | Reset with ongoing txn | Server commands (single session) | Wait ~60s for full cycle | RequestStartTransaction → Accepted → TransactionEvent.Started → Reset(Immediate) → station stops transaction → StatusNotification(Unavailable) → re-boot → Available |
+
+---
+
+## Offline / Reconnection
+
+### Server: `--boot-status Accepted` (kill/restart cycle)
+
+| TC       | Use Case                   | Steps                                                   | Expected                                                                                                                                                                        |
+| -------- | -------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| B04-FULL | Server down and reconnect  | Kill server → wait 10s → restart                        | Station enters reconnection loop, reconnects, re-boots, returns to Available                                                                                                    |
+| B04-TXN  | Offline during transaction | Start ATG → kill server → wait 15s → restart → stop ATG | Transaction stopped on server kill (stopTransactionsOnStopped=true). After reconnect: BootNotification → Accepted → StatusNotification(Available). No queued TransactionEvents. |
+
+---
+
+## Edge Cases / Negative Tests
+
+### Server: `--boot-status Accepted`
+
+| TC     | Description                         | Via                                                    | Expected                                    |
+| ------ | ----------------------------------- | ------------------------------------------------------ | ------------------------------------------- |
+| ERR-03 | Multi-measurand MeterValues         | MCP `meterValues` with Voltage+Power+Current+Energy    | All accepted                                |
+| ERR-04 | FirmwareStatus all statuses         | MCP `firmwareStatusNotification` × 14 statuses         | All succeed                                 |
+| ERR-05 | Orphaned LogStatusNotification      | MCP `logStatusNotification` with `requestId: 999`      | Succeeds (no prior GetLog required)         |
+| ERR-06 | Orphaned FirmwareStatusNotification | MCP `firmwareStatusNotification` with `requestId: 999` | Succeeds (no prior UpdateFirmware required) |
+
+### Server: `--boot-status Accepted --commands "RequestStartTransaction:15,RequestStartTransaction:25"`
+
+| TC             | Description                                           | Expected                                                                             |
+| -------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------ |
+| E-DOUBLE-START | Second RequestStartTransaction while first txn active | First → Accepted + TransactionEvent.Started. Second → Rejected (connector occupied). |
+
+### Server: `--boot-status-sequence Pending,Accepted --command Reset --delay 8`
+
+| TC          | Description                  | Expected                                                                                                                               |
+| ----------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
+| B11-PENDING | Reset while in Pending state | Station receives Reset → Accepted (Reset is not blocked by registration state). StatusNotification(Unavailable) → reconnect → re-boot. |
+
+---
+
+## Execution Order
+
+Tests grouped by server configuration to minimize restarts.
+Use `closeConnection`/`openConnection` between groups 8-18 to avoid cumulative reconnect delays.
+
+| #   | Server Config                                                                               | Test Cases                                                                                                                                     |
+| --- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+| 1   | `--boot-status Accepted`                                                                    | B01, B04, A03, A04, C01, G01, G02, J01, J02, P02, E01-ATG, E01-DIRECT, L01-MCP, M01, M06, N01-MCP, N09-MCP, N-NOTIF-REPORT, ERR-03→06          |
+| 2   | `--boot-status Accepted --auth-mode whitelist --whitelist valid_token test_token`           | C01-WL-OK, C01-WL-REJECT                                                                                                                       |
+| 3   | `--boot-status Accepted --auth-mode blacklist --blacklist blocked_token`                    | C01-BL-OK, C01-BL-REJECT                                                                                                                       |
+| 4   | `--boot-status Accepted --auth-mode rate_limit`                                             | C01-RL                                                                                                                                         |
+| 5   | `--boot-status Accepted --offline`                                                          | C01-OFFLINE                                                                                                                                    |
+| 6   | `--boot-status Accepted --auth-group-id MyGroup --auth-cache-expiry 3600`                   | C09                                                                                                                                            |
+| 7   | `--boot-status Accepted --total-cost 25.50`                                                 | I02                                                                                                                                            |
+| 8   | `--boot-status Accepted --command X --delay 5` (sequential restarts)                        | A02, B05, B06, B07, B09, B11, B11b, C11, E14, F05, F06-SN, F06-BN, F06-HB, F06-MV, F06-FW, F06-LS, G03, G04, L01, M03, M04, M05, N01, N09, P01 |
+| 9   | `--boot-status Accepted --commands "RequestStartTransaction:15,RequestStopTransaction:45"`  | F01+F03                                                                                                                                        |
+| 10  | `--boot-status Accepted --command RequestStopTransaction --delay 5`                         | F03b                                                                                                                                           |
+| 11  | `--boot-status Accepted --commands "RequestStartTransaction:15,Reset:30"`                   | B12                                                                                                                                            |
+| 12  | `--boot-status Accepted` (kill/restart cycle)                                               | B04-FULL, B04-TXN                                                                                                                              |
+| 13  | `--boot-status Accepted --commands "RequestStartTransaction:15,RequestStartTransaction:25"` | E-DOUBLE-START                                                                                                                                 |
+| 14  | `--boot-status Accepted --commands "RequestStartTransaction:15,GetTransactionStatus:25"`    | E14-ACTIVE                                                                                                                                     |
+| 15  | `--boot-status Accepted --commands "RequestStartTransaction:15,UnlockConnector:25"`         | F05-TXN                                                                                                                                        |
+| 16  | `--boot-status-sequence Pending,Accepted`                                                   | B02                                                                                                                                            |
+| 17  | `--boot-status-sequence Pending,Accepted --command Reset --delay 8`                         | B11-PENDING                                                                                                                                    |
+| 18  | `--boot-status Rejected`                                                                    | B03                                                                                                                                            |
+
+## Coverage
+
+| Block             | Implemented Use Cases Covered   | Test Count |
+| ----------------- | ------------------------------- | ---------- |
+| A. Security       | A02, A03, A04                   | 3          |
+| B. Provisioning   | B01-B04, B05-B07, B09, B11, B12 | 11         |
+| C. Authorization  | C01, C09, C11                   | 8          |
+| E. Transactions   | E01, E14                        | 5          |
+| F. Remote Control | F01, F03, F05, F06              | 10         |
+| G. Availability   | G01-G04                         | 6          |
+| I. Tariff/Cost    | I02                             | 1          |
+| J. MeterValues    | J01, J02                        | 2          |
+| L. Firmware       | L01                             | 2          |
+| M. ISO15118 Certs | M01, M03-M06                    | 5          |
+| N. Diagnostics    | N01, N09                        | 5          |
+| P. DataTransfer   | P01, P02                        | 2          |
+| Edge/Negative     | —                               | 7          |
+| **Total**         | **34/34 commands**              | **~70**    |
+
+### Not Testable (simulator not implemented)
+
+D (LocalAuthList), H (Reservation), K (SmartCharging), O (DisplayMessage), N02-N08 (Monitoring).
index b69d270617c378a00e85ec9f61398e95aa2ff176..7ba1621631626feb94078ef4905eaacf9e49988e 100644 (file)
@@ -200,9 +200,9 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
       assert.strictEqual(connectorsStatus.length, 1)
       assert.strictEqual(connectorsStatus[0][0], 1)
-      const connector = connectorsStatus[0][1]
-      assert.ok(!('transactionMeterValuesSetInterval' in connector))
-      assert.ok(!('transactionEventQueue' in connector))
+      const connectorStatus = connectorsStatus[0][1]
+      assert.ok(!('transactionMeterValuesSetInterval' in connectorStatus))
+      assert.ok(!('transactionEventQueue' in connectorStatus))
     })
 
     await it('should preserve connector IDs across serialization', () => {
@@ -275,30 +275,30 @@ await describe('ChargingStationConfigurationUtils', async () => {
 
   await describe('buildChargingStationAutomaticTransactionGeneratorConfiguration', async () => {
     await it('should return ATG configuration when present', () => {
-      const atgConfig = { enable: true, maxDuration: 120, minDuration: 60 }
+      const atgConfiguration = { enable: true, maxDuration: 120, minDuration: 60 }
       const station = createMockStationForConfigUtils({
         automaticTransactionGenerator: {
           connectorsStatus: new Map([[1, { start: false }]]),
         },
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfig,
+        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
-      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfiguration)
       assert.notStrictEqual(result.automaticTransactionGeneratorStatuses, undefined)
       assert.ok(Array.isArray(result.automaticTransactionGeneratorStatuses))
       assert.strictEqual(result.automaticTransactionGeneratorStatuses.length, 1)
     })
 
     await it('should return ATG configuration without statuses when no ATG instance', () => {
-      const atgConfig = { enable: false }
+      const atgConfiguration = { enable: false }
       const station = createMockStationForConfigUtils({
         automaticTransactionGenerator: undefined,
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfig,
+        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
-      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfiguration)
       assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined)
     })
 
@@ -314,16 +314,16 @@ await describe('ChargingStationConfigurationUtils', async () => {
     })
 
     await it('should return ATG configuration without statuses when connectorsStatus is null', () => {
-      const atgConfig = { enable: true }
+      const atgConfiguration = { enable: true }
       const station = createMockStationForConfigUtils({
         automaticTransactionGenerator: {
           connectorsStatus: undefined,
         },
-        getAutomaticTransactionGeneratorConfiguration: () => atgConfig,
+        getAutomaticTransactionGeneratorConfiguration: () => atgConfiguration,
       })
       const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
 
-      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+      assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfiguration)
       assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined)
     })
   })
index 76f0b23c07869bffd9bd697f256d300be726f432..7e2be5ae078a5e02fb3202c372e1555acfa002d2 100644 (file)
@@ -160,10 +160,10 @@ await describe('MessageChannelUtils', async () => {
     assert.strictEqual(message.data.id, 'test-station-id')
     assert.strictEqual(message.data.name, 'test-station')
 
-    const heartbeatStats = message.data.statisticsData.get('Heartbeat')
-    assert.notStrictEqual(heartbeatStats, undefined)
-    assert.ok(Array.isArray(heartbeatStats?.measurementTimeSeries))
-    const timeSeries = heartbeatStats.measurementTimeSeries
+    const heartbeatStatistics = message.data.statisticsData.get('Heartbeat')
+    assert.notStrictEqual(heartbeatStatistics, undefined)
+    assert.ok(Array.isArray(heartbeatStatistics?.measurementTimeSeries))
+    const timeSeries = heartbeatStatistics.measurementTimeSeries
     assert.strictEqual(timeSeries.length, 2)
     assert.strictEqual(timeSeries[0].value, 42)
     assert.strictEqual(timeSeries[1].value, 84)
index 1bb98ec7d4856c99df7297e2a5042bb04c557890..fabef6268ab200f6154134064079eb54a1c3d965 100644 (file)
@@ -323,9 +323,9 @@ export class UIClient {
 
     if (isProtocolResponse) {
       const [uuid, responsePayload] = message as ProtocolResponse
-      if (this.responseHandlers.has(uuid)) {
-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        const { procedureName, reject, resolve } = this.responseHandlers.get(uuid)!
+      const responseHandler = this.responseHandlers.get(uuid)
+      if (responseHandler != null) {
+        const { procedureName, reject, resolve } = responseHandler
         switch (responsePayload.status) {
           case ResponseStatus.FAILURE:
             reject(responsePayload)
index cfc3dca795ca62c71b9f7b5095905fcc6cdc8278..14e70ee3afd9a8f876e00435b69d1fb0d4b8fbfa 100644 (file)
@@ -116,8 +116,11 @@ describe('CSConnector', () => {
 
   describe('transaction actions', () => {
     it('should call stopTransaction with correct params', async () => {
-      const connector = createConnectorStatus({ transactionId: 12345, transactionStarted: true })
-      const wrapper = mountCSConnector({ connector })
+      const connectorStatus = createConnectorStatus({
+        transactionId: 12345,
+        transactionStarted: true,
+      })
+      const wrapper = mountCSConnector({ connector: connectorStatus })
       const buttons = wrapper.findAll('button')
       const stopBtn = buttons.find(b => b.text() === 'Stop Transaction')
       await stopBtn?.trigger('click')
@@ -129,11 +132,11 @@ describe('CSConnector', () => {
     })
 
     it('should show error toast when no transaction to stop', async () => {
-      const connector = createConnectorStatus({
+      const connectorStatus = createConnectorStatus({
         transactionId: undefined,
         transactionStarted: true,
       })
-      const wrapper = mountCSConnector({ connector })
+      const wrapper = mountCSConnector({ connector: connectorStatus })
       const buttons = wrapper.findAll('button')
       const stopBtn = buttons.find(b => b.text() === 'Stop Transaction')
       await stopBtn?.trigger('click')
@@ -141,8 +144,8 @@ describe('CSConnector', () => {
     })
 
     it('should show success toast after stopping transaction', async () => {
-      const connector = createConnectorStatus({ transactionId: 99, transactionStarted: true })
-      const wrapper = mountCSConnector({ connector })
+      const connectorStatus = createConnectorStatus({ transactionId: 99, transactionStarted: true })
+      const wrapper = mountCSConnector({ connector: connectorStatus })
       const buttons = wrapper.findAll('button')
       const stopBtn = buttons.find(b => b.text() === 'Stop Transaction')
       await stopBtn?.trigger('click')
index 3990ccd88721fc9030292ca9749ea766ad472c18..50e411e89f196bf5fec4ddfce5d9a06a604b6a01 100644 (file)
@@ -24,11 +24,11 @@ vi.mock('@/composables', async importOriginal => {
 
 // ── Configuration fixtures ────────────────────────────────────────────────────
 
-const singleServerConfig = {
+const singleServerConfiguration = {
   uiServer: [createUIServerConfig()],
 }
 
-const multiServerConfig = {
+const multiServerConfiguration = {
   uiServer: [
     createUIServerConfig({ name: 'Server 1' }),
     createUIServerConfig({ host: 'server2', name: 'Server 2' }),
@@ -63,11 +63,15 @@ function getWSHandler (eventType: string): ((...args: unknown[]) => void) | unde
 function mountView (
   options: {
     chargingStations?: ReturnType<typeof createChargingStationData>[]
-    configuration?: typeof multiServerConfig | typeof singleServerConfig
+    configuration?: typeof multiServerConfiguration | typeof singleServerConfiguration
     templates?: string[]
   } = {}
 ) {
-  const { chargingStations = [], configuration = singleServerConfig, templates = [] } = options
+  const {
+    chargingStations = [],
+    configuration = singleServerConfiguration,
+    templates = [],
+  } = options
 
   return mount(ChargingStationsView, {
     global: {
@@ -234,27 +238,27 @@ describe('ChargingStationsView', () => {
 
   describe('UI server selector', () => {
     it('should hide server selector for single server configuration', () => {
-      const wrapper = mountView({ configuration: singleServerConfig })
+      const wrapper = mountView({ configuration: singleServerConfiguration })
       const selectorContainer = wrapper.find('#ui-server-container')
       expect(selectorContainer.exists()).toBe(true)
       expect((selectorContainer.element as HTMLElement).style.display).toBe('none')
     })
 
     it('should show server selector for multiple server configuration', () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const selectorContainer = wrapper.find('#ui-server-container')
       expect(selectorContainer.exists()).toBe(true)
       expect((selectorContainer.element as HTMLElement).style.display).not.toBe('none')
     })
 
     it('should render an option for each server', () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const options = wrapper.findAll('#ui-server-selector option')
       expect(options).toHaveLength(2)
     })
 
     it('should display server name in options', () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const options = wrapper.findAll('#ui-server-selector option')
       expect(options[0].text()).toContain('Server 1')
       expect(options[1].text()).toContain('Server 2')
@@ -347,14 +351,14 @@ describe('ChargingStationsView', () => {
 
   describe('server switching', () => {
     it('should call setConfiguration when server index changes', async () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const selector = wrapper.find('#ui-server-selector')
       await selector.setValue(1)
       expect(mockClient.setConfiguration).toHaveBeenCalled()
     })
 
     it('should register new WS event listeners after server switch', async () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       // Reset call count from initial mount registration
       vi.mocked(mockClient.registerWSEventListener).mockClear()
       const selector = wrapper.find('#ui-server-selector')
@@ -364,7 +368,7 @@ describe('ChargingStationsView', () => {
     })
 
     it('should save server index to localStorage on successful switch', async () => {
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const selector = wrapper.find('#ui-server-selector')
       await selector.setValue(1)
       // Simulate the WS open for the new connection (once-listener from server switching)
@@ -386,7 +390,7 @@ describe('ChargingStationsView', () => {
 
     it('should revert server index on connection error', async () => {
       localStorage.setItem('uiServerConfigurationIndex', '0')
-      const wrapper = mountView({ configuration: multiServerConfig })
+      const wrapper = mountView({ configuration: multiServerConfiguration })
       const selector = wrapper.find('#ui-server-selector')
       await selector.setValue(1)
       // Find the once-error listener