await describe('ChargingStationFactory', async () => {
await describe('OCPP Service Mocking', async () => {
- await it('Should throw error when OCPPRequestService.requestHandler is not mocked', 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(
)
})
- await it('Should throw error when OCPPIncomingRequestService.stop is not mocked', () => {
+ await it('should throw error when OCPPIncomingRequestService.stop is not mocked', () => {
const station = createChargingStation({ connectorsCount: 1 })
expect(() => {
)
})
- await it('Should allow custom OCPPRequestService.requestHandler mock', async () => {
+ await it('should allow custom OCPPRequestService.requestHandler mock', async () => {
const mockRequestHandler = async () => {
return Promise.resolve({ success: true })
}
expect(result.success).toBe(true)
})
- await it('Should allow custom OCPPIncomingRequestService.stop mock', () => {
+ await it('should allow custom OCPPIncomingRequestService.stop mock', () => {
let stopCalled = false
const station = createChargingStation({
connectorsCount: 1,
expect(stopCalled).toBe(true)
})
- await it('Should throw error when OCPPRequestService.sendError is not mocked', async () => {
+ await it('should throw error when OCPPRequestService.sendError is not mocked', async () => {
const station = createChargingStation({ connectorsCount: 1 })
await expect(station.ocppRequestService.sendError()).rejects.toThrow(
)
})
- await it('Should throw error when OCPPRequestService.sendResponse is not mocked', async () => {
+ await it('should throw error when OCPPRequestService.sendResponse is not mocked', async () => {
const station = createChargingStation({ connectorsCount: 1 })
await expect(station.ocppRequestService.sendResponse()).rejects.toThrow(
)
})
- await it('Should allow custom OCPPRequestService.sendError mock', async () => {
+ await it('should allow custom OCPPRequestService.sendError mock', async () => {
const mockSendError = async () => {
return Promise.resolve({ error: 'test-error' })
}
expect(result.error).toBe('test-error')
})
- await it('Should allow custom OCPPRequestService.sendResponse mock', async () => {
+ await it('should allow custom OCPPRequestService.sendResponse mock', async () => {
const mockSendResponse = async () => {
return Promise.resolve({ response: 'test-response' })
}
expect(result.response).toBe('test-response')
})
- await it('Should throw error when OCPPIncomingRequestService.incomingRequestHandler is not mocked', async () => {
+ await it('should throw error when OCPPIncomingRequestService.incomingRequestHandler is not mocked', async () => {
const station = createChargingStation({ connectorsCount: 1 })
await expect(station.ocppIncomingRequestService.incomingRequestHandler()).rejects.toThrow(
)
})
- await it('Should allow custom OCPPIncomingRequestService.incomingRequestHandler mock', async () => {
+ await it('should allow custom OCPPIncomingRequestService.incomingRequestHandler mock', async () => {
const mockIncomingRequestHandler = async () => {
return Promise.resolve({ handled: true })
}
await describe('Configuration Validation', async () => {
await describe('StationInfo Properties', async () => {
- await it('Should create station with valid stationInfo', () => {
+ await it('should create station with valid stationInfo', () => {
const station = createChargingStation({
connectorsCount: 1,
stationInfo: {
})
await describe('Connector Configuration', async () => {
- await it('Should create station with no connectors when connectorsCount is 0', () => {
+ await it('should create station with no connectors when connectorsCount is 0', () => {
const station = createChargingStation({
connectorsCount: 0,
})
expect(station.connectors.size).toBe(0)
})
- await it('Should create station with specified number of connectors', () => {
+ await it('should create station with specified number of connectors', () => {
const station = createChargingStation({
connectorsCount: 3,
})
expect(station.connectors.size).toBe(4)
})
- await it('Should handle connector status properly', () => {
+ await it('should handle connector status properly', () => {
const station = createChargingStation({
connectorsCount: 2,
})
expect(station.getConnectorStatus(2)).toBeDefined()
})
- await it('Should create station with custom connector defaults', () => {
+ await it('should create station with custom connector defaults', () => {
const station = createChargingStation({
connectorDefaults: {
availability: AvailabilityType.Inoperative,
})
await describe('OCPP Version-Specific Configuration', async () => {
- await it('Should configure OCPP 1.6 station correctly', () => {
+ await it('should configure OCPP 1.6 station correctly', () => {
const station = createChargingStation({
connectorsCount: 2,
stationInfo: {
expect(station.hasEvses).toBe(false)
})
- await it('Should configure OCPP 2.0 station with EVSEs', () => {
+ await it('should configure OCPP 2.0 station with EVSEs', () => {
const station = createChargingStation({
connectorsCount: 0, // OCPP 2.0 uses EVSEs instead of connectors
stationInfo: {
expect(station.hasEvses).toBe(true)
})
- await it('Should configure OCPP 2.0.1 station with EVSEs', () => {
+ 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: {
})
await describe('EVSE Configuration', async () => {
- await it('Should create station with EVSEs when configuration is provided', () => {
+ await it('should create station with EVSEs when configuration is provided', () => {
const station = createChargingStation({
connectorsCount: 6,
evseConfiguration: {
expect(station.connectors.size).toBe(7) // 0 + 6 connectors
})
- await it('Should automatically enable EVSEs for OCPP 2.0+ versions', () => {
+ await it('should automatically enable EVSEs for OCPP 2.0+ versions', () => {
const station = createChargingStation({
connectorsCount: 3,
stationInfo: {
})
await describe('Factory Default Values', async () => {
- await it('Should provide sensible defaults for all required properties', () => {
+ await it('should provide sensible defaults for all required properties', () => {
const station = createChargingStation({
connectorsCount: 1,
})
expect(station.stationInfo?.templateHash).toBeUndefined() // Factory doesn't set templateHash by default
})
- await it('Should allow overriding factory defaults', () => {
+ await it('should allow overriding factory defaults', () => {
const customStationId = 'custom-station-123'
const customHashId = 'custom-hash-456'
expect(station.stationInfo?.ocppVersion).toBeDefined()
})
- await it('Should use default base name when not provided', () => {
+ await it('should use default base name when not provided', () => {
const station = createChargingStation({
connectorsCount: 1,
})
expect(station.stationInfo?.chargingStationId).toBe('CS-TEST-00001')
})
- await it('Should use custom base name when provided', () => {
+ await it('should use custom base name when provided', () => {
const customBaseName = 'CUSTOM-STATION'
const station = createChargingStation({
baseName: customBaseName,
})
await describe('Configuration Options', async () => {
- await it('Should respect connection timeout setting', () => {
+ await it('should respect connection timeout setting', () => {
const customTimeout = 45000
const station = createChargingStation({
connectionTimeout: customTimeout,
expect(station.getConnectionTimeout()).toBe(customTimeout)
})
- await it('Should respect heartbeat interval setting', () => {
+ await it('should respect heartbeat interval setting', () => {
const customInterval = 120000
const station = createChargingStation({
connectorsCount: 1,
expect(station.getHeartbeatInterval()).toBe(customInterval)
})
- await it('Should respect websocket ping interval setting', () => {
+ await it('should respect websocket ping interval setting', () => {
const customPingInterval = 90000
const station = createChargingStation({
connectorsCount: 1,
expect(station.getWebSocketPingInterval()).toBe(customPingInterval)
})
- await it('Should respect started and starting flags', () => {
+ await it('should respect started and starting flags', () => {
const station = createChargingStation({
connectorsCount: 1,
started: true,
})
await describe('Integration with Helpers', async () => {
- await it('Should properly integrate with helper functions', () => {
+ await it('should properly integrate with helper functions', () => {
const station = createChargingStation({
connectorsCount: 1,
stationInfo: {
await describe('Mock Behavioral Parity', async () => {
await describe('getConnectorIdByTransactionId', async () => {
- await it('Should return undefined for null transaction ID', () => {
+ 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', () => {
+ 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)', () => {
+ 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
expect(station.getConnectorIdByTransactionId('test-transaction-123')).toBe(1)
})
- await it('Should return connector ID when transaction ID matches (EVSE mode)', () => {
+ 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
expect(station.getConnectorIdByTransactionId('test-evse-transaction-456')).toBe(1)
})
- await it('Should return undefined when transaction ID does not match any connector', () => {
+ 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', () => {
+ await it('should handle numeric transaction IDs', () => {
const station = createChargingStation({ connectorsCount: 2 })
// Set up a transaction with numeric ID on connector 2
})
await describe('getEvseIdByConnectorId', async () => {
- await it('Should return undefined for stations without EVSEs', () => {
+ 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(2)).toBeUndefined()
})
- await it('Should return correct EVSE ID for connectors in EVSE mode', () => {
+ 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
expect(station.getEvseIdByConnectorId(6)).toBe(2)
})
- await it('Should return undefined for non-existent connector IDs', () => {
+ await it('should return undefined for non-existent connector IDs', () => {
const station = createChargingStation({
connectorsCount: 4,
evseConfiguration: { evsesCount: 2 },
expect(station.getEvseIdByConnectorId(-1)).toBeUndefined() // Invalid connector ID
})
- await it('Should handle single EVSE with multiple connectors', () => {
+ await it('should handle single EVSE with multiple connectors', () => {
const station = createChargingStation({
connectorsCount: 3,
evseConfiguration: { evsesCount: 1 }, // Single EVSE with all connectors
})
await describe('getEvseIdByTransactionId', async () => {
- await it('Should return undefined for null transaction ID', () => {
+ await it('should return undefined for null transaction ID', () => {
const station = createChargingStation({
connectorsCount: 2,
stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
expect(station.getEvseIdByTransactionId(null)).toBeUndefined()
})
- await it('Should return undefined for undefined transaction ID', () => {
+ await it('should return undefined for undefined transaction ID', () => {
const station = createChargingStation({
connectorsCount: 2,
stationInfo: { ocppVersion: OCPPVersion.VERSION_201 },
expect(station.getEvseIdByTransactionId(undefined)).toBeUndefined()
})
- await it('Should return undefined for stations without EVSEs', () => {
+ 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.getEvseIdByTransactionId('test-transaction-123')).toBeUndefined()
})
- await it('Should return correct EVSE ID when transaction ID matches (single EVSE)', () => {
+ 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
expect(station.getEvseIdByTransactionId(456)).toBe(1)
})
- await it('Should return correct EVSE ID when transaction ID matches (multiple EVSEs)', () => {
+ 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
expect(station.getEvseIdByTransactionId(999)).toBe(2)
})
- await it('Should return undefined when transaction ID does not match any connector', () => {
+ await it('should return undefined when transaction ID does not match any connector', () => {
const station = createChargingStation({
connectorsCount: 4,
evseConfiguration: { evsesCount: 2 },
expect(station.getEvseIdByTransactionId(12345)).toBeUndefined()
})
- await it('Should handle numeric transaction IDs', () => {
+ await it('should handle numeric transaction IDs', () => {
const station = createChargingStation({
connectorsCount: 4,
evseConfiguration: { evsesCount: 2 },
expect(station.getEvseIdByTransactionId(789)).toBe(2)
})
- await it('Should handle string transaction IDs', () => {
+ await it('should handle string transaction IDs', () => {
const station = createChargingStation({
connectorsCount: 4,
evseConfiguration: { evsesCount: 2 },
expect(station.getEvseIdByTransactionId('string-transaction-id-abc123')).toBe(1)
})
- await it('Should maintain consistency with getConnectorIdByTransactionId', () => {
+ await it('should maintain consistency with getConnectorIdByTransactionId', () => {
const station = createChargingStation({
connectorsCount: 6,
evseConfiguration: { evsesCount: 3 },
}
})
- await it('Should handle mixed transaction ID types correctly', () => {
+ await it('should handle mixed transaction ID types correctly', () => {
const station = createChargingStation({
connectorsCount: 4,
evseConfiguration: { evsesCount: 2 },
})
await describe('isConnectorAvailable', async () => {
- await it('Should return false for connector ID 0', () => {
+ 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', () => {
+ 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', () => {
+ await it('should return true for available operative connector', () => {
const station = createChargingStation({
connectorDefaults: {
availability: AvailabilityType.Operative,
expect(station.isConnectorAvailable(2)).toBe(true)
})
- await it('Should return false for inoperative connector', () => {
+ await it('should return false for inoperative connector', () => {
const station = createChargingStation({
connectorDefaults: {
availability: AvailabilityType.Inoperative,
expect(station.isConnectorAvailable(2)).toBe(false)
})
- await it('Should check availability regardless of status (matches real class)', () => {
+ await it('should check availability regardless of status (matches real class)', () => {
const station = createChargingStation({
connectorDefaults: {
availability: AvailabilityType.Operative,
expect(station.isConnectorAvailable(2)).toBe(true)
})
- await it('Should return false for non-existent connector', () => {
+ 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', () => {
+ await it('should work correctly in EVSE mode', () => {
const station = createChargingStation({
connectorDefaults: {
availability: AvailabilityType.Operative,
})
await describe('getConnectorStatus behavioral parity', async () => {
- await it('Should return undefined for non-existent connector in standard mode', () => {
+ 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', () => {
+ 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', () => {
+ await it('should return connector status for valid connector in both modes', () => {
const stationStandard = createChargingStation({
connectorsCount: 2,
stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
})
await describe('Method interaction behavioral parity', async () => {
- await it('Should maintain consistency between getConnectorStatus and isConnectorAvailable', () => {
+ 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
expect(station.isConnectorAvailable(1)).toBe(false)
})
- await it('Should maintain consistency between getConnectorIdByTransactionId and getConnectorStatus', () => {
+ await it('should maintain consistency between getConnectorIdByTransactionId and getConnectorStatus', () => {
const station = createChargingStation({ connectorsCount: 2 })
// Set up transaction
})
await describe('Edge Cases and Error Handling', async () => {
- await it('Should handle empty station (no connectors)', () => {
+ await it('should handle empty station (no connectors)', () => {
const station = createChargingStation({ connectorsCount: 0 })
expect(station.getConnectorIdByTransactionId('any-transaction')).toBeUndefined()
expect(station.getConnectorStatus(1)).toBeUndefined()
})
- await it('Should handle mixed transaction ID types in search', () => {
+ await it('should handle mixed transaction ID types in search', () => {
const station = createChargingStation({ connectorsCount: 3 })
// Set up mixed transaction types
expect(station.getConnectorIdByTransactionId('999')).toBeUndefined() // String vs number
})
- await it('Should handle partially configured connectors', () => {
+ await it('should handle partially configured connectors', () => {
const station = createChargingStation({ connectorsCount: 2 })
// Manually modify one connector to test resilience
import {
AvailabilityType,
type BootNotificationResponse,
- type ChargingProfile,
type ChargingStationConfiguration,
type ChargingStationInfo,
type ChargingStationTemplate,
OCPP20OptionalVariableName,
OCPPVersion,
RegistrationStatusEnumType,
- type SampledValueTemplate,
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
return { connectors, evses }
}
- const createConnectorStatus = (connectorId: number) => {
- const baseStatus = {
- availability: options.connectorDefaults?.availability ?? AvailabilityType.Operative,
- chargingProfiles: [] as ChargingProfile[],
- energyActiveImportRegisterValue: 0,
- idTagAuthorized: false,
- idTagLocalAuthorized: false,
- MeterValues: [] as SampledValueTemplate[],
- status: options.connectorDefaults?.status ?? ConnectorStatusEnum.Available,
- transactionEnergyActiveImportRegisterValue: 0,
- transactionId: undefined,
- transactionIdTag: undefined,
- transactionRemoteStarted: false,
- transactionStart: undefined,
- transactionStarted: false,
- }
-
- return clone(baseStatus)
+ // 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)
+ const connector0 = createConnectorStatus(0, connectorStatusOptions)
connectors.set(0, connector0)
for (let evseId = 1; evseId <= evsesCount; evseId++) {
)
for (let connectorId = startConnectorId; connectorId <= endConnectorId; connectorId++) {
- const connectorStatus = createConnectorStatus(connectorId)
+ const connectorStatus = createConnectorStatus(connectorId, connectorStatusOptions)
connectors.set(connectorId, connectorStatus)
evseConnectors.set(connectorId, clone(connectorStatus))
}
}
} else {
for (let connectorId = 0; connectorId <= connectorsCount; connectorId++) {
- connectors.set(connectorId, createConnectorStatus(connectorId))
+ connectors.set(connectorId, createConnectorStatus(connectorId, connectorStatusOptions))
}
}
## Naming Conventions
+### Test Case Naming (MANDATORY)
+
+Use consistent `should [verb]` pattern in **lowercase**:
+
+✅ **Good:**
+
+```typescript
+it('should start successfully with valid configuration', async () => {})
+it('should reject invalid identifier', () => {})
+it('should handle Reset request with Immediate type', async () => {})
+```
+
+❌ **Bad:**
+
+```typescript
+// Inconsistent capitalization
+it('Should start successfully', () => {}) // Capital 'S'
+
+// Imperative style
+it('Verify generateUUID()', () => {}) // Not declarative
+
+// Missing 'should'
+it('starts successfully', () => {}) // No 'should'
+```
+
+### Files & Suites
+
- **Files**: Use descriptive names matching the module under test: `ModuleName.test.ts`
- **Test suites**: Use `describe()` with clear, specific descriptions
-- **Test cases**: Use `it()` or `test()` with descriptive names starting with action verbs
+- **OCPP tests**: Use requirement codes: `describe('B11 & B12 - Reset', () => {})`
+- **Auth tests**: Reference spec sections: `describe('G03.FR.01 - AuthCache Conformance', () => {})`
- **Variables**: Use camelCase for variables, functions, and test helpers
- **Constants**: Use SCREAMING_SNAKE_CASE for test constants
})
```
+### Shared Test Utilities
+
+The following utilities are available for reuse across test files:
+
+| Utility | Location | Purpose |
+| ----------------------------- | ------------------------------------ | ----------------------------------------- |
+| `createMockChargingStation()` | `ChargingStationTestUtils.ts` | Lightweight mock station stub |
+| `createChargingStation()` | `ChargingStationFactory.ts` | Full test station with OCPP services |
+| `createConnectorStatus()` | `helpers/StationHelpers.ts` | ConnectorStatus factory with defaults |
+| `MockWebSocket` | `mocks/MockWebSocket.ts` | WebSocket simulation with message capture |
+| `MockIdTagsCache` | `mocks/MockCaches.ts` | In-memory IdTags cache mock |
+| `MockSharedLRUCache` | `mocks/MockCaches.ts` | In-memory LRU cache mock |
+| `waitForCondition()` | `helpers/StationHelpers.ts` | Async condition waiting with timeout |
+| `cleanupChargingStation()` | `helpers/StationHelpers.ts` | Proper station cleanup for afterEach |
+| Auth factories | `ocpp/auth/helpers/MockFactories.ts` | Auth-specific mock creation |
+
+**DO NOT duplicate these utilities.** Import and reuse them.
+
+```typescript
+// Good: Import shared utilities
+import { createMockChargingStation, cleanupChargingStation } from './ChargingStationTestUtils.js'
+import { waitForCondition } from './helpers/StationHelpers.js'
+```
+
### Mocking Best Practices
- Use `mock.method()` for function mocking (Node.js native)
// Verify first and last message are in correct positions
expect(station.messageQueue[0]).toContain('msg-0')
- expect(station.messageQueue[messageCount - 1]).toContain(
- `msg-${(messageCount - 1).toString()}`
- )
+ expect(station.messageQueue[messageCount - 1]).toContain(`msg-${(messageCount - 1).toString()}`)
})
// -------------------------------------------------------------------------
// Re-export all helper functions and types
export type {
ChargingStationMocks,
+ CreateConnectorStatusOptions,
MockChargingStationOptions,
MockChargingStationResult,
} from './helpers/StationHelpers.js'
export {
cleanupChargingStation,
+ createConnectorStatus,
createMockChargingStation,
createMockTemplate,
resetChargingStationState,
reservationId: 1,
}) as Reservation
- await it('Verify getChargingStationId()', () => {
+ await it('should verify getChargingStationId()', () => {
expect(getChargingStationId(1, chargingStationTemplate)).toBe(`${baseName}-00001`)
})
- await it('Verify getHashId()', () => {
+ await it('should verify getHashId()', () => {
expect(getHashId(1, chargingStationTemplate)).toBe(
'b4b1e8ec4fca79091d99ea9a7ea5901548010e6c0e98be9296f604b9d68734444dfdae73d7d406b6124b42815214d088'
)
})
- await it('Verify validateStationInfo() - Missing stationInfo', () => {
+ await it('should verify validateStationInfo() - Missing stationInfo', () => {
// For validation edge cases, we need to manually create invalid states
// since the factory is designed to create valid configurations
const stationNoInfo = createChargingStation({ baseName })
}).toThrow(new BaseError('Missing charging station information'))
})
- await it('Verify validateStationInfo() - Empty stationInfo', () => {
+ await it('should verify validateStationInfo() - Empty stationInfo', () => {
// For validation edge cases, manually create empty stationInfo
const stationEmptyInfo = createChargingStation({ baseName })
stationEmptyInfo.stationInfo = {} as ChargingStationInfo
}).toThrow(new BaseError('Missing charging station information'))
})
- await it('Verify validateStationInfo() - Missing chargingStationId', () => {
+ await it('should verify validateStationInfo() - Missing chargingStationId', () => {
const stationMissingId = createChargingStation({
baseName,
stationInfo: { baseName, chargingStationId: undefined },
}).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
})
- await it('Verify validateStationInfo() - Empty chargingStationId', () => {
+ await it('should verify validateStationInfo() - Empty chargingStationId', () => {
const stationEmptyId = createChargingStation({
baseName,
stationInfo: { baseName, chargingStationId: '' },
}).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
})
- await it('Verify validateStationInfo() - Missing hashId', () => {
+ await it('should verify validateStationInfo() - Missing hashId', () => {
const stationMissingHash = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing hashId in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Empty hashId', () => {
+ await it('should verify validateStationInfo() - Empty hashId', () => {
const stationEmptyHash = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing hashId in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Missing templateIndex', () => {
+ await it('should verify validateStationInfo() - Missing templateIndex', () => {
const stationMissingTemplate = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing templateIndex in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Invalid templateIndex (zero)', () => {
+ await it('should verify validateStationInfo() - Invalid templateIndex (zero)', () => {
const stationInvalidTemplate = createChargingStation({
baseName,
stationInfo: {
)
})
- await it('Verify validateStationInfo() - Missing templateName', () => {
+ await it('should verify validateStationInfo() - Missing templateName', () => {
const stationMissingName = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing templateName in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Empty templateName', () => {
+ await it('should verify validateStationInfo() - Empty templateName', () => {
const stationEmptyName = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing templateName in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Missing maximumPower', () => {
+ await it('should verify validateStationInfo() - Missing maximumPower', () => {
const stationMissingPower = createChargingStation({
baseName,
stationInfo: {
}).toThrow(new BaseError(`${baseName}-00001: Missing maximumPower in stationInfo properties`))
})
- await it('Verify validateStationInfo() - Invalid maximumPower (zero)', () => {
+ await it('should verify validateStationInfo() - Invalid maximumPower (zero)', () => {
const stationInvalidPower = createChargingStation({
baseName,
stationInfo: {
)
})
- await it('Verify validateStationInfo() - Missing maximumAmperage', () => {
+ await it('should verify validateStationInfo() - Missing maximumAmperage', () => {
const stationMissingAmperage = createChargingStation({
baseName,
stationInfo: {
)
})
- await it('Verify validateStationInfo() - Invalid maximumAmperage (zero)', () => {
+ await it('should verify validateStationInfo() - Invalid maximumAmperage (zero)', () => {
const stationInvalidAmperage = createChargingStation({
baseName,
stationInfo: {
)
})
- await it('Verify validateStationInfo() - Valid configuration passes', () => {
+ await it('should verify validateStationInfo() - Valid configuration passes', () => {
const validStation = createChargingStation({
baseName,
stationInfo: {
}).not.toThrow()
})
- await it('Verify validateStationInfo() - OCPP 2.0 requires EVSE', () => {
+ await it('should verify validateStationInfo() - OCPP 2.0 requires EVSE', () => {
const stationOcpp20 = createChargingStation({
baseName,
connectorsCount: 0, // Ensure no EVSEs are created
)
})
- await it('Verify validateStationInfo() - OCPP 2.0.1 requires EVSE', () => {
+ await it('should verify validateStationInfo() - OCPP 2.0.1 requires EVSE', () => {
const stationOcpp201 = createChargingStation({
baseName,
connectorsCount: 0, // Ensure no EVSEs are created
)
})
- await it('Verify checkChargingStationState() - Not started or starting', t => {
+ await it('should verify checkChargingStationState() - Not started or starting', t => {
const warnMock = t.mock.method(logger, 'warn')
const stationNotStarted = createChargingStation({ baseName, started: false, starting: false })
expect(checkChargingStationState(stationNotStarted, 'log prefix |')).toBe(false)
expect(warnMock.mock.calls.length).toBe(1)
})
- await it('Verify checkChargingStationState() - Starting returns true', t => {
+ await it('should verify checkChargingStationState() - Starting returns true', t => {
const warnMock = t.mock.method(logger, 'warn')
const stationStarting = createChargingStation({ baseName, started: false, starting: true })
expect(checkChargingStationState(stationStarting, 'log prefix |')).toBe(true)
expect(warnMock.mock.calls.length).toBe(0)
})
- await it('Verify checkChargingStationState() - Started returns true', t => {
+ await it('should verify checkChargingStationState() - Started returns true', t => {
const warnMock = t.mock.method(logger, 'warn')
const stationStarted = createChargingStation({ baseName, started: true, starting: false })
expect(checkChargingStationState(stationStarted, 'log prefix |')).toBe(true)
expect(warnMock.mock.calls.length).toBe(0)
})
- await it('Verify getPhaseRotationValue()', () => {
+ await it('should verify getPhaseRotationValue()', () => {
expect(getPhaseRotationValue(0, 0)).toBe('0.RST')
expect(getPhaseRotationValue(1, 0)).toBe('1.NotApplicable')
expect(getPhaseRotationValue(2, 0)).toBe('2.NotApplicable')
expect(getPhaseRotationValue(2, 3)).toBe('2.RST')
})
- await it('Verify getMaxNumberOfEvses()', () => {
+ await it('should verify getMaxNumberOfEvses()', () => {
expect(getMaxNumberOfEvses(undefined)).toBe(-1)
expect(getMaxNumberOfEvses({})).toBe(0)
})
- await it('Verify checkTemplate()', t => {
+ await it('should verify checkTemplate()', t => {
const warnMock = t.mock.method(logger, 'warn')
const errorMock = t.mock.method(logger, 'error')
expect(() => {
expect(warnMock.mock.calls.length).toBe(1)
})
- await it('Verify checkConfiguration()', t => {
+ await it('should verify checkConfiguration()', t => {
const errorMock = t.mock.method(logger, 'error')
expect(() => {
checkConfiguration(undefined, 'log prefix |', 'configuration.json')
expect(errorMock.mock.calls.length).toBe(2)
})
- await it('Verify checkStationInfoConnectorStatus()', t => {
+ await it('should verify checkStationInfoConnectorStatus()', t => {
const warnMock = t.mock.method(logger, 'warn')
checkStationInfoConnectorStatus(1, {} as ConnectorStatus, 'log prefix |', 'test-template.json')
expect(warnMock.mock.calls.length).toBe(0)
expect(connectorStatus.status).toBeUndefined()
})
- await it('Verify getBootConnectorStatus() - default to Available when no bootStatus', () => {
+ await it('should verify getBootConnectorStatus() - default to Available when no bootStatus', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
const connectorStatus = {} as ConnectorStatus
expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
)
})
- await it('Verify getBootConnectorStatus() - use bootStatus from template', () => {
+ await it('should verify getBootConnectorStatus() - use bootStatus from template', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
const connectorStatus = {
bootStatus: ConnectorStatusEnum.Unavailable,
)
})
- await it('Verify getBootConnectorStatus() - charging station unavailable overrides bootStatus', () => {
+ await it('should verify getBootConnectorStatus() - charging station unavailable overrides bootStatus', () => {
const chargingStation = createChargingStation({
baseName,
connectorDefaults: { availability: AvailabilityType.Inoperative },
)
})
- await it('Verify getBootConnectorStatus() - connector unavailable overrides bootStatus', () => {
+ await it('should verify getBootConnectorStatus() - connector unavailable overrides bootStatus', () => {
const chargingStation = createChargingStation({
baseName,
connectorDefaults: { availability: AvailabilityType.Inoperative },
)
})
- await it('Verify getBootConnectorStatus() - transaction in progress restores previous status', () => {
+ await it('should verify getBootConnectorStatus() - transaction in progress restores previous status', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
const connectorStatus = {
bootStatus: ConnectorStatusEnum.Available,
)
})
- await it('Verify getBootConnectorStatus() - no transaction uses bootStatus over previous status', () => {
+ await it('should verify getBootConnectorStatus() - no transaction uses bootStatus over previous status', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
const connectorStatus = {
bootStatus: ConnectorStatusEnum.Available,
})
// Tests for reservation helper functions
- await it('Verify hasReservationExpired() - expired reservation', () => {
+ await it('should verify hasReservationExpired() - expired reservation', () => {
expect(hasReservationExpired(createTestReservation(true))).toBe(true)
})
- await it('Verify hasReservationExpired() - valid reservation', () => {
+ await it('should verify hasReservationExpired() - valid reservation', () => {
expect(hasReservationExpired(createTestReservation(false))).toBe(false)
})
- await it('Verify hasPendingReservation() - no reservation', () => {
+ await it('should verify hasPendingReservation() - no reservation', () => {
const connectorStatus = {} as ConnectorStatus
expect(hasPendingReservation(connectorStatus)).toBe(false)
})
- await it('Verify hasPendingReservation() - with valid reservation', () => {
+ await it('should verify hasPendingReservation() - with valid reservation', () => {
const connectorStatus = { reservation: createTestReservation(false) } as ConnectorStatus
expect(hasPendingReservation(connectorStatus)).toBe(true)
})
- await it('Verify hasPendingReservation() - with expired reservation', () => {
+ await it('should verify hasPendingReservation() - with expired reservation', () => {
const connectorStatus = { reservation: createTestReservation(true) } as ConnectorStatus
expect(hasPendingReservation(connectorStatus)).toBe(false)
})
- await it('Verify hasPendingReservations() - no reservations (without EVSEs)', () => {
+ await it('should verify hasPendingReservations() - no reservations (without EVSEs)', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
expect(hasPendingReservations(chargingStation)).toBe(false)
})
- await it('Verify hasPendingReservations() - with pending reservation (without EVSEs)', () => {
+ await it('should verify hasPendingReservations() - with pending reservation (without EVSEs)', () => {
const chargingStation = createChargingStation({ baseName, connectorsCount: 2 })
const connectorStatus = chargingStation.connectors.get(1)
if (connectorStatus != null) {
expect(hasPendingReservations(chargingStation)).toBe(true)
})
- await it('Verify hasPendingReservations() - no reservations (with EVSEs)', () => {
+ await it('should verify hasPendingReservations() - no reservations (with EVSEs)', () => {
const chargingStation = createChargingStation({
baseName,
connectorsCount: 2,
expect(hasPendingReservations(chargingStation)).toBe(false)
})
- await it('Verify hasPendingReservations() - with pending reservation (with EVSEs)', () => {
+ await it('should verify hasPendingReservations() - with pending reservation (with EVSEs)', () => {
const chargingStation = createChargingStation({
baseName,
connectorsCount: 2,
expect(hasPendingReservations(chargingStation)).toBe(true)
})
- await it('Verify hasPendingReservations() - with expired reservation only (with EVSEs)', () => {
+ await it('should verify hasPendingReservations() - with expired reservation only (with EVSEs)', () => {
const chargingStation = createChargingStation({
baseName,
connectorsCount: 2,
webSocket: MockWebSocket
}
+/**
+ * Options for customizing connector status creation
+ */
+export interface CreateConnectorStatusOptions {
+ /** Override availability (default: AvailabilityType.Operative) */
+ availability?: AvailabilityType
+ /** Override status (default: ConnectorStatusEnum.Available) */
+ status?: ConnectorStatusEnum
+}
+
/**
* Options for creating a mock ChargingStation instance
*/
MockIdTagsCache.resetInstance()
}
+/**
+ * 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.
+ * @param _connectorId - Connector ID (unused, kept for API consistency)
+ * @param options - Optional overrides for default values
+ * @returns ConnectorStatus with default or customized values
+ * @example
+ * ```typescript
+ * // Default connector status
+ * const status = createConnectorStatus(1)
+ *
+ * // Customized connector status
+ * const status = createConnectorStatus(1, { availability: AvailabilityType.Inoperative })
+ * ```
+ */
+export function createConnectorStatus (
+ _connectorId: number,
+ options: CreateConnectorStatusOptions = {}
+): ConnectorStatus {
+ return {
+ availability: options.availability ?? AvailabilityType.Operative,
+ bootStatus: ConnectorStatusEnum.Available,
+ chargingProfiles: [],
+ energyActiveImportRegisterValue: 0,
+ idTagAuthorized: false,
+ idTagLocalAuthorized: false,
+ MeterValues: [],
+ status: options.status ?? ConnectorStatusEnum.Available,
+ transactionEnergyActiveImportRegisterValue: 0,
+ transactionId: undefined,
+ transactionIdTag: undefined,
+ transactionRemoteStarted: false,
+ transactionStart: undefined,
+ transactionStarted: false,
+ } as unknown as ConnectorStatus
+}
+
/**
* Creates a minimal mock ChargingStation-like object for testing
*
}
}
-/**
- * Create a connector status object with default values
- * @param connectorId - Connector ID
- * @returns Default connector status
- */
-function createConnectorStatus (connectorId: number): ConnectorStatus {
- return {
- availability: AvailabilityType.Operative,
- bootStatus: ConnectorStatusEnum.Available,
- chargingProfiles: [],
- energyActiveImportRegisterValue: 0,
- idTagAuthorized: false,
- idTagLocalAuthorized: false,
- MeterValues: [],
- status: ConnectorStatusEnum.Available,
- transactionEnergyActiveImportRegisterValue: 0,
- transactionId: undefined,
- transactionIdTag: undefined,
- transactionRemoteStarted: false,
- transactionStart: undefined,
- transactionStarted: false,
- } as unknown as ConnectorStatus
-}
-
/**
* Reset a single connector status to default values
* @param status - Connector status object to reset
this.emit('close', 1006, Buffer.from('Connection terminated'))
}
}
+
+/**
+ * Factory function to create a MockWebSocket configured for UI protocol
+ * @param protocol - UI protocol version (default: 'ui0.0.1')
+ * @returns MockWebSocket instance configured for UI testing
+ * @example
+ * ```typescript
+ * const uiWs = createUIProtocolMock()
+ * expect(uiWs.protocol).toBe('ui0.0.1')
+ * ```
+ */
+export function createUIProtocolMock (protocol = 'ui0.0.1'): MockWebSocket {
+ const ws = new MockWebSocket('ws://localhost:8080/ui')
+ ws.protocol = protocol
+ return ws
+}
})
await describe('storeCertificate', async () => {
- await it('Should store a valid PEM certificate to the correct path', async () => {
+ await it('should store a valid PEM certificate to the correct path', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.storeCertificate(
expect(result.filePath).toMatch(/\.pem$/)
})
- await it('Should reject invalid PEM certificate without BEGIN/END markers', async () => {
+ await it('should reject invalid PEM certificate without BEGIN/END markers', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.storeCertificate(
expect(result.error).toContain('Invalid PEM format')
})
- await it('Should reject empty certificate data', async () => {
+ await it('should reject empty certificate data', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.storeCertificate(
expect(result.error).toBeDefined()
})
- await it('Should create certificate directory structure if not exists', async () => {
+ await it('should create certificate directory structure if not exists', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.storeCertificate(
})
await describe('deleteCertificate', async () => {
- await it('Should delete certificate by hash data', async () => {
+ await it('should delete certificate by hash data', async () => {
const manager = new OCPP20CertificateManager()
const hashData: CertificateHashDataType = {
expect(['Accepted', 'NotFound', 'Failed']).toContain(result.status)
})
- await it('Should return NotFound for non-existent certificate', async () => {
+ await it('should return NotFound for non-existent certificate', async () => {
const manager = new OCPP20CertificateManager()
const hashData: CertificateHashDataType = {
expect(result.status).toBe('NotFound')
})
- await it('Should handle filesystem errors gracefully', async () => {
+ await it('should handle filesystem errors gracefully', async () => {
const manager = new OCPP20CertificateManager()
const hashData: CertificateHashDataType = {
})
await describe('getInstalledCertificates', async () => {
- await it('Should return list of installed certificates for station', async () => {
+ await it('should return list of installed certificates for station', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.getInstalledCertificates(TEST_STATION_HASH_ID)
expect(Array.isArray(result.certificateHashDataChain)).toBe(true)
})
- await it('Should filter certificates by type when filter provided', async () => {
+ await it('should filter certificates by type when filter provided', async () => {
const manager = new OCPP20CertificateManager()
const filterTypes = [InstallCertificateUseEnumType.CSMSRootCertificate]
expect(Array.isArray(result.certificateHashDataChain)).toBe(true)
})
- await it('Should return empty list when no certificates installed', async () => {
+ await it('should return empty list when no certificates installed', async () => {
const manager = new OCPP20CertificateManager()
const result = await manager.getInstalledCertificates('empty-station-hash-id')
expect(result.certificateHashDataChain).toHaveLength(0)
})
- await it('Should support multiple certificate type filters', async () => {
+ await it('should support multiple certificate type filters', async () => {
const manager = new OCPP20CertificateManager()
const filterTypes = [
})
await describe('computeCertificateHash', async () => {
- await it('Should compute hash data for valid PEM certificate', () => {
+ await it('should compute hash data for valid PEM certificate', () => {
const manager = new OCPP20CertificateManager()
const hashData = manager.computeCertificateHash(VALID_PEM_CERTIFICATE)
expect(typeof hashData.serialNumber).toBe('string')
})
- await it('Should return hex-encoded hash values', () => {
+ await it('should return hex-encoded hash values', () => {
const manager = new OCPP20CertificateManager()
const hashData = manager.computeCertificateHash(VALID_PEM_CERTIFICATE)
expect(hashData.issuerKeyHash).toMatch(hexPattern)
})
- await it('Should throw error for invalid PEM certificate', () => {
+ await it('should throw error for invalid PEM certificate', () => {
const manager = new OCPP20CertificateManager()
expect(() => {
}).toThrow()
})
- await it('Should throw error for empty certificate', () => {
+ await it('should throw error for empty certificate', () => {
const manager = new OCPP20CertificateManager()
expect(() => {
}).toThrow()
})
- await it('Should support SHA384 hash algorithm', () => {
+ await it('should support SHA384 hash algorithm', () => {
const manager = new OCPP20CertificateManager()
const hashData = manager.computeCertificateHash(
expect(hashData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA384)
})
- await it('Should support SHA512 hash algorithm', () => {
+ await it('should support SHA512 hash algorithm', () => {
const manager = new OCPP20CertificateManager()
const hashData = manager.computeCertificateHash(
})
await describe('validateCertificateFormat', async () => {
- await it('Should return true for valid PEM certificate', () => {
+ await it('should return true for valid PEM certificate', () => {
const manager = new OCPP20CertificateManager()
const isValid = manager.validateCertificateFormat(VALID_PEM_CERTIFICATE)
expect(isValid).toBe(true)
})
- await it('Should return false for certificate without BEGIN marker', () => {
+ await it('should return false for certificate without BEGIN marker', () => {
const manager = new OCPP20CertificateManager()
const isValid = manager.validateCertificateFormat(INVALID_PEM_NO_MARKERS)
expect(isValid).toBe(false)
})
- await it('Should return false for certificate with wrong markers', () => {
+ await it('should return false for certificate with wrong markers', () => {
const manager = new OCPP20CertificateManager()
const isValid = manager.validateCertificateFormat(INVALID_PEM_WRONG_MARKERS)
expect(isValid).toBe(false)
})
- await it('Should return false for empty string', () => {
+ await it('should return false for empty string', () => {
const manager = new OCPP20CertificateManager()
const isValid = manager.validateCertificateFormat(EMPTY_PEM_CERTIFICATE)
expect(isValid).toBe(false)
})
- await it('Should return false for null/undefined input', () => {
+ await it('should return false for null/undefined input', () => {
const manager = new OCPP20CertificateManager()
expect(manager.validateCertificateFormat(null as any)).toBe(false)
expect(manager.validateCertificateFormat(undefined as any)).toBe(false)
})
- await it('Should return true for certificate with extra whitespace', () => {
+ await it('should return true for certificate with extra whitespace', () => {
const manager = new OCPP20CertificateManager()
const pemWithWhitespace = `
})
await describe('getCertificatePath', async () => {
- await it('Should return correct file path for certificate', () => {
+ await it('should return correct file path for certificate', () => {
const manager = new OCPP20CertificateManager()
const path = manager.getCertificatePath(TEST_STATION_HASH_ID, TEST_CERT_TYPE, 'SERIAL-12345')
expect(path).toMatch(/\.pem$/)
})
- await it('Should handle special characters in serial number', () => {
+ await it('should handle special characters in serial number', () => {
const manager = new OCPP20CertificateManager()
const path = manager.getCertificatePath(
expect(filename).not.toContain('/')
})
- await it('Should return different paths for different certificate types', () => {
+ await it('should return different paths for different certificate types', () => {
const manager = new OCPP20CertificateManager()
const csmsPath = manager.getCertificatePath(
expect(v2gPath).toContain('V2GRootCertificate')
})
- await it('Should return path following project convention', () => {
+ await it('should return path following project convention', () => {
const manager = new OCPP20CertificateManager()
const path = manager.getCertificatePath(TEST_STATION_HASH_ID, TEST_CERT_TYPE, 'SERIAL-12345')
})
await describe('Edge cases and error handling', async () => {
- await it('Should handle concurrent certificate operations', async () => {
+ await it('should handle concurrent certificate operations', async () => {
const manager = new OCPP20CertificateManager()
const results = await Promise.all([
})
})
- await it('Should handle very long certificate chains', async () => {
+ await it('should handle very long certificate chains', async () => {
const manager = new OCPP20CertificateManager()
const longChain = Array(5).fill(VALID_PEM_CERTIFICATE).join('\n')
expect(result).toBeDefined()
})
- await it('Should sanitize station hash ID for filesystem safety', () => {
+ await it('should sanitize station hash ID for filesystem safety', () => {
const manager = new OCPP20CertificateManager()
const maliciousHashId = '../../../etc/passwd'
const testableService = createTestableIncomingRequestService(incomingRequestService)
await describe('Valid Certificate Chain Installation', async () => {
- await it('Should accept valid certificate chain', async () => {
+ await it('should accept valid certificate chain', async () => {
mockChargingStation.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
expect(response.status).toBe(GenericStatus.Accepted)
})
- await it('Should accept single certificate (no chain)', async () => {
+ await it('should accept single certificate (no chain)', async () => {
mockChargingStation.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
})
await describe('Invalid Certificate Handling', async () => {
- await it('Should reject certificate with invalid PEM format', async () => {
+ await it('should reject certificate with invalid PEM format', async () => {
const request: OCPP20CertificateSignedRequest = {
certificateChain: INVALID_PEM_CERTIFICATE_MISSING_MARKERS,
certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
})
await describe('ChargingStationCertificate Reconnect Logic', async () => {
- await it('Should trigger websocket reconnect for ChargingStationCertificate type', async () => {
+ await it('should trigger websocket reconnect for ChargingStationCertificate type', async () => {
const mockCertManager = createMockCertificateManager({
storeCertificateResult: true,
})
})
await describe('V2GCertificate Storage', async () => {
- await it('Should store V2GCertificate separately without reconnect', async () => {
+ await it('should store V2GCertificate separately without reconnect', async () => {
const mockCertManager = createMockCertificateManager({
storeCertificateResult: true,
})
})
await describe('Certificate Manager Missing', async () => {
- await it('Should return Rejected status with InternalError when certificate manager is missing', async () => {
+ await it('should return Rejected status with InternalError when certificate manager is missing', async () => {
// Create a separate mock charging station without certificateManager
const stationWithoutCertManager = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
})
await describe('Storage Failure Handling', async () => {
- await it('Should return Rejected status when storage fails', async () => {
+ await it('should return Rejected status when storage fails', async () => {
mockChargingStation.certificateManager = createMockCertificateManager({
storeCertificateResult: false,
})
expect(response.statusInfo?.reasonCode).toBeDefined()
})
- await it('Should return Rejected status when storage throws error', async () => {
+ await it('should return Rejected status when storage throws error', async () => {
mockChargingStation.certificateManager = createMockCertificateManager({
storeCertificateError: new Error('Storage full'),
})
})
await describe('Response Structure Validation', async () => {
- await it('Should return response matching CertificateSignedResponse schema', async () => {
+ await it('should return response matching CertificateSignedResponse schema', async () => {
mockChargingStation.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
}
})
- await it('Should include statusInfo with reasonCode for rejection', async () => {
+ await it('should include statusInfo with reasonCode for rejection', async () => {
const request: OCPP20CertificateSignedRequest = {
certificateChain: INVALID_PEM_CERTIFICATE_MISSING_MARKERS,
certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
const testableService = createTestableIncomingRequestService(incomingRequestService)
// FR: C11.FR.01 - CS SHALL attempt to clear its Authorization Cache
- await it('Should handle ClearCache request successfully', async () => {
+ await it('should handle ClearCache request successfully', async () => {
const response = await testableService.handleRequestClearCache(mockChargingStation)
expect(response).toBeDefined()
})
// FR: C11.FR.02 - Return correct status based on cache clearing result
- await it('Should return correct status based on cache clearing result', async () => {
+ await it('should return correct status based on cache clearing result', async () => {
const response = await testableService.handleRequestClearCache(mockChargingStation)
expect(response).toBeDefined()
// CLR-001: Verify Authorization Cache is cleared (not IdTagsCache)
await describe('CLR-001 - ClearCache clears Authorization Cache', async () => {
- await it('Should call authService.clearCache() on ClearCache request', async () => {
+ await it('should call authService.clearCache() on ClearCache request', async () => {
// Create a mock auth service to verify clearCache is called
let clearCacheCalled = false
const mockAuthService = {
}
})
- await it('Should NOT call idTagsCache.deleteIdTags() on ClearCache request', async () => {
+ await it('should NOT call idTagsCache.deleteIdTags() on ClearCache request', async () => {
// Verify that IdTagsCache is not touched
let deleteIdTagsCalled = false
const originalDeleteIdTags = mockChargingStation.idTagsCache.deleteIdTags.bind(mockChargingStation.idTagsCache)
// CLR-002: Verify AuthCacheEnabled check per C11.FR.04
await describe('CLR-002 - AuthCacheEnabled Check (C11.FR.04)', async () => {
- await it('Should return Rejected when AuthCacheEnabled is false', async () => {
+ await it('should return Rejected when AuthCacheEnabled is false', async () => {
// Create a mock auth service with cache disabled
const mockAuthService = {
clearCache: (): Promise<void> => {
}
})
- await it('Should return Accepted when AuthCacheEnabled is true and clear succeeds', async () => {
+ await it('should return Accepted when AuthCacheEnabled is true and clear succeeds', async () => {
// Create a mock auth service with cache enabled
const mockAuthService = {
clearCache: (): Promise<void> => {
}
})
- await it('Should return Rejected when clearCache throws an error', async () => {
+ await it('should return Rejected when clearCache throws an error', async () => {
// Create a mock auth service that throws on clearCache
const mockAuthService = {
clearCache: (): Promise<void> => {
}
})
- await it('Should not attempt to clear cache when AuthCacheEnabled is false', async () => {
+ await it('should not attempt to clear cache when AuthCacheEnabled is false', async () => {
let clearCacheAttempted = false
const mockAuthService = {
clearCache: (): Promise<void> => {
// C11.FR.05: IF the CS does not support an Authorization Cache → Rejected
await describe('C11.FR.05 - No Authorization Cache Support', async () => {
- await it('Should return Rejected when authService factory fails (no cache support)', async () => {
+ await it('should return Rejected when authService factory fails (no cache support)', async () => {
// Mock factory to throw error (simulates no Authorization Cache support)
const originalGetInstance = OCPPAuthServiceFactory.getInstance.bind(OCPPAuthServiceFactory)
Object.assign(OCPPAuthServiceFactory, {
const testableService = createTestableIncomingRequestService(incomingRequestService)
await describe('Valid Certificate Deletion', async () => {
- await it('Should accept deletion of existing certificate', async () => {
+ await it('should accept deletion of existing certificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateResult: { status: 'Accepted' },
})
expect(response.statusInfo).toBeUndefined()
})
- await it('Should accept deletion with SHA384 hash algorithm', async () => {
+ await it('should accept deletion with SHA384 hash algorithm', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateResult: { status: 'Accepted' },
})
expect(response.statusInfo).toBeUndefined()
})
- await it('Should accept deletion with SHA512 hash algorithm', async () => {
+ await it('should accept deletion with SHA512 hash algorithm', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateResult: { status: 'Accepted' },
})
})
await describe('Certificate Not Found', async () => {
- await it('Should return NotFound for non-existent certificate', async () => {
+ await it('should return NotFound for non-existent certificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateResult: { status: 'NotFound' },
})
})
await describe('Deletion Failure Handling', async () => {
- await it('Should return Failed status when deletion throws error', async () => {
+ await it('should return Failed status when deletion throws error', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateError: new Error('Deletion failed'),
})
expect(response.statusInfo?.reasonCode).toBeDefined()
})
- await it('Should return Failed with InternalError when certificateManager is missing', async () => {
+ await it('should return Failed with InternalError when certificateManager is missing', async () => {
const stationWithoutCertManager = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 3,
})
await describe('Response Structure Validation', async () => {
- await it('Should return response matching DeleteCertificateResponse schema', async () => {
+ await it('should return response matching DeleteCertificateResponse schema', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateResult: { status: 'Accepted' },
})
}
})
- await it('Should include statusInfo with reasonCode for failure', async () => {
+ await it('should include statusInfo with reasonCode for failure', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
deleteCertificateError: new Error('Deletion failed'),
})
})
// FR: B07.FR.01, B07.FR.07
- await it('Should handle GetBaseReport request with ConfigurationInventory', () => {
+ await it('should handle GetBaseReport request with ConfigurationInventory', () => {
const request: OCPP20GetBaseReportRequest = {
reportBase: ReportBaseEnumType.ConfigurationInventory,
requestId: 1,
})
// FR: B08.FR.02
- await it('Should handle GetBaseReport request with FullInventory', () => {
+ await it('should handle GetBaseReport request with FullInventory', () => {
const request: OCPP20GetBaseReportRequest = {
reportBase: ReportBaseEnumType.FullInventory,
requestId: 2,
expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
})
- await it('Should include registry variables with Actual attribute only for unsupported types', () => {
+ await it('should include registry variables with Actual attribute only for unsupported types', () => {
const reportData = testableService.buildReportData(
mockChargingStation,
ReportBaseEnumType.FullInventory
})
// FR: B08.FR.03
- await it('Should handle GetBaseReport request with SummaryInventory', () => {
+ await it('should handle GetBaseReport request with SummaryInventory', () => {
const request: OCPP20GetBaseReportRequest = {
reportBase: ReportBaseEnumType.SummaryInventory,
requestId: 3,
})
// FR: B08.FR.04
- await it('Should return NotSupported for unsupported reportBase', () => {
+ await it('should return NotSupported for unsupported reportBase', () => {
const request: OCPP20GetBaseReportRequest = {
reportBase: 'UnsupportedReportBase' as unknown as ReportBaseEnumType,
requestId: 4,
})
// FR: B08.FR.05
- await it('Should return Accepted for ConfigurationInventory with configured station', () => {
+ await it('should return Accepted for ConfigurationInventory with configured station', () => {
// Create a charging station with minimal configuration
const request: OCPP20GetBaseReportRequest = {
})
// FR: B08.FR.06
- await it('Should build correct report data for ConfigurationInventory', () => {
+ await it('should build correct report data for ConfigurationInventory', () => {
const request: OCPP20GetBaseReportRequest = {
reportBase: ReportBaseEnumType.ConfigurationInventory,
requestId: 6,
})
// FR: B08.FR.07
- await it('Should build correct report data for FullInventory with station info', () => {
+ await it('should build correct report data for FullInventory with station info', () => {
const reportData = testableService.buildReportData(
mockChargingStation,
ReportBaseEnumType.FullInventory
})
// FR: B08.FR.08
- await it('Should build correct report data for SummaryInventory', () => {
+ await it('should build correct report data for SummaryInventory', () => {
const reportData = testableService.buildReportData(
mockChargingStation,
ReportBaseEnumType.SummaryInventory
})
// ReportingValueSize truncation test
- await it('Should truncate long SequenceList/MemberList values per ReportingValueSize', () => {
+ await it('should truncate long SequenceList/MemberList values per ReportingValueSize', () => {
// Ensure ReportingValueSize is at a small value (default is Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH). We will override configuration key if absent.
const reportingSizeKey = StandardParametersKey.ReportingValueSize
// Add or lower configuration key to 10 to force truncation
})
// FR: B08.FR.09
- await it('Should handle GetBaseReport with EVSE structure', () => {
+ await it('should handle GetBaseReport with EVSE structure', () => {
// The createChargingStation should create a station with EVSEs
const stationWithEvses = createChargingStation({
baseName: 'CS-EVSE-001',
})
// FR: B08.FR.10
- await it('Should validate unsupported reportBase correctly', () => {
+ await it('should validate unsupported reportBase correctly', () => {
const reportData = testableService.buildReportData(
mockChargingStation,
'InvalidReportBase' as unknown as ReportBaseEnumType
const testableService = createTestableIncomingRequestService(incomingRequestService)
await describe('Request All Certificate Types', async () => {
- await it('Should return all certificates when no filter is provided', async () => {
+ await it('should return all certificates when no filter is provided', async () => {
const mockCerts: CertificateHashDataChainType[] = [
createMockCertificateHashDataChain(GetCertificateIdUseEnumType.V2GRootCertificate, '111'),
createMockCertificateHashDataChain(GetCertificateIdUseEnumType.MORootCertificate, '222'),
})
await describe('Request Filtered Certificate Types', async () => {
- await it('Should return only V2GRootCertificate when filtered', async () => {
+ await it('should return only V2GRootCertificate when filtered', async () => {
const v2gCert = createMockCertificateHashDataChain(
GetCertificateIdUseEnumType.V2GRootCertificate,
'111'
)
})
- await it('Should return multiple types when multiple filters provided', async () => {
+ await it('should return multiple types when multiple filters provided', async () => {
const mockCerts: CertificateHashDataChainType[] = [
createMockCertificateHashDataChain(GetCertificateIdUseEnumType.V2GRootCertificate, '111'),
createMockCertificateHashDataChain(GetCertificateIdUseEnumType.CSMSRootCertificate, '222'),
})
await describe('No Certificates Found', async () => {
- await it('Should return Accepted with empty array when no certificates found', async () => {
+ await it('should return Accepted with empty array when no certificates found', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
getInstalledCertificatesResult: [],
})
expect(response.certificateHashDataChain).toBeUndefined()
})
- await it('Should return NotFound when filtered type has no certificates', async () => {
+ await it('should return NotFound when filtered type has no certificates', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
getInstalledCertificatesResult: [],
})
})
await describe('Response Structure Validation', async () => {
- await it('Should return response with required status field', async () => {
+ await it('should return response with required status field', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
getInstalledCertificatesResult: [],
})
]).toContain(response.status)
})
- await it('Should return valid CertificateHashDataChain structure', async () => {
+ await it('should return valid CertificateHashDataChain structure', async () => {
const mockCert = createMockCertificateHashDataChain(
GetCertificateIdUseEnumType.V2GRootCertificate,
'123456'
})
await describe('Certificate Manager Missing', async () => {
- await it('Should return NotFound when certificate manager is not available', async () => {
+ await it('should return NotFound when certificate manager is not available', async () => {
const stationWithoutCertManager = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 3,
})
// FR: B06.FR.01
- await it('Should handle GetVariables request with valid variables', () => {
+ await it('should handle GetVariables request with valid variables', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
})
// FR: B06.FR.02
- await it('Should handle GetVariables request with invalid variables', () => {
+ await it('should handle GetVariables request with invalid variables', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
})
// FR: B06.FR.03
- await it('Should handle GetVariables request with unsupported attribute types', () => {
+ await it('should handle GetVariables request with unsupported attribute types', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
})
// FR: B06.FR.04
- await it('Should reject AuthorizeRemoteStart under Connector component', () => {
+ await it('should reject AuthorizeRemoteStart under Connector component', () => {
resetLimits(mockChargingStation)
resetReportingValueSize(mockChargingStation)
const request: OCPP20GetVariablesRequest = {
})
// FR: B06.FR.05
- await it('Should reject Target attribute for WebSocketPingInterval', () => {
+ await it('should reject Target attribute for WebSocketPingInterval', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
})
- await it('Should truncate variable value based on ReportingValueSize', () => {
+ await it('should truncate variable value based on ReportingValueSize', () => {
// Set size below actual value length to force truncation
setReportingValueSize(mockChargingStation, 2)
const request: OCPP20GetVariablesRequest = {
resetReportingValueSize(mockChargingStation)
})
- await it('Should allow ReportingValueSize retrieval from DeviceDataCtrlr', () => {
+ await it('should allow ReportingValueSize retrieval from DeviceDataCtrlr', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeValue).toBeDefined()
})
- await it('Should enforce ItemsPerMessage limit', () => {
+ await it('should enforce ItemsPerMessage limit', () => {
setStrictLimits(mockChargingStation, 1, 10000)
const request: OCPP20GetVariablesRequest = {
getVariableData: [
resetLimits(mockChargingStation)
})
- await it('Should enforce BytesPerMessage limit (pre-calculation)', () => {
+ await it('should enforce BytesPerMessage limit (pre-calculation)', () => {
setStrictLimits(mockChargingStation, 100, 10)
const request: OCPP20GetVariablesRequest = {
getVariableData: [
resetLimits(mockChargingStation)
})
- await it('Should enforce BytesPerMessage limit (post-calculation)', () => {
+ await it('should enforce BytesPerMessage limit (post-calculation)', () => {
// Build request likely to produce larger response due to status info entries
const request: OCPP20GetVariablesRequest = {
getVariableData: [
})
// Added tests for relocated components
- await it('Should retrieve immutable DateTime from ClockCtrlr', () => {
+ await it('should retrieve immutable DateTime from ClockCtrlr', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeValue).toBeDefined()
})
- await it('Should retrieve MessageTimeout from OCPPCommCtrlr', () => {
+ await it('should retrieve MessageTimeout from OCPPCommCtrlr', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeValue).toBeDefined()
})
- await it('Should retrieve TxUpdatedInterval from SampledDataCtrlr and show default value', () => {
+ await it('should retrieve TxUpdatedInterval from SampledDataCtrlr and show default value', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeValue).toBe('30')
})
- await it('Should retrieve list/sequence defaults for FileTransferProtocols, TimeSource, NetworkConfigurationPriority', () => {
+ await it('should retrieve list/sequence defaults for FileTransferProtocols, TimeSource, NetworkConfigurationPriority', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(netConfigPriority.attributeValue).toBe('1,2,3')
})
- await it('Should retrieve list defaults for TxStartedMeasurands, TxEndedMeasurands, TxUpdatedMeasurands', () => {
+ await it('should retrieve list defaults for TxStartedMeasurands, TxEndedMeasurands, TxUpdatedMeasurands', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
})
// FR: B06.FR.13
- await it('Should reject Target attribute for NetworkConfigurationPriority', () => {
+ await it('should reject Target attribute for NetworkConfigurationPriority', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
})
// FR: B06.FR.15
- await it('Should return UnknownVariable when instance omitted for instance-specific MessageTimeout', () => {
+ await it('should return UnknownVariable when instance omitted for instance-specific MessageTimeout', () => {
// MessageTimeout only registered with instance 'Default'
const request: OCPP20GetVariablesRequest = {
getVariableData: [
})
// FR: B06.FR.09
- await it('Should reject retrieval of explicit write-only variable CertificatePrivateKey', () => {
+ await it('should reject retrieval of explicit write-only variable CertificatePrivateKey', () => {
// Explicit vendor-specific write-only variable from SecurityCtrlr
const request: OCPP20GetVariablesRequest = {
getVariableData: [
expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.WriteOnly)
})
- await it('Should reject MinSet and MaxSet for WebSocketPingInterval', () => {
+ await it('should reject MinSet and MaxSet for WebSocketPingInterval', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(maxSet.attributeValue).toBeUndefined()
})
- await it('Should reject MinSet for MemberList variable TxStartPoint', () => {
+ await it('should reject MinSet for MemberList variable TxStartPoint', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
})
- await it('Should reject MaxSet for variable SecurityProfile (Actual only)', () => {
+ await it('should reject MaxSet for variable SecurityProfile (Actual only)', () => {
const request: OCPP20GetVariablesRequest = {
getVariableData: [
{
expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
})
- await it('Should apply ValueSize then ReportingValueSize sequential truncation', () => {
+ await it('should apply ValueSize then ReportingValueSize sequential truncation', () => {
// First apply a smaller ValueSize (5) then a smaller ReportingValueSize (3)
setValueSize(mockChargingStation, 5)
setReportingValueSize(mockChargingStation, 3)
const testableService = createTestableIncomingRequestService(incomingRequestService)
await describe('Valid Certificate Installation', async () => {
- await it('Should accept valid V2GRootCertificate', async () => {
+ await it('should accept valid V2GRootCertificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
expect(response.statusInfo).toBeUndefined()
})
- await it('Should accept valid MORootCertificate', async () => {
+ await it('should accept valid MORootCertificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
expect(response.statusInfo).toBeUndefined()
})
- await it('Should accept valid CSMSRootCertificate', async () => {
+ await it('should accept valid CSMSRootCertificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
expect(response.statusInfo).toBeUndefined()
})
- await it('Should accept valid ManufacturerRootCertificate', async () => {
+ await it('should accept valid ManufacturerRootCertificate', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
})
await describe('Invalid Certificate Handling', async () => {
- await it('Should reject certificate with invalid PEM format', async () => {
+ await it('should reject certificate with invalid PEM format', async () => {
const request: OCPP20InstallCertificateRequest = {
certificate: INVALID_PEM_CERTIFICATE_MISSING_MARKERS,
certificateType: InstallCertificateUseEnumType.V2GRootCertificate,
expect(typeof response.statusInfo?.reasonCode).toBe('string')
})
- await it('Should reject expired certificate when validation is enabled', async () => {
+ await it('should reject expired certificate when validation is enabled', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: false,
})
})
await describe('Storage Failure Handling', async () => {
- await it('Should return Failed status when storage is full', async () => {
+ await it('should return Failed status when storage is full', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateError: new Error('Storage full'),
})
})
await describe('Response Structure Validation', async () => {
- await it('Should return response matching InstallCertificateResponse schema', async () => {
+ await it('should return response matching InstallCertificateResponse schema', async () => {
stationWithCertManager.certificateManager = createMockCertificateManager({
storeCertificateResult: true,
})
}
})
- await it('Should include statusInfo with reasonCode for rejection', async () => {
+ await it('should include statusInfo with reasonCode for rejection', async () => {
const request: OCPP20InstallCertificateRequest = {
certificate: INVALID_PEM_CERTIFICATE_MISSING_MARKERS,
certificateType: InstallCertificateUseEnumType.V2GRootCertificate,
})
// FR: F01.FR.03, F01.FR.04, F01.FR.05, F01.FR.13
- await it('Should handle RequestStartTransaction with valid evseId and idToken', async () => {
+ await it('should handle RequestStartTransaction with valid evseId and idToken', async () => {
const validRequest: OCPP20RequestStartTransactionRequest = {
evseId: 1,
idToken: {
})
// FR: F01.FR.17, F02.FR.05 - Verify remoteStartId and idToken are stored for later TransactionEvent
- await it('Should store remoteStartId and idToken in connector status for TransactionEvent', async () => {
+ await it('should store remoteStartId and idToken in connector status for TransactionEvent', async () => {
const spyChargingStation = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 3,
})
// FR: F01.FR.19
- await it('Should handle RequestStartTransaction with groupIdToken', async () => {
+ await it('should handle RequestStartTransaction with groupIdToken', async () => {
const requestWithGroupToken: OCPP20RequestStartTransactionRequest = {
evseId: 3,
groupIdToken: {
})
// OCPP 2.0.1 §2.10 ChargingProfile validation tests
- await it('Should accept RequestStartTransaction with valid TxProfile (no transactionId)', async () => {
+ await it('should accept RequestStartTransaction with valid TxProfile (no transactionId)', async () => {
const validChargingProfile: OCPP20ChargingProfileType = {
chargingProfileKind: OCPP20ChargingProfileKindEnumType.Relative,
chargingProfilePurpose: OCPP20ChargingProfilePurposeEnumType.TxProfile,
})
// OCPP 2.0.1 §2.10: RequestStartTransaction requires chargingProfilePurpose=TxProfile
- await it('Should reject RequestStartTransaction with non-TxProfile purpose (OCPP 2.0.1 §2.10)', async () => {
+ await it('should reject RequestStartTransaction with non-TxProfile purpose (OCPP 2.0.1 §2.10)', async () => {
const invalidPurposeProfile: OCPP20ChargingProfileType = {
chargingProfileKind: OCPP20ChargingProfileKindEnumType.Relative,
chargingProfilePurpose: OCPP20ChargingProfilePurposeEnumType.TxDefaultProfile,
})
// OCPP 2.0.1 §2.10: transactionId MUST NOT be present at RequestStartTransaction time
- await it('Should reject RequestStartTransaction with TxProfile having transactionId set (OCPP 2.0.1 §2.10)', async () => {
+ await it('should reject RequestStartTransaction with TxProfile having transactionId set (OCPP 2.0.1 §2.10)', async () => {
const profileWithTransactionId: OCPP20ChargingProfileType = {
chargingProfileKind: OCPP20ChargingProfileKindEnumType.Relative,
chargingProfilePurpose: OCPP20ChargingProfilePurposeEnumType.TxProfile,
})
// FR: F01.FR.07
- await it('Should reject RequestStartTransaction for invalid evseId', async () => {
+ await it('should reject RequestStartTransaction for invalid evseId', async () => {
const invalidEvseRequest: OCPP20RequestStartTransactionRequest = {
evseId: 999, // Non-existent EVSE
idToken: {
})
// FR: F01.FR.09, F01.FR.10
- await it('Should reject RequestStartTransaction when connector is already occupied', async () => {
+ await it('should reject RequestStartTransaction when connector is already occupied', async () => {
// First, start a transaction to occupy the connector
const firstRequest: OCPP20RequestStartTransactionRequest = {
evseId: 1,
})
// FR: F02.FR.01
- await it('Should return proper response structure', async () => {
+ await it('should return proper response structure', async () => {
const validRequest: OCPP20RequestStartTransactionRequest = {
evseId: 1,
idToken: {
}
// FR: F03.FR.02, F03.FR.03, F03.FR.07, F03.FR.09
- await it('Should successfully stop an active transaction', async () => {
+ await it('should successfully stop an active transaction', async () => {
// Start a transaction first
const transactionId = await startTransaction(1, 100)
})
// FR: F03.FR.02, F03.FR.03
- await it('Should handle multiple active transactions correctly', async () => {
+ await it('should handle multiple active transactions correctly', async () => {
// Reset once before starting multiple transactions
resetConnectorTransactionStates()
})
// FR: F03.FR.08
- await it('Should reject stop transaction for non-existent transaction ID', async () => {
+ await it('should reject stop transaction for non-existent transaction ID', async () => {
// Clear previous transaction events
sentTransactionEvents = []
})
// FR: F03.FR.08
- await it('Should reject stop transaction for invalid transaction ID format - empty string', async () => {
+ await it('should reject stop transaction for invalid transaction ID format - empty string', async () => {
// Clear previous transaction events
sentTransactionEvents = []
})
// FR: F03.FR.08
- await it('Should reject stop transaction for invalid transaction ID format - too long', async () => {
+ await it('should reject stop transaction for invalid transaction ID format - too long', async () => {
// Clear previous transaction events
sentTransactionEvents = []
})
// FR: F03.FR.02
- await it('Should accept valid transaction ID format - exactly 36 characters', async () => {
+ await it('should accept valid transaction ID format - exactly 36 characters', async () => {
// Start a transaction first
const transactionId = await startTransaction(1, 300)
expect(sentTransactionEvents).toHaveLength(1)
})
- await it('Should handle TransactionEvent request failure gracefully', async () => {
+ await it('should handle TransactionEvent request failure gracefully', async () => {
sentTransactionEvents = []
const failingChargingStation = createChargingStation({
})
// FR: F04.FR.01
- await it('Should return proper response structure', async () => {
+ await it('should return proper response structure', async () => {
// Clear previous transaction events
sentTransactionEvents = []
expect(Object.keys(response as object)).toEqual(['status'])
})
- await it('Should handle custom data in request payload', async () => {
+ await it('should handle custom data in request payload', async () => {
// Start a transaction first
const transactionId = await startTransaction(1, 500)
})
// FR: F03.FR.07, F03.FR.09
- await it('Should validate TransactionEvent content correctly', async () => {
+ await it('should validate TransactionEvent content correctly', async () => {
// Start a transaction first
const transactionId = await startTransaction(2, 600) // Use EVSE 2
})
// FR: F03.FR.09
- await it('Should include final meter values in TransactionEvent(Ended)', async () => {
+ await it('should include final meter values in TransactionEvent(Ended)', async () => {
resetConnectorTransactionStates()
const transactionId = await startTransaction(3, 700)
await describe('B11 - Reset - Without Ongoing Transaction', async () => {
// FR: B11.FR.01
- await it('Should handle Reset request with Immediate type when no transactions', async () => {
+ await it('should handle Reset request with Immediate type when no transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
type: ResetEnumType.Immediate,
}
]).toContain(response.status)
})
- await it('Should handle Reset request with OnIdle type when no transactions', async () => {
+ await it('should handle Reset request with OnIdle type when no transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
type: ResetEnumType.OnIdle,
}
})
// FR: B11.FR.03
- await it('Should handle EVSE-specific reset request when no transactions', async () => {
+ await it('should handle EVSE-specific reset request when no transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
evseId: 1,
type: ResetEnumType.Immediate,
]).toContain(response.status)
})
- await it('Should reject reset for non-existent EVSE when no transactions', async () => {
+ await it('should reject reset for non-existent EVSE when no transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
evseId: 999, // Non-existent EVSE
type: ResetEnumType.Immediate,
expect(response.statusInfo?.additionalInfo).toContain('EVSE 999')
})
- await it('Should return proper response structure for immediate reset without transactions', async () => {
+ await it('should return proper response structure for immediate reset without transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
type: ResetEnumType.Immediate,
}
}
})
- await it('Should return proper response structure for OnIdle reset without transactions', async () => {
+ await it('should return proper response structure for OnIdle reset without transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
type: ResetEnumType.OnIdle,
}
expect(response.status).toBe(ResetStatusEnumType.Accepted)
})
- await it('Should reject EVSE reset when not supported and no transactions', async () => {
+ await it('should reject EVSE reset when not supported and no transactions', async () => {
// Mock charging station without EVSE support
const originalHasEvses = mockChargingStation.hasEvses
;(mockChargingStation as { hasEvses: boolean }).hasEvses = false
;(mockChargingStation as { hasEvses: boolean }).hasEvses = originalHasEvses
})
- await it('Should handle EVSE-specific reset without transactions', async () => {
+ await it('should handle EVSE-specific reset without transactions', async () => {
const resetRequest: OCPP20ResetRequest = {
evseId: 1,
type: ResetEnumType.Immediate,
await describe('B12 - Reset - With Ongoing Transaction', async () => {
// FR: B12.FR.02
- await it('Should handle immediate reset with active transactions', async () => {
+ await it('should handle immediate reset with active transactions', async () => {
// Mock active transactions
mockStation.getNumberOfRunningTransactions = () => 1
})
// FR: B12.FR.01
- await it('Should handle OnIdle reset with active transactions', async () => {
+ await it('should handle OnIdle reset with active transactions', async () => {
// Mock active transactions
mockStation.getNumberOfRunningTransactions = () => 1
})
// FR: B12.FR.03
- await it('Should handle EVSE-specific reset with active transactions', async () => {
+ await it('should handle EVSE-specific reset with active transactions', async () => {
// Mock active transactions
mockStation.getNumberOfRunningTransactions = () => 1
mockStation.getNumberOfRunningTransactions = () => 0
})
- await it('Should reject EVSE reset when not supported with active transactions', async () => {
+ await it('should reject EVSE reset when not supported with active transactions', async () => {
// Mock charging station without EVSE support and active transactions
const originalHasEvses = mockChargingStation.hasEvses
;(mockChargingStation as { hasEvses: boolean }).hasEvses = false
// Errata 2.14: OnIdle definition includes firmware updates
// Charging station is NOT idle when firmware is Downloading, Downloaded, or Installing
- await it('Should return Scheduled when firmware is Downloading', async () => {
+ await it('should return Scheduled when firmware is Downloading', async () => {
const station = createTestStation()
// Mock firmware status as Downloading
Object.assign(station.stationInfo, {
expect(response.status).toBe(ResetStatusEnumType.Scheduled)
})
- await it('Should return Scheduled when firmware is Downloaded', async () => {
+ await it('should return Scheduled when firmware is Downloaded', async () => {
const station = createTestStation()
// Mock firmware status as Downloaded (waiting to install)
Object.assign(station.stationInfo, {
expect(response.status).toBe(ResetStatusEnumType.Scheduled)
})
- await it('Should return Scheduled when firmware is Installing', async () => {
+ await it('should return Scheduled when firmware is Installing', async () => {
const station = createTestStation()
// Mock firmware status as Installing
Object.assign(station.stationInfo, {
expect(response.status).toBe(ResetStatusEnumType.Scheduled)
})
- await it('Should return Accepted when firmware is Installed (complete)', async () => {
+ await it('should return Accepted when firmware is Installed (complete)', async () => {
const station = createTestStation()
// Mock firmware status as Installed (update complete)
Object.assign(station.stationInfo, {
expect(response.status).toBe(ResetStatusEnumType.Accepted)
})
- await it('Should return Accepted when firmware status is Idle', async () => {
+ await it('should return Accepted when firmware status is Idle', async () => {
const station = createTestStation()
// Mock firmware status as Idle (no update in progress)
Object.assign(station.stationInfo, {
// Errata 2.14: OnIdle definition includes pending reservations
// Charging station is NOT idle when a connector has a non-expired reservation
- await it('Should return Scheduled when connector has non-expired reservation', async () => {
+ await it('should return Scheduled when connector has non-expired reservation', async () => {
const station = createTestStation()
// Create a reservation that expires in 1 hour (future)
const futureExpiryDate = new Date(Date.now() + 3600000)
expect(response.status).toBe(ResetStatusEnumType.Scheduled)
})
- await it('Should return Accepted when reservation is expired', async () => {
+ await it('should return Accepted when reservation is expired', async () => {
const station = createTestStation()
// Create a reservation that expired 1 hour ago (past)
const pastExpiryDate = new Date(Date.now() - 3600000)
expect(response.status).toBe(ResetStatusEnumType.Accepted)
})
- await it('Should return Accepted when no reservations exist', async () => {
+ await it('should return Accepted when no reservations exist', async () => {
const station = createTestStation()
// No reservations set (default state)
await describe('Idle Condition', async () => {
// Errata 2.14: Station is idle when NO transactions, NO firmware update, NO reservations
- await it('Should return Accepted when all conditions clear (true idle state)', async () => {
+ await it('should return Accepted when all conditions clear (true idle state)', async () => {
const station = createTestStation()
// Ensure no transactions
station.getNumberOfRunningTransactions = () => 0
expect(response.status).toBe(ResetStatusEnumType.Accepted)
})
- await it('Should return Scheduled when multiple blocking conditions exist', async () => {
+ await it('should return Scheduled when multiple blocking conditions exist', async () => {
const station = createTestStation()
// Active transaction
station.getNumberOfRunningTransactions = () => 1
})
// FR: B05.FR.01, B05.FR.10
- await it('Should handle SetVariables request with valid writable variables', () => {
+ await it('should handle SetVariables request with valid writable variables', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.02
- await it('Should handle SetVariables request with invalid variables/components', () => {
+ await it('should handle SetVariables request with invalid variables/components', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.03
- await it('Should handle SetVariables request with unsupported attribute type', () => {
+ await it('should handle SetVariables request with unsupported attribute type', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.04
- await it('Should reject AuthorizeRemoteStart under Connector component for write', () => {
+ await it('should reject AuthorizeRemoteStart under Connector component for write', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.05
- await it('Should reject value exceeding max length at service level', () => {
+ await it('should reject value exceeding max length at service level', () => {
const longValue = 'x'.repeat(2501)
const request: OCPP20SetVariablesRequest = {
setVariableData: [
})
// FR: B07.FR.07
- await it('Should handle mixed SetVariables request with multiple outcomes', () => {
+ await it('should handle mixed SetVariables request with multiple outcomes', () => {
const longValue = 'y'.repeat(2501)
const request: OCPP20SetVariablesRequest = {
setVariableData: [
})
// FR: B07.FR.08
- await it('Should reject Target attribute for WebSocketPingInterval explicitly', () => {
+ await it('should reject Target attribute for WebSocketPingInterval explicitly', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.09
- await it('Should reject immutable DateTime variable', () => {
+ await it('should reject immutable DateTime variable', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// FR: B07.FR.10
- await it('Should persist HeartbeatInterval and WebSocketPingInterval after setting', () => {
+ await it('should persist HeartbeatInterval and WebSocketPingInterval after setting', () => {
const hbNew = (millisecondsToSeconds(Constants.DEFAULT_HEARTBEAT_INTERVAL) + 20).toString()
const wsNew = (Constants.DEFAULT_WEBSOCKET_PING_INTERVAL + 20).toString()
const setRequest: OCPP20SetVariablesRequest = {
})
// FR: B07.FR.11
- await it('Should revert non-persistent TxUpdatedInterval after runtime reset', async () => {
+ await it('should revert non-persistent TxUpdatedInterval after runtime reset', async () => {
const txValue = '77'
const setRequest: OCPP20SetVariablesRequest = {
setVariableData: [
})
// FR: B07.FR.12
- await it('Should reject all SetVariables when ItemsPerMessage limit exceeded', () => {
+ await it('should reject all SetVariables when ItemsPerMessage limit exceeded', () => {
setStrictLimits(mockChargingStation, 1, 10000)
const request: OCPP20SetVariablesRequest = {
setVariableData: [
resetLimits(mockChargingStation)
})
- await it('Should reject all SetVariables when BytesPerMessage limit exceeded (pre-calculation)', () => {
+ await it('should reject all SetVariables when BytesPerMessage limit exceeded (pre-calculation)', () => {
// Set strict bytes limit low enough for request pre-estimate to exceed
setStrictLimits(mockChargingStation, 100, 10)
const request: OCPP20SetVariablesRequest = {
resetLimits(mockChargingStation)
})
- await it('Should reject all SetVariables when BytesPerMessage limit exceeded (post-calculation)', () => {
+ await it('should reject all SetVariables when BytesPerMessage limit exceeded (post-calculation)', () => {
const request: OCPP20SetVariablesRequest = {
setVariableData: [
{
})
// Effective ConfigurationValueSize / ValueSize propagation tests
- await it('Should enforce ConfigurationValueSize when ValueSize unset (service propagation)', () => {
+ await it('should enforce ConfigurationValueSize when ValueSize unset (service propagation)', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 100)
upsertConfigurationKey(mockChargingStation, OCPP20RequiredVariableName.ValueSize, '')
resetValueSizeLimits(mockChargingStation)
})
- await it('Should enforce ValueSize when ConfigurationValueSize unset (service propagation)', () => {
+ await it('should enforce ValueSize when ConfigurationValueSize unset (service propagation)', () => {
resetValueSizeLimits(mockChargingStation)
upsertConfigurationKey(
mockChargingStation,
resetValueSizeLimits(mockChargingStation)
})
- await it('Should use smaller ValueSize when ValueSize < ConfigurationValueSize (service propagation)', () => {
+ await it('should use smaller ValueSize when ValueSize < ConfigurationValueSize (service propagation)', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 400)
setValueSize(mockChargingStation, 350)
resetValueSizeLimits(mockChargingStation)
})
- await it('Should use smaller ConfigurationValueSize when ConfigurationValueSize < ValueSize (service propagation)', () => {
+ await it('should use smaller ConfigurationValueSize when ConfigurationValueSize < ValueSize (service propagation)', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 260)
setValueSize(mockChargingStation, 500)
resetValueSizeLimits(mockChargingStation)
})
- await it('Should fallback to default absolute max length when both limits invalid/non-positive', () => {
+ await it('should fallback to default absolute max length when both limits invalid/non-positive', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 0)
setValueSize(mockChargingStation, -5)
})
// FR: B07.FR.12 (updated behavior: ConnectionUrl now readable after set)
- await it('Should allow ConnectionUrl read-back after setting', () => {
+ await it('should allow ConnectionUrl read-back after setting', () => {
resetLimits(mockChargingStation)
const url = 'wss://central.example.com/ocpp'
const setRequest: OCPP20SetVariablesRequest = {
resetLimits(mockChargingStation)
})
- await it('Should accept ConnectionUrl with custom mqtt scheme (no scheme restriction)', () => {
+ await it('should accept ConnectionUrl with custom mqtt scheme (no scheme restriction)', () => {
resetLimits(mockChargingStation)
const url = 'mqtt://broker.internal:1883/ocpp'
const setRequest: OCPP20SetVariablesRequest = {
})
// FR: B01.FR.01
- await it('Should build BootNotification request payload correctly with PowerUp reason', () => {
+ await it('should build BootNotification request payload correctly with PowerUp reason', () => {
const chargingStationInfo: ChargingStationType = {
firmwareVersion: TEST_FIRMWARE_VERSION,
model: TEST_CHARGE_POINT_MODEL,
})
// FR: B01.FR.02
- await it('Should build BootNotification request payload correctly with ApplicationReset reason', () => {
+ await it('should build BootNotification request payload correctly with ApplicationReset reason', () => {
const chargingStationInfo: ChargingStationType = {
firmwareVersion: '2.1.3',
model: 'Advanced Model X1',
})
// FR: B01.FR.03
- await it('Should build BootNotification request payload correctly with minimal required fields', () => {
+ await it('should build BootNotification request payload correctly with minimal required fields', () => {
const chargingStationInfo: ChargingStationType = {
model: 'Basic Model',
vendorName: 'Basic Vendor',
})
// FR: B01.FR.04
- await it('Should handle all BootReasonEnumType values correctly', () => {
+ await it('should handle all BootReasonEnumType values correctly', () => {
const chargingStationInfo: ChargingStationType = {
model: TEST_CHARGE_POINT_MODEL,
vendorName: TEST_CHARGE_POINT_VENDOR,
})
// FR: B01.FR.05
- await it('Should validate payload structure matches OCPP20BootNotificationRequest interface', () => {
+ await it('should validate payload structure matches OCPP20BootNotificationRequest interface', () => {
const chargingStationInfo: ChargingStationType = {
customData: {
vendorId: 'TEST_VENDOR',
})
// FR: G02.FR.01
- await it('Should build HeartBeat request payload correctly with empty object', () => {
+ await it('should build HeartBeat request payload correctly with empty object', () => {
const requestParams: OCPP20HeartbeatRequest = {}
// Access the private buildRequestPayload method via type assertion
})
// FR: G02.FR.02
- await it('Should build HeartBeat request payload correctly without parameters', () => {
+ await it('should build HeartBeat request payload correctly without parameters', () => {
// Test without passing any request parameters
const payload = (requestService as any).buildRequestPayload(
mockChargingStation,
})
// FR: G02.FR.03
- await it('Should validate payload structure matches OCPP20HeartbeatRequest interface', () => {
+ await it('should validate payload structure matches OCPP20HeartbeatRequest interface', () => {
const requestParams: OCPP20HeartbeatRequest = {}
const payload = (requestService as any).buildRequestPayload(
})
// FR: G02.FR.04
- await it('Should handle HeartBeat request consistently across multiple calls', () => {
+ await it('should handle HeartBeat request consistently across multiple calls', () => {
const requestParams: OCPP20HeartbeatRequest = {}
// Call buildRequestPayload multiple times to ensure consistency
})
// FR: G02.FR.05
- await it('Should handle HeartBeat request with different charging station configurations', () => {
+ await it('should handle HeartBeat request with different charging station configurations', () => {
const alternativeChargingStation = createChargingStation({
baseName: 'CS-ALTERNATIVE-002',
connectorsCount: 3,
})
// FR: G02.FR.06
- await it('Should verify HeartBeat request conforms to OCPP 2.0 specification', () => {
+ await it('should verify HeartBeat request conforms to OCPP 2.0 specification', () => {
const requestParams: OCPP20HeartbeatRequest = {}
const payload = (requestService as any).buildRequestPayload(
})
await describe('EXI Install Action', async () => {
- await it('Should forward EXI request unmodified for Install action', async () => {
+ await it('should forward EXI request unmodified for Install action', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
})
await describe('EXI Update Action', async () => {
- await it('Should forward EXI request unmodified for Update action', async () => {
+ await it('should forward EXI request unmodified for Update action', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
})
await describe('CSMS Response Handling', async () => {
- await it('Should return Accepted response with exiResponse from CSMS', async () => {
+ await it('should return Accepted response with exiResponse from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
exiResponse: MOCK_EXI_RESPONSE,
expect(response.exiResponse).toBe(MOCK_EXI_RESPONSE)
})
- await it('Should return Failed response from CSMS', async () => {
+ await it('should return Failed response from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
exiResponse: '',
})
await describe('Schema Version Parameter', async () => {
- await it('Should pass schema version correctly', async () => {
+ await it('should pass schema version correctly', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
})
await describe('Base64 EXI Pass-Through', async () => {
- await it('Should pass Base64 EXI string unchanged', async () => {
+ await it('should pass Base64 EXI string unchanged', async () => {
const complexBase64EXI =
'VGhpcyBpcyBhIG1vcmUgY29tcGxleCBFWEkgcGF5bG9hZCB3aXRoIHNwZWNpYWwgY2hhcmFjdGVycyArLz0='
})
await describe('OCSP Request Data', async () => {
- await it('Should send OCSP request data correctly', async () => {
+ await it('should send OCSP request data correctly', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20GetCertificateStatusResponse>({
sendMessageResponse: {
})
await describe('CSMS Response Handling', async () => {
- await it('Should return Accepted response with ocspResult from CSMS', async () => {
+ await it('should return Accepted response with ocspResult from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20GetCertificateStatusResponse>({
sendMessageResponse: {
ocspResult: MOCK_OCSP_RESULT,
expect(response.ocspResult).toBe(MOCK_OCSP_RESULT)
})
- await it('Should return Failed response from CSMS', async () => {
+ await it('should return Failed response from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20GetCertificateStatusResponse>({
sendMessageResponse: {
status: GetCertificateStatusEnumType.Failed,
})
await describe('Stub OCSP Response', async () => {
- await it('Should handle stub OCSP response correctly', async () => {
+ await it('should handle stub OCSP response correctly', async () => {
// This tests that the simulator doesn't make real network calls
// Response is stubbed/mocked at the sendMessage level
const stubOcspResult = 'U3R1YiBPQ1NQIFJlc3BvbnNlIERhdGE='
websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
})
- await it('Should send GET_15118_EV_CERTIFICATE command name', async () => {
+ await it('should send GET_15118_EV_CERTIFICATE command name', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20Get15118EVCertificateResponse>({
sendMessageResponse: {
expect(commandName).toBe(OCPP20RequestCommand.GET_15118_EV_CERTIFICATE)
})
- await it('Should send GET_CERTIFICATE_STATUS command name', async () => {
+ await it('should send GET_CERTIFICATE_STATUS command name', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20GetCertificateStatusResponse>({
sendMessageResponse: {
})
// FR: B07.FR.03, B07.FR.04
- await it('Should build NotifyReport request payload correctly with minimal required fields', () => {
+ await it('should build NotifyReport request payload correctly with minimal required fields', () => {
const requestParams: OCPP20NotifyReportRequest = {
generatedAt: new Date('2023-10-22T10:30:00.000Z'),
requestId: 123,
expect(payload.reportData).toBeUndefined()
})
- await it('Should build NotifyReport request payload correctly with reportData', () => {
+ await it('should build NotifyReport request payload correctly with reportData', () => {
const reportData: ReportDataType[] = [
{
component: {
expect(payload.reportData).toHaveLength(1)
})
- await it('Should build NotifyReport request payload correctly with multiple reportData items', () => {
+ await it('should build NotifyReport request payload correctly with multiple reportData items', () => {
const reportData: ReportDataType[] = [
{
component: {
expect(payload.reportData).toHaveLength(3)
})
- await it('Should build NotifyReport request payload correctly with fragmented report (tbc=true)', () => {
+ await it('should build NotifyReport request payload correctly with fragmented report (tbc=true)', () => {
const reportData: ReportDataType[] = [
{
component: {
expect(payload.reportData).toHaveLength(1)
})
- await it('Should build NotifyReport request payload correctly with empty reportData array', () => {
+ await it('should build NotifyReport request payload correctly with empty reportData array', () => {
const requestParams: OCPP20NotifyReportRequest = {
generatedAt: new Date('2023-10-22T09:00:00.000Z'),
reportData: [], // Empty array
expect(payload.reportData).toHaveLength(0)
})
- await it('Should handle different AttributeEnumType values correctly', () => {
+ await it('should handle different AttributeEnumType values correctly', () => {
const testAttributes = [AttributeEnumType.Actual]
testAttributes.forEach((attributeType, index) => {
})
})
- await it('Should handle different DataEnumType values correctly', () => {
+ await it('should handle different DataEnumType values correctly', () => {
const testDataTypes = [
{ dataType: DataEnumType.string, value: 'test string' },
{ dataType: DataEnumType.integer, value: '42' },
})
})
- await it('Should validate payload structure matches OCPP20NotifyReportRequest interface', () => {
+ await it('should validate payload structure matches OCPP20NotifyReportRequest interface', () => {
const reportData: ReportDataType[] = [
{
component: {
}
})
- await it('Should handle complex reportData with multiple variable attributes', () => {
+ await it('should handle complex reportData with multiple variable attributes', () => {
const reportData: ReportDataType[] = [
{
component: {
expect(payload.reportData[0].variableAttribute[0].type).toBe(AttributeEnumType.Actual)
})
- await it('Should preserve all payload properties correctly', () => {
+ await it('should preserve all payload properties correctly', () => {
const testDate = new Date('2023-10-22T11:22:33.444Z')
const reportData: ReportDataType[] = [
{
}
await describe('CSR Generation', async () => {
- await it('Should generate CSR with PKCS#10 PEM format', async () => {
+ await it('should generate CSR with PKCS#10 PEM format', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
expect(sentPayload.csr).toContain('-----END CERTIFICATE REQUEST-----')
})
- await it('Should include OrganizationName from SecurityCtrlr config in CSR', async () => {
+ await it('should include OrganizationName from SecurityCtrlr config in CSR', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
})
await describe('ChargingStationCertificate Type', async () => {
- await it('Should send SignCertificateRequest with ChargingStationCertificate type', async () => {
+ await it('should send SignCertificateRequest with ChargingStationCertificate type', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
})
await describe('V2GCertificate Type', async () => {
- await it('Should send SignCertificateRequest with V2GCertificate type', async () => {
+ await it('should send SignCertificateRequest with V2GCertificate type', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
})
await describe('CSMS Response Handling', async () => {
- await it('Should return Accepted response from CSMS', async () => {
+ await it('should return Accepted response from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
status: GenericStatus.Accepted,
expect(response.status).toBe(GenericStatus.Accepted)
})
- await it('Should return Rejected response from CSMS', async () => {
+ await it('should return Rejected response from CSMS', async () => {
const { service } = createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
status: GenericStatus.Rejected,
})
await describe('Optional Certificate Type', async () => {
- await it('Should send SignCertificateRequest without certificateType when omitted', async () => {
+ await it('should send SignCertificateRequest without certificateType when omitted', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
})
await describe('Request Payload Validation', async () => {
- await it('Should build valid OCPP20SignCertificateRequest payload', async () => {
+ await it('should build valid OCPP20SignCertificateRequest payload', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
expect(sentPayload.csr.length).toBeLessThanOrEqual(5500) // Max length per schema
})
- await it('Should send SIGN_CERTIFICATE command name', async () => {
+ await it('should send SIGN_CERTIFICATE command name', async () => {
const { sendMessageMock, service } =
createTestableRequestService<OCPP20SignCertificateResponse>({
sendMessageResponse: {
})
await describe('Error Handling', async () => {
- await it('Should generate CSR without certificate manager dependency', async () => {
+ await it('should generate CSR without certificate manager dependency', async () => {
const stationWithoutCertManager = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 1,
})
// FR: G01.FR.01
- await it('Should build StatusNotification request payload correctly with Available status', () => {
+ await it('should build StatusNotification request payload correctly with Available status', () => {
const testTimestamp = new Date('2024-01-15T10:30:00.000Z')
const requestParams: OCPP20StatusNotificationRequest = {
})
// FR: G01.FR.02
- await it('Should build StatusNotification request payload correctly with Occupied status', () => {
+ await it('should build StatusNotification request payload correctly with Occupied status', () => {
const testTimestamp = new Date('2024-01-15T11:45:30.000Z')
const requestParams: OCPP20StatusNotificationRequest = {
})
// FR: G01.FR.03
- await it('Should build StatusNotification request payload correctly with Faulted status', () => {
+ await it('should build StatusNotification request payload correctly with Faulted status', () => {
const testTimestamp = new Date('2024-01-15T12:15:45.500Z')
const requestParams: OCPP20StatusNotificationRequest = {
})
// FR: G01.FR.04
- await it('Should handle all OCPP20ConnectorStatusEnumType values correctly', () => {
+ await it('should handle all OCPP20ConnectorStatusEnumType values correctly', () => {
const testTimestamp = new Date('2024-01-15T13:00:00.000Z')
const statusValues = [
})
// FR: G01.FR.05
- await it('Should validate payload structure matches OCPP20StatusNotificationRequest interface', () => {
+ await it('should validate payload structure matches OCPP20StatusNotificationRequest interface', () => {
const testTimestamp = new Date('2024-01-15T14:30:15.123Z')
const requestParams: OCPP20StatusNotificationRequest = {
})
// FR: G01.FR.06
- await it('Should handle edge case connector and EVSE IDs correctly', () => {
+ await it('should handle edge case connector and EVSE IDs correctly', () => {
const testTimestamp = new Date('2024-01-15T15:45:00.000Z')
// Test with connector ID 0 (valid in OCPP 2.0 for the charging station itself)
})
// FR: G01.FR.07
- await it('Should handle different timestamp formats correctly', () => {
+ await it('should handle different timestamp formats correctly', () => {
const testCases = [
new Date('2024-01-01T00:00:00.000Z'), // Start of year
new Date('2024-12-31T23:59:59.999Z'), // End of year
// E02.FR.01: Cable Plug Event Flow Tests
// =========================================================================
await describe('Cable Plug Event Sequencing', async () => {
- await it('Should generate CablePluggedIn event as first event in cable-first flow', () => {
+ await it('should generate CablePluggedIn event as first event in cable-first flow', () => {
const connectorId = 1
const transactionId = generateUUID()
expect(cablePluggedEvent.transactionInfo.transactionId).toBe(transactionId)
})
- await it('Should sequence CablePluggedIn → EVDetected → Charging correctly', () => {
+ await it('should sequence CablePluggedIn → EVDetected → Charging correctly', () => {
const connectorId = 1
const transactionId = generateUUID()
expect(chargingStartedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
})
- await it('Should handle EVDeparted for cable removal ending transaction', () => {
+ await it('should handle EVDeparted for cable removal ending transaction', () => {
const connectorId = 2
const transactionId = generateUUID()
// E02.FR.02: EV Detection Flow Tests
// =========================================================================
await describe('EV Detection Flow', async () => {
- await it('Should include EVDetected between cable plug and charging start', () => {
+ await it('should include EVDetected between cable plug and charging start', () => {
const connectorId = 1
const transactionId = generateUUID()
// E02.FR.03: Connector Status Transitions
// =========================================================================
await describe('Connector Status Transitions', async () => {
- await it('Should track connector status through cable-first lifecycle', () => {
+ await it('should track connector status through cable-first lifecycle', () => {
const connectorId = 1
// Get connector status object
expect(connectorStatus.transactionStarted).toBe(false)
})
- await it('Should preserve transaction ID through cable-first flow states', () => {
+ await it('should preserve transaction ID through cable-first flow states', () => {
const connectorId = 2
const transactionId = generateUUID()
// Full E02 Transaction Lifecycle Tests
// =========================================================================
await describe('Full Cable-First Transaction Lifecycle', async () => {
- await it('Should support complete cable-first → charging → cable-removal flow', () => {
+ await it('should support complete cable-first → charging → cable-removal flow', () => {
const connectorId = 1
const transactionId = generateUUID()
expect(lifecycle.evDeparted.transactionInfo.transactionId).toBe(transactionId)
})
- await it('Should handle suspended charging states in cable-first flow', () => {
+ await it('should handle suspended charging states in cable-first flow', () => {
const connectorId = 3
const transactionId = generateUUID()
// Context-Based Trigger Reason Selection for Cable Events
// =========================================================================
await describe('Context-Based Cable Event Trigger Selection', async () => {
- await it('Should select CablePluggedIn from cable_action context with plugged_in state', () => {
+ await it('should select CablePluggedIn from cable_action context with plugged_in state', () => {
const context: OCPP20TransactionContext = {
cableState: 'plugged_in',
source: 'cable_action',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
})
- await it('Should select EVDetected from cable_action context with detected state', () => {
+ await it('should select EVDetected from cable_action context with detected state', () => {
const context: OCPP20TransactionContext = {
cableState: 'detected',
source: 'cable_action',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected)
})
- await it('Should select EVDeparted from cable_action context with unplugged state', () => {
+ await it('should select EVDeparted from cable_action context with unplugged state', () => {
const context: OCPP20TransactionContext = {
cableState: 'unplugged',
source: 'cable_action',
// Multiple Connector Independence Tests
// =========================================================================
await describe('Multiple Connector Independence', async () => {
- await it('Should maintain independent transaction sequences on different connectors', () => {
+ await it('should maintain independent transaction sequences on different connectors', () => {
const transactionId1 = generateUUID()
const transactionId2 = generateUUID()
// E03.FR.13: Trigger Reason Selection for IdToken-First
// =========================================================================
await describe('E03.FR.13 - Trigger Reason Selection', async () => {
- await it('Should select Authorized trigger for IdToken-first transaction start', () => {
+ await it('should select Authorized trigger for IdToken-first transaction start', () => {
// E03.FR.13: triggerReason SHALL be Authorized for IdToken-first
const context: OCPP20TransactionContext = {
authorizationMethod: 'idToken',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
})
- await it('Should select groupIdToken trigger for group authorization', () => {
+ await it('should select groupIdToken trigger for group authorization', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'groupIdToken',
source: 'local_authorization',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
})
- await it('Should differentiate IdToken-first from Cable-first by trigger reason', () => {
+ await it('should differentiate IdToken-first from Cable-first by trigger reason', () => {
// IdToken-first: Authorized trigger
const idTokenFirstContext: OCPP20TransactionContext = {
authorizationMethod: 'idToken',
// E03.FR.01: IdToken Inclusion in TransactionEvent
// =========================================================================
await describe('E03.FR.01 - IdToken in TransactionEvent', async () => {
- await it('Should include idToken in first TransactionEvent after authorization', () => {
+ await it('should include idToken in first TransactionEvent after authorization', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
expect(startedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
})
- await it('Should not include idToken in subsequent events (E03.FR.01 compliance)', () => {
+ await it('should not include idToken in subsequent events (E03.FR.01 compliance)', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
expect(updatedEvent.idToken).toBeUndefined()
})
- await it('Should support various IdToken types for E03 flow', () => {
+ await it('should support various IdToken types for E03 flow', () => {
const connectorId = 1
const transactionId = generateUUID()
// Full E03 IdToken-First Transaction Lifecycle
// =========================================================================
await describe('Full IdToken-First Transaction Lifecycle', async () => {
- await it('Should support complete IdToken-first to cable to charging to end flow', () => {
+ await it('should support complete IdToken-first to cable to charging to end flow', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
expect(endedEvent.transactionInfo.transactionId).toBe(transactionId)
})
- await it('Should differentiate E03 lifecycle from E02 Cable-First lifecycle', () => {
+ await it('should differentiate E03 lifecycle from E02 Cable-First lifecycle', () => {
const connectorId = 1
const e03TransactionId = generateUUID()
const e02TransactionId = generateUUID()
// E03.FR.05/06: EVConnectionTimeOut Handling
// =========================================================================
await describe('E03.FR.05/06 - EVConnectionTimeOut', async () => {
- await it('Should support authorization cancellation event (cable not connected)', () => {
+ await it('should support authorization cancellation event (cable not connected)', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
)
})
- await it('Should track sequence numbers correctly for timeout scenario', () => {
+ await it('should track sequence numbers correctly for timeout scenario', () => {
const connectorId = 1
const transactionId = generateUUID()
// Authorization Status Handling
// =========================================================================
await describe('Authorization Status in E03 Flow', async () => {
- await it('Should support Deauthorized trigger for rejected authorization', () => {
+ await it('should support Deauthorized trigger for rejected authorization', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'idToken',
isDeauthorized: true,
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
})
- await it('Should handle transaction end after token revocation', () => {
+ await it('should handle transaction end after token revocation', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
expect(revokedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
})
- await it('Should support StopAuthorized trigger for normal transaction end', () => {
+ await it('should support StopAuthorized trigger for normal transaction end', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'stopAuthorized',
source: 'local_authorization',
// E03.FR.07/08: Sequence Numbers and Transaction ID
// =========================================================================
await describe('E03.FR.07/08 - Sequence Numbers and Transaction ID', async () => {
- await it('Should maintain continuous sequence numbers throughout E03 lifecycle', () => {
+ await it('should maintain continuous sequence numbers throughout E03 lifecycle', () => {
const connectorId = 1
const transactionId = generateUUID()
const idToken: OCPP20IdTokenType = {
})
})
- await it('Should use unique transaction ID (E03.FR.08)', () => {
+ await it('should use unique transaction ID (E03.FR.08)', () => {
const connectorId = 1
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockChargingStation, connectorId)
// Multiple Connector Independence
// =========================================================================
await describe('Multiple Connector Independence in E03 Flow', async () => {
- await it('Should handle independent E03 transactions on different connectors', () => {
+ await it('should handle independent E03 transactions on different connectors', () => {
const connector1 = 1
const connector2 = 2
const transaction1Id = generateUUID()
})
await describe('Queue formation when offline', async () => {
- await it('Should queue TransactionEvent when WebSocket is disconnected', async () => {
+ await it('should queue TransactionEvent when WebSocket is disconnected', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(connector.transactionEventQueue[0].seqNo).toBe(0)
})
- await it('Should queue multiple TransactionEvents in order when offline', async () => {
+ await it('should queue multiple TransactionEvents in order when offline', async () => {
const connectorId = 1
const transactionId = generateUUID()
)
})
- await it('Should preserve seqNo in queued events', async () => {
+ await it('should preserve seqNo in queued events', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(connector.transactionEventQueue[1].seqNo).toBe(2)
})
- await it('Should include timestamp in queued events', async () => {
+ await it('should include timestamp in queued events', async () => {
const connectorId = 1
const transactionId = generateUUID()
})
await describe('Queue draining when coming online', async () => {
- await it('Should send all queued events when sendQueuedTransactionEvents is called', async () => {
+ await it('should send all queued events when sendQueuedTransactionEvents is called', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(sentRequests[1].payload.seqNo).toBe(1)
})
- await it('Should clear queue after sending', async () => {
+ await it('should clear queue after sending', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(connector.transactionEventQueue.length).toBe(0)
})
- await it('Should preserve FIFO order when draining queue', async () => {
+ await it('should preserve FIFO order when draining queue', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(sentRequests[2].payload.seqNo).toBe(2)
})
- await it('Should handle empty queue gracefully', async () => {
+ await it('should handle empty queue gracefully', async () => {
const connectorId = 1
await expect(
expect(sentRequests.length).toBe(0)
})
- await it('Should handle null queue gracefully', async () => {
+ await it('should handle null queue gracefully', async () => {
const connectorId = 1
const connector = mockChargingStation.getConnectorStatus(connectorId)
connector.transactionEventQueue = undefined
})
await describe('Sequence number continuity across queue boundary', async () => {
- await it('Should maintain seqNo continuity: online → offline → online', async () => {
+ await it('should maintain seqNo continuity: online → offline → online', async () => {
const connectorId = 1
const transactionId = generateUUID()
})
await describe('Multiple connectors with independent queues', async () => {
- await it('Should maintain separate queues for each connector', async () => {
+ await it('should maintain separate queues for each connector', async () => {
const transactionId1 = generateUUID()
const transactionId2 = generateUUID()
)
})
- await it('Should drain queues independently per connector', async () => {
+ await it('should drain queues independently per connector', async () => {
const transactionId1 = generateUUID()
const transactionId2 = generateUUID()
})
await describe('Error handling during queue drain', async () => {
- await it('Should continue sending remaining events if one fails', async () => {
+ await it('should continue sending remaining events if one fails', async () => {
const connectorId = 1
const transactionId = generateUUID()
let callCount = 0
})
await describe('startTxUpdatedInterval', async () => {
- await it('Should not start timer for non-OCPP 2.0 stations', () => {
+ await it('should not start timer for non-OCPP 2.0 stations', () => {
const ocpp16Station = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 1,
expect(connector?.transactionTxUpdatedSetInterval).toBeUndefined()
})
- await it('Should not start timer when interval is zero', () => {
+ await it('should not start timer when interval is zero', () => {
const connectorId = 1
// Simulate startTxUpdatedInterval with zero interval
expect(connector.transactionTxUpdatedSetInterval).toBeUndefined()
})
- await it('Should not start timer when interval is negative', () => {
+ await it('should not start timer when interval is negative', () => {
const connectorId = 1
const connector = mockChargingStation.getConnectorStatus(connectorId)
expect(connector).toBeDefined()
expect(connector.transactionTxUpdatedSetInterval).toBeUndefined()
})
- await it('Should handle non-existent connector gracefully', () => {
+ await it('should handle non-existent connector gracefully', () => {
const nonExistentConnectorId = 999
// Should not throw for non-existent connector
})
await describe('Periodic TransactionEvent generation', async () => {
- await it('Should send TransactionEvent with MeterValuePeriodic trigger reason', async () => {
+ await it('should send TransactionEvent with MeterValuePeriodic trigger reason', async () => {
const connectorId = 1
const transactionId = generateUUID()
)
})
- await it('Should increment seqNo for each periodic event', async () => {
+ await it('should increment seqNo for each periodic event', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(connector?.transactionSeqNo).toBe(3)
})
- await it('Should maintain correct eventType (Updated) for periodic events', async () => {
+ await it('should maintain correct eventType (Updated) for periodic events', async () => {
const connectorId = 2
const transactionId = generateUUID()
expect(sentRequests[0].payload.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
})
- await it('Should include EVSE information in periodic events', async () => {
+ await it('should include EVSE information in periodic events', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(sentRequests[0].payload.evse.id).toBe(connectorId)
})
- await it('Should include transactionInfo with correct transactionId', async () => {
+ await it('should include transactionInfo with correct transactionId', async () => {
const connectorId = 1
const transactionId = generateUUID()
})
await describe('Timer lifecycle integration', async () => {
- await it('Should continue seqNo sequence across multiple periodic events', async () => {
+ await it('should continue seqNo sequence across multiple periodic events', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(endEvent.seqNo).toBe(4)
})
- await it('Should handle multiple connectors with independent timers', async () => {
+ await it('should handle multiple connectors with independent timers', async () => {
const transactionId1 = generateUUID()
const transactionId2 = generateUUID()
})
await describe('Error handling', async () => {
- await it('Should handle network errors gracefully during periodic event', async () => {
+ await it('should handle network errors gracefully during periodic event', async () => {
const errorMockChargingStation = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 1,
// FR: E01.FR.01 - TransactionEventRequest structure validation
await describe('buildTransactionEvent', async () => {
- await it('Should build valid TransactionEvent Started with sequence number 0', () => {
+ await it('should build valid TransactionEvent Started with sequence number 0', () => {
const connectorId = 1
const transactionId = generateUUID()
const triggerReason = OCPP20TriggerReasonEnumType.Authorized
expect(transactionEvent.seqNo).toBeGreaterThanOrEqual(0)
})
- await it('Should increment sequence number for subsequent events', () => {
+ await it('should increment sequence number for subsequent events', () => {
const connectorId = 2
const transactionId = generateUUID()
expect(endEvent.transactionInfo.transactionId).toBe(transactionId)
})
- await it('Should handle optional parameters correctly', () => {
+ await it('should handle optional parameters correctly', () => {
const connectorId = 3
const transactionId = generateUUID()
const options = {
expect(transactionEvent.reservationId).toBe(67890)
})
- await it('Should validate transaction ID format (identifier string ≤36 chars)', () => {
+ await it('should validate transaction ID format (identifier string ≤36 chars)', () => {
const connectorId = 1
const invalidTransactionId =
'this-string-is-way-too-long-for-a-valid-transaction-id-exceeds-36-chars'
}
})
- await it('Should handle all TriggerReason enum values', () => {
+ await it('should handle all TriggerReason enum values', () => {
const connectorId = 1
const transactionId = generateUUID()
// FR: E02.FR.01 - TransactionEventRequest message sending
await describe('sendTransactionEvent', async () => {
- await it('Should send TransactionEvent and return response', async () => {
+ await it('should send TransactionEvent and return response', async () => {
const connectorId = 1
const transactionId = generateUUID()
expect(typeof response).toBe('object')
})
- await it('Should handle errors gracefully', async () => {
+ await it('should handle errors gracefully', async () => {
// Create a mock charging station that throws an error
const errorMockChargingStation = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
// FR: E01.FR.03 - Sequence number management
await describe('resetTransactionSequenceNumber', async () => {
- await it('Should reset sequence number to undefined', () => {
+ await it('should reset sequence number to undefined', () => {
const connectorId = 1
// First, build a transaction event to set sequence number
expect(connectorStatus?.transactionSeqNo).toBeUndefined()
})
- await it('Should handle non-existent connector gracefully', () => {
+ await it('should handle non-existent connector gracefully', () => {
const nonExistentConnectorId = 999
// Should not throw error for non-existent connector
// FR: E01.FR.02 - Schema compliance verification
await describe('OCPP 2.0.1 Schema Compliance', async () => {
- await it('Should produce schema-compliant TransactionEvent payloads', () => {
+ await it('should produce schema-compliant TransactionEvent payloads', () => {
const connectorId = 1
const transactionId = generateUUID()
expect(Object.values(OCPP20TriggerReasonEnumType)).toContain(transactionEvent.triggerReason)
})
- await it('Should handle EVSE/connector mapping correctly', () => {
+ await it('should handle EVSE/connector mapping correctly', () => {
const connectorId = 2
const transactionId = generateUUID()
// FR: E01.FR.04 - TriggerReason selection based on transaction context
await describe('Context-Aware TriggerReason Selection', async () => {
await describe('selectTriggerReason', async () => {
- await it('Should select RemoteStart for remote_command context with RequestStartTransaction', () => {
+ await it('should select RemoteStart for remote_command context with RequestStartTransaction', () => {
const context: OCPP20TransactionContext = {
command: 'RequestStartTransaction',
source: 'remote_command',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart)
})
- await it('Should select RemoteStop for remote_command context with RequestStopTransaction', () => {
+ await it('should select RemoteStop for remote_command context with RequestStopTransaction', () => {
const context: OCPP20TransactionContext = {
command: 'RequestStopTransaction',
source: 'remote_command',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStop)
})
- await it('Should select UnlockCommand for remote_command context with UnlockConnector', () => {
+ await it('should select UnlockCommand for remote_command context with UnlockConnector', () => {
const context: OCPP20TransactionContext = {
command: 'UnlockConnector',
source: 'remote_command',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.UnlockCommand)
})
- await it('Should select ResetCommand for remote_command context with Reset', () => {
+ await it('should select ResetCommand for remote_command context with Reset', () => {
const context: OCPP20TransactionContext = {
command: 'Reset',
source: 'remote_command',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ResetCommand)
})
- await it('Should select Trigger for remote_command context with TriggerMessage', () => {
+ await it('should select Trigger for remote_command context with TriggerMessage', () => {
const context: OCPP20TransactionContext = {
command: 'TriggerMessage',
source: 'remote_command',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger)
})
- await it('Should select Authorized for local_authorization context with idToken', () => {
+ await it('should select Authorized for local_authorization context with idToken', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'idToken',
source: 'local_authorization',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
})
- await it('Should select StopAuthorized for local_authorization context with stopAuthorized', () => {
+ await it('should select StopAuthorized for local_authorization context with stopAuthorized', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'stopAuthorized',
source: 'local_authorization',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.StopAuthorized)
})
- await it('Should select Deauthorized when isDeauthorized flag is true', () => {
+ await it('should select Deauthorized when isDeauthorized flag is true', () => {
const context: OCPP20TransactionContext = {
authorizationMethod: 'idToken',
isDeauthorized: true,
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
})
- await it('Should select CablePluggedIn for cable_action context with plugged_in', () => {
+ await it('should select CablePluggedIn for cable_action context with plugged_in', () => {
const context: OCPP20TransactionContext = {
cableState: 'plugged_in',
source: 'cable_action',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
})
- await it('Should select EVDetected for cable_action context with detected', () => {
+ await it('should select EVDetected for cable_action context with detected', () => {
const context: OCPP20TransactionContext = {
cableState: 'detected',
source: 'cable_action',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected)
})
- await it('Should select EVDeparted for cable_action context with unplugged', () => {
+ await it('should select EVDeparted for cable_action context with unplugged', () => {
const context: OCPP20TransactionContext = {
cableState: 'unplugged',
source: 'cable_action',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted)
})
- await it('Should select ChargingStateChanged for charging_state context', () => {
+ await it('should select ChargingStateChanged for charging_state context', () => {
const context: OCPP20TransactionContext = {
chargingStateChange: {
from: OCPP20ChargingStateEnumType.Idle,
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ChargingStateChanged)
})
- await it('Should select MeterValuePeriodic for meter_value context with periodic flag', () => {
+ await it('should select MeterValuePeriodic for meter_value context with periodic flag', () => {
const context: OCPP20TransactionContext = {
isPeriodicMeterValue: true,
source: 'meter_value',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValuePeriodic)
})
- await it('Should select MeterValueClock for meter_value context without periodic flag', () => {
+ await it('should select MeterValueClock for meter_value context without periodic flag', () => {
const context: OCPP20TransactionContext = {
isPeriodicMeterValue: false,
source: 'meter_value',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValueClock)
})
- await it('Should select SignedDataReceived when isSignedDataReceived flag is true', () => {
+ await it('should select SignedDataReceived when isSignedDataReceived flag is true', () => {
const context: OCPP20TransactionContext = {
isSignedDataReceived: true,
source: 'meter_value',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.SignedDataReceived)
})
- await it('Should select appropriate system events for system_event context', () => {
+ await it('should select appropriate system events for system_event context', () => {
const testCases = [
{ expected: OCPP20TriggerReasonEnumType.EVDeparted, systemEvent: 'ev_departed' as const },
{ expected: OCPP20TriggerReasonEnumType.EVDetected, systemEvent: 'ev_detected' as const },
}
})
- await it('Should select EnergyLimitReached for energy_limit context', () => {
+ await it('should select EnergyLimitReached for energy_limit context', () => {
const context: OCPP20TransactionContext = {
source: 'energy_limit',
}
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EnergyLimitReached)
})
- await it('Should select TimeLimitReached for time_limit context', () => {
+ await it('should select TimeLimitReached for time_limit context', () => {
const context: OCPP20TransactionContext = {
source: 'time_limit',
}
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.TimeLimitReached)
})
- await it('Should select AbnormalCondition for abnormal_condition context', () => {
+ await it('should select AbnormalCondition for abnormal_condition context', () => {
const context: OCPP20TransactionContext = {
abnormalCondition: 'OverCurrent',
source: 'abnormal_condition',
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.AbnormalCondition)
})
- await it('Should handle priority ordering with multiple applicable contexts', () => {
+ await it('should handle priority ordering with multiple applicable contexts', () => {
// Test context with multiple applicable triggers - priority should be respected
const context: OCPP20TransactionContext = {
cableState: 'plugged_in', // Even lower priority
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart)
})
- await it('Should fallback to Trigger for unknown context source', () => {
+ await it('should fallback to Trigger for unknown context source', () => {
const context: OCPP20TransactionContext = {
source: 'unknown_source' as any, // Invalid source to test fallback
}
expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger)
})
- await it('Should fallback to Trigger for incomplete context', () => {
+ await it('should fallback to Trigger for incomplete context', () => {
const context: OCPP20TransactionContext = {
source: 'remote_command',
// Missing command field
})
await describe('buildTransactionEvent with context parameter', async () => {
- await it('Should build TransactionEvent with auto-selected TriggerReason from context', () => {
+ await it('should build TransactionEvent with auto-selected TriggerReason from context', () => {
const connectorId = 1
const transactionId = generateUUID()
const context: OCPP20TransactionContext = {
expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId)
})
- await it('Should pass through optional parameters correctly', () => {
+ await it('should pass through optional parameters correctly', () => {
const connectorId = 2
const transactionId = generateUUID()
const context: OCPP20TransactionContext = {
})
await describe('sendTransactionEvent with context parameter', async () => {
- await it('Should send TransactionEvent with context-aware TriggerReason selection', async () => {
+ await it('should send TransactionEvent with context-aware TriggerReason selection', async () => {
const connectorId = 1
const transactionId = generateUUID()
const context: OCPP20TransactionContext = {
expect(typeof response).toBe('object')
})
- await it('Should handle context-aware error scenarios gracefully', async () => {
+ await it('should handle context-aware error scenarios gracefully', async () => {
// Create error mock for this test
const errorMockChargingStation = createChargingStation({
baseName: TEST_CHARGING_STATION_BASE_NAME,
})
await describe('Backward Compatibility', async () => {
- await it('Should maintain compatibility with existing buildTransactionEvent calls', () => {
+ await it('should maintain compatibility with existing buildTransactionEvent calls', () => {
const connectorId = 1
const transactionId = generateUUID()
expect(oldEvent.seqNo).toBe(0)
})
- await it('Should maintain compatibility with existing sendTransactionEvent calls', async () => {
+ await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
const connectorId = 1
const transactionId = generateUUID()
OCPP20VariableManager.getInstance().resetRuntimeOverrides()
})
- await it('Verify that OCPP20VariableManager can be instantiated as singleton', () => {
+ await it('should verify that OCPP20VariableManager can be instantiated as singleton', () => {
const manager1 = OCPP20VariableManager.getInstance()
const manager2 = OCPP20VariableManager.getInstance()
await describe('getVariables method tests', async () => {
const manager = OCPP20VariableManager.getInstance()
- await it('Should handle valid OCPPCommCtrlr and TxCtrlr component requests', () => {
+ await it('should handle valid OCPPCommCtrlr and TxCtrlr component requests', () => {
const request: OCPP20GetVariableDataType[] = [
{
attributeType: AttributeEnumType.Actual,
expect(result[1].attributeStatusInfo).toBeUndefined()
})
- await it('Should accept default true value for AuthorizeRemoteStart (AuthCtrlr)', () => {
+ await it('should accept default true value for AuthorizeRemoteStart (AuthCtrlr)', () => {
const manager = OCPP20VariableManager.getInstance()
const request: OCPP20GetVariableDataType[] = [
{
expect(result[0].component.name).toBe(OCPP20ComponentName.AuthCtrlr)
})
- await it('Should accept setting and getting AuthorizeRemoteStart = true (AuthCtrlr)', () => {
+ await it('should accept setting and getting AuthorizeRemoteStart = true (AuthCtrlr)', () => {
const setRes = manager.setVariables(mockChargingStation, [
{
attributeValue: 'true',
expect(getRes.attributeValue).toBe('true')
})
- await it('Should reject invalid values for AuthorizeRemoteStart (AuthCtrlr)', () => {
+ await it('should reject invalid values for AuthorizeRemoteStart (AuthCtrlr)', () => {
const invalidValues = ['', '1', 'TRUE', 'False', 'yes']
for (const val of invalidValues) {
const res = manager.setVariables(mockChargingStation, [
}
})
- await it('Should handle invalid component gracefully', () => {
+ await it('should handle invalid component gracefully', () => {
const request: OCPP20GetVariableDataType[] = [
{
component: { name: 'InvalidComponent' as unknown as OCPP20ComponentName },
expect(result[0].attributeStatusInfo?.additionalInfo).toContain('Component InvalidComponent')
})
- await it('Should handle unsupported attribute type gracefully', () => {
+ await it('should handle unsupported attribute type gracefully', () => {
const request: OCPP20GetVariableDataType[] = [
{
attributeType: AttributeEnumType.Target, // Not supported for this variable
)
})
- await it('Should reject Target attribute for WebSocketPingInterval', () => {
+ await it('should reject Target attribute for WebSocketPingInterval', () => {
const request: OCPP20GetVariableDataType[] = [
{
attributeType: AttributeEnumType.Target,
expect(result[0].variable.name).toBe(OCPP20OptionalVariableName.WebSocketPingInterval)
})
- await it('Should handle non-existent connector instance', () => {
+ await it('should handle non-existent connector instance', () => {
const request: OCPP20GetVariableDataType[] = [
{
component: {
)
})
- await it('Should handle multiple variables in single request', () => {
+ await it('should handle multiple variables in single request', () => {
const request: OCPP20GetVariableDataType[] = [
{
component: { name: OCPP20ComponentName.OCPPCommCtrlr },
expect(result[2].attributeStatusInfo).toBeUndefined()
})
- await it('Should reject EVSE component as unsupported', () => {
+ await it('should reject EVSE component as unsupported', () => {
const request: OCPP20GetVariableDataType[] = [
{
component: {
const manager = OCPP20VariableManager.getInstance()
const testable = createTestableVariableManager(manager)
- await it('Should validate OCPPCommCtrlr component as always valid', () => {
+ await it('should validate OCPPCommCtrlr component as always valid', () => {
// Behavior: Connector components are unsupported and isComponentValid returns false.
// Scope: Per-connector variable validation not implemented; tests assert current behavior.
const component: ComponentType = { name: OCPP20ComponentName.OCPPCommCtrlr }
// Behavior: Connector component validation returns false (unsupported).
// Change process: Enable via OpenSpec proposal before altering this expectation.
- await it('Should reject Connector component as unsupported even when connectors exist', () => {
+ await it('should reject Connector component as unsupported even when connectors exist', () => {
const component: ComponentType = { instance: '1', name: OCPP20ComponentName.Connector }
const isValid = testable.isComponentValid(mockChargingStation, component)
expect(isValid).toBe(false)
})
- await it('Should reject invalid connector instance', () => {
+ await it('should reject invalid connector instance', () => {
const component: ComponentType = { instance: '999', name: OCPP20ComponentName.Connector }
const isValid = testable.isComponentValid(mockChargingStation, component)
const manager = OCPP20VariableManager.getInstance()
const testable = createTestableVariableManager(manager)
- await it('Should support standard HeartbeatInterval variable', () => {
+ await it('should support standard HeartbeatInterval variable', () => {
const component: ComponentType = { name: OCPP20ComponentName.OCPPCommCtrlr }
const variable: VariableType = { name: OCPP20OptionalVariableName.HeartbeatInterval }
expect(isSupported).toBe(true)
})
- await it('Should support known OCPP variables', () => {
+ await it('should support known OCPP variables', () => {
const component: ComponentType = { name: OCPP20ComponentName.ChargingStation }
const variable: VariableType = { name: OCPP20OptionalVariableName.WebSocketPingInterval }
expect(isSupported).toBe(true)
})
- await it('Should reject unknown variables', () => {
+ await it('should reject unknown variables', () => {
const component: ComponentType = { name: OCPP20ComponentName.OCPPCommCtrlr }
- const variable: VariableType = { name: 'UnknownVariable' as unknown as OCPP20OptionalVariableName }
+ const variable: VariableType = {
+ name: 'UnknownVariable' as unknown as OCPP20OptionalVariableName,
+ }
const isSupported = testable.isVariableSupported(component, variable)
expect(isSupported).toBe(false)
await describe('setVariables method tests', async () => {
const manager = OCPP20VariableManager.getInstance()
- await it('Should accept setting writable variables (Actual default)', () => {
+ await it('should accept setting writable variables (Actual default)', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeValue: (Constants.DEFAULT_WEBSOCKET_PING_INTERVAL + 1).toString(),
expect(result[1].attributeStatusInfo).toBeUndefined()
})
- await it('Should reject setting variable on unknown component', () => {
+ await it('should reject setting variable on unknown component', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeValue: '20',
expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
})
- await it('Should reject setting unknown variable', () => {
+ await it('should reject setting unknown variable', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeValue: '10',
expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
})
- await it('Should reject unsupported attribute type', () => {
+ await it('should reject unsupported attribute type', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeType: AttributeEnumType.Target,
expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
})
- await it('Should reject value exceeding max length', () => {
+ await it('should reject value exceeding max length', () => {
const longValue = 'x'.repeat(2501)
const request: OCPP20SetVariableDataType[] = [
{
)
})
- await it('Should handle multiple mixed SetVariables in one call', () => {
+ await it('should handle multiple mixed SetVariables in one call', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeValue: (Constants.DEFAULT_WEBSOCKET_PING_INTERVAL + 2).toString(),
expect(result[0].attributeStatusInfo).toBeUndefined()
})
- await it('Should reject TxUpdatedInterval zero and negative and non-integer', () => {
+ await it('should reject TxUpdatedInterval zero and negative and non-integer', () => {
const zeroReq: OCPP20SetVariableDataType[] = [
{
attributeValue: '0',
expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
})
- await it('Should accept setting ConnectionUrl with valid ws URL', () => {
+ await it('should accept setting ConnectionUrl with valid ws URL', () => {
const req: OCPP20SetVariableDataType[] = [
{
attributeValue: 'ws://example.com/ocpp',
expect(res.attributeStatusInfo).toBeUndefined()
})
- await it('Should accept ConnectionUrl with ftp scheme (no scheme restriction)', () => {
+ await it('should accept ConnectionUrl with ftp scheme (no scheme restriction)', () => {
const req: OCPP20SetVariableDataType[] = [
{
attributeValue: 'ftp://example.com/ocpp',
expect(res.attributeStatusInfo).toBeUndefined()
})
- await it('Should accept ConnectionUrl with custom mqtt scheme', () => {
+ await it('should accept ConnectionUrl with custom mqtt scheme', () => {
const req: OCPP20SetVariableDataType[] = [
{
attributeValue: 'mqtt://broker.example.com/ocpp',
expect(res.attributeStatusInfo).toBeUndefined()
})
- await it('Should allow ConnectionUrl retrieval after set', () => {
+ await it('should allow ConnectionUrl retrieval after set', () => {
manager.setVariables(mockChargingStation, [
{
attributeValue: 'wss://example.com/ocpp',
expect(getResult.attributeStatusInfo).toBeUndefined()
})
- await it('Should revert non-persistent TxUpdatedInterval after simulated restart', () => {
+ await it('should revert non-persistent TxUpdatedInterval after simulated restart', () => {
manager.setVariables(mockChargingStation, [
{
attributeValue: '99',
expect(afterReset.attributeValue).toBe('30')
})
- await it('Should keep persistent ConnectionUrl after simulated restart', () => {
+ await it('should keep persistent ConnectionUrl after simulated restart', () => {
manager.setVariables(mockChargingStation, [
{
attributeValue: 'https://central.example.com/ocpp',
expect(getResult.attributeStatusInfo).toBeUndefined()
})
- await it('Should reject Target attribute for WebSocketPingInterval', () => {
+ await it('should reject Target attribute for WebSocketPingInterval', () => {
const request: OCPP20SetVariableDataType[] = [
{
attributeType: AttributeEnumType.Target,
expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
})
- await it('Should validate HeartbeatInterval positive integer >0', () => {
+ await it('should validate HeartbeatInterval positive integer >0', () => {
const req: OCPP20SetVariableDataType[] = [
{
attributeValue: (
expect(res.attributeStatusInfo).toBeUndefined()
})
- await it('Should reject HeartbeatInterval zero, negative, non-integer', () => {
+ await it('should reject HeartbeatInterval zero, negative, non-integer', () => {
const zeroRes = manager.setVariables(mockChargingStation, [
{
attributeValue: '0',
expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
})
- await it('Should accept WebSocketPingInterval zero (disable) and positive', () => {
+ await it('should accept WebSocketPingInterval zero (disable) and positive', () => {
const zeroRes = manager.setVariables(mockChargingStation, [
{
attributeValue: '0',
expect(posRes.attributeStatusInfo).toBeUndefined()
})
- await it('Should reject WebSocketPingInterval negative and non-integer', () => {
+ await it('should reject WebSocketPingInterval negative and non-integer', () => {
const negRes = manager.setVariables(mockChargingStation, [
{
attributeValue: '-2',
expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Integer >= 0 required')
})
- await it('Should validate EVConnectionTimeOut positive integer >0 and reject invalid', () => {
+ await it('should validate EVConnectionTimeOut positive integer >0 and reject invalid', () => {
const okRes = manager.setVariables(mockChargingStation, [
{
attributeValue: (Constants.DEFAULT_EV_CONNECTION_TIMEOUT + 5).toString(),
expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
})
- await it('Should validate MessageTimeout positive integer >0 and reject invalid', () => {
+ await it('should validate MessageTimeout positive integer >0 and reject invalid', () => {
const okRes = manager.setVariables(mockChargingStation, [
{
attributeValue: (mockChargingStation.getConnectionTimeout() + 5).toString(),
expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
})
- await it('Should avoid duplicate persistence operations when value unchanged', () => {
+ await it('should avoid duplicate persistence operations when value unchanged', () => {
const keyBefore = getConfigurationKey(
mockChargingStation,
OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name']
expect(keyAfterRevert?.value).toBe(originalValue)
})
- await it('Should add missing configuration key with default during self-check', () => {
+ await it('should add missing configuration key with default during self-check', () => {
deleteConfigurationKey(
mockChargingStation,
OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name'],
expect(after).toBeDefined()
})
- await it('Should clear runtime overrides via resetRuntimeOverrides()', () => {
+ await it('should clear runtime overrides via resetRuntimeOverrides()', () => {
manager.setVariables(mockChargingStation, [
{
attributeValue: '123',
expect(afterReset.attributeValue).toBe('30')
})
- await it('Should reject HeartbeatInterval with leading whitespace', () => {
+ await it('should reject HeartbeatInterval with leading whitespace', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: ' 60',
)
})
- await it('Should reject HeartbeatInterval with trailing whitespace', () => {
+ await it('should reject HeartbeatInterval with trailing whitespace', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '60 ',
)
})
- await it('Should reject HeartbeatInterval with plus sign prefix', () => {
+ await it('should reject HeartbeatInterval with plus sign prefix', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '+10',
)
})
- await it('Should accept HeartbeatInterval with leading zeros', () => {
+ await it('should accept HeartbeatInterval with leading zeros', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '007',
expect(res.attributeStatusInfo).toBeUndefined()
})
- await it('Should reject HeartbeatInterval blank string', () => {
+ await it('should reject HeartbeatInterval blank string', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '',
)
})
- await it('Should reject HeartbeatInterval with internal space', () => {
+ await it('should reject HeartbeatInterval with internal space', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '6 0',
)
})
- await it('Should reject ConnectionUrl missing scheme', () => {
+ await it('should reject ConnectionUrl missing scheme', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: 'example.com/ocpp',
expect(res.attributeStatusInfo?.additionalInfo).toContain('Invalid URL format')
})
- await it('Should reject ConnectionUrl exceeding max length', () => {
+ await it('should reject ConnectionUrl exceeding max length', () => {
const longUrl = 'wss://example.com/' + 'a'.repeat(600)
const res = manager.setVariables(mockChargingStation, [
{
)
})
- await it('Should reject HeartbeatInterval exceeding max length', () => {
+ await it('should reject HeartbeatInterval exceeding max length', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: '1'.repeat(11),
})
// Effective value size limit tests combining ConfigurationValueSize and ValueSize
- await it('Should enforce ConfigurationValueSize when ValueSize unset', () => {
+ await it('should enforce ConfigurationValueSize when ValueSize unset', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 50)
// remove ValueSize to simulate unset
expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
})
- await it('Should enforce ValueSize when ConfigurationValueSize unset', () => {
+ await it('should enforce ValueSize when ConfigurationValueSize unset', () => {
resetValueSizeLimits(mockChargingStation)
setValueSize(mockChargingStation, 40)
deleteConfigurationKey(
expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
})
- await it('Should use smaller of ConfigurationValueSize and ValueSize (ValueSize smaller)', () => {
+ await it('should use smaller of ConfigurationValueSize and ValueSize (ValueSize smaller)', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 60)
setValueSize(mockChargingStation, 55)
expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
})
- await it('Should use smaller of ConfigurationValueSize and ValueSize (ConfigurationValueSize smaller)', () => {
+ await it('should use smaller of ConfigurationValueSize and ValueSize (ConfigurationValueSize smaller)', () => {
resetValueSizeLimits(mockChargingStation)
setConfigurationValueSize(mockChargingStation, 30)
setValueSize(mockChargingStation, 100)
expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
})
- await it('Should fallback to default limit when both invalid/non-positive', () => {
+ await it('should fallback to default limit when both invalid/non-positive', () => {
resetValueSizeLimits(mockChargingStation)
// set invalid values
setConfigurationValueSize(mockChargingStation, 0)
await describe('List validation tests', async () => {
const manager = OCPP20VariableManager.getInstance()
- await it('Should accept valid updates to writable list/sequence list variables and reject read-only', () => {
+ await it('should accept valid updates to writable list/sequence list variables and reject read-only', () => {
const updateAttempts: OCPP20SetVariableDataType[] = [
{
attributeValue: 'HTTP,HTTPS', // FileTransferProtocols now ReadOnly -> expect rejection
}
})
- await it('Should retrieve FileTransferProtocols default including FTPS (ReadOnly)', () => {
+ await it('should retrieve FileTransferProtocols default including FTPS (ReadOnly)', () => {
const getRes = manager.getVariables(mockChargingStation, [
{
component: { name: OCPP20ComponentName.OCPPCommCtrlr },
expect(getRes.attributeValue).toBe('HTTPS,FTPS,SFTP')
})
- await it('Should keep FileTransferProtocols value unchanged after rejected update attempt', () => {
+ await it('should keep FileTransferProtocols value unchanged after rejected update attempt', () => {
const beforeCfg = getConfigurationKey(
mockChargingStation,
OCPP20RequiredVariableName.FileTransferProtocols as unknown as VariableType['name']
expect(afterCfg?.value).toBe(beforeCfg?.value)
})
- await it('Should reject removed TimeSource members RTC and Manual', () => {
+ await it('should reject removed TimeSource members RTC and Manual', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: 'NTP,GPS,RTC,Manual', // RTC & Manual no longer valid
expect(res.attributeStatusInfo?.additionalInfo).toContain('Member not in enumeration')
})
- await it('Should accept extended TimeSource including RealTimeClock and MobileNetwork', () => {
+ await it('should accept extended TimeSource including RealTimeClock and MobileNetwork', () => {
const res = manager.setVariables(mockChargingStation, [
{
attributeValue: 'MobileNetwork,Heartbeat,NTP,GPS,RealTimeClock',
expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
})
- await it('Should reject invalid list formats and members', () => {
+ await it('should reject invalid list formats and members', () => {
interface ListVar {
component: OCPP20ComponentName
name: OCPP20RequiredVariableName
})
})
- await it('Should reject DataSigned in TxStopPoint list value', () => {
+ await it('should reject DataSigned in TxStopPoint list value', () => {
const manager = OCPP20VariableManager.getInstance()
const res = manager.setVariables(mockChargingStation, [
{
await describe('Get-time value truncation tests', async () => {
const manager = OCPP20VariableManager.getInstance()
- await it('Should truncate retrieved value using ValueSize only when ReportingValueSize absent', () => {
+ await it('should truncate retrieved value using ValueSize only when ReportingValueSize absent', () => {
resetValueSizeLimits(mockChargingStation)
// Ensure ReportingValueSize unset
deleteConfigurationKey(
resetValueSizeLimits(mockChargingStation)
})
- await it('Should apply ValueSize then ReportingValueSize sequential truncation', () => {
+ await it('should apply ValueSize then ReportingValueSize sequential truncation', () => {
resetValueSizeLimits(mockChargingStation)
// Store long value with large limits
setValueSize(mockChargingStation, 300)
resetReportingValueSize(mockChargingStation)
})
- await it('Should enforce absolute max character cap after truncation chain', () => {
+ await it('should enforce absolute max character cap after truncation chain', () => {
resetValueSizeLimits(mockChargingStation)
resetReportingValueSize(mockChargingStation)
// Directly upsert configuration key with > absolute max length value bypassing set-time limit (which rejects > absolute max length)
resetReportingValueSize(mockChargingStation)
})
- await it('Should not exceed variable maxLength even if ValueSize and ReportingValueSize set above it', () => {
+ await it('should not exceed variable maxLength even if ValueSize and ReportingValueSize set above it', () => {
resetValueSizeLimits(mockChargingStation)
resetReportingValueSize(mockChargingStation)
// Store exactly variable maxLength value via setVariables (allowed per registry/spec)
await describe('Additional persistence and instance-scoped variable tests', async () => {
const manager = OCPP20VariableManager.getInstance()
- await it('Should auto-create persistent OrganizationName configuration key during self-check', () => {
+ await it('should auto-create persistent OrganizationName configuration key during self-check', () => {
deleteConfigurationKey(
mockChargingStation,
OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name'],
expect(after?.value).toBe('Example Charging Services Ltd')
})
- await it('Should accept setting OrganizationName and require reboot per OCPP 2.0.1 specification', () => {
+ await it('should accept setting OrganizationName and require reboot per OCPP 2.0.1 specification', () => {
const setRes = manager.setVariables(mockChargingStation, [
{
attributeValue: 'NewOrgName',
expect(getRes.attributeValue).toBe('NewOrgName')
})
- await it('Should preserve OrganizationName value after resetRuntimeOverrides()', () => {
+ await it('should preserve OrganizationName value after resetRuntimeOverrides()', () => {
manager.resetRuntimeOverrides()
const res = manager.getVariables(mockChargingStation, [
{
expect(res.attributeValue).toBe('NewOrgName')
})
- await it('Should create configuration key for instance-scoped MessageAttemptInterval and persist Actual value (Actual-only, no MinSet/MaxSet)', () => {
+ await it('should create configuration key for instance-scoped MessageAttemptInterval and persist Actual value (Actual-only, no MinSet/MaxSet)', () => {
// Ensure no configuration key exists before operations
const cfgBefore = getConfigurationKey(
mockChargingStation,
}
/**
- * Create a mock ChargingStation for auth module testing.
+ * Create a mock ChargingStation for auth module unit testing.
+ *
+ * Returns MockChargingStation interface - minimal interface for auth strategies.
+ * For OCPPAuthService tests requiring full ChargingStation type, use createMockAuthServiceTestStation().
* @param overrides - Partial MockChargingStation properties to override defaults
* @returns Mock ChargingStation object with stubbed methods
*/
-export const createMockChargingStation = (
+export const createMockAuthChargingStation = (
overrides?: Partial<MockChargingStation>
): MockChargingStation => ({
getConnectorStatus: () => ({ status: 'Available' }),
})
await describe('UIHttpServer test suite', async () => {
- await it('Verify sendResponse() deletes handler after sending', () => {
+ await it('should verify sendResponse() deletes handler after sending', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.statusCode).toBe(200)
})
- await it('Verify sendResponse() logs error when handler not found', () => {
+ await it('should verify sendResponse() logs error when handler not found', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify sendResponse() sets correct status code for failure', () => {
+ await it('should verify sendResponse() sets correct status code for failure', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.statusCode).toBe(400)
})
- await it('Verify sendResponse() handles send errors gracefully', () => {
+ await it('should verify sendResponse() handles send errors gracefully', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
res.end = (): never => {
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify sendResponse() sets correct Content-Type header', () => {
+ await it('should verify sendResponse() sets correct Content-Type header', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.headers['Content-Type']).toBe('application/json')
})
- await it('Verify response handlers cleanup', () => {
+ await it('should verify response handlers cleanup', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res1 = new MockServerResponse()
const res2 = new MockServerResponse()
expect(server.getResponseHandlersSize()).toBe(0)
})
- await it('Verify handlers cleared on server stop', () => {
+ await it('should verify handlers cleared on server stop', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(server.getResponseHandlersSize()).toBe(0)
})
- await it('Verify response payload serialization', () => {
+ await it('should verify response payload serialization', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
const payload = {
expect(parsedBody.hashIdsSucceeded).toEqual(['station-1', 'station-2'])
})
- await it('Verify response with error details', () => {
+ await it('should verify response with error details', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
const payload = {
expect(parsedBody.hashIdsFailed).toEqual(['station-1'])
})
- await it('Verify valid HTTP configuration', () => {
+ await it('should verify valid HTTP configuration', () => {
const server = new UIHttpServer(createHttpServerConfig())
expect(server).toBeDefined()
})
- await it('Verify HTTP server with custom config', () => {
+ await it('should verify HTTP server with custom config', () => {
const config = createMockUIServerConfiguration({
options: {
host: 'localhost',
})
await describe('Gzip compression', async () => {
- await it('Verify no compression when acceptsGzip is false', () => {
+ await it('should verify no compression when acceptsGzip is false', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.headers['Content-Type']).toBe('application/json')
})
- await it('Verify no compression for small responses', () => {
+ await it('should verify no compression for small responses', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.headers['Content-Type']).toBe('application/json')
})
- await it('Verify no compression below threshold', () => {
+ await it('should verify no compression below threshold', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
const smallPayload = {
expect(res.headers['Content-Encoding']).toBeUndefined()
})
- await it('Verify compression headers for large responses', async () => {
+ await it('should verify compression headers for large responses', async () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.headers.Vary).toBe('Accept-Encoding')
})
- await it('Verify compressed response decompresses to original payload', async () => {
+ await it('should verify compressed response decompresses to original payload', async () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
const payload = createLargePayload()
expect(parsedBody.data).toBe(payload.data)
})
- await it('Verify no compression when acceptsGzip context is missing', () => {
+ await it('should verify no compression when acceptsGzip context is missing', () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
expect(res.headers['Content-Type']).toBe('application/json')
})
- await it('Verify acceptsGzip context cleanup after response', async () => {
+ await it('should verify acceptsGzip context cleanup after response', async () => {
const server = new TestableUIHttpServer(createHttpServerConfig())
const res = new MockServerResponse()
await describe('UIServerSecurity test suite', async () => {
await describe('isValidCredential()', async () => {
- await it('Verify matching credentials return true', () => {
+ await it('should verify matching credentials return true', () => {
expect(isValidCredential('myPassword123', 'myPassword123')).toBe(true)
})
- await it('Verify non-matching credentials return false', () => {
+ await it('should verify non-matching credentials return false', () => {
expect(isValidCredential('password1', 'password2')).toBe(false)
})
- await it('Verify empty string credentials match', () => {
+ await it('should verify empty string credentials match', () => {
expect(isValidCredential('', '')).toBe(true)
})
- await it('Verify different length credentials return false', () => {
+ await it('should verify different length credentials return false', () => {
// cspell:disable-next-line
expect(isValidCredential('short', 'verylongpassword')).toBe(false)
})
})
await describe('createBodySizeLimiter()', async () => {
- await it('Verify bytes under limit return true', () => {
+ await it('should verify bytes under limit return true', () => {
const limiter = createBodySizeLimiter(1000)
expect(limiter(500)).toBe(true)
})
- await it('Verify accumulated bytes exceeding limit return false', () => {
+ await it('should verify accumulated bytes exceeding limit return false', () => {
const limiter = createBodySizeLimiter(1000)
limiter(600)
expect(limiter(500)).toBe(false)
})
- await it('Verify exact limit boundary returns true', () => {
+ await it('should verify exact limit boundary returns true', () => {
const limiter = createBodySizeLimiter(1000)
expect(limiter(1000)).toBe(true)
})
await describe('createRateLimiter()', async () => {
- await it('Verify requests under limit are allowed', () => {
+ await it('should verify requests under limit are allowed', () => {
const limiter = createRateLimiter(5, 1000)
for (let i = 0; i < 5; i++) {
}
})
- await it('Verify requests exceeding limit are blocked', () => {
+ await it('should verify requests exceeding limit are blocked', () => {
const limiter = createRateLimiter(3, 1000)
limiter('192.168.1.1')
limiter('192.168.1.1')
expect(limiter('192.168.1.1')).toBe(false)
})
- await it('Verify window resets after time expires', async () => {
+ await it('should verify window resets after time expires', async () => {
const limiter = createRateLimiter(2, 100)
limiter('10.0.0.1')
limiter('10.0.0.1')
expect(limiter('10.0.0.1')).toBe(true)
})
- await it('Verify new IPs rejected when at max tracked capacity', () => {
+ await it('should verify new IPs rejected when at max tracked capacity', () => {
const limiter = createRateLimiter(10, 60000, 3)
expect(limiter('192.168.1.1')).toBe(true)
expect(limiter('192.168.1.4')).toBe(false)
})
- await it('Verify existing IPs still allowed when at capacity', () => {
+ await it('should verify existing IPs still allowed when at capacity', () => {
const limiter = createRateLimiter(10, 60000, 2)
expect(limiter('192.168.1.1')).toBe(true)
expect(limiter('192.168.1.2')).toBe(true)
})
- await it('Verify expired entries cleanup when at capacity', async () => {
+ await it('should verify expired entries cleanup when at capacity', async () => {
const limiter = createRateLimiter(10, 50, 2)
expect(limiter('192.168.1.1')).toBe(true)
expect(limiter('192.168.1.2')).toBe(true)
})
await describe('isValidNumberOfStations()', async () => {
- await it('Verify valid number of stations returns true', () => {
+ await it('should verify valid number of stations returns true', () => {
expect(isValidNumberOfStations(50, DEFAULT_MAX_STATIONS)).toBe(true)
})
- await it('Verify exceeding max stations returns false', () => {
+ await it('should verify exceeding max stations returns false', () => {
expect(isValidNumberOfStations(150, DEFAULT_MAX_STATIONS)).toBe(false)
})
- await it('Verify zero stations returns false', () => {
+ await it('should verify zero stations returns false', () => {
expect(isValidNumberOfStations(0, DEFAULT_MAX_STATIONS)).toBe(false)
})
- await it('Verify negative stations returns false', () => {
+ await it('should verify negative stations returns false', () => {
expect(isValidNumberOfStations(-5, DEFAULT_MAX_STATIONS)).toBe(false)
})
- await it('Verify exact max stations boundary returns true', () => {
+ await it('should verify exact max stations boundary returns true', () => {
expect(isValidNumberOfStations(DEFAULT_MAX_STATIONS, DEFAULT_MAX_STATIONS)).toBe(true)
})
})
ProcedureName,
ResponseStatus,
} from '../../../src/types/index.js'
+import { waitForCondition } from '../helpers/StationHelpers.js'
+import { MockWebSocket as BaseMockWebSocket } from '../mocks/MockWebSocket.js'
+
+// Re-export waitForCondition for backward compatibility
+export { waitForCondition }
export const createMockUIServerConfiguration = (
overrides?: Partial<UIServerConfiguration>
}
}
-export class MockWebSocket extends EventEmitter {
- public protocol = 'ui0.0.1'
- public readyState = 1 // OPEN
- public sentMessages: string[] = []
-
- public close (code?: number): void {
- this.readyState = 3 // CLOSED
- this.emit('close', code, Buffer.from(''))
+/**
+ * MockWebSocket for UI protocol testing
+ *
+ * Extends base MockWebSocket with UI-specific helper methods.
+ * Uses 'ui0.0.1' protocol by default.
+ */
+export class MockWebSocket extends BaseMockWebSocket {
+ constructor () {
+ super('ws://localhost:8080/ui')
+ this.protocol = 'ui0.0.1'
}
- public getLastSentMessage (): ProtocolResponse | undefined {
- if (this.sentMessages.length === 0) {
+ /**
+ * Get last sent message parsed as ProtocolResponse
+ * @returns Parsed ProtocolResponse or undefined if no messages
+ */
+ public getLastSentMessageAsResponse (): ProtocolResponse | undefined {
+ const lastMsg = this.getLastSentMessage()
+ if (lastMsg == null) {
return undefined
}
- return JSON.parse(this.sentMessages[this.sentMessages.length - 1]) as ProtocolResponse
- }
-
- public send (data: string): void {
- this.sentMessages.push(data)
- }
-
- public simulateError (error: Error): void {
- this.emit('error', error)
- }
-
- public simulateMessage (data: string): void {
- this.emit('message', Buffer.from(data))
+ return JSON.parse(lastMsg) as ProtocolResponse
}
}
return JSON.stringify({ not: 'an array' })
}
-export const waitForCondition = async (
- condition: () => boolean,
- timeout = 1000,
- interval = 10
-): Promise<void> => {
- const startTime = Date.now()
- while (!condition()) {
- if (Date.now() - startTime > timeout) {
- throw new Error('Timeout waiting for condition')
- }
- await new Promise(resolve => {
- setTimeout(resolve, interval)
- })
- }
-}
-
export const createMockBroadcastResponse = (
uuid: string,
hashId: string,
}
await describe('UIWebSocketServer test suite', async () => {
- await it('Verify sendResponse() deletes handler after sending', () => {
+ await it('should verify sendResponse() deletes handler after sending', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const ws = new MockWebSocket()
expect(ws.sentMessages.length).toBe(1)
})
- await it('Verify sendResponse() logs error when handler not found', () => {
+ await it('should verify sendResponse() logs error when handler not found', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify sendResponse() deletes handler when WebSocket not open', () => {
+ await it('should verify sendResponse() deletes handler when WebSocket not open', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const ws = new MockWebSocket()
expect(ws.sentMessages.length).toBe(0)
})
- await it('Verify sendResponse() handles send errors gracefully', () => {
+ await it('should verify sendResponse() handles send errors gracefully', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const ws = new MockWebSocket()
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify broadcast handler persistence (issue #1642)', async () => {
+ await it('should verify broadcast handler persistence (issue #1642)', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const mockService = new MockUIServiceBroadcast()
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify non-broadcast handler immediate deletion', async () => {
+ await it('should verify non-broadcast handler immediate deletion', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const mockService = new MockUIServiceNonBroadcast()
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify error handler cleanup', async () => {
+ await it('should verify error handler cleanup', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const mockService = new MockUIServiceError()
expect(server.getResponseHandlersSize()).toBe(1)
})
- await it('Verify response handlers cleanup', () => {
+ await it('should verify response handlers cleanup', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const ws1 = new MockWebSocket()
expect(server.getResponseHandlersSize()).toBe(0)
})
- await it('Verify handlers cleared on server stop', () => {
+ await it('should verify handlers cleared on server stop', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
const ws = new MockWebSocket()
expect(server.getResponseHandlersSize()).toBe(0)
})
- await it('Verify valid WebSocket configuration', () => {
+ await it('should verify valid WebSocket configuration', () => {
const config = createMockUIServerConfiguration()
const server = new UIWebSocketServer(config)
expect(server).toBeDefined()
})
- await it('Verify WebSocket server with custom config', () => {
+ await it('should verify WebSocket server with custom config', () => {
const config = createMockUIServerConfiguration({
options: {
host: 'localhost',
}
await describe('AbstractUIService test suite', async () => {
- await it('Verify sendResponse checks for response handler existence', () => {
+ await it('should verify sendResponse checks for response handler existence', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
})
- await it('Verify requestHandler returns response for LIST_CHARGING_STATIONS', async () => {
+ await it('should verify requestHandler returns response for LIST_CHARGING_STATIONS', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify requestHandler returns response for LIST_TEMPLATES', async () => {
+ await it('should verify requestHandler returns response for LIST_TEMPLATES', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify requestHandler returns error response for unknown procedure', async () => {
+ await it('should verify requestHandler returns error response for unknown procedure', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify broadcast channel request tracking initialization', () => {
+ await it('should verify broadcast channel request tracking initialization', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify broadcast channel cleanup on stop', () => {
+ await it('should verify broadcast channel cleanup on stop', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify requestHandler handles errors gracefully', async () => {
+ await it('should verify requestHandler handles errors gracefully', async () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify UI service initialization', () => {
+ await it('should verify UI service initialization', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
}
})
- await it('Verify multiple service registrations', () => {
+ await it('should verify multiple service registrations', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
import { BaseError } from '../../src/exception/BaseError.js'
await describe('BaseError test suite', async () => {
- await it('Verify that BaseError can be instantiated', () => {
+ await it('should verify that BaseError can be instantiated', () => {
const baseError = new BaseError()
expect(baseError).toBeInstanceOf(BaseError)
expect(baseError.name).toBe('BaseError')
expect(baseError.date).toBeInstanceOf(Date)
})
- await it('Verify that BaseError can be instantiated with a message', () => {
+ await it('should verify that BaseError can be instantiated with a message', () => {
const baseError = new BaseError('Test message')
expect(baseError).toBeInstanceOf(BaseError)
expect(baseError.message).toBe('Test message')
import { Constants } from '../../src/utils/Constants.js'
await describe('OCPPError test suite', async () => {
- await it('Verify that OCPPError can be instantiated', () => {
+ await it('should verify that OCPPError can be instantiated', () => {
const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, '')
expect(ocppError).toBeInstanceOf(OCPPError)
expect(ocppError.name).toBe('OCPPError')
} from '../../src/types/ConfigurationData.js'
await describe('ConfigurationData test suite', async () => {
- await it('Verify ConfigurationSection enumeration', () => {
+ await it('should verify ConfigurationSection enumeration', () => {
expect(ConfigurationSection.log).toBe('log')
expect(ConfigurationSection.performanceStorage).toBe('performanceStorage')
expect(ConfigurationSection.uiServer).toBe('uiServer')
expect(ConfigurationSection.worker).toBe('worker')
})
- await it('Verify SupervisionUrlDistribution enumeration', () => {
+ await it('should verify SupervisionUrlDistribution enumeration', () => {
expect(SupervisionUrlDistribution.CHARGING_STATION_AFFINITY).toBe('charging-station-affinity')
expect(SupervisionUrlDistribution.RANDOM).toBe('random')
expect(SupervisionUrlDistribution.ROUND_ROBIN).toBe('round-robin')
})
- await it('Verify ApplicationProtocolVersion enumeration', () => {
+ await it('should verify ApplicationProtocolVersion enumeration', () => {
expect(ApplicationProtocolVersion.VERSION_11).toBe('1.1')
expect(ApplicationProtocolVersion.VERSION_20).toBe('2.0')
})
import { AsyncLock, AsyncLockType } from '../../src/utils/AsyncLock.js'
await describe('AsyncLock test suite', async () => {
- await it('Verify runExclusive() on sync fn', () => {
+ await it('should verify runExclusive() on sync fn', () => {
const runs = 10
const executed: number[] = []
let count = 0
}
})
- await it('Verify runExclusive() on async fn', () => {
+ await it('should verify runExclusive() on async fn', () => {
const runs = 10
const executed: number[] = []
let count = 0
} from '../../src/utils/ConfigurationUtils.js'
await describe('ConfigurationUtils test suite', async () => {
- await it('Verify logPrefix()', () => {
+ await it('should verify logPrefix()', () => {
expect(logPrefix()).toContain(' Simulator configuration |')
})
- await it('Verify buildPerformanceUriFilePath()', () => {
+ await it('should verify buildPerformanceUriFilePath()', () => {
const result = buildPerformanceUriFilePath('test.json')
expect(result).toContain('test.json')
expect(result).toMatch(/^file:\/\/.*test\.json$/)
})
- await it('Verify getDefaultPerformanceStorageUri()', () => {
+ await it('should verify getDefaultPerformanceStorageUri()', () => {
// Test JSON_FILE storage type
const jsonUri = getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
expect(jsonUri).toMatch(/^file:\/\/.*\.json$/)
}).toThrow(Error)
})
- await it('Verify handleFileException()', t => {
+ await it('should verify handleFileException()', t => {
const mockConsoleError = t.mock.method(console, 'error')
const error = new Error() as NodeJS.ErrnoException
error.code = 'ENOENT'
expect(mockConsoleError.mock.calls.length).toBe(1)
})
- await it('Verify checkWorkerElementsPerWorker()', () => {
+ await it('should verify checkWorkerElementsPerWorker()', () => {
// These calls should not throw exceptions
expect(() => {
checkWorkerElementsPerWorker(undefined)
import { ACElectricUtils, DCElectricUtils } from '../../src/utils/ElectricUtils.js'
await describe('ElectricUtils test suite', async () => {
- await it('Verify DCElectricUtils.power()', () => {
+ await it('should verify DCElectricUtils.power()', () => {
expect(DCElectricUtils.power(230, 1)).toBe(230)
})
- await it('Verify DCElectricUtils.amperage()', () => {
+ await it('should verify DCElectricUtils.amperage()', () => {
expect(DCElectricUtils.amperage(1, 230)).toBe(0)
})
- await it('Verify ACElectricUtils.powerTotal()', () => {
+ await it('should verify ACElectricUtils.powerTotal()', () => {
expect(ACElectricUtils.powerTotal(3, 230, 1)).toBe(690)
})
- await it('Verify ACElectricUtils.powerPerPhase()', () => {
+ await it('should verify ACElectricUtils.powerPerPhase()', () => {
expect(ACElectricUtils.powerPerPhase(230, 1)).toBe(230)
})
- await it('Verify ACElectricUtils.amperageTotal()', () => {
+ await it('should verify ACElectricUtils.amperageTotal()', () => {
expect(ACElectricUtils.amperageTotal(3, 1)).toBe(3)
})
- await it('Verify ACElectricUtils.amperageTotalFromPower()', () => {
+ await it('should verify ACElectricUtils.amperageTotalFromPower()', () => {
expect(ACElectricUtils.amperageTotalFromPower(690, 230)).toBe(3)
})
- await it('Verify ACElectricUtils.amperagePerPhaseFromPower()', () => {
+ await it('should verify ACElectricUtils.amperagePerPhaseFromPower()', () => {
expect(ACElectricUtils.amperagePerPhaseFromPower(3, 690, 230)).toBe(1)
})
})
await describe('ErrorUtils test suite', async () => {
const chargingStation = createChargingStation({ baseName: 'CS-TEST' })
- await it('Verify handleFileException()', t => {
+ await it('should verify handleFileException()', t => {
const consoleWarnMock = t.mock.method(console, 'warn')
const consoleErrorMock = t.mock.method(console, 'error')
const warnMock = t.mock.method(logger, 'warn')
expect(consoleErrorMock.mock.calls.length).toBe(1)
})
- await it('Verify handleSendMessageError()', t => {
+ await it('should verify handleSendMessageError()', t => {
const errorMock = t.mock.method(logger, 'error')
const logPrefixMock = t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
expect(errorMock.mock.calls.length).toBe(2)
})
- await it('Verify handleIncomingRequestError()', t => {
+ await it('should verify handleIncomingRequestError()', t => {
const errorMock = t.mock.method(logger, 'error')
const logPrefixMock = t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
import { average, max, median, min, percentile, std } from '../../src/utils/StatisticUtils.js'
await describe('StatisticUtils test suite', async () => {
- await it('Verify average()', () => {
+ await it('should verify average()', () => {
expect(average([])).toBe(0)
expect(average([0.08])).toBe(0.08)
expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.1642857142857146)
expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.8533333333333335)
})
- await it('Verify median()', () => {
+ await it('should verify median()', () => {
expect(median([])).toBe(0)
expect(median([0.08])).toBe(0.08)
expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.05)
expect(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.535)
})
- await it('Verify min()', () => {
+ await it('should verify min()', () => {
expect(min()).toBe(Number.POSITIVE_INFINITY)
expect(min(0, 1)).toBe(0)
expect(min(1, 0)).toBe(0)
expect(min(-1, 0)).toBe(-1)
})
- await it('Verify max()', () => {
+ await it('should verify max()', () => {
expect(max()).toBe(Number.NEGATIVE_INFINITY)
expect(max(0, 1)).toBe(1)
expect(max(1, 0)).toBe(1)
expect(max(-1, 0)).toBe(0)
})
- await it('Verify percentile()', () => {
+ await it('should verify percentile()', () => {
expect(percentile([], 25)).toBe(0)
expect(percentile([0.08], 50)).toBe(0.08)
const array0 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]
expect(percentile(array0, 100)).toBe(6.04)
})
- await it('Verify std()', () => {
+ await it('should verify std()', () => {
expect(std([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.1879050645374383)
})
})
} from '../../src/utils/Utils.js'
await describe('Utils test suite', async () => {
- await it('Verify generateUUID()/validateUUID()', () => {
+ await it('should verify generateUUID()/validateUUID()', () => {
const uuid = generateUUID()
expect(uuid).toBeDefined()
expect(uuid.length).toEqual(36)
expect(validateUUID(true)).toBe(false)
})
- await it('Verify validateIdentifierString()', () => {
+ await it('should verify validateIdentifierString()', () => {
expect(validateIdentifierString('550e8400-e29b-41d4-a716-446655440000', 36)).toBe(true)
expect(validateIdentifierString('CSMS-TXN-12345', 36)).toBe(true)
expect(validateIdentifierString('a', 36)).toBe(true)
expect(validateIdentifierString('valid', 4)).toBe(false)
})
- await it('Verify sleep()', async t => {
+ await it('should verify sleep()', async t => {
/**
* Timer mock pattern for testing asynchronous timer-based operations.
* Uses Node.js test module's built-in timer mocking API.
}
})
- await it('Verify formatDurationMilliSeconds()', () => {
+ await it('should verify formatDurationMilliSeconds()', () => {
expect(formatDurationMilliSeconds(0)).toBe('0 seconds')
expect(formatDurationMilliSeconds(900)).toBe('0 seconds')
expect(formatDurationMilliSeconds(1000)).toBe('1 second')
expect(formatDurationMilliSeconds(hoursToMilliseconds(4380))).toBe('182 days 12 hours')
})
- await it('Verify formatDurationSeconds()', () => {
+ await it('should verify formatDurationSeconds()', () => {
expect(formatDurationSeconds(0)).toBe('0 seconds')
expect(formatDurationSeconds(0.9)).toBe('0 seconds')
expect(formatDurationSeconds(1)).toBe('1 second')
expect(formatDurationSeconds(hoursToSeconds(4380))).toBe('182 days 12 hours')
})
- await it('Verify isValidDate()', () => {
+ await it('should verify isValidDate()', () => {
expect(isValidDate(undefined)).toBe(false)
expect(isValidDate(-1)).toBe(true)
expect(isValidDate(0)).toBe(true)
expect(isValidDate(new Date())).toBe(true)
})
- await it('Verify convertToDate()', () => {
+ await it('should verify convertToDate()', () => {
expect(convertToDate(undefined)).toBe(undefined)
expect(convertToDate(null)).toBe(undefined)
expect(() => convertToDate('')).toThrow(new Error("Cannot convert to date: ''"))
expect(date).toStrictEqual(new Date(dateStr))
})
- await it('Verify convertToInt()', () => {
+ await it('should verify convertToInt()', () => {
expect(convertToInt(undefined)).toBe(0)
expect(convertToInt(null)).toBe(0)
expect(convertToInt(0)).toBe(0)
}).toThrow("Cannot convert to integer: 'NaN'")
})
- await it('Verify convertToFloat()', () => {
+ await it('should verify convertToFloat()', () => {
expect(convertToFloat(undefined)).toBe(0)
expect(convertToFloat(null)).toBe(0)
expect(convertToFloat(0)).toBe(0)
}).toThrow("Cannot convert to float: 'NaN'")
})
- await it('Verify convertToBoolean()', () => {
+ await it('should verify convertToBoolean()', () => {
expect(convertToBoolean(undefined)).toBe(false)
expect(convertToBoolean(null)).toBe(false)
expect(convertToBoolean('true')).toBe(true)
expect(convertToBoolean('NoNBoolean')).toBe(false)
})
- await it('Verify secureRandom()', () => {
+ await it('should verify secureRandom()', () => {
const random = secureRandom()
expect(typeof random === 'number').toBe(true)
expect(random).toBeGreaterThanOrEqual(0)
expect(random).toBeLessThan(1)
})
- await it('Verify roundTo()', () => {
+ await it('should verify roundTo()', () => {
expect(roundTo(0, 2)).toBe(0)
expect(roundTo(0.5, 0)).toBe(1)
expect(roundTo(0.5, 2)).toBe(0.5)
expect(roundTo(-5.015, 2)).toBe(-5.02)
})
- await it('Verify getRandomFloat()', () => {
+ await it('should verify getRandomFloat()', () => {
let randomFloat = getRandomFloat()
expect(typeof randomFloat === 'number').toBe(true)
expect(randomFloat).toBeGreaterThanOrEqual(0)
expect(randomFloat).toBeLessThanOrEqual(0)
})
- await it('Verify extractTimeSeriesValues()', () => {
+ await it('should verify extractTimeSeriesValues()', () => {
expect(
extractTimeSeriesValues(
new CircularBuffer<TimestampedData>(Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY)
expect(extractTimeSeriesValues(circularBuffer)).toEqual([1.1, 2.2, 3.3])
})
- await it('Verify isAsyncFunction()', () => {
+ await it('should verify isAsyncFunction()', () => {
expect(isAsyncFunction(null)).toBe(false)
expect(isAsyncFunction(undefined)).toBe(false)
expect(isAsyncFunction(true)).toBe(false)
expect(isAsyncFunction(TestClass.testStaticAsync)).toBe(true)
})
- await it('Verify clone()', () => {
+ await it('should verify clone()', () => {
const obj = { 1: 1 }
expect(clone(obj)).toStrictEqual(obj)
expect(clone(obj) === obj).toBe(false)
expect(() => clone(weakSet)).toThrow(new Error('#<WeakSet> could not be cloned.'))
})
- await it('Verify once()', () => {
+ await it('should verify once()', () => {
let called = 0
const fn = (): number => ++called
const onceFn = once(fn)
expect(result3).toBe(1)
})
- await it('Verify has()', () => {
+ await it('should verify has()', () => {
expect(has('', 'test')).toBe(false)
expect(has('test', '')).toBe(false)
expect(has('test', 'test')).toBe(false)
expect(has(2, { 1: '1' })).toBe(false)
})
- await it('Verify isEmpty()', () => {
+ await it('should verify isEmpty()', () => {
expect(isEmpty('')).toBe(true)
expect(isEmpty(' ')).toBe(true)
expect(isEmpty(' ')).toBe(true)
expect(isEmpty(new WeakSet())).toBe(false)
})
- await it('Verify isNotEmptyString()', () => {
+ await it('should verify isNotEmptyString()', () => {
expect(isNotEmptyString('')).toBe(false)
expect(isNotEmptyString(' ')).toBe(false)
expect(isNotEmptyString(' ')).toBe(false)
expect(isNotEmptyString(new WeakSet())).toBe(false)
})
- await it('Verify isNotEmptyArray()', () => {
+ await it('should verify isNotEmptyArray()', () => {
expect(isNotEmptyArray([])).toBe(false)
expect(isNotEmptyArray([1, 2])).toBe(true)
expect(isNotEmptyArray(['1', '2'])).toBe(true)
expect(isNotEmptyArray(new WeakSet())).toBe(false)
})
- await it('Verify insertAt()', () => {
+ await it('should verify insertAt()', () => {
expect(insertAt('test', 'ing', 'test'.length)).toBe('testing')
// eslint-disable-next-line @cspell/spellchecker
expect(insertAt('test', 'ing', 2)).toBe('teingst')
})
- await it('Verify convertToIntOrNaN()', () => {
+ await it('should verify convertToIntOrNaN()', () => {
expect(convertToIntOrNaN(undefined)).toBe(0)
expect(convertToIntOrNaN(null)).toBe(0)
expect(convertToIntOrNaN('0')).toBe(0)
expect(Number.isNaN(convertToIntOrNaN('abc'))).toBe(true)
})
- await it('Verify isArraySorted()', () => {
+ await it('should verify isArraySorted()', () => {
expect(isArraySorted<number>([], (a, b) => a - b)).toBe(true)
expect(isArraySorted<number>([1], (a, b) => a - b)).toBe(true)
expect(isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true)
expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
})
- await it('Verify clampToSafeTimerValue()', () => {
+ await it('should verify clampToSafeTimerValue()', () => {
expect(clampToSafeTimerValue(0)).toBe(0)
expect(clampToSafeTimerValue(1000)).toBe(1000)
expect(clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY)).toBe(
// Exponential Backoff Algorithm Tests (WebSocket Reconnection)
// -------------------------------------------------------------------------
- await it('Verify exponentialDelay() with default parameters', () => {
+ await it('should verify exponentialDelay() with default parameters', () => {
// Formula: delay = 2^retryNumber * delayFactor + (0-20% random jitter)
// With default delayFactor = 100ms
expect(delay3).toBeLessThanOrEqual(960) // 800 + 20% max jitter
})
- await it('Verify exponentialDelay() with custom delayFactor', () => {
+ await it('should verify exponentialDelay() with custom delayFactor', () => {
// Custom delayFactor = 50ms
const delay0 = exponentialDelay(0, 50)
expect(delay0).toBeGreaterThanOrEqual(50)
expect(delay2).toBeLessThanOrEqual(960)
})
- await it('Verify exponentialDelay() exponential growth pattern', () => {
+ await it('should verify exponentialDelay() exponential growth pattern', () => {
// Verify that delays follow 2^n exponential growth pattern
const delayFactor = 100
}
})
- await it('Verify exponentialDelay() includes random jitter', () => {
+ await it('should verify exponentialDelay() includes random jitter', () => {
// Run multiple times to verify jitter produces different values
const delays = new Set<number>()
const retryNumber = 3
expect(delays.size).toBeGreaterThan(1)
})
- await it('Verify exponentialDelay() jitter is within 0-20% range', () => {
+ await it('should verify exponentialDelay() jitter is within 0-20% range', () => {
// For a given retry, jitter should add 0-20% of base delay
const retryNumber = 4
const delayFactor = 100
}
})
- await it('Verify exponentialDelay() handles edge cases', () => {
+ await it('should verify exponentialDelay() handles edge cases', () => {
// Default retryNumber (0)
const defaultRetry = exponentialDelay()
expect(defaultRetry).toBeGreaterThanOrEqual(100) // 2^0 * 100
expect(smallFactor).toBeLessThan(5) // 4 + 20%
})
- await it('Verify exponentialDelay() for WebSocket reconnection scenarios', () => {
+ await it('should verify exponentialDelay() for WebSocket reconnection scenarios', () => {
// Simulate typical WebSocket reconnection delay sequence
const delayFactor = 100 // Default used in ChargingStation.reconnect()
} from '../../src/worker/WorkerUtils.js'
await describe('WorkerUtils test suite', async () => {
- await it('Verify checkWorkerProcessType()', () => {
+ await it('should verify checkWorkerProcessType()', () => {
// Valid worker process types should not throw
expect(() => {
checkWorkerProcessType(WorkerProcessType.dynamicPool)
}).toThrow(SyntaxError)
})
- await it('Verify sleep()', async t => {
+ await it('should verify sleep()', async t => {
t.mock.timers.enable({ apis: ['setTimeout'] })
try {
const delay = 10 // 10ms for fast test execution
}
})
- await it('Verify defaultExitHandler()', t => {
+ await it('should verify defaultExitHandler()', t => {
const mockConsoleInfo = t.mock.method(console, 'info')
const mockConsoleError = t.mock.method(console, 'error')
expect(mockConsoleError.mock.calls.length).toBe(1)
})
- await it('Verify defaultErrorHandler()', t => {
+ await it('should verify defaultErrorHandler()', t => {
const mockConsoleError = t.mock.method(console, 'error')
const testError = new Error('Test error message')
expect(mockConsoleError.mock.calls.length).toBe(2)
})
- await it('Verify randomizeDelay()', () => {
+ await it('should verify randomizeDelay()', () => {
const baseDelay = 1000
const tolerance = baseDelay * 0.2 // 20% tolerance as per implementation