From: Jérôme Benoit Date: Sat, 28 Feb 2026 19:54:21 +0000 (+0100) Subject: refactor(tests): delete deprecated ChargingStationFactory and fix remaining imports X-Git-Tag: ocpp-server@v3.0.0~73 X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=d6c6fcfc2de964a19b56f0d81675e8aa59393f2c;p=e-mobility-charging-stations-simulator.git refactor(tests): delete deprecated ChargingStationFactory and fix remaining imports - Deleted ChargingStationFactory.ts and ChargingStationFactory.test.ts - Fixed broken imports in 3 utility test files (ConfigurationKeyUtils, Helpers, ErrorUtils) - Migrated remaining createChargingStation() calls to createMockChargingStation() - Added createMockChargingStationTemplate() support in StationHelpers - All 291 tests passing Task 8 complete --- diff --git a/tests/ChargingStationFactory.test.ts b/tests/ChargingStationFactory.test.ts deleted file mode 100644 index 80a03920..00000000 --- a/tests/ChargingStationFactory.test.ts +++ /dev/null @@ -1,892 +0,0 @@ -/** - * @file Tests for ChargingStationFactory - * @description Unit tests for charging station factory utilities and OCPP service mocking - */ -import { expect } from '@std/expect' -import { afterEach, describe, it, mock } from 'node:test' - -import { getHashId } from '../src/charging-station/Helpers.js' -import { AvailabilityType, ConnectorStatusEnum, OCPPVersion } from '../src/types/index.js' -import { createChargingStation, createChargingStationTemplate } from './ChargingStationFactory.js' - -await describe('ChargingStationFactory', async () => { - afterEach(() => { - mock.restoreAll() - }) - - await describe('OCPP Service Mocking', async () => { - await it('should throw error when OCPPRequestService.requestHandler is not mocked', async () => { - const station = createChargingStation({ connectorsCount: 1 }) - - await expect(station.ocppRequestService.requestHandler()).rejects.toThrow( - 'ocppRequestService.requestHandler not mocked. Define in createChargingStation options.' - ) - }) - - await it('should throw error when OCPPIncomingRequestService.stop is not mocked', () => { - const station = createChargingStation({ connectorsCount: 1 }) - - expect(() => { - station.ocppIncomingRequestService.stop() - }).toThrow( - 'ocppIncomingRequestService.stop not mocked. Define in createChargingStation options.' - ) - }) - - await it('should allow custom OCPPRequestService.requestHandler mock', async () => { - const mockRequestHandler = async () => { - return Promise.resolve({ success: true }) - } - - const station = createChargingStation({ - connectorsCount: 1, - ocppRequestService: { - requestHandler: mockRequestHandler, - }, - }) - - const result = (await station.ocppRequestService.requestHandler()) as { success: boolean } - expect(result.success).toBe(true) - }) - - await it('should allow custom OCPPIncomingRequestService.stop mock', () => { - let stopCalled = false - const station = createChargingStation({ - connectorsCount: 1, - ocppIncomingRequestService: { - stop: () => { - stopCalled = true - }, - }, - }) - - station.ocppIncomingRequestService.stop() - expect(stopCalled).toBe(true) - }) - - await it('should throw error when OCPPRequestService.sendError is not mocked', async () => { - const station = createChargingStation({ connectorsCount: 1 }) - - await expect(station.ocppRequestService.sendError()).rejects.toThrow( - 'ocppRequestService.sendError not mocked. Define in createChargingStation options.' - ) - }) - - await it('should throw error when OCPPRequestService.sendResponse is not mocked', async () => { - const station = createChargingStation({ connectorsCount: 1 }) - - await expect(station.ocppRequestService.sendResponse()).rejects.toThrow( - 'ocppRequestService.sendResponse not mocked. Define in createChargingStation options.' - ) - }) - - await it('should allow custom OCPPRequestService.sendError mock', async () => { - const mockSendError = async () => { - return Promise.resolve({ error: 'test-error' }) - } - - const station = createChargingStation({ - connectorsCount: 1, - ocppRequestService: { - sendError: mockSendError, - }, - }) - - const result = (await station.ocppRequestService.sendError()) as { error: string } - expect(result.error).toBe('test-error') - }) - - await it('should allow custom OCPPRequestService.sendResponse mock', async () => { - const mockSendResponse = async () => { - return Promise.resolve({ response: 'test-response' }) - } - - const station = createChargingStation({ - connectorsCount: 1, - ocppRequestService: { - sendResponse: mockSendResponse, - }, - }) - - const result = (await station.ocppRequestService.sendResponse()) as { response: string } - expect(result.response).toBe('test-response') - }) - - await it('should throw error when OCPPIncomingRequestService.incomingRequestHandler is not mocked', async () => { - const station = createChargingStation({ connectorsCount: 1 }) - - await expect(station.ocppIncomingRequestService.incomingRequestHandler()).rejects.toThrow( - 'ocppIncomingRequestService.incomingRequestHandler not mocked. Define in createChargingStation options.' - ) - }) - - await it('should allow custom OCPPIncomingRequestService.incomingRequestHandler mock', async () => { - const mockIncomingRequestHandler = async () => { - return Promise.resolve({ handled: true }) - } - - const station = createChargingStation({ - connectorsCount: 1, - ocppIncomingRequestService: { - incomingRequestHandler: mockIncomingRequestHandler, - }, - }) - - const result = (await station.ocppIncomingRequestService.incomingRequestHandler()) as { - handled: boolean - } - expect(result.handled).toBe(true) - }) - }) - - await describe('Configuration Validation', async () => { - await describe('StationInfo Properties', async () => { - await it('should create station with valid stationInfo', () => { - const station = createChargingStation({ - connectorsCount: 1, - stationInfo: { - baseName: 'test-base', - chargingStationId: 'test-station-001', - hashId: 'test-hash', - ocppVersion: OCPPVersion.VERSION_16, - templateHash: 'template-hash-123', - }, - }) - - expect(station.stationInfo?.chargingStationId).toBe('test-station-001') - expect(station.stationInfo?.hashId).toBe('test-hash') - expect(station.stationInfo?.baseName).toBe('test-base') - expect(station.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_16) - expect(station.stationInfo?.templateHash).toBe('template-hash-123') - }) - }) - - await describe('Connector Configuration', async () => { - await it('should create station with no connectors when connectorsCount is 0', () => { - const station = createChargingStation({ - connectorsCount: 0, - }) - - // Verify no connectors exist (connector map should be empty except for connector 0 if EVSEs are used) - expect(station.connectors.size).toBe(0) - }) - - await it('should create station with specified number of connectors', () => { - const station = createChargingStation({ - connectorsCount: 3, - }) - - // Should have 4 connectors (0, 1, 2, 3) when not using EVSEs - expect(station.connectors.size).toBe(4) - }) - - await it('should handle connector status properly', () => { - const station = createChargingStation({ - connectorsCount: 2, - }) - - // Verify connectors are properly initialized - expect(station.getConnectorStatus(1)).toBeDefined() - expect(station.getConnectorStatus(2)).toBeDefined() - }) - - await it('should create station with custom connector defaults', () => { - const station = createChargingStation({ - connectorDefaults: { - availability: AvailabilityType.Inoperative, - status: ConnectorStatusEnum.Unavailable, - }, - connectorsCount: 1, - }) - - const connectorStatus = station.getConnectorStatus(1) - expect(connectorStatus?.availability).toBe(AvailabilityType.Inoperative) - expect(connectorStatus?.status).toBe(ConnectorStatusEnum.Unavailable) - }) - }) - - await describe('OCPP Version-Specific Configuration', async () => { - await it('should configure OCPP 1.6 station correctly', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { - ocppVersion: OCPPVersion.VERSION_16, - }, - }) - - expect(station.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_16) - expect(station.connectors.size).toBe(3) // 0 + 2 connectors - expect(station.hasEvses).toBe(false) - }) - - await it('should configure OCPP 2.0 station with EVSEs', () => { - const station = createChargingStation({ - connectorsCount: 0, // OCPP 2.0 uses EVSEs instead of connectors - stationInfo: { - ocppVersion: OCPPVersion.VERSION_20, - }, - }) - - expect(station.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_20) - expect(station.connectors.size).toBe(0) - expect(station.hasEvses).toBe(true) - }) - - await it('should configure OCPP 2.0.1 station with EVSEs', () => { - const station = createChargingStation({ - connectorsCount: 0, // OCPP 2.0.1 uses EVSEs instead of connectors - stationInfo: { - ocppVersion: OCPPVersion.VERSION_201, - }, - }) - - expect(station.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_201) - expect(station.connectors.size).toBe(0) - expect(station.hasEvses).toBe(true) - }) - }) - - await describe('EVSE Configuration', async () => { - await it('should create station with EVSEs when configuration is provided', () => { - const station = createChargingStation({ - connectorsCount: 6, - evseConfiguration: { - evsesCount: 2, - }, - }) - - expect(station.hasEvses).toBe(true) - expect(station.evses.size).toBe(2) - expect(station.connectors.size).toBe(7) // 0 + 6 connectors - }) - - await it('should automatically enable EVSEs for OCPP 2.0+ versions', () => { - const station = createChargingStation({ - connectorsCount: 3, - stationInfo: { - ocppVersion: OCPPVersion.VERSION_201, - }, - }) - - expect(station.hasEvses).toBe(true) - expect(station.connectors.size).toBe(4) // 0 + 3 connectors - }) - }) - - await describe('Factory Default Values', async () => { - await it('should provide sensible defaults for all required properties', () => { - const station = createChargingStation({ - connectorsCount: 1, - }) - - // Verify factory provides all required defaults - expect(station.stationInfo?.chargingStationId).toBeDefined() - expect(station.stationInfo?.hashId).toBeDefined() - expect(station.stationInfo?.baseName).toBeDefined() - expect(station.stationInfo?.ocppVersion).toBeDefined() - expect(station.stationInfo?.templateHash).toBeUndefined() // Factory doesn't set templateHash by default - }) - - await it('should allow overriding factory defaults', () => { - const customStationId = 'custom-station-123' - const customHashId = 'custom-hash-456' - - const station = createChargingStation({ - connectorsCount: 1, - stationInfo: { - chargingStationId: customStationId, - hashId: customHashId, - }, - }) - - expect(station.stationInfo?.chargingStationId).toBe(customStationId) - expect(station.stationInfo?.hashId).toBe(customHashId) - // Other defaults should still be provided - expect(station.stationInfo?.baseName).toBeDefined() - expect(station.stationInfo?.ocppVersion).toBeDefined() - }) - - await it('should use default base name when not provided', () => { - const station = createChargingStation({ - connectorsCount: 1, - }) - - expect(station.stationInfo?.baseName).toBe('CS-TEST') - expect(station.stationInfo?.chargingStationId).toBe('CS-TEST-00001') - }) - - await it('should use custom base name when provided', () => { - const customBaseName = 'CUSTOM-STATION' - const station = createChargingStation({ - baseName: customBaseName, - connectorsCount: 1, - }) - - expect(station.stationInfo?.baseName).toBe(customBaseName) - expect(station.stationInfo?.chargingStationId).toBe('CUSTOM-STATION-00001') - }) - }) - - await describe('Configuration Options', async () => { - await it('should respect connection timeout setting', () => { - const customTimeout = 45000 - const station = createChargingStation({ - connectionTimeout: customTimeout, - connectorsCount: 1, - }) - - expect(station.getConnectionTimeout()).toBe(customTimeout) - }) - - await it('should respect heartbeat interval setting', () => { - const customInterval = 120000 - const station = createChargingStation({ - connectorsCount: 1, - heartbeatInterval: customInterval, - }) - - expect(station.getHeartbeatInterval()).toBe(customInterval) - }) - - await it('should respect websocket ping interval setting', () => { - const customPingInterval = 90000 - const station = createChargingStation({ - connectorsCount: 1, - websocketPingInterval: customPingInterval, - }) - - expect(station.getWebSocketPingInterval()).toBe(customPingInterval) - }) - - await it('should respect started and starting flags', () => { - const station = createChargingStation({ - connectorsCount: 1, - started: true, - starting: false, - }) - - expect(station.started).toBe(true) - expect(station.starting).toBe(false) - }) - }) - - await describe('Integration with Helpers', async () => { - await it('should properly integrate with helper functions', () => { - const station = createChargingStation({ - connectorsCount: 1, - stationInfo: { - baseName: 'HELPER-TEST', - chargingStationId: 'HELPER-TEST-001', - }, - }) - - // Verify the station info is properly set - expect(station.stationInfo?.chargingStationId).toBe('HELPER-TEST-001') - - // Verify hash ID generation works with the helpers - const template = createChargingStationTemplate('HELPER-TEST') - const hashId = getHashId(1, template) - expect(hashId).toBeDefined() - expect(typeof hashId).toBe('string') - }) - }) - }) - - await describe('Mock Behavioral Parity', async () => { - await describe('getConnectorIdByTransactionId', async () => { - await it('should return undefined for null transaction ID', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Test null handling (matches real class behavior) - expect(station.getConnectorIdByTransactionId(null)).toBeUndefined() - }) - - await it('should return undefined for undefined transaction ID', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Test undefined handling (matches real class behavior) - expect(station.getConnectorIdByTransactionId(undefined)).toBeUndefined() - }) - - await it('should return connector ID when transaction ID matches (standard connectors)', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, // Force non-EVSE mode - }) - - // Set up a transaction on connector 1 - const connector1Status = station.getConnectorStatus(1) - if (connector1Status) { - connector1Status.transactionId = 'test-transaction-123' - } - - expect(station.getConnectorIdByTransactionId('test-transaction-123')).toBe(1) - }) - - await it('should return connector ID when transaction ID matches (EVSE mode)', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, // Force EVSE mode - }) - - // Set up a transaction on connector 1 - const connector1Status = station.getConnectorStatus(1) - if (connector1Status) { - connector1Status.transactionId = 'test-evse-transaction-456' - } - - expect(station.getConnectorIdByTransactionId('test-evse-transaction-456')).toBe(1) - }) - - await it('should return undefined when transaction ID does not match any connector', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - expect(station.getConnectorIdByTransactionId('non-existent-transaction')).toBeUndefined() - }) - - await it('should handle numeric transaction IDs', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Set up a transaction with numeric ID on connector 2 - const connector2Status = station.getConnectorStatus(2) - if (connector2Status) { - connector2Status.transactionId = 12345 - } - - expect(station.getConnectorIdByTransactionId(12345)).toBe(2) - }) - }) - - await describe('getEvseIdByConnectorId', async () => { - await it('should return undefined for stations without EVSEs', () => { - const station = createChargingStation({ - connectorsCount: 3, - stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, // OCPP 1.6 doesn't use EVSEs - }) - - expect(station.getEvseIdByConnectorId(1)).toBeUndefined() - expect(station.getEvseIdByConnectorId(2)).toBeUndefined() - }) - - await it('should return correct EVSE ID for connectors in EVSE mode', () => { - const station = createChargingStation({ - connectorsCount: 6, - evseConfiguration: { evsesCount: 2 }, // 2 EVSEs with 3 connectors each - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // EVSE 1 should have connectors 1, 2, 3 - expect(station.getEvseIdByConnectorId(1)).toBe(1) - expect(station.getEvseIdByConnectorId(2)).toBe(1) - expect(station.getEvseIdByConnectorId(3)).toBe(1) - - // EVSE 2 should have connectors 4, 5, 6 - expect(station.getEvseIdByConnectorId(4)).toBe(2) - expect(station.getEvseIdByConnectorId(5)).toBe(2) - expect(station.getEvseIdByConnectorId(6)).toBe(2) - }) - - await it('should return undefined for non-existent connector IDs', () => { - const station = createChargingStation({ - connectorsCount: 4, - evseConfiguration: { evsesCount: 2 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - expect(station.getEvseIdByConnectorId(0)).toBeUndefined() // Connector 0 not in EVSEs - expect(station.getEvseIdByConnectorId(99)).toBeUndefined() // Non-existent connector - expect(station.getEvseIdByConnectorId(-1)).toBeUndefined() // Invalid connector ID - }) - - await it('should handle single EVSE with multiple connectors', () => { - const station = createChargingStation({ - connectorsCount: 3, - evseConfiguration: { evsesCount: 1 }, // Single EVSE with all connectors - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // All connectors should belong to EVSE 1 - expect(station.getEvseIdByConnectorId(1)).toBe(1) - expect(station.getEvseIdByConnectorId(2)).toBe(1) - expect(station.getEvseIdByConnectorId(3)).toBe(1) - }) - }) - - await describe('getEvseIdByTransactionId', async () => { - await it('should return undefined for null transaction ID', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Test null handling (matches real class behavior) - expect(station.getEvseIdByTransactionId(null)).toBeUndefined() - }) - - await it('should return undefined for undefined transaction ID', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Test undefined handling (matches real class behavior) - expect(station.getEvseIdByTransactionId(undefined)).toBeUndefined() - }) - - await it('should return undefined for stations without EVSEs', () => { - const station = createChargingStation({ - connectorsCount: 3, - stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, // OCPP 1.6 doesn't use EVSEs - }) - - // Set up transactions on connectors (should still return undefined without EVSEs) - const connector1Status = station.getConnectorStatus(1) - if (connector1Status) { - connector1Status.transactionId = 'test-transaction-123' - } - - expect(station.getEvseIdByTransactionId('test-transaction-123')).toBeUndefined() - }) - - await it('should return correct EVSE ID when transaction ID matches (single EVSE)', () => { - const station = createChargingStation({ - connectorsCount: 3, - evseConfiguration: { evsesCount: 1 }, // Single EVSE with all connectors - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up transactions on different connectors within the same EVSE - const connector1Status = station.getConnectorStatus(1) - const connector2Status = station.getConnectorStatus(2) - if (connector1Status) { - connector1Status.transactionId = 'transaction-on-connector-1' - } - if (connector2Status) { - connector2Status.transactionId = 456 - } - - // Both should return EVSE ID 1 - expect(station.getEvseIdByTransactionId('transaction-on-connector-1')).toBe(1) - expect(station.getEvseIdByTransactionId(456)).toBe(1) - }) - - await it('should return correct EVSE ID when transaction ID matches (multiple EVSEs)', () => { - const station = createChargingStation({ - connectorsCount: 6, - evseConfiguration: { evsesCount: 2 }, // 2 EVSEs with 3 connectors each - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up transactions on connectors in different EVSEs - const connector1Status = station.getConnectorStatus(1) // EVSE 1 - const connector4Status = station.getConnectorStatus(4) // EVSE 2 - const connector5Status = station.getConnectorStatus(5) // EVSE 2 - - if (connector1Status) { - connector1Status.transactionId = 'evse1-transaction' - } - if (connector4Status) { - connector4Status.transactionId = 'evse2-transaction-a' - } - if (connector5Status) { - connector5Status.transactionId = 999 - } - - // Verify correct EVSE mapping - expect(station.getEvseIdByTransactionId('evse1-transaction')).toBe(1) - expect(station.getEvseIdByTransactionId('evse2-transaction-a')).toBe(2) - expect(station.getEvseIdByTransactionId(999)).toBe(2) - }) - - await it('should return undefined when transaction ID does not match any connector', () => { - const station = createChargingStation({ - connectorsCount: 4, - evseConfiguration: { evsesCount: 2 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up some transactions - const connector1Status = station.getConnectorStatus(1) - if (connector1Status) { - connector1Status.transactionId = 'existing-transaction' - } - - expect(station.getEvseIdByTransactionId('non-existent-transaction')).toBeUndefined() - expect(station.getEvseIdByTransactionId(12345)).toBeUndefined() - }) - - await it('should handle numeric transaction IDs', () => { - const station = createChargingStation({ - connectorsCount: 4, - evseConfiguration: { evsesCount: 2 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up numeric transaction ID on connector in EVSE 2 - const connector3Status = station.getConnectorStatus(3) - if (connector3Status) { - connector3Status.transactionId = 789 - } - - expect(station.getEvseIdByTransactionId(789)).toBe(2) - }) - - await it('should handle string transaction IDs', () => { - const station = createChargingStation({ - connectorsCount: 4, - evseConfiguration: { evsesCount: 2 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up string transaction ID on connector in EVSE 1 - const connector2Status = station.getConnectorStatus(2) - if (connector2Status) { - connector2Status.transactionId = 'string-transaction-id-abc123' - } - - expect(station.getEvseIdByTransactionId('string-transaction-id-abc123')).toBe(1) - }) - - await it('should maintain consistency with getConnectorIdByTransactionId', () => { - const station = createChargingStation({ - connectorsCount: 6, - evseConfiguration: { evsesCount: 3 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - const testTransactionId = 'consistency-test-transaction' - - // Set up a transaction on connector 5 (which should be in EVSE 3) - const connector5Status = station.getConnectorStatus(5) - if (connector5Status) { - connector5Status.transactionId = testTransactionId - } - - // Both methods should find the transaction - const foundConnectorId = station.getConnectorIdByTransactionId(testTransactionId) - const foundEvseId = station.getEvseIdByTransactionId(testTransactionId) - - expect(foundConnectorId).toBe(5) - expect(foundEvseId).toBe(3) - - // Verify consistency: getEvseIdByConnectorId should match - if (foundConnectorId !== undefined) { - expect(station.getEvseIdByConnectorId(foundConnectorId)).toBe(foundEvseId) - } - }) - - await it('should handle mixed transaction ID types correctly', () => { - const station = createChargingStation({ - connectorsCount: 4, - evseConfiguration: { evsesCount: 2 }, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - // Set up mixed types of transaction IDs - const connector1Status = station.getConnectorStatus(1) - const connector3Status = station.getConnectorStatus(3) - - if (connector1Status) { - connector1Status.transactionId = 'string-transaction' - } - if (connector3Status) { - connector3Status.transactionId = 999 - } - - // Test string transaction ID - expect(station.getEvseIdByTransactionId('string-transaction')).toBe(1) - // Test numeric transaction ID - expect(station.getEvseIdByTransactionId(999)).toBe(2) - // Test wrong type should not match - expect(station.getEvseIdByTransactionId('999')).toBeUndefined() // String vs number - expect(station.getEvseIdByTransactionId(Number('string-transaction'))).toBeUndefined() // NaN - }) - }) - - await describe('isConnectorAvailable', async () => { - await it('should return false for connector ID 0', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Connector 0 should never be available (matches real class behavior) - expect(station.isConnectorAvailable(0)).toBe(false) - }) - - await it('should return false for negative connector ID', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Negative connectorId should return false (matches real class behavior) - expect(station.isConnectorAvailable(-1)).toBe(false) - }) - - await it('should return true for available operative connector', () => { - const station = createChargingStation({ - connectorDefaults: { - availability: AvailabilityType.Operative, - status: ConnectorStatusEnum.Available, - }, - connectorsCount: 2, - }) - - expect(station.isConnectorAvailable(1)).toBe(true) - expect(station.isConnectorAvailable(2)).toBe(true) - }) - - await it('should return false for inoperative connector', () => { - const station = createChargingStation({ - connectorDefaults: { - availability: AvailabilityType.Inoperative, - status: ConnectorStatusEnum.Available, - }, - connectorsCount: 2, - }) - - expect(station.isConnectorAvailable(1)).toBe(false) - expect(station.isConnectorAvailable(2)).toBe(false) - }) - - await it('should check availability regardless of status (matches real class)', () => { - const station = createChargingStation({ - connectorDefaults: { - availability: AvailabilityType.Operative, - status: ConnectorStatusEnum.Occupied, // Status should not affect availability check - }, - connectorsCount: 2, - }) - - // Real class only checks availability, not status - expect(station.isConnectorAvailable(1)).toBe(true) - expect(station.isConnectorAvailable(2)).toBe(true) - }) - - await it('should return false for non-existent connector', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Connector 3 doesn't exist - expect(station.isConnectorAvailable(3)).toBe(false) - }) - - await it('should work correctly in EVSE mode', () => { - const station = createChargingStation({ - connectorDefaults: { - availability: AvailabilityType.Operative, - status: ConnectorStatusEnum.Available, - }, - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, // Force EVSE mode - }) - - expect(station.isConnectorAvailable(1)).toBe(true) - expect(station.isConnectorAvailable(2)).toBe(true) - }) - }) - - await describe('getConnectorStatus behavioral parity', async () => { - await it('should return undefined for non-existent connector in standard mode', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, - }) - - expect(station.getConnectorStatus(999)).toBeUndefined() - }) - - await it('should return undefined for non-existent connector in EVSE mode', () => { - const station = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - expect(station.getConnectorStatus(999)).toBeUndefined() - }) - - await it('should return connector status for valid connector in both modes', () => { - const stationStandard = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_16 }, - }) - const stationEVSE = createChargingStation({ - connectorsCount: 2, - stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, - }) - - expect(stationStandard.getConnectorStatus(1)).toBeDefined() - expect(stationEVSE.getConnectorStatus(1)).toBeDefined() - }) - }) - - await describe('Method interaction behavioral parity', async () => { - await it('should maintain consistency between getConnectorStatus and isConnectorAvailable', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Test consistency - if connector status exists and is operative, should be available - const connector1Status = station.getConnectorStatus(1) - expect(connector1Status).toBeDefined() - expect(station.isConnectorAvailable(1)).toBe(true) - - // Make connector inoperative - if (connector1Status) { - connector1Status.availability = AvailabilityType.Inoperative - } - expect(station.isConnectorAvailable(1)).toBe(false) - }) - - await it('should maintain consistency between getConnectorIdByTransactionId and getConnectorStatus', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Set up transaction - const testTransactionId = 'test-consistency-transaction' - const connector2Status = station.getConnectorStatus(2) - if (connector2Status) { - connector2Status.transactionId = testTransactionId - } - - // Both methods should work with the same transaction - const foundConnectorId = station.getConnectorIdByTransactionId(testTransactionId) - expect(foundConnectorId).toBe(2) - - const foundConnectorStatus = station.getConnectorStatus(foundConnectorId) - expect(foundConnectorStatus?.transactionId).toBe(testTransactionId) - }) - }) - - await describe('Edge Cases and Error Handling', async () => { - await it('should handle empty station (no connectors)', () => { - const station = createChargingStation({ connectorsCount: 0 }) - - expect(station.getConnectorIdByTransactionId('any-transaction')).toBeUndefined() - expect(station.isConnectorAvailable(1)).toBe(false) - expect(station.getConnectorStatus(1)).toBeUndefined() - }) - - await it('should handle mixed transaction ID types in search', () => { - const station = createChargingStation({ connectorsCount: 3 }) - - // Set up mixed transaction types - const connector1Status = station.getConnectorStatus(1) - const connector2Status = station.getConnectorStatus(2) - if (connector1Status && connector2Status) { - connector1Status.transactionId = 'string-transaction' - connector2Status.transactionId = 999 - } - - expect(station.getConnectorIdByTransactionId('string-transaction')).toBe(1) - expect(station.getConnectorIdByTransactionId(999)).toBe(2) - expect(station.getConnectorIdByTransactionId('999')).toBeUndefined() // String vs number - }) - - await it('should handle partially configured connectors', () => { - const station = createChargingStation({ connectorsCount: 2 }) - - // Manually modify one connector to test resilience - const connector1Status = station.getConnectorStatus(1) - if (connector1Status) { - connector1Status.availability = undefined // Remove availability property - } - - // Should handle missing availability gracefully - expect(station.isConnectorAvailable(1)).toBe(false) - expect(station.isConnectorAvailable(2)).toBe(true) // Other connector still works - }) - }) - }) -}) diff --git a/tests/ChargingStationFactory.ts b/tests/ChargingStationFactory.ts deleted file mode 100644 index 1ac8b869..00000000 --- a/tests/ChargingStationFactory.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { millisecondsToSeconds } from 'date-fns' - -import type { ChargingStation } from '../src/charging-station/index.js' - -import { getConfigurationKey } from '../src/charging-station/ConfigurationKeyUtils.js' -import { IdTagsCache } from '../src/charging-station/IdTagsCache.js' -import { - AvailabilityType, - type BootNotificationResponse, - type ChargingStationConfiguration, - type ChargingStationInfo, - type ChargingStationTemplate, - type ConnectorStatus, - ConnectorStatusEnum, - type EvseStatus, - OCPP20OptionalVariableName, - OCPPVersion, - RegistrationStatusEnumType, - StandardParametersKey, -} from '../src/types/index.js' -import { clone, Constants, convertToBoolean } from '../src/utils/index.js' -import { createConnectorStatus } from './charging-station/helpers/StationHelpers.js' - -/** - * Options to customize the construction of a ChargingStation test instance - * @example createChargingStation({ connectorsCount: 2, ocppRequestService: mockService }) - */ -export interface ChargingStationOptions { - baseName?: string - connectionTimeout?: number - connectorDefaults?: { - availability?: AvailabilityType - status?: ConnectorStatusEnum - } - /** Number of connectors to create (default: 3 if EVSEs enabled, 0 otherwise) */ - connectorsCount?: number - /** EVSE configuration for OCPP 2.0 - enables EVSE mode when present */ - evseConfiguration?: { - evsesCount?: number - } - - heartbeatInterval?: number - ocppConfiguration?: ChargingStationConfiguration - /** Custom OCPP incoming request service for test mocking */ - ocppIncomingRequestService?: Partial - /** Custom OCPP request service for test mocking */ - ocppRequestService?: Partial - started?: boolean - starting?: boolean - stationInfo?: Partial - websocketPingInterval?: number -} - -/** - * Mock OCPP incoming request service interface for testing - * Provides typed access to mock handlers without eslint-disable comments - */ -export interface MockOCPPIncomingRequestService { - incomingRequestHandler: () => Promise - stop: () => void -} - -/** - * Mock OCPP request service interface for testing - * Provides typed access to mock handlers without eslint-disable comments - */ -export interface MockOCPPRequestService { - requestHandler: () => Promise - sendError: () => Promise - sendResponse: () => Promise -} - -/** - * Test-specific ChargingStation interface exposing mock services - * Allows typed access to mock OCPP services in tests - */ -export interface TestChargingStation extends ChargingStation { - ocppIncomingRequestService: MockOCPPIncomingRequestService - ocppRequestService: MockOCPPRequestService -} - -const CHARGING_STATION_BASE_NAME = 'CS-TEST' - -/** - * Creates a ChargingStation instance for tests - * @param options - Configuration options for the charging station - * @returns TestChargingStation instance configured for testing - */ -export function createChargingStation (options: ChargingStationOptions = {}): TestChargingStation { - const baseName = options.baseName ?? CHARGING_STATION_BASE_NAME - const templateIndex = 1 - const connectionTimeout = options.connectionTimeout ?? Constants.DEFAULT_CONNECTION_TIMEOUT - const heartbeatInterval = options.heartbeatInterval ?? Constants.DEFAULT_HEARTBEAT_INTERVAL - const websocketPingInterval = - options.websocketPingInterval ?? Constants.DEFAULT_WEBSOCKET_PING_INTERVAL - const useEvses = determineEvseUsage(options) - const connectorsCount = options.connectorsCount ?? (useEvses ? 3 : 0) - const { connectors, evses } = createConnectorsConfiguration(options, connectorsCount, useEvses) - - const chargingStation = { - bootNotificationResponse: { - currentTime: new Date(), - interval: heartbeatInterval, - status: RegistrationStatusEnumType.ACCEPTED, - } as BootNotificationResponse, - connectors, - emitChargingStationEvent: (): void => { - /* no-op for tests */ - }, - evses, - getConnectionTimeout: (): number => connectionTimeout, - getConnectorIdByTransactionId: ( - transactionId: number | string | undefined - ): number | undefined => { - if (transactionId == null) { - return undefined - } - // Search through connectors to find one with matching transaction ID - if (chargingStation.hasEvses) { - for (const evseStatus of chargingStation.evses.values()) { - for (const [connectorId, connectorStatus] of evseStatus.connectors.entries()) { - if (connectorStatus.transactionId === transactionId) { - return connectorId - } - } - } - } else { - for (const [connectorId, connectorStatus] of chargingStation.connectors.entries()) { - if (connectorStatus.transactionId === transactionId) { - return connectorId - } - } - } - return undefined - }, - getConnectorStatus: (connectorId: number): ConnectorStatus | undefined => { - if (chargingStation.hasEvses) { - for (const evseStatus of chargingStation.evses.values()) { - if (evseStatus.connectors.has(connectorId)) { - return evseStatus.connectors.get(connectorId) - } - } - return undefined - } - return chargingStation.connectors.get(connectorId) - }, - getEvseIdByConnectorId: (connectorId: number): number | undefined => { - if (!chargingStation.hasEvses) { - return undefined - } - for (const [evseId, evseStatus] of chargingStation.evses.entries()) { - if (evseStatus.connectors.has(connectorId)) { - return evseId - } - } - return undefined - }, - getEvseIdByTransactionId: (transactionId: number | string | undefined): number | undefined => { - if (transactionId == null) { - return undefined - } - // Search through EVSEs to find one with matching transaction ID - if (chargingStation.hasEvses) { - for (const [evseId, evseStatus] of chargingStation.evses.entries()) { - for (const connectorStatus of evseStatus.connectors.values()) { - if (connectorStatus.transactionId === transactionId) { - return evseId - } - } - } - } - return undefined - }, - getEvseStatus: (evseId: number): EvseStatus | undefined => { - return chargingStation.evses.get(evseId) - }, - getHeartbeatInterval: (): number => heartbeatInterval, - getLocalAuthListEnabled: (): boolean => { - const localAuthListEnabled = getConfigurationKey( - chargingStation, - StandardParametersKey.LocalAuthListEnabled - ) - return localAuthListEnabled != null ? convertToBoolean(localAuthListEnabled.value) : false - }, - getNumberOfEvses: (): number => evses.size, - getWebSocketPingInterval: (): number => websocketPingInterval, - hasEvses: useEvses, - hasIdTags: (): boolean => false, - idTagsCache: IdTagsCache.getInstance(), - inAcceptedState: (): boolean => { - return ( - chargingStation.bootNotificationResponse?.status === RegistrationStatusEnumType.ACCEPTED - ) - }, - isChargingStationAvailable: (): boolean => { - return chargingStation.getConnectorStatus(0)?.availability === AvailabilityType.Operative - }, - isConnectorAvailable: (connectorId: number): boolean => { - return ( - connectorId > 0 && - chargingStation.getConnectorStatus(connectorId)?.availability === AvailabilityType.Operative - ) - }, - isWebSocketConnectionOpened: (): boolean => true, - logPrefix: (): string => { - const stationId = - chargingStation.stationInfo?.chargingStationId ?? - `${baseName}-0000${templateIndex.toString()}` - return `${stationId} |` - }, - ocppConfiguration: { - configurationKey: [ - { - key: OCPP20OptionalVariableName.WebSocketPingInterval, - value: websocketPingInterval.toString(), - }, - { - key: OCPP20OptionalVariableName.HeartbeatInterval, - value: millisecondsToSeconds(heartbeatInterval).toString(), - }, - { - key: StandardParametersKey.LocalAuthListEnabled, - value: 'true', - }, - ], - ...options.ocppConfiguration, - }, - ocppIncomingRequestService: { - incomingRequestHandler: async () => { - return await Promise.reject( - new Error( - 'ocppIncomingRequestService.incomingRequestHandler not mocked. Define in createChargingStation options.' - ) - ) - }, - stop: (): void => { - throw new Error( - 'ocppIncomingRequestService.stop not mocked. Define in createChargingStation options.' - ) - }, - ...options.ocppIncomingRequestService, - }, - ocppRequestService: { - requestHandler: async () => { - return await Promise.reject( - new Error( - 'ocppRequestService.requestHandler not mocked. Define in createChargingStation options.' - ) - ) - }, - sendError: async () => { - return await Promise.reject( - new Error( - 'ocppRequestService.sendError not mocked. Define in createChargingStation options.' - ) - ) - }, - sendResponse: async () => { - return await Promise.reject( - new Error( - 'ocppRequestService.sendResponse not mocked. Define in createChargingStation options.' - ) - ) - }, - ...options.ocppRequestService, - }, - restartHeartbeat: (): void => { - /* no-op for tests */ - }, - restartWebSocketPing: (): void => { - /* no-op for tests */ - }, - saveOcppConfiguration: (): void => { - /* no-op for tests */ - }, - started: options.started ?? false, - starting: options.starting ?? false, - startTxUpdatedInterval: (_connectorId: number, _interval: number): void => { - /* no-op for tests */ - }, - stationInfo: { - baseName, - chargingStationId: `${baseName}-00001`, - hashId: 'test-hash-id', - maximumAmperage: 16, - maximumPower: 12000, - ocppVersion: OCPPVersion.VERSION_16, - remoteAuthorization: true, - templateIndex, - templateName: 'test-template.json', - ...options.stationInfo, - } as ChargingStationInfo, - stopMeterValues: (connectorId: number): void => { - const connectorStatus = chargingStation.getConnectorStatus(connectorId) - if (connectorStatus?.transactionSetInterval != null) { - clearInterval(connectorStatus.transactionSetInterval) - } - }, - stopTxUpdatedInterval: (_connectorId: number): void => { - /* no-op for tests */ - }, - } as unknown as TestChargingStation - - return chargingStation -} - -/** - * Creates a ChargingStation template for tests - * @param baseName - Base name for the charging station - * @returns ChargingStation template for testing - */ -export function createChargingStationTemplate ( - baseName = CHARGING_STATION_BASE_NAME -): ChargingStationTemplate { - return { - baseName, - } as ChargingStationTemplate -} - -/** - * Creates connector and EVSE configuration - * @param options - Configuration options - * @param connectorsCount - Number of connectors to create - * @param useEvses - Whether to use EVSE mode - * @returns Object containing connectors and evses maps - */ -function createConnectorsConfiguration ( - options: ChargingStationOptions, - connectorsCount: number, - useEvses: boolean -) { - const connectors = new Map() - const evses = new Map() - - if (connectorsCount === 0) { - return { connectors, evses } - } - - // Helper to create connector status with options defaults - const connectorStatusOptions = { - availability: options.connectorDefaults?.availability, - status: options.connectorDefaults?.status, - } - - if (useEvses) { - const evsesCount = options.evseConfiguration?.evsesCount ?? connectorsCount - const connectorsCountPerEvse = Math.ceil(connectorsCount / evsesCount) - - const connector0 = createConnectorStatus(0, connectorStatusOptions) - connectors.set(0, connector0) - - for (let evseId = 1; evseId <= evsesCount; evseId++) { - const evseConnectors = new Map() - const startConnectorId = (evseId - 1) * connectorsCountPerEvse + 1 - const endConnectorId = Math.min( - startConnectorId + connectorsCountPerEvse - 1, - connectorsCount - ) - - for (let connectorId = startConnectorId; connectorId <= endConnectorId; connectorId++) { - const connectorStatus = createConnectorStatus(connectorId, connectorStatusOptions) - connectors.set(connectorId, connectorStatus) - evseConnectors.set(connectorId, clone(connectorStatus)) - } - - evses.set(evseId, { - availability: AvailabilityType.Operative, - connectors: evseConnectors, - }) - } - } else { - for (let connectorId = 0; connectorId <= connectorsCount; connectorId++) { - connectors.set(connectorId, createConnectorStatus(connectorId, connectorStatusOptions)) - } - } - - return { connectors, evses } -} - -/** - * Determines whether EVSEs should be used based on configuration - * @param options - Configuration options to check - * @returns True if EVSEs should be used, false otherwise - */ -function determineEvseUsage (options: ChargingStationOptions): boolean { - return ( - options.evseConfiguration?.evsesCount != null || - options.stationInfo?.ocppVersion === OCPPVersion.VERSION_20 || - options.stationInfo?.ocppVersion === OCPPVersion.VERSION_201 - ) -} diff --git a/tests/charging-station/ChargingStationTestUtils.ts b/tests/charging-station/ChargingStationTestUtils.ts index 8b813115..7c536b71 100644 --- a/tests/charging-station/ChargingStationTestUtils.ts +++ b/tests/charging-station/ChargingStationTestUtils.ts @@ -33,11 +33,11 @@ export { cleanupChargingStation, createConnectorStatus, createMockChargingStation, + createMockChargingStationTemplate, resetChargingStationState, waitForCondition, } from './helpers/StationHelpers.js' -export { createChargingStation, createChargingStationTemplate } from '../ChargingStationFactory.js' export { MockIdTagsCache, MockSharedLRUCache } from './mocks/MockCaches.js' // Re-export all mock classes diff --git a/tests/charging-station/ConfigurationKeyUtils.test.ts b/tests/charging-station/ConfigurationKeyUtils.test.ts index eab3f6d6..e517e3b7 100644 --- a/tests/charging-station/ConfigurationKeyUtils.test.ts +++ b/tests/charging-station/ConfigurationKeyUtils.test.ts @@ -14,7 +14,7 @@ import { setConfigurationKeyValue, } from '../../src/charging-station/ConfigurationKeyUtils.js' import { logger } from '../../src/utils/Logger.js' -import { createChargingStation } from './ChargingStationTestUtils.js' +import { createMockChargingStation } from './ChargingStationTestUtils.js' const TEST_KEY_1 = 'TestKey1' const MIXED_CASE_KEY = 'MiXeDkEy' @@ -27,14 +27,14 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await describe('getConfigurationKey()', async () => { await it('should return undefined when configurationKey array is missing', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() // Simulate missing configurationKey array cs.ocppConfiguration = {} as Partial expect(getConfigurationKey(cs, TEST_KEY_1)).toBeUndefined() }) await it('should find existing key (case-sensitive)', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const k = getConfigurationKey(cs, TEST_KEY_1) expect(k?.key).toBe(TEST_KEY_1) @@ -42,13 +42,13 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should respect case sensitivity (no match)', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) expect(getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase())).toBeUndefined() }) await it('should support caseInsensitive lookup', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) const k = getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase(), true) expect(k?.key).toBe(MIXED_CASE_KEY) @@ -57,7 +57,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { await describe('addConfigurationKey()', async () => { await it('should no-op when configurationKey array missing', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() // Simulate missing configurationKey array cs.ocppConfiguration = {} as Partial addConfigurationKey(cs, TEST_KEY_1, VALUE_A) @@ -65,7 +65,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should add new key with default options', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const k = getConfigurationKey(cs, TEST_KEY_1) expect(k).toBeDefined() @@ -77,7 +77,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should add new key with custom options', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey( cs, TEST_KEY_1, @@ -92,7 +92,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should log error and not overwrite value when key exists and overwrite=false', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, { readonly: false }, { save: false }) const errorMock = t.mock.method(logger, 'error') // Attempt to add same key with different value and option change @@ -114,7 +114,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should log error and leave key untouched when identical options & value attempted (overwrite=false)', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey( cs, TEST_KEY_1, @@ -140,7 +140,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should overwrite existing key value and options when overwrite=true', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, { readonly: false }, { save: false }) addConfigurationKey( cs, @@ -157,7 +157,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should caseInsensitive overwrite update existing differently cased key', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) addConfigurationKey( cs, @@ -172,7 +172,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should case-insensitive false create separate key with different case', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) addConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase(), VALUE_B, undefined, { overwrite: true, @@ -186,14 +186,14 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should call saveOcppConfiguration when params.save=true (new key)', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() const saveMock = t.mock.method(cs, 'saveOcppConfiguration') addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: true }) expect(saveMock.mock.calls.length).toBe(1) }) await it('should call saveOcppConfiguration when overwriting existing key and save=true', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const saveMock = t.mock.method(cs, 'saveOcppConfiguration') addConfigurationKey( @@ -209,7 +209,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { await describe('setConfigurationKeyValue()', async () => { await it('should return undefined and log error for non-existing key', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() const errorMock = t.mock.method(logger, 'error') const res = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_A) expect(res).toBeUndefined() @@ -217,7 +217,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should return undefined without logging when configurationKey array missing', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() // Simulate missing configurationKey array cs.ocppConfiguration = {} as Partial const errorMock = t.mock.method(logger, 'error') @@ -227,7 +227,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should update existing key value and save', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const saveMock = t.mock.method(cs, 'saveOcppConfiguration') const updated = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_B) @@ -236,7 +236,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should caseInsensitive value update work', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) const updated = setConfigurationKeyValue(cs, MIXED_CASE_KEY.toLowerCase(), VALUE_B, true) expect(updated?.value).toBe(VALUE_B) @@ -245,7 +245,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { await describe('deleteConfigurationKey()', async () => { await it('should return undefined when configurationKey array missing', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() // Simulate missing configurationKey array cs.ocppConfiguration = {} as Partial const res = deleteConfigurationKey(cs, TEST_KEY_1) @@ -253,13 +253,13 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should return undefined when key does not exist', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() const res = deleteConfigurationKey(cs, TEST_KEY_1) expect(res).toBeUndefined() }) await it('should delete existing key and save by default', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const saveMock = t.mock.method(cs, 'saveOcppConfiguration') const deleted = deleteConfigurationKey(cs, TEST_KEY_1) @@ -271,7 +271,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should not save when params.save=false', t => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: false }) const saveMock = t.mock.method(cs, 'saveOcppConfiguration') const deleted = deleteConfigurationKey(cs, TEST_KEY_1, { save: false }) @@ -280,7 +280,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { }) await it('should caseInsensitive deletion remove key with different case', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false }) const deleted = deleteConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase(), { caseInsensitive: true, @@ -293,7 +293,7 @@ await describe('ConfigurationKeyUtils test suite', async () => { await describe('Combined scenarios', async () => { await it('should add then set then delete lifecycle', () => { - const cs = createChargingStation() + const { station: cs } = createMockChargingStation() addConfigurationKey(cs, TEST_KEY_1, VALUE_A, { readonly: false }, { save: false }) const setRes = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_B) expect(setRes?.value).toBe(VALUE_B) diff --git a/tests/charging-station/Helpers.test.ts b/tests/charging-station/Helpers.test.ts index 70d7b972..3ae8df8a 100644 --- a/tests/charging-station/Helpers.test.ts +++ b/tests/charging-station/Helpers.test.ts @@ -33,12 +33,12 @@ import { type Reservation, } from '../../src/types/index.js' import { logger } from '../../src/utils/Logger.js' -import { createChargingStation, createChargingStationTemplate } from './ChargingStationTestUtils.js' +import { createMockChargingStation, createMockChargingStationTemplate } from './ChargingStationTestUtils.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' await describe('Helpers test suite', async () => { const baseName = 'CS-TEST' - const chargingStationTemplate = createChargingStationTemplate(baseName) + const chargingStationTemplate = createMockChargingStationTemplate(baseName) afterEach(() => { standardCleanup() @@ -66,7 +66,7 @@ await describe('Helpers test suite', async () => { await it('should throw when stationInfo is missing', () => { // For validation edge cases, we need to manually create invalid states // since the factory is designed to create valid configurations - const stationNoInfo = createChargingStation({ baseName }) + const { station: stationNoInfo } = createMockChargingStation({ baseName }) stationNoInfo.stationInfo = undefined expect(() => { validateStationInfo(stationNoInfo) @@ -75,7 +75,7 @@ await describe('Helpers test suite', async () => { await it('should throw when stationInfo is empty object', () => { // For validation edge cases, manually create empty stationInfo - const stationEmptyInfo = createChargingStation({ baseName }) + const { station: stationEmptyInfo } = createMockChargingStation({ baseName }) stationEmptyInfo.stationInfo = {} as ChargingStationInfo expect(() => { validateStationInfo(stationEmptyInfo) @@ -83,7 +83,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when chargingStationId is undefined', () => { - const stationMissingId = createChargingStation({ + const { station: stationMissingId } = createMockChargingStation({ baseName, stationInfo: { baseName, chargingStationId: undefined }, }) @@ -93,7 +93,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when chargingStationId is empty string', () => { - const stationEmptyId = createChargingStation({ + const { station: stationEmptyId } = createMockChargingStation({ baseName, stationInfo: { baseName, chargingStationId: '' }, }) @@ -103,7 +103,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when hashId is undefined', () => { - const stationMissingHash = createChargingStation({ + const { station: stationMissingHash } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -117,7 +117,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when hashId is empty string', () => { - const stationEmptyHash = createChargingStation({ + const { station: stationEmptyHash } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -131,7 +131,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when templateIndex is undefined', () => { - const stationMissingTemplate = createChargingStation({ + const { station: stationMissingTemplate } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -146,7 +146,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when templateIndex is zero', () => { - const stationInvalidTemplate = createChargingStation({ + const { station: stationInvalidTemplate } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -163,7 +163,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when templateName is undefined', () => { - const stationMissingName = createChargingStation({ + const { station: stationMissingName } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -179,7 +179,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when templateName is empty string', () => { - const stationEmptyName = createChargingStation({ + const { station: stationEmptyName } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -195,7 +195,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when maximumPower is undefined', () => { - const stationMissingPower = createChargingStation({ + const { station: stationMissingPower } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -212,7 +212,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when maximumPower is zero', () => { - const stationInvalidPower = createChargingStation({ + const { station: stationInvalidPower } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -231,7 +231,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when maximumAmperage is undefined', () => { - const stationMissingAmperage = createChargingStation({ + const { station: stationMissingAmperage } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -251,7 +251,7 @@ await describe('Helpers test suite', async () => { }) await it('should throw when maximumAmperage is zero', () => { - const stationInvalidAmperage = createChargingStation({ + const { station: stationInvalidAmperage } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -271,7 +271,7 @@ await describe('Helpers test suite', async () => { }) await it('should pass validation with complete valid configuration', () => { - const validStation = createChargingStation({ + const { station: validStation } = createMockChargingStation({ baseName, stationInfo: { baseName, @@ -289,9 +289,10 @@ await describe('Helpers test suite', async () => { }) await it('should throw for OCPP 2.0 without EVSE configuration', () => { - const stationOcpp20 = createChargingStation({ + const { station: stationOcpp20 } = createMockChargingStation({ baseName, connectorsCount: 0, // Ensure no EVSEs are created + evseConfiguration: { evsesCount: 0 }, stationInfo: { baseName, chargingStationId: getChargingStationId(1, chargingStationTemplate), @@ -313,9 +314,10 @@ await describe('Helpers test suite', async () => { }) await it('should throw for OCPP 2.0.1 without EVSE configuration', () => { - const stationOcpp201 = createChargingStation({ + const { station: stationOcpp201 } = createMockChargingStation({ baseName, connectorsCount: 0, // Ensure no EVSEs are created + evseConfiguration: { evsesCount: 0 }, stationInfo: { baseName, chargingStationId: getChargingStationId(1, chargingStationTemplate), @@ -338,21 +340,21 @@ await describe('Helpers test suite', async () => { await it('should return false and warn when station is not started or starting', t => { const warnMock = t.mock.method(logger, 'warn') - const stationNotStarted = createChargingStation({ baseName, started: false, starting: false }) + const { station: stationNotStarted } = createMockChargingStation({ baseName, started: false, starting: false }) expect(checkChargingStationState(stationNotStarted, 'log prefix |')).toBe(false) expect(warnMock.mock.calls.length).toBe(1) }) await it('should return true when station is starting', t => { const warnMock = t.mock.method(logger, 'warn') - const stationStarting = createChargingStation({ baseName, started: false, starting: true }) + const { station: stationStarting } = createMockChargingStation({ baseName, started: false, starting: true }) expect(checkChargingStationState(stationStarting, 'log prefix |')).toBe(true) expect(warnMock.mock.calls.length).toBe(0) }) await it('should return true when station is started', t => { const warnMock = t.mock.method(logger, 'warn') - const stationStarted = createChargingStation({ baseName, started: true, starting: false }) + const { station: stationStarted } = createMockChargingStation({ baseName, started: true, starting: false }) expect(checkChargingStationState(stationStarted, 'log prefix |')).toBe(true) expect(warnMock.mock.calls.length).toBe(0) }) @@ -421,7 +423,7 @@ await describe('Helpers test suite', async () => { }) await it('should return Available when no bootStatus is defined', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) const connectorStatus = {} as ConnectorStatus expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( ConnectorStatusEnum.Available @@ -429,7 +431,7 @@ await describe('Helpers test suite', async () => { }) await it('should return bootStatus from template when defined', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) const connectorStatus = { bootStatus: ConnectorStatusEnum.Unavailable, } as ConnectorStatus @@ -439,7 +441,7 @@ await describe('Helpers test suite', async () => { }) await it('should return Unavailable when charging station is inoperative', () => { - const chargingStation = createChargingStation({ + const { station: chargingStation } = createMockChargingStation({ baseName, connectorDefaults: { availability: AvailabilityType.Inoperative }, connectorsCount: 2, @@ -453,7 +455,7 @@ await describe('Helpers test suite', async () => { }) await it('should return Unavailable when connector is inoperative', () => { - const chargingStation = createChargingStation({ + const { station: chargingStation } = createMockChargingStation({ baseName, connectorDefaults: { availability: AvailabilityType.Inoperative }, connectorsCount: 2, @@ -468,7 +470,7 @@ await describe('Helpers test suite', async () => { }) await it('should restore previous status when transaction is in progress', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) const connectorStatus = { bootStatus: ConnectorStatusEnum.Available, status: ConnectorStatusEnum.Charging, @@ -480,7 +482,7 @@ await describe('Helpers test suite', async () => { }) await it('should use bootStatus over previous status when no transaction', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) const connectorStatus = { bootStatus: ConnectorStatusEnum.Available, status: ConnectorStatusEnum.Charging, @@ -516,12 +518,12 @@ await describe('Helpers test suite', async () => { }) await it('should return false when no reservations exist (connector mode)', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) expect(hasPendingReservations(chargingStation)).toBe(false) }) await it('should return true when pending reservation exists (connector mode)', () => { - const chargingStation = createChargingStation({ baseName, connectorsCount: 2 }) + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2 }) const connectorStatus = chargingStation.connectors.get(1) if (connectorStatus != null) { connectorStatus.reservation = createTestReservation(false) @@ -530,7 +532,7 @@ await describe('Helpers test suite', async () => { }) await it('should return false when no reservations exist (EVSE mode)', () => { - const chargingStation = createChargingStation({ + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2, stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, @@ -539,7 +541,7 @@ await describe('Helpers test suite', async () => { }) await it('should return true when pending reservation exists (EVSE mode)', () => { - const chargingStation = createChargingStation({ + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2, stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, @@ -553,7 +555,7 @@ await describe('Helpers test suite', async () => { }) await it('should return false when only expired reservations exist (EVSE mode)', () => { - const chargingStation = createChargingStation({ + const { station: chargingStation } = createMockChargingStation({ baseName, connectorsCount: 2, stationInfo: { ocppVersion: OCPPVersion.VERSION_201 }, diff --git a/tests/charging-station/helpers/StationHelpers.ts b/tests/charging-station/helpers/StationHelpers.ts index 7c6c394f..070a8c15 100644 --- a/tests/charging-station/helpers/StationHelpers.ts +++ b/tests/charging-station/helpers/StationHelpers.ts @@ -8,6 +8,7 @@ import type { ChargingStation } from '../../../src/charging-station/ChargingStat import type { ChargingStationInfo, ChargingStationOcppConfiguration, + ChargingStationTemplate, ConnectorStatus, EvseStatus, StopTransactionReason, @@ -223,7 +224,7 @@ export function cleanupChargingStation (station: ChargingStation): void { * Create a connector status object with default values * * This is the canonical factory for creating ConnectorStatus objects in tests. - * Both ChargingStationFactory and StationHelpers use this function. + * This is the canonical factory for creating ConnectorStatus objects in tests. * @param _connectorId - Connector ID (unused, kept for API consistency) * @param options - Optional overrides for default values * @returns ConnectorStatus with default or customized values @@ -936,6 +937,10 @@ function determineEvseUsage ( options: MockChargingStationOptions, legacyEvsesCount: number ): boolean { + // If explicitly set to 0, don't use EVSEs + if (options.evseConfiguration?.evsesCount === 0) { + return false + } // Get the ocppVersion from stationInfo overrides or options const effectiveOcppVersion = options.stationInfo?.ocppVersion ?? options.ocppVersion return ( @@ -970,3 +975,16 @@ function resetConnectorStatus (status: ConnectorStatus, isConnectorZero: boolean status.transactionSetInterval = undefined } } + +/** + * Create a mock charging station template for testing + * @param baseName - Base name for the template + * @returns ChargingStationTemplate with minimal required properties for testing + */ +export function createMockChargingStationTemplate ( + baseName: string = TEST_CHARGING_STATION_BASE_NAME +): ChargingStationTemplate { + return { + baseName, + } as ChargingStationTemplate +} diff --git a/tests/utils/ErrorUtils.test.ts b/tests/utils/ErrorUtils.test.ts index b8ba1420..cb4d6e62 100644 --- a/tests/utils/ErrorUtils.test.ts +++ b/tests/utils/ErrorUtils.test.ts @@ -18,11 +18,11 @@ import { handleSendMessageError, } from '../../src/utils/ErrorUtils.js' import { logger } from '../../src/utils/Logger.js' -import { createChargingStation } from '../charging-station/ChargingStationTestUtils.js' +import { createMockChargingStation } from '../charging-station/ChargingStationTestUtils.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' await describe('ErrorUtils test suite', async () => { - const chargingStation = createChargingStation({ baseName: 'CS-TEST' }) + const { station: chargingStation } = createMockChargingStation({ baseName: 'CS-TEST' }) afterEach(() => { standardCleanup()