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,
}
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,
}
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,
}
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,
}
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
// 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
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,
}
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>
OCPP20ComponentName,
OCPP20IdTokenEnumType,
OCPP20IncomingRequestCommand,
+ OCPP20OperationalStatusEnumType,
OCPP20RequestCommand,
OCPP20RequiredVariableName,
OCPP20TransactionEventEnumType,
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,
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,
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()
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>