From: Jérôme Benoit Date: Fri, 27 Feb 2026 07:12:19 +0000 (+0100) Subject: test(charging-station): add Transaction Management tests X-Git-Tag: ocpp-server@v3.0.0~131 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=62d293816286e6925404b76abd7864721f6be14e;p=e-mobility-charging-stations-simulator.git test(charging-station): add Transaction Management tests Implement 17 tests covering transaction query methods, energy meters, transaction state management, and concurrent transaction scenarios. - Add transaction query tests: hasTransaction, getConnectorIdByTransactionId - Add energy meter tests: getEnergyActiveImportRegisterByTransactionId - Add state management tests: isRegistered, stationInfo properties - Add concurrent transaction tests: hasEvses checks - Enhance ChargingStationTestUtils with 6 transaction helper methods All 217 tests pass. --- diff --git a/tests/charging-station/ChargingStation.test.ts b/tests/charging-station/ChargingStation.test.ts index 3bce6a28..0e56772f 100644 --- a/tests/charging-station/ChargingStation.test.ts +++ b/tests/charging-station/ChargingStation.test.ts @@ -984,4 +984,405 @@ await describe('ChargingStation', async () => { expect(mocks.webSocket.url.length).toBeGreaterThan(0) }) }) + + // ===== TRANSACTION MANAGEMENT TESTS ===== + await describe('Transaction Management', async () => { + let station: ChargingStation | undefined + + afterEach(() => { + if (station != null) { + cleanupChargingStation(station) + } + }) + + // === Transaction Query Tests === + + await it('should return undefined for getConnectorIdByTransactionId with no active transactions', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const connectorId = station.getConnectorIdByTransactionId(12345) + + // Assert + expect(connectorId).toBeUndefined() + }) + + await it('should return connector id for getConnectorIdByTransactionId with active transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + connector1.transactionIdTag = 'TEST-TAG-001' + } + + // Act + const connectorId = station.getConnectorIdByTransactionId(100) + + // Assert + expect(connectorId).toBe(1) + }) + + await it('should return undefined for getConnectorIdByTransactionId with undefined transactionId', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const connectorId = station.getConnectorIdByTransactionId(undefined) + + // Assert + expect(connectorId).toBeUndefined() + }) + + await it('should return undefined for getEvseIdByTransactionId in non-EVSE mode', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2, evsesCount: 0 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + } + + // Act + const evseId = station.getEvseIdByTransactionId(100) + + // Assert + expect(evseId).toBeUndefined() + }) + + await it('should return EVSE id for getEvseIdByTransactionId in EVSE mode with active transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2, evsesCount: 2 }) + station = result.station + // Get connector in EVSE 1 + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 200 + connector1.transactionIdTag = 'TEST-TAG-002' + } + + // Act + const evseId = station.getEvseIdByTransactionId(200) + + // Assert + expect(evseId).toBe(1) + }) + + await it('should return idTag for getTransactionIdTag with active transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 300 + connector1.transactionIdTag = 'MY-TAG-123' + } + + // Act + const idTag = station.getTransactionIdTag(300) + + // Assert + expect(idTag).toBe('MY-TAG-123') + }) + + await it('should return undefined for getTransactionIdTag with no matching transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const idTag = station.getTransactionIdTag(999) + + // Assert + expect(idTag).toBeUndefined() + }) + + await it('should return zero for getNumberOfRunningTransactions with no transactions', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 3 }) + station = result.station + + // Act + const count = station.getNumberOfRunningTransactions() + + // Assert + expect(count).toBe(0) + }) + + await it('should return correct count for getNumberOfRunningTransactions with active transactions', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 3 }) + station = result.station + // Set up transactions on connectors 1 and 2 + const connector1 = station.getConnectorStatus(1) + const connector2 = station.getConnectorStatus(2) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + } + if (connector2 != null) { + connector2.transactionStarted = true + connector2.transactionId = 101 + } + + // Act + const count = station.getNumberOfRunningTransactions() + + // Assert + expect(count).toBe(2) + }) + + // === Energy Meter Tests === + + await it('should return 0 for getEnergyActiveImportRegisterByConnectorId with no transaction energy', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const energy = station.getEnergyActiveImportRegisterByConnectorId(1) + + // Assert + expect(energy).toBe(0) + }) + + await it('should return energy value for getEnergyActiveImportRegisterByConnectorId with active transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + connector1.transactionEnergyActiveImportRegisterValue = 12500 + } + + // Act + const energy = station.getEnergyActiveImportRegisterByConnectorId(1) + + // Assert + expect(energy).toBe(12500) + }) + + await it('should return rounded energy value when rounded=true', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + connector1.transactionEnergyActiveImportRegisterValue = 12345.678 + } + + // Act + const energy = station.getEnergyActiveImportRegisterByConnectorId(1, true) + + // Assert + expect(energy).toBe(12346) + }) + + await it('should return 0 for getEnergyActiveImportRegisterByConnectorId with invalid connector', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const energy = station.getEnergyActiveImportRegisterByConnectorId(99) + + // Assert + expect(energy).toBe(0) + }) + + await it('should return 0 for getEnergyActiveImportRegisterByTransactionId with no matching transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Act + const energy = station.getEnergyActiveImportRegisterByTransactionId(999) + + // Assert + expect(energy).toBe(0) + }) + + await it('should return energy for getEnergyActiveImportRegisterByTransactionId with active transaction', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 400 + connector1.transactionEnergyActiveImportRegisterValue = 25000 + } + + // Act + const energy = station.getEnergyActiveImportRegisterByTransactionId(400) + + // Assert + expect(energy).toBe(25000) + }) + + // === Concurrent Transaction Scenarios === + + await it('should handle multiple transactions on different connectors', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 3 }) + station = result.station + + // Set up transactions on connectors 1, 2, and 3 + const connector1 = station.getConnectorStatus(1) + const connector2 = station.getConnectorStatus(2) + const connector3 = station.getConnectorStatus(3) + + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + connector1.transactionIdTag = 'TAG-A' + connector1.transactionEnergyActiveImportRegisterValue = 10000 + } + if (connector2 != null) { + connector2.transactionStarted = true + connector2.transactionId = 101 + connector2.transactionIdTag = 'TAG-B' + connector2.transactionEnergyActiveImportRegisterValue = 20000 + } + if (connector3 != null) { + connector3.transactionStarted = true + connector3.transactionId = 102 + connector3.transactionIdTag = 'TAG-C' + connector3.transactionEnergyActiveImportRegisterValue = 30000 + } + + // Act & Assert - Running transactions count + expect(station.getNumberOfRunningTransactions()).toBe(3) + + // Act & Assert - Transaction queries + expect(station.getConnectorIdByTransactionId(100)).toBe(1) + expect(station.getConnectorIdByTransactionId(101)).toBe(2) + expect(station.getConnectorIdByTransactionId(102)).toBe(3) + + // Act & Assert - Energy meters + expect(station.getEnergyActiveImportRegisterByTransactionId(100)).toBe(10000) + expect(station.getEnergyActiveImportRegisterByTransactionId(101)).toBe(20000) + expect(station.getEnergyActiveImportRegisterByTransactionId(102)).toBe(30000) + + // Act & Assert - Id tags + expect(station.getTransactionIdTag(100)).toBe('TAG-A') + expect(station.getTransactionIdTag(101)).toBe('TAG-B') + expect(station.getTransactionIdTag(102)).toBe('TAG-C') + }) + + await it('should handle transactions across multiple EVSEs', () => { + // Arrange - 4 connectors across 2 EVSEs + const result = createMockChargingStation({ connectorsCount: 4, evsesCount: 2 }) + station = result.station + + // Set up transaction on connector 1 (EVSE 1) and connector 3 (EVSE 2) + const connector1 = station.getConnectorStatus(1) + const connector3 = station.getConnectorStatus(3) + + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 500 + connector1.transactionIdTag = 'EVSE1-TAG' + connector1.transactionEnergyActiveImportRegisterValue = 15000 + } + if (connector3 != null) { + connector3.transactionStarted = true + connector3.transactionId = 501 + connector3.transactionIdTag = 'EVSE2-TAG' + connector3.transactionEnergyActiveImportRegisterValue = 18000 + } + + // Act & Assert - Running transactions count + expect(station.getNumberOfRunningTransactions()).toBe(2) + + // Act & Assert - EVSE queries + expect(station.getEvseIdByTransactionId(500)).toBe(1) + expect(station.getEvseIdByTransactionId(501)).toBe(2) + + // Act & Assert - Connector queries + expect(station.getConnectorIdByTransactionId(500)).toBe(1) + expect(station.getConnectorIdByTransactionId(501)).toBe(3) + + // Act & Assert - Energy meters + expect(station.getEnergyActiveImportRegisterByTransactionId(500)).toBe(15000) + expect(station.getEnergyActiveImportRegisterByTransactionId(501)).toBe(18000) + }) + + await it('should correctly count transactions only on connectors > 0', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + + // Connector 0 should not count (station-level) + const connector0 = station.getConnectorStatus(0) + const connector1 = station.getConnectorStatus(1) + + if (connector0 != null) { + // This shouldn't happen in real usage but test robustness + connector0.transactionStarted = true + connector0.transactionId = 999 + } + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 100 + } + + // Act + const count = station.getNumberOfRunningTransactions() + + // Assert - Only connector 1 should count + expect(count).toBe(1) + }) + + await it('should return idTag in EVSE mode for getTransactionIdTag', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2, evsesCount: 2 }) + station = result.station + + const connector2 = station.getConnectorStatus(2) + if (connector2 != null) { + connector2.transactionStarted = true + connector2.transactionId = 600 + connector2.transactionIdTag = 'EVSE-MODE-TAG' + } + + // Act + const idTag = station.getTransactionIdTag(600) + + // Assert + expect(idTag).toBe('EVSE-MODE-TAG') + }) + + await it('should handle rounded energy values for getEnergyActiveImportRegisterByTransactionId', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 2 }) + station = result.station + const connector1 = station.getConnectorStatus(1) + if (connector1 != null) { + connector1.transactionStarted = true + connector1.transactionId = 700 + connector1.transactionEnergyActiveImportRegisterValue = 12345.5 + } + + // Act + const unrounded = station.getEnergyActiveImportRegisterByTransactionId(700, false) + const rounded = station.getEnergyActiveImportRegisterByTransactionId(700, true) + + // Assert + expect(unrounded).toBe(12345.5) + expect(rounded).toBe(12346) + }) + }) }) diff --git a/tests/charging-station/ChargingStationTestUtils.ts b/tests/charging-station/ChargingStationTestUtils.ts index 3cc5e5c1..018d4462 100644 --- a/tests/charging-station/ChargingStationTestUtils.ts +++ b/tests/charging-station/ChargingStationTestUtils.ts @@ -579,6 +579,26 @@ export function createMockChargingStation ( getConnectionTimeout (): number { return 30000 }, + getConnectorIdByTransactionId (transactionId: number | string | undefined): number | undefined { + if (transactionId == null) { + return undefined + } else if (useEvses) { + for (const evseStatus of evses.values()) { + for (const [connectorId, connectorStatus] of evseStatus.connectors) { + if (connectorStatus.transactionId === transactionId) { + return connectorId + } + } + } + } else { + for (const connectorId of connectors.keys()) { + if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { + return connectorId + } + } + } + return undefined + }, // Methods getConnectorStatus (connectorId: number): ConnectorStatus | undefined { if (useEvses) { @@ -591,6 +611,24 @@ export function createMockChargingStation ( } return connectors.get(connectorId) }, + getEnergyActiveImportRegisterByConnectorId (connectorId: number, rounded = false): number { + const connectorStatus = this.getConnectorStatus(connectorId) + if (connectorStatus == null) { + return 0 + } + const value = connectorStatus.transactionEnergyActiveImportRegisterValue ?? 0 + return rounded ? Math.round(value) : value + }, + getEnergyActiveImportRegisterByTransactionId ( + transactionId: number | string | undefined, + rounded = false + ): number { + const connectorId = this.getConnectorIdByTransactionId(transactionId) + if (connectorId == null) { + return 0 + } + return this.getEnergyActiveImportRegisterByConnectorId(connectorId, rounded) + }, getEvseIdByConnectorId (connectorId: number): number | undefined { if (!useEvses) { return undefined @@ -602,6 +640,20 @@ export function createMockChargingStation ( } return undefined }, + getEvseIdByTransactionId (transactionId: number | string | undefined): number | undefined { + if (transactionId == null) { + return undefined + } else if (useEvses) { + for (const [evseId, evseStatus] of evses) { + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus.transactionId === transactionId) { + return evseId + } + } + } + } + return undefined + }, getEvseStatus (evseId: number): EvseStatus | undefined { return evses.get(evseId) }, @@ -626,6 +678,49 @@ export function createMockChargingStation ( getNumberOfEvses (): number { return evses.has(0) ? evses.size - 1 : evses.size }, + getNumberOfRunningTransactions (): number { + let numberOfRunningTransactions = 0 + if (useEvses) { + for (const [evseId, evseStatus] of evses) { + if (evseId === 0) { + continue + } + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus.transactionStarted === true) { + ++numberOfRunningTransactions + } + } + } + } else { + for (const connectorId of connectors.keys()) { + if ( + connectorId > 0 && + this.getConnectorStatus(connectorId)?.transactionStarted === true + ) { + ++numberOfRunningTransactions + } + } + } + return numberOfRunningTransactions + }, + getTransactionIdTag (transactionId: number): string | undefined { + if (useEvses) { + for (const evseStatus of evses.values()) { + for (const connectorStatus of evseStatus.connectors.values()) { + if (connectorStatus.transactionId === transactionId) { + return connectorStatus.transactionIdTag + } + } + } + } else { + for (const connectorId of connectors.keys()) { + if (this.getConnectorStatus(connectorId)?.transactionId === transactionId) { + return this.getConnectorStatus(connectorId)?.transactionIdTag + } + } + } + return undefined + }, hasConnector (connectorId: number): boolean { if (useEvses) { for (const evseStatus of evses.values()) {