]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
test: harmonize RemoteStartTransaction tests across OCPP stacks
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 5 Apr 2026 15:46:39 +0000 (17:46 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 5 Apr 2026 15:46:39 +0000 (17:46 +0200)
OCPP 1.6:
- Remove fabricated TC_0XX_CS codes (no official 1.6 test case spec)
- Reorder tests by category: happy path, input validation, availability,
  transaction state, event listeners

OCPP 2.0:
- Add 2 EVSE availability tests (skip inoperative, all inoperative)
- Add official TC refs where tests match Part 6 spec (TC_F_02_CS,
  TC_F_03_CS, TC_K_37_CS)
- Harmonize FR comment format on new tests

tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStartTransaction.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts

index bcf40e75c93261883e995bc04ad5796bcb5ab396..2010ee0dcba4f9f6b185df0590ef958e4018bddc 100644 (file)
@@ -41,12 +41,14 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     standardCleanup()
   })
 
-  // @spec §5.11 — TC_021_CS: connectorId=0 must be rejected
-  await it('should reject remote start transaction with connectorId=0', async () => {
+  // --- Happy path ---
+
+  // @spec §5.11 — Valid connectorId with available connector
+  await it('should accept remote start transaction with valid connectorId and available connector', async () => {
     // Arrange
     const { station, testableService } = testContext
     const request: RemoteStartTransactionRequest = {
-      connectorId: 0,
+      connectorId: 1,
       idTag: TEST_ID_TAG,
     }
 
@@ -54,15 +56,14 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     const response = await testableService.handleRequestRemoteStartTransaction(station, request)
 
     // Assert
-    assert.strictEqual(response.status, GenericStatus.Rejected)
+    assert.strictEqual(response.status, GenericStatus.Accepted)
   })
 
-  // @spec §5.11 — TC_013_CS: Valid connectorId with available connector
-  await it('should accept remote start transaction with valid connectorId and available connector', async () => {
+  // @spec §5.11 — No connectorId specified, finds first available connector
+  await it('should accept remote start transaction without connectorId when connector is available', async () => {
     // Arrange
     const { station, testableService } = testContext
     const request: RemoteStartTransactionRequest = {
-      connectorId: 1,
       idTag: TEST_ID_TAG,
     }
 
@@ -73,17 +74,14 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     assert.strictEqual(response.status, GenericStatus.Accepted)
   })
 
-  // @spec §5.11 — TC_014_CS: All connectors have active transactions, no connectorId specified
-  await it('should reject remote start transaction when all connectors have active transactions', async () => {
+  // --- Input validation ---
+
+  // @spec §5.11 — connectorId=0 must be rejected
+  await it('should reject remote start transaction with connectorId=0', async () => {
     // Arrange
     const { station, testableService } = testContext
-
-    // Set all connectors as having active transactions
-    for (let connectorId = 1; connectorId <= station.getNumberOfConnectors(); connectorId++) {
-      setupConnectorWithTransaction(station, connectorId, { transactionId: connectorId * 100 })
-    }
-
     const request: RemoteStartTransactionRequest = {
+      connectorId: 0,
       idTag: TEST_ID_TAG,
     }
 
@@ -94,11 +92,12 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     assert.strictEqual(response.status, GenericStatus.Rejected)
   })
 
-  // @spec §5.11 — TC_015_CS: No connectorId specified, finds first available connector
-  await it('should accept remote start transaction without connectorId when connector is available', async () => {
+  // @spec §5.11 — Non-existing connector
+  await it('should reject remote start transaction with non-existing connectorId', async () => {
     // Arrange
     const { station, testableService } = testContext
     const request: RemoteStartTransactionRequest = {
+      connectorId: 99,
       idTag: TEST_ID_TAG,
     }
 
@@ -106,9 +105,11 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     const response = await testableService.handleRequestRemoteStartTransaction(station, request)
 
     // Assert
-    assert.strictEqual(response.status, GenericStatus.Accepted)
+    assert.strictEqual(response.status, GenericStatus.Rejected)
   })
 
+  // --- Availability ---
+
   // @spec §5.11 — Connector in Unavailable (Inoperative) status
   await it('should reject remote start transaction when connector is unavailable', async () => {
     // Arrange
@@ -175,11 +176,7 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
 
     // Assert — should select connector 2 (which is Operative) and accept
     assert.strictEqual(response.status, GenericStatus.Accepted)
-    assert.strictEqual(
-      request.connectorId,
-      2,
-      'should auto-select connector 2 since connector 1 is Inoperative'
-    )
+    assert.strictEqual(request.connectorId, 2)
   })
 
   // @spec §5.11 — All connectors Inoperative, no connectorId specified
@@ -206,12 +203,19 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     assert.strictEqual(response.status, GenericStatus.Rejected)
   })
 
-  // @spec §5.11 — Non-existing connector
-  await it('should reject remote start transaction with non-existing connectorId', async () => {
+  // --- Transaction state ---
+
+  // @spec §5.11 — All connectors have active transactions, no connectorId specified
+  await it('should reject remote start transaction when all connectors have active transactions', async () => {
     // Arrange
     const { station, testableService } = testContext
+
+    // Set all connectors as having active transactions
+    for (let connectorId = 1; connectorId <= station.getNumberOfConnectors(); connectorId++) {
+      setupConnectorWithTransaction(station, connectorId, { transactionId: connectorId * 100 })
+    }
+
     const request: RemoteStartTransactionRequest = {
-      connectorId: 99,
       idTag: TEST_ID_TAG,
     }
 
@@ -222,6 +226,8 @@ await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async
     assert.strictEqual(response.status, GenericStatus.Rejected)
   })
 
+  // --- Event listeners ---
+
   await describe('REMOTE_START_TRANSACTION event listener', async () => {
     let incomingRequestService: OCPP16IncomingRequestService
     let requestHandlerMock: ReturnType<typeof mock.fn>
index 4eea501f810dfee6a8ed1efadb8c46119adf2939..7a632ca909bb10ddf164d1c1983a26fc19914a07 100644 (file)
@@ -32,6 +32,7 @@ import {
   OCPP20ComponentName,
   OCPP20IdTokenEnumType,
   OCPP20IncomingRequestCommand,
+  OCPP20OperationalStatusEnumType,
   OCPP20RequestCommand,
   OCPP20RequiredVariableName,
   OCPP20TransactionEventEnumType,
@@ -87,7 +88,7 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     OCPPAuthServiceFactory.clearAllInstances()
   })
 
-  // FR: F01.FR.03, F01.FR.04, F01.FR.05, F01.FR.13
+  // FR: F01.FR.03, F01.FR.04, F01.FR.05, F01.FR.13 — TC_F_02_CS
   await it('should handle RequestStartTransaction with valid evseId and idToken', async () => {
     const validRequest: OCPP20RequestStartTransactionRequest = {
       evseId: 1,
@@ -262,7 +263,7 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     assert.notStrictEqual(response.transactionId, undefined)
   })
 
-  // OCPP 2.0.1 §2.10 ChargingProfile validation tests
+  // OCPP 2.0.1 §2.10 ChargingProfile validation tests — TC_K_37_CS
   await it('should accept RequestStartTransaction with valid TxProfile (no transactionId)', async () => {
     const validChargingProfile: OCPP20ChargingProfileType = {
       chargingProfileKind: OCPP20ChargingProfileKindEnumType.Relative,
@@ -458,6 +459,7 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
   })
 
+  // FR: F01.FR.02 — TC_F_03_CS
   await it('should accept RequestStartTransaction when AuthorizeRemoteStart is false', async () => {
     // Arrange
     const variableManager = OCPP20VariableManager.getInstance()
@@ -519,6 +521,51 @@ await describe('F01 & F02 - Remote Start Transaction', async () => {
     assert.ok(response.transactionId.length > 0, 'transactionId should not be empty')
   })
 
+  // FR: F01 — EVSE availability during auto-selection
+  await it('should skip inoperative EVSEs during auto-selection without evseId', async () => {
+    // Arrange
+    const evse1Status = mockStation.getEvseStatus(1)
+    if (evse1Status != null) {
+      evse1Status.availability = OCPP20OperationalStatusEnumType.Inoperative
+    }
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      idToken: {
+        idToken: 'VALID_TOKEN_123',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 500,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+  })
+
+  // FR: F01 — All EVSEs Inoperative, no evseId specified
+  await it('should reject remote start when all EVSEs are inoperative and no evseId specified', async () => {
+    // Arrange
+    for (const { evseStatus } of mockStation.iterateEvses(true)) {
+      evseStatus.availability = OCPP20OperationalStatusEnumType.Inoperative
+    }
+
+    const request: OCPP20RequestStartTransactionRequest = {
+      idToken: {
+        idToken: 'VALID_TOKEN_123',
+        type: OCPP20IdTokenEnumType.ISO14443,
+      },
+      remoteStartId: 501,
+    }
+
+    // Act
+    const response = await testableService.handleRequestStartTransaction(mockStation, request)
+
+    // Assert
+    assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
+  })
+
   await describe('REQUEST_START_TRANSACTION event listener', async () => {
     let listenerService: OCPP20IncomingRequestService
     let requestHandlerMock: ReturnType<typeof mock.fn>