+++ /dev/null
-/**
- * @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
- })
- })
- })
-})
+++ /dev/null
-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<MockOCPPIncomingRequestService>
- /** Custom OCPP request service for test mocking */
- ocppRequestService?: Partial<MockOCPPRequestService>
- started?: boolean
- starting?: boolean
- stationInfo?: Partial<ChargingStationInfo>
- 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<unknown>
- stop: () => void
-}
-
-/**
- * Mock OCPP request service interface for testing
- * Provides typed access to mock handlers without eslint-disable comments
- */
-export interface MockOCPPRequestService {
- requestHandler: () => Promise<unknown>
- sendError: () => Promise<unknown>
- sendResponse: () => Promise<unknown>
-}
-
-/**
- * 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
- )
-}
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
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'
})
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<ChargingStationOcppConfiguration>
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)
})
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)
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<ChargingStationOcppConfiguration>
addConfigurationKey(cs, TEST_KEY_1, VALUE_A)
})
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()
})
await it('should add new key with custom options', () => {
- const cs = createChargingStation()
+ const { station: cs } = createMockChargingStation()
addConfigurationKey(
cs,
TEST_KEY_1,
})
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
})
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,
})
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,
})
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,
})
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,
})
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(
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()
})
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<ChargingStationOcppConfiguration>
const errorMock = t.mock.method(logger, 'error')
})
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)
})
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)
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<ChargingStationOcppConfiguration>
const res = deleteConfigurationKey(cs, TEST_KEY_1)
})
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)
})
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 })
})
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,
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)
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()
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)
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)
})
await it('should throw when chargingStationId is undefined', () => {
- const stationMissingId = createChargingStation({
+ const { station: stationMissingId } = createMockChargingStation({
baseName,
stationInfo: { baseName, chargingStationId: undefined },
})
})
await it('should throw when chargingStationId is empty string', () => {
- const stationEmptyId = createChargingStation({
+ const { station: stationEmptyId } = createMockChargingStation({
baseName,
stationInfo: { baseName, chargingStationId: '' },
})
})
await it('should throw when hashId is undefined', () => {
- const stationMissingHash = createChargingStation({
+ const { station: stationMissingHash } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when hashId is empty string', () => {
- const stationEmptyHash = createChargingStation({
+ const { station: stationEmptyHash } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when templateIndex is undefined', () => {
- const stationMissingTemplate = createChargingStation({
+ const { station: stationMissingTemplate } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when templateIndex is zero', () => {
- const stationInvalidTemplate = createChargingStation({
+ const { station: stationInvalidTemplate } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when templateName is undefined', () => {
- const stationMissingName = createChargingStation({
+ const { station: stationMissingName } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when templateName is empty string', () => {
- const stationEmptyName = createChargingStation({
+ const { station: stationEmptyName } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when maximumPower is undefined', () => {
- const stationMissingPower = createChargingStation({
+ const { station: stationMissingPower } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when maximumPower is zero', () => {
- const stationInvalidPower = createChargingStation({
+ const { station: stationInvalidPower } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when maximumAmperage is undefined', () => {
- const stationMissingAmperage = createChargingStation({
+ const { station: stationMissingAmperage } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should throw when maximumAmperage is zero', () => {
- const stationInvalidAmperage = createChargingStation({
+ const { station: stationInvalidAmperage } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
await it('should pass validation with complete valid configuration', () => {
- const validStation = createChargingStation({
+ const { station: validStation } = createMockChargingStation({
baseName,
stationInfo: {
baseName,
})
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),
})
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),
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)
})
})
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
})
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
})
await it('should return Unavailable when charging station is inoperative', () => {
- const chargingStation = createChargingStation({
+ const { station: chargingStation } = createMockChargingStation({
baseName,
connectorDefaults: { availability: AvailabilityType.Inoperative },
connectorsCount: 2,
})
await it('should return Unavailable when connector is inoperative', () => {
- const chargingStation = createChargingStation({
+ const { station: chargingStation } = createMockChargingStation({
baseName,
connectorDefaults: { availability: AvailabilityType.Inoperative },
connectorsCount: 2,
})
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,
})
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,
})
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)
})
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 },
})
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 },
})
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 },
import type {
ChargingStationInfo,
ChargingStationOcppConfiguration,
+ ChargingStationTemplate,
ConnectorStatus,
EvseStatus,
StopTransactionReason,
* 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
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 (
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
+}
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()