- Language: TypeScript (strict mode)
- Package Manager: pnpm (>=9.0.0)
- Build Tool: esbuild
- - Testing: Node.js native test runner with @std/expect
+ - Testing: Node.js native test runner with node:assert/strict
- Code Quality: ESLint, Prettier, neostandard
## OCPP Protocol Support
"@cspell/eslint-plugin": "^9.7.0",
"@eslint/js": "^9.39.4",
"@mikro-orm/cli": "^6.6.9",
- "@std/expect": "jsr:@std/expect@^1.0.18",
"@types/node": "^24.12.0",
"@types/semver": "^7.7.1",
"@types/ws": "^8.18.1",
'@mikro-orm/cli':
specifier: ^6.6.9
version: 6.6.9(better-sqlite3@11.10.0)(mariadb@3.4.5)
- '@std/expect':
- specifier: jsr:@std/expect@^1.0.18
- version: '@jsr/std__expect@1.0.18'
'@types/node':
specifier: ^24.12.0
version: 24.12.0
'@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
- '@jsr/std__assert@1.0.19':
- resolution: {integrity: sha512-pEj6RPkGbqlgRmyKwATp4cUs6+ijxtdrv3bq8v1d2I2CEcMEyPaO8cVKro61wGRDH4cNg8Zx6haztvK/9m7gkA==, tarball: https://npm.jsr.io/~/11/@jsr/std__assert/1.0.19.tgz}
-
- '@jsr/std__expect@1.0.18':
- resolution: {integrity: sha512-Ct+xcCSlfA7X0zhKjCAAIdQYDFVuQq+62RxbCCDUr4A417x4BTdYB2vIcu+sXqbkXdBIddAq0QZiVrgp5xsNaw==, tarball: https://npm.jsr.io/~/11/@jsr/std__expect/1.0.18.tgz}
-
- '@jsr/std__internal@1.0.12':
- resolution: {integrity: sha512-6xReMW9p+paJgqoFRpOE2nogJFvzPfaLHLIlyADYjKMUcwDyjKZxryIbgcU+gxiTygn8yCjld1HoI0ET4/iZeA==, tarball: https://npm.jsr.io/~/11/@jsr/std__internal/1.0.12.tgz}
-
- '@jsr/std__path@1.1.4':
- resolution: {integrity: sha512-SK4u9H6NVTfolhPdlvdYXfNFefy1W04AEHWJydryYbk+xqzNiVmr5o7TLJLJFqwHXuwMRhwrn+mcYeUfS0YFaA==, tarball: https://npm.jsr.io/~/11/@jsr/std__path/1.1.4.tgz}
-
'@keyv/serialize@1.1.1':
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@jsr/std__assert@1.0.19':
- dependencies:
- '@jsr/std__internal': 1.0.12
-
- '@jsr/std__expect@1.0.18':
- dependencies:
- '@jsr/std__assert': 1.0.19
- '@jsr/std__internal': 1.0.12
- '@jsr/std__path': 1.1.4
-
- '@jsr/std__internal@1.0.12': {}
-
- '@jsr/std__path@1.1.4':
- dependencies:
- '@jsr/std__internal': 1.0.12
-
'@keyv/serialize@1.1.1': {}
'@mikro-orm/better-sqlite@6.6.9(@mikro-orm/core@6.6.9)(mariadb@3.4.5)':
--- /dev/null
+/**
+ * Testable interface for OCPP 1.6 IncomingRequestService and RequestService
+ *
+ * This module provides type-safe access to private handler methods for testing purposes.
+ * It replaces `as any` casts with a properly typed interface, enabling:
+ * - Type-safe method invocations in tests
+ * - IntelliSense and autocompletion for handler parameters/returns
+ * - Compile-time checking for test code
+ * @example
+ * ```typescript
+ * import { createTestableIncomingRequestService } from './__testable__/index.js'
+ *
+ * const testable = createTestableIncomingRequestService(incomingRequestService)
+ * const response = await testable.handleRequestReset(mockChargingStation, resetRequest)
+ * ```
+ */
+
+import type {
+ ChangeConfigurationRequest,
+ ChangeConfigurationResponse,
+ ClearCacheResponse,
+ GenericResponse,
+ GetConfigurationRequest,
+ GetConfigurationResponse,
+ GetDiagnosticsRequest,
+ GetDiagnosticsResponse,
+ JsonType,
+ OCPP16CancelReservationRequest,
+ OCPP16ChangeAvailabilityRequest,
+ OCPP16ChangeAvailabilityResponse,
+ OCPP16ClearChargingProfileRequest,
+ OCPP16ClearChargingProfileResponse,
+ OCPP16DataTransferRequest,
+ OCPP16DataTransferResponse,
+ OCPP16GetCompositeScheduleRequest,
+ OCPP16GetCompositeScheduleResponse,
+ OCPP16RequestCommand,
+ OCPP16ReserveNowRequest,
+ OCPP16ReserveNowResponse,
+ OCPP16TriggerMessageRequest,
+ OCPP16TriggerMessageResponse,
+ OCPP16UpdateFirmwareRequest,
+ OCPP16UpdateFirmwareResponse,
+ RemoteStartTransactionRequest,
+ RemoteStopTransactionRequest,
+ ResetRequest,
+ SetChargingProfileRequest,
+ SetChargingProfileResponse,
+ UnlockConnectorRequest,
+ UnlockConnectorResponse,
+} from '../../../../types/index.js'
+import type { ChargingStation } from '../../../index.js'
+import type { OCPP16IncomingRequestService } from '../OCPP16IncomingRequestService.js'
+import type { OCPP16RequestService } from '../OCPP16RequestService.js'
+
+/**
+ * Interface exposing private handler methods of OCPP16IncomingRequestService for testing.
+ * Each method signature matches the corresponding private method in the service class.
+ */
+export interface TestableOCPP16IncomingRequestService {
+ /**
+ * Handles OCPP 1.6 CancelReservation request from central system.
+ * Cancels an existing reservation on the charging station.
+ */
+ handleRequestCancelReservation: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16CancelReservationRequest
+ ) => Promise<GenericResponse>
+
+ /**
+ * Handles OCPP 1.6 ChangeAvailability request from central system.
+ * Changes availability status of a connector or the entire station.
+ */
+ handleRequestChangeAvailability: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16ChangeAvailabilityRequest
+ ) => Promise<OCPP16ChangeAvailabilityResponse>
+
+ /**
+ * Handles OCPP 1.6 ChangeConfiguration request from central system.
+ * Changes the value of a configuration key.
+ */
+ handleRequestChangeConfiguration: (
+ chargingStation: ChargingStation,
+ commandPayload: ChangeConfigurationRequest
+ ) => ChangeConfigurationResponse
+
+ /**
+ * Handles OCPP 1.6 ClearCache request by clearing the authorization cache.
+ */
+ handleRequestClearCache: (chargingStation: ChargingStation) => ClearCacheResponse
+
+ /**
+ * Handles OCPP 1.6 ClearChargingProfile request from central system.
+ * Clears charging profiles matching the specified criteria.
+ */
+ handleRequestClearChargingProfile: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16ClearChargingProfileRequest
+ ) => OCPP16ClearChargingProfileResponse
+
+ /**
+ * Handles OCPP 1.6 DataTransfer request from central system.
+ * Processes vendor-specific data transfer messages.
+ */
+ handleRequestDataTransfer: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16DataTransferRequest
+ ) => OCPP16DataTransferResponse
+
+ /**
+ * Handles OCPP 1.6 GetCompositeSchedule request from central system.
+ * Returns the composite charging schedule for a connector.
+ */
+ handleRequestGetCompositeSchedule: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16GetCompositeScheduleRequest
+ ) => OCPP16GetCompositeScheduleResponse
+
+ /**
+ * Handles OCPP 1.6 GetConfiguration request from central system.
+ * Returns configuration keys and their values.
+ */
+ handleRequestGetConfiguration: (
+ chargingStation: ChargingStation,
+ commandPayload: GetConfigurationRequest
+ ) => GetConfigurationResponse
+
+ /**
+ * Handles OCPP 1.6 GetDiagnostics request from central system.
+ * Uploads diagnostics data to the specified location.
+ */
+ handleRequestGetDiagnostics: (
+ chargingStation: ChargingStation,
+ commandPayload: GetDiagnosticsRequest
+ ) => Promise<GetDiagnosticsResponse>
+
+ /**
+ * Handles OCPP 1.6 RemoteStartTransaction request from central system.
+ * Initiates charging transaction on specified or available connector.
+ */
+ handleRequestRemoteStartTransaction: (
+ chargingStation: ChargingStation,
+ commandPayload: RemoteStartTransactionRequest
+ ) => Promise<GenericResponse>
+
+ /**
+ * Handles OCPP 1.6 RemoteStopTransaction request from central system.
+ * Stops an ongoing transaction by transaction ID.
+ */
+ handleRequestRemoteStopTransaction: (
+ chargingStation: ChargingStation,
+ commandPayload: RemoteStopTransactionRequest
+ ) => GenericResponse
+
+ /**
+ * Handles OCPP 1.6 ReserveNow request from central system.
+ * Creates a reservation on a connector for a specific ID tag.
+ */
+ handleRequestReserveNow: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16ReserveNowRequest
+ ) => Promise<OCPP16ReserveNowResponse>
+
+ /**
+ * Handles OCPP 1.6 Reset request from central system.
+ * Performs immediate or scheduled reset of the charging station.
+ */
+ handleRequestReset: (
+ chargingStation: ChargingStation,
+ commandPayload: ResetRequest
+ ) => GenericResponse
+
+ /**
+ * Handles OCPP 1.6 SetChargingProfile request from central system.
+ * Sets or updates a charging profile on a connector.
+ */
+ handleRequestSetChargingProfile: (
+ chargingStation: ChargingStation,
+ commandPayload: SetChargingProfileRequest
+ ) => SetChargingProfileResponse
+
+ /**
+ * Handles OCPP 1.6 TriggerMessage request from central system.
+ * Triggers the station to send a specific message type.
+ */
+ handleRequestTriggerMessage: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16TriggerMessageRequest
+ ) => OCPP16TriggerMessageResponse
+
+ /**
+ * Handles OCPP 1.6 UnlockConnector request from central system.
+ * Unlocks a connector and optionally stops any ongoing transaction.
+ */
+ handleRequestUnlockConnector: (
+ chargingStation: ChargingStation,
+ commandPayload: UnlockConnectorRequest
+ ) => Promise<UnlockConnectorResponse>
+
+ /**
+ * Handles OCPP 1.6 UpdateFirmware request from central system.
+ * Initiates firmware download and installation simulation.
+ */
+ handleRequestUpdateFirmware: (
+ chargingStation: ChargingStation,
+ commandPayload: OCPP16UpdateFirmwareRequest
+ ) => OCPP16UpdateFirmwareResponse
+}
+
+/**
+ * Interface exposing private methods of OCPP16RequestService for testing.
+ * This allows type-safe testing without `as any` casts.
+ */
+export interface TestableOCPP16RequestService {
+ /**
+ * Build a request payload for the given OCPP 1.6 command.
+ * Exposes the private `buildRequestPayload` method for testing.
+ * @param chargingStation - The charging station instance
+ * @param commandName - The OCPP 1.6 request command
+ * @param commandParams - Optional command parameters
+ * @returns The built request payload
+ */
+ buildRequestPayload: (
+ chargingStation: ChargingStation,
+ commandName: OCPP16RequestCommand,
+ commandParams?: JsonType
+ ) => JsonType
+}
+
+/**
+ * Creates a testable wrapper around OCPP16IncomingRequestService.
+ * Provides type-safe access to private handler methods without `as any` casts.
+ * @param service - The OCPP16IncomingRequestService instance to wrap
+ * @returns A typed interface exposing private handler methods
+ * @example
+ * ```typescript
+ * // Before (with as any cast):
+ * const response = await (service as any).handleRequestReset(station, request)
+ *
+ * // After (with testable interface):
+ * const testable = createTestableIncomingRequestService(service)
+ * const response = await testable.handleRequestReset(station, request)
+ * ```
+ */
+export function createTestableIncomingRequestService (
+ service: OCPP16IncomingRequestService
+): TestableOCPP16IncomingRequestService {
+ // Cast to unknown first to satisfy TypeScript while preserving runtime behavior
+ const serviceImpl = service as unknown as TestableOCPP16IncomingRequestService
+
+ return {
+ handleRequestCancelReservation: serviceImpl.handleRequestCancelReservation.bind(service),
+ handleRequestChangeAvailability: serviceImpl.handleRequestChangeAvailability.bind(service),
+ handleRequestChangeConfiguration: serviceImpl.handleRequestChangeConfiguration.bind(service),
+ handleRequestClearCache: serviceImpl.handleRequestClearCache.bind(service),
+ handleRequestClearChargingProfile: serviceImpl.handleRequestClearChargingProfile.bind(service),
+ handleRequestDataTransfer: serviceImpl.handleRequestDataTransfer.bind(service),
+ handleRequestGetCompositeSchedule: serviceImpl.handleRequestGetCompositeSchedule.bind(service),
+ handleRequestGetConfiguration: serviceImpl.handleRequestGetConfiguration.bind(service),
+ handleRequestGetDiagnostics: serviceImpl.handleRequestGetDiagnostics.bind(service),
+ handleRequestRemoteStartTransaction:
+ serviceImpl.handleRequestRemoteStartTransaction.bind(service),
+ handleRequestRemoteStopTransaction:
+ serviceImpl.handleRequestRemoteStopTransaction.bind(service),
+ handleRequestReserveNow: serviceImpl.handleRequestReserveNow.bind(service),
+ handleRequestReset: serviceImpl.handleRequestReset.bind(service),
+ handleRequestSetChargingProfile: serviceImpl.handleRequestSetChargingProfile.bind(service),
+ handleRequestTriggerMessage: serviceImpl.handleRequestTriggerMessage.bind(service),
+ handleRequestUnlockConnector: serviceImpl.handleRequestUnlockConnector.bind(service),
+ handleRequestUpdateFirmware: serviceImpl.handleRequestUpdateFirmware.bind(service),
+ }
+}
+
+/**
+ * Creates a testable wrapper around OCPP16RequestService.
+ * Provides type-safe access to the private `buildRequestPayload` method.
+ * @param requestService - The OCPP16RequestService instance to wrap
+ * @returns A typed interface exposing private methods
+ * @example
+ * ```typescript
+ * const testable = createTestableOCPP16RequestService(requestService)
+ * const payload = testable.buildRequestPayload(station, OCPP16RequestCommand.HEARTBEAT)
+ * ```
+ */
+export function createTestableOCPP16RequestService (
+ requestService: OCPP16RequestService
+): TestableOCPP16RequestService {
+ // Use type assertion at the boundary only, providing type-safe interface to tests
+ const service = requestService as unknown as {
+ buildRequestPayload: TestableOCPP16RequestService['buildRequestPayload']
+ }
+ return {
+ buildRequestPayload: service.buildRequestPayload.bind(requestService),
+ }
+}
min,
roundTo,
} from '../../utils/index.js'
-import { OCPP16Constants } from './1.6/OCPP16Constants.js'
-import { OCPP20Constants } from './2.0/OCPP20Constants.js'
import { OCPPConstants } from './OCPPConstants.js'
const moduleName = 'OCPPServiceUtils'
return
}
if (options.send) {
- checkConnectorStatusTransition(chargingStation, connectorId, status)
+ await checkConnectorStatusTransition(chargingStation, connectorId, status)
await chargingStation.ocppRequestService.requestHandler<
StatusNotificationRequest,
StatusNotificationResponse
}
}
-const checkConnectorStatusTransition = (
+const checkConnectorStatusTransition = async (
chargingStation: ChargingStation,
connectorId: number,
status: ConnectorStatusEnum
-): boolean => {
+): Promise<boolean> => {
const fromStatus = chargingStation.getConnectorStatus(connectorId)?.status
let transitionAllowed = false
switch (chargingStation.stationInfo?.ocppVersion) {
- case OCPPVersion.VERSION_16:
+ case OCPPVersion.VERSION_16: {
+ const { OCPP16Constants } = await import('./1.6/OCPP16Constants.js')
if (
(connectorId === 0 &&
OCPP16Constants.ChargePointStatusChargingStationTransitions.findIndex(
transitionAllowed = true
}
break
+ }
case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
+ case OCPPVersion.VERSION_201: {
+ const { OCPP20Constants } = await import('./2.0/OCPP20Constants.js')
if (
(connectorId === 0 &&
OCPP20Constants.ChargingStationStatusTransitions.findIndex(
transitionAllowed = true
}
break
+ }
default:
throw new OCPPError(
ErrorType.INTERNAL_ERROR,
- **Test behavior, not implementation**: Focus on what code does, not how
- **Isolation is mandatory**: Each test runs independently with fresh state
- **Determinism required**: Tests must produce identical results on every run
-- **Strict assertions**: Use `toBe`, `toStrictEqual` — never `toEqual`, `toBeTruthy`
+- **Strict assertions**: Use `assert.strictEqual`, `assert.deepStrictEqual`, `assert.ok` — never loose equality
- **Coverage target**: 80%+ on new code
---
const actualPower = station.getTotalPower()
// Assert
- expect(actualPower).toBe(expectedPower)
+ assert.strictEqual(actualPower, expectedPower)
})
```
it('should start charging session', async () => {
const { station } = createMockChargingStation()
const result = await station.startTransaction(1, 'VALID_TAG')
- expect(result.status).toBe('Accepted')
+ assert.strictEqual(result.status, 'Accepted')
})
it('should reject invalid connector', async () => {
const { station } = createMockChargingStation()
- await expect(station.startTransaction(99, 'TAG')).rejects.toThrow('Invalid')
+ await assert.rejects(station.startTransaction(99, 'TAG'), { message: /Invalid/ })
})
```
await withMockTimers(t, ['setTimeout'], async () => {
const promise = station.sendHeartbeat()
t.mock.timers.tick(30000)
- await expect(promise).rejects.toThrow('Timeout')
+ await assert.rejects(promise, { message: /Timeout/ })
})
})
```typescript
// ✅ Good
-expect(result).toStrictEqual({ status: 'ok' }) // Exact match
-expect(count).toBe(5) // Primitive
-expect(value).toBe(true) // Explicit boolean
-expect(item).toBeDefined() // Existence check
+assert.deepStrictEqual(result, { status: 'ok' }) // Exact match
+assert.strictEqual(count, 5) // Primitive
+assert.strictEqual(value, true) // Explicit boolean
+assert.notStrictEqual(item, undefined) // Existence check
// ❌ Bad
-expect(result).toEqual({ status: 'ok' }) // Ignores extra properties
-expect(value).toBeTruthy() // Too vague
-expect(count == '5').toBe(true) // Type coercion
+assert.deepEqual(result, { status: 'ok' }) // Not strict
+assert.ok(value) // Too vague for specific value checks
+assert.strictEqual(count == '5', true) // Type coercion
```
---
```typescript
// Acceptable when testing defensive code
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-expect(AuthValidators.isValidIdentifierValue(123 as any)).toBe(false)
+assert.strictEqual(AuthValidators.isValidIdentifierValue(123 as any), false)
```
---
})
// Verify sent messages
-expect(mocks.webSocket.sentMessages).toContain(expectedMessage)
+assert.ok(mocks.webSocket.sentMessages.includes(expectedMessage))
```
---
4. **Async**: Use `async/await`, mock timers
5. **Platform**: Single top-level `describe`, `--test-force-exit` for Windows
6. **Constants**: Import from `ChargingStationTestConstants.ts`
-7. **Assert**: Strict only (`toBe`, `toStrictEqual`)
+7. **Assert**: Strict only (`assert.strictEqual`, `assert.deepStrictEqual`)
8. **Types**: No `as any`, use testable interfaces
9. **Mocks**: Use appropriate factory for your use case
10. **Utils**: Leverage lifecycle helpers and mock classes
* ChargingStation interaction. Those are integration-level concerns.
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
connectorId: number
): NonNullable<ConnectorStatus> {
const status = atg.connectorsStatus.get(connectorId)
- expect(status).toBeDefined()
+ assert.notStrictEqual(status, undefined)
if (status == null) {
throw new BaseError(`Connector ${String(connectorId)} status unexpectedly undefined`)
}
*/
function getDefinedATG (station: ChargingStation): AutomaticTransactionGenerator {
const atg = AutomaticTransactionGenerator.getInstance(station)
- expect(atg).toBeDefined()
+ assert.notStrictEqual(atg, undefined)
if (atg == null) {
throw new BaseError('ATG instance unexpectedly undefined')
}
const atg = getDefinedATG(station)
- expect(atg.connectorsStatus.size).toBe(2)
+ assert.strictEqual(atg.connectorsStatus.size, 2)
})
await it('should return the same instance for the same station', () => {
const atg1 = AutomaticTransactionGenerator.getInstance(station)
const atg2 = AutomaticTransactionGenerator.getInstance(station)
- expect(atg1).toBe(atg2)
+ assert.strictEqual(atg1, atg2)
})
await it('should delete an instance', () => {
AutomaticTransactionGenerator.deleteInstance(station)
const atg2 = AutomaticTransactionGenerator.getInstance(station)
- expect(atg1).not.toBe(atg2)
+ assert.notStrictEqual(atg1, atg2)
})
})
atg.start()
- expect(atg.started).toBe(true)
+ assert.strictEqual(atg.started, true)
})
await it('should not start when station is not started', () => {
atg.start()
- expect(atg.started).toBe(false)
+ assert.strictEqual(atg.started, false)
})
await it('should warn and not restart when already started', () => {
atg.start()
atg.start()
- expect(atg.started).toBe(true)
+ assert.strictEqual(atg.started, true)
})
})
mockInternalStartConnector(atg)
atg.start()
- expect(atg.started).toBe(true)
+ assert.strictEqual(atg.started, true)
atg.stop()
- expect(atg.started).toBe(false)
+ assert.strictEqual(atg.started, false)
})
await it('should warn when stopping an already stopped ATG', () => {
atg.stop()
- expect(atg.started).toBe(false)
+ assert.strictEqual(atg.started, false)
})
})
atg.stopConnector(1)
- expect(connectorStatus.start).toBe(false)
+ assert.strictEqual(connectorStatus.start, false)
})
await it('should throw when stopping a non-existent connector', () => {
const station = createStationForATG()
const atg = getDefinedATG(station)
- expect(() => {
+ assert.throws(() => {
atg.stopConnector(99)
- }).toThrow(BaseError)
+ }, BaseError)
})
})
transactionId: 1,
} as StartTransactionResponse)
- expect(connectorStatus.startTransactionRequests).toBe(1)
- expect(connectorStatus.acceptedStartTransactionRequests).toBe(1)
- expect(connectorStatus.rejectedStartTransactionRequests).toBe(0)
+ assert.strictEqual(connectorStatus.startTransactionRequests, 1)
+ assert.strictEqual(connectorStatus.acceptedStartTransactionRequests, 1)
+ assert.strictEqual(connectorStatus.rejectedStartTransactionRequests, 0)
})
await it('should increment rejected counters on rejected start response', () => {
transactionId: 1,
} as StartTransactionResponse)
- expect(connectorStatus.startTransactionRequests).toBe(1)
- expect(connectorStatus.acceptedStartTransactionRequests).toBe(0)
- expect(connectorStatus.rejectedStartTransactionRequests).toBe(1)
+ assert.strictEqual(connectorStatus.startTransactionRequests, 1)
+ assert.strictEqual(connectorStatus.acceptedStartTransactionRequests, 0)
+ assert.strictEqual(connectorStatus.rejectedStartTransactionRequests, 1)
})
})
})
* @file Tests for ChargingStation Configuration Management
* @description Unit tests for boot notification, config persistence, and WebSocket handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
station = result.station
// Assert - Pending response should have interval for retry
- expect(station.bootNotificationResponse).toBeDefined()
- expect(station.bootNotificationResponse?.interval).toBeGreaterThan(0)
- expect(station.inPendingState()).toBe(true)
+ if (station.bootNotificationResponse == null) {
+ assert.fail('Expected bootNotificationResponse to be defined')
+ }
+ assert.ok(station.bootNotificationResponse.interval > 0)
+ assert.strictEqual(station.inPendingState(), true)
})
// B02.FR.02: Station should be able to transition out of Pending via new response
bootNotificationStatus: RegistrationStatusEnumType.PENDING,
})
station = result.station
- expect(station.inPendingState()).toBe(true)
+ assert.strictEqual(station.inPendingState(), true)
// Act - Simulate receiving Accepted response (as would happen after retry)
station.bootNotificationResponse = {
}
// Assert - Should now be in Accepted state
- expect(station.inAcceptedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
+ assert.strictEqual(station.inAcceptedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
})
// B02.FR.03: Pending station should have valid heartbeat interval for operation
station = result.station
// Assert - Heartbeat interval should match response interval
- expect(station.getHeartbeatInterval()).toBe(customInterval * 1000)
- expect(station.inPendingState()).toBe(true)
+ assert.strictEqual(station.getHeartbeatInterval(), customInterval * 1000)
+ assert.strictEqual(station.inPendingState(), true)
})
// B02.FR.06: Station should handle clock synchronization from response
station = result.station
// Assert - currentTime should be present for clock synchronization
- expect(station.bootNotificationResponse?.currentTime).toBeDefined()
- expect(station.bootNotificationResponse?.currentTime instanceof Date).toBe(true)
+ assert.notStrictEqual(station.bootNotificationResponse?.currentTime, undefined)
+ assert.ok(station.bootNotificationResponse?.currentTime instanceof Date)
})
// B02.FR.04/05: Station should be able to transition to Rejected from Pending
bootNotificationStatus: RegistrationStatusEnumType.PENDING,
})
station = result.station
- expect(station.inPendingState()).toBe(true)
+ assert.strictEqual(station.inPendingState(), true)
// Act - Simulate receiving Rejected response
station.bootNotificationResponse = {
}
// Assert - Should now be in Rejected state
- expect(station.inRejectedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
+ assert.strictEqual(station.inRejectedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
})
})
station = result.station
// Assert - Rejected response should have interval for retry (typically longer)
- expect(station.bootNotificationResponse).toBeDefined()
- expect(station.bootNotificationResponse?.interval).toBeGreaterThan(0)
- expect(station.inRejectedState()).toBe(true)
+ if (station.bootNotificationResponse == null) {
+ assert.fail('Expected bootNotificationResponse to be defined')
+ }
+ assert.ok(station.bootNotificationResponse.interval > 0)
+ assert.strictEqual(station.inRejectedState(), true)
})
// B03.FR.03: Station should NOT initiate non-boot messages when Rejected
mocks.webSocket.clearMessages()
// Assert - Station is in rejected state
- expect(station.inRejectedState()).toBe(true)
+ assert.strictEqual(station.inRejectedState(), true)
// Assert - No messages should have been sent (station should be silent)
// Per B03.FR.03: CS SHALL NOT send any OCPP message until interval expires
- expect(mocks.webSocket.sentMessages.length).toBe(0)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 0)
})
// B03.FR.04: Station should transition from Rejected to Accepted
bootNotificationStatus: RegistrationStatusEnumType.REJECTED,
})
station = result.station
- expect(station.inRejectedState()).toBe(true)
+ assert.strictEqual(station.inRejectedState(), true)
// Act - Simulate receiving Accepted response after retry
station.bootNotificationResponse = {
}
// Assert - Should now be in Accepted state
- expect(station.inAcceptedState()).toBe(true)
- expect(station.inRejectedState()).toBe(false)
+ assert.strictEqual(station.inAcceptedState(), true)
+ assert.strictEqual(station.inRejectedState(), false)
})
// B03.FR.05: Station should have currentTime for clock synchronization
station = result.station
// Assert - currentTime should be present
- expect(station.bootNotificationResponse?.currentTime).toBeDefined()
- expect(station.bootNotificationResponse?.currentTime instanceof Date).toBe(true)
+ assert.notStrictEqual(station.bootNotificationResponse?.currentTime, undefined)
+ assert.ok(station.bootNotificationResponse?.currentTime instanceof Date)
})
// B03.FR.02: Rejected state should use different (typically longer) retry interval
})
// Assert - Both should have their respective intervals
- expect(pendingStation.station.inPendingState()).toBe(true)
- expect(rejectedStation.station.inRejectedState()).toBe(true)
- expect(pendingStation.station.getHeartbeatInterval()).toBe(60000)
- expect(rejectedStation.station.getHeartbeatInterval()).toBe(TEST_ONE_HOUR_MS)
+ assert.strictEqual(pendingStation.station.inPendingState(), true)
+ assert.strictEqual(rejectedStation.station.inRejectedState(), true)
+ assert.strictEqual(pendingStation.station.getHeartbeatInterval(), 60000)
+ assert.strictEqual(rejectedStation.station.getHeartbeatInterval(), TEST_ONE_HOUR_MS)
// Cleanup
cleanupChargingStation(pendingStation.station)
}
// Assert - Connector state should be preserved even in Rejected state
- expect(station.getConnectorStatus(1)?.availability).toBe(AvailabilityType.Operative)
- expect(station.hasConnector(1)).toBe(true)
+ assert.strictEqual(station.getConnectorStatus(1)?.availability, AvailabilityType.Operative)
+ assert.strictEqual(station.hasConnector(1), true)
})
})
station = result.station
// Act & Assert - should convert seconds to milliseconds
- expect(station.getHeartbeatInterval()).toBe(60000)
+ assert.strictEqual(station.getHeartbeatInterval(), 60000)
})
await it('should return default heartbeat interval when not explicitly configured', () => {
station = result.station
// Act & Assert - default 60s * 1000 = 60000ms
- expect(station.getHeartbeatInterval()).toBe(60000)
+ assert.strictEqual(station.getHeartbeatInterval(), 60000)
})
await it('should return connection timeout in milliseconds', () => {
station = result.station
// Act & Assert - default connection timeout is 30 seconds
- expect(station.getConnectionTimeout()).toBe(TEST_HEARTBEAT_INTERVAL_MS)
+ assert.strictEqual(station.getConnectionTimeout(), TEST_HEARTBEAT_INTERVAL_MS)
})
await it('should return authorize remote TX requests as boolean', () => {
// Act & Assert - getAuthorizeRemoteTxRequests returns boolean
const authorizeRemoteTx = station.getAuthorizeRemoteTxRequests()
- expect(typeof authorizeRemoteTx).toBe('boolean')
+ assert.strictEqual(typeof authorizeRemoteTx, 'boolean')
})
await it('should return local auth list enabled as boolean', () => {
// Act & Assert - getLocalAuthListEnabled returns boolean
const localAuthEnabled = station.getLocalAuthListEnabled()
- expect(typeof localAuthEnabled).toBe('boolean')
+ assert.strictEqual(typeof localAuthEnabled, 'boolean')
})
// === Configuration Save Operations ===
station = result.station
// Act & Assert - should not throw
- expect(() => station?.saveOcppConfiguration()).not.toThrow()
+ assert.doesNotThrow(() => {
+ station?.saveOcppConfiguration()
+ })
})
await it('should have ocppConfiguration object with configurationKey array', () => {
station = result.station
// Act & Assert - configuration structure should be present
- expect(station.ocppConfiguration).toBeDefined()
- expect(station.ocppConfiguration?.configurationKey).toBeDefined()
- expect(Array.isArray(station.ocppConfiguration?.configurationKey)).toBe(true)
+ assert.notStrictEqual(station.ocppConfiguration, undefined)
+ assert.notStrictEqual(station.ocppConfiguration?.configurationKey, undefined)
+ assert.ok(Array.isArray(station.ocppConfiguration?.configurationKey))
})
// === Configuration Mutation ===
const result = createMockChargingStation({ heartbeatInterval: 60 })
station = result.station
const initialInterval = station.getHeartbeatInterval()
- expect(initialInterval).toBe(60000)
+ assert.strictEqual(initialInterval, 60000)
// Act - simulate configuration change by creating new station with different interval
const result2 = createMockChargingStation({ heartbeatInterval: 120 })
const station2 = result2.station
// Assert - different configurations have different intervals
- expect(station2.getHeartbeatInterval()).toBe(120000)
- expect(station.getHeartbeatInterval()).toBe(60000) // Original unchanged
+ assert.strictEqual(station2.getHeartbeatInterval(), 120000)
+ assert.strictEqual(station.getHeartbeatInterval(), 60000) // Original unchanged
// Cleanup second station
cleanupChargingStation(station2)
// Act & Assert - setSupervisionUrl should be a function if available
if ('setSupervisionUrl' in station && typeof station.setSupervisionUrl === 'function') {
- expect(() => station?.setSupervisionUrl('ws://new-server:8080')).not.toThrow()
+ assert.doesNotThrow(() => {
+ station?.setSupervisionUrl('ws://new-server:8080')
+ })
} else {
// Mock station may not have setSupervisionUrl, which is expected
- expect(station).toBeDefined()
+ assert.notStrictEqual(station, undefined)
}
})
station = result.station
// Act & Assert - station info should have template reference
- expect(station.stationInfo?.templateName).toBe('custom-template.json')
+ assert.strictEqual(station.stationInfo?.templateName, 'custom-template.json')
})
await it('should have hashId for configuration persistence', () => {
station = result.station
// Act & Assert - hashId is used for configuration file naming
- expect(station.stationInfo?.hashId).toBeDefined()
- expect(typeof station.stationInfo?.hashId).toBe('string')
+ assert.notStrictEqual(station.stationInfo?.hashId, undefined)
+ assert.strictEqual(typeof station.stationInfo?.hashId, 'string')
})
await it('should preserve station info properties for persistence', () => {
station = result.station
// Act & Assert - station info should have all properties for persistence
- expect(station.stationInfo).toBeDefined()
- expect(station.stationInfo?.baseName).toBe('PERSIST-CS')
- expect(station.stationInfo?.chargingStationId).toContain('PERSIST-CS')
- expect(station.stationInfo?.templateIndex).toBe(5)
+ assert.notStrictEqual(station.stationInfo, undefined)
+ assert.strictEqual(station.stationInfo?.baseName, 'PERSIST-CS')
+ assert.ok(station.stationInfo.chargingStationId?.includes('PERSIST-CS'))
+ assert.strictEqual(station.stationInfo.templateIndex, 5)
})
await it('should track configuration file path via templateFile', () => {
station = result.station
// Act & Assert - templateFile is used to track configuration source
- expect(station.templateFile).toBeDefined()
- expect(typeof station.templateFile).toBe('string')
+ assert.notStrictEqual(station.templateFile, undefined)
+ assert.strictEqual(typeof station.templateFile, 'string')
})
await it('should use mocked file system without real file writes', () => {
station.saveOcppConfiguration()
// Assert - mock file system is available for tracking (no real writes)
- expect(mocks.fileSystem).toBeDefined()
- expect(mocks.fileSystem.writtenFiles).toBeInstanceOf(Map)
+ assert.notStrictEqual(mocks.fileSystem, undefined)
+ assert.ok(mocks.fileSystem.writtenFiles instanceof Map)
// In mock mode, saveOcppConfiguration is a no-op, so no files are written
- expect(mocks.fileSystem.writtenFiles.size).toBe(0)
+ assert.strictEqual(mocks.fileSystem.writtenFiles.size, 0)
})
})
const mocks = result.mocks
// Assert - connection is open by default
- expect(station.isWebSocketConnectionOpened()).toBe(true)
+ assert.strictEqual(station.isWebSocketConnectionOpened(), true)
// Act - change ready state to CLOSED
mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED
// Assert
- expect(station.isWebSocketConnectionOpened()).toBe(false)
+ assert.strictEqual(station.isWebSocketConnectionOpened(), false)
})
await it('should return false when WebSocket is CONNECTING', () => {
mocks.webSocket.readyState = 0 // WebSocketReadyState.CONNECTING
// Assert
- expect(station.isWebSocketConnectionOpened()).toBe(false)
+ assert.strictEqual(station.isWebSocketConnectionOpened(), false)
})
await it('should return false when WebSocket is CLOSING', () => {
mocks.webSocket.readyState = 2 // WebSocketReadyState.CLOSING
// Assert
- expect(station.isWebSocketConnectionOpened()).toBe(false)
+ assert.strictEqual(station.isWebSocketConnectionOpened(), false)
})
await it('should close WebSocket connection via closeWSConnection()', () => {
station = result.station
// Assert - connection exists initially
- expect(station.wsConnection).not.toBeNull()
+ assert.notStrictEqual(station.wsConnection, null)
// Act
station.closeWSConnection()
// Assert - connection is nullified
- expect(station.wsConnection).toBeNull()
+ assert.strictEqual(station.wsConnection, null)
})
await it('should handle closeWSConnection() when already closed', () => {
station.closeWSConnection()
// Assert - no error, connection remains null
- expect(station.wsConnection).toBeNull()
+ assert.strictEqual(station.wsConnection, null)
})
// === Message Capture Tests ===
mocks.webSocket.send('["2","uuid-2","StatusNotification",{"connectorId":1}]')
// Assert
- expect(mocks.webSocket.sentMessages.length).toBe(2)
- expect(mocks.webSocket.sentMessages[0]).toBe('["2","uuid-1","Heartbeat",{}]')
- expect(mocks.webSocket.sentMessages[1]).toBe(
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 2)
+ assert.strictEqual(mocks.webSocket.sentMessages[0], '["2","uuid-1","Heartbeat",{}]')
+ assert.strictEqual(
+ mocks.webSocket.sentMessages[1],
'["2","uuid-2","StatusNotification",{"connectorId":1}]'
)
})
mocks.webSocket.send('["2","uuid-2","BootNotification",{}]')
// Assert
- expect(mocks.webSocket.getLastSentMessage()).toBe('["2","uuid-2","BootNotification",{}]')
+ assert.strictEqual(
+ mocks.webSocket.getLastSentMessage(),
+ '["2","uuid-2","BootNotification",{}]'
+ )
})
await it('should return undefined for getLastSentMessage() when no messages sent', () => {
const mocks = result.mocks
// Assert
- expect(mocks.webSocket.getLastSentMessage()).toBeUndefined()
+ assert.strictEqual(mocks.webSocket.getLastSentMessage(), undefined)
})
await it('should parse sent messages as JSON via getSentMessagesAsJson()', () => {
// Assert
const parsed = mocks.webSocket.getSentMessagesAsJson()
- expect(parsed.length).toBe(1)
- expect(parsed[0]).toStrictEqual([2, 'uuid-1', 'Heartbeat', {}])
+ assert.strictEqual(parsed.length, 1)
+ assert.deepStrictEqual(parsed[0], [2, 'uuid-1', 'Heartbeat', {}])
})
await it('should clear captured messages via clearMessages()', () => {
// Populate messages
mocks.webSocket.send('["2","uuid-1","Heartbeat",{}]')
mocks.webSocket.send('["2","uuid-2","Heartbeat",{}]')
- expect(mocks.webSocket.sentMessages.length).toBe(2)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 2)
// Act
mocks.webSocket.clearMessages()
// Assert
- expect(mocks.webSocket.sentMessages.length).toBe(0)
- expect(mocks.webSocket.sentBinaryMessages.length).toBe(0)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 0)
+ assert.strictEqual(mocks.webSocket.sentBinaryMessages.length, 0)
})
// === Event Simulation Tests ===
mocks.webSocket.simulateMessage('[3,"uuid-1",{}]')
// Assert
- expect(receivedData).toBeDefined()
- expect(Buffer.isBuffer(receivedData)).toBe(true)
+ assert.notStrictEqual(receivedData, undefined)
+ assert.ok(Buffer.isBuffer(receivedData))
})
await it('should emit open event and set readyState via simulateOpen()', () => {
mocks.webSocket.simulateOpen()
// Assert
- expect(openEventFired).toBe(true)
- expect(mocks.webSocket.readyState).toBe(1) // WebSocketReadyState.OPEN
+ assert.strictEqual(openEventFired, true)
+ assert.strictEqual(mocks.webSocket.readyState, 1) // WebSocketReadyState.OPEN
})
await it('should emit close event and set readyState via simulateClose()', () => {
mocks.webSocket.simulateClose(1001, 'Going away')
// Assert
- expect(closeCode).toBe(1001)
- expect(mocks.webSocket.readyState).toBe(3) // WebSocketReadyState.CLOSED
+ assert.strictEqual(closeCode, 1001)
+ assert.strictEqual(mocks.webSocket.readyState, 3) // WebSocketReadyState.CLOSED
})
await it('should emit error event via simulateError()', () => {
mocks.webSocket.simulateError(testError)
// Assert
- expect(receivedError).toBe(testError)
- expect(receivedError?.message).toBe('Connection refused')
+ assert.strictEqual(receivedError, testError)
+ assert.strictEqual(receivedError.message, 'Connection refused')
})
await it('should emit ping event via simulatePing()', () => {
mocks.webSocket.simulatePing(Buffer.from('ping-data'))
// Assert
- expect(pingReceived).toBe(true)
- expect(pingData?.toString()).toBe('ping-data')
+ assert.strictEqual(pingReceived, true)
+ assert.strictEqual(pingData?.toString(), 'ping-data')
})
await it('should emit pong event via simulatePong()', () => {
mocks.webSocket.simulatePong(Buffer.from('pong-data'))
// Assert
- expect(pongReceived).toBe(true)
- expect(pongData?.toString()).toBe('pong-data')
+ assert.strictEqual(pongReceived, true)
+ assert.strictEqual(pongData?.toString(), 'pong-data')
})
// === Edge Case Tests ===
mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED
// Act & Assert
- expect(() => {
- mocks.webSocket.send('["2","uuid","Heartbeat",{}]')
- }).toThrow('WebSocket is not open')
+ assert.throws(
+ () => {
+ mocks.webSocket.send('["2","uuid","Heartbeat",{}]')
+ },
+ { message: /WebSocket is not open/ }
+ )
})
await it('should capture URL from WebSocket connection', () => {
const mocks = result.mocks
// Assert
- expect(mocks.webSocket.url).toBeDefined()
- expect(typeof mocks.webSocket.url).toBe('string')
- expect(mocks.webSocket.url.length).toBeGreaterThan(0)
+ assert.notStrictEqual(mocks.webSocket.url, undefined)
+ assert.strictEqual(typeof mocks.webSocket.url, 'string')
+ assert.ok(mocks.webSocket.url.length > 0)
})
})
const pingInterval = station.getWebSocketPingInterval()
// Assert - should return a valid interval value
- expect(pingInterval).toBeGreaterThanOrEqual(0)
- expect(typeof pingInterval).toBe('number')
+ assert.ok(pingInterval >= 0)
+ assert.strictEqual(typeof pingInterval, 'number')
})
})
station.restartWebSocketPing()
// Assert - should complete without error
- expect(station).toBeDefined()
+ assert.notStrictEqual(station, undefined)
})
})
})
* @file Tests for ChargingStation Connector and EVSE Operations
* @description Unit tests for connector queries, EVSE management, and availability
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.hasConnector(0)).toBe(true)
- expect(station.hasConnector(1)).toBe(true)
- expect(station.hasConnector(2)).toBe(true)
+ assert.strictEqual(station.hasConnector(0), true)
+ assert.strictEqual(station.hasConnector(1), true)
+ assert.strictEqual(station.hasConnector(2), true)
})
await it('should return false for hasConnector() with non-existing connector IDs', () => {
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.hasConnector(3)).toBe(false)
- expect(station.hasConnector(999)).toBe(false)
- expect(station.hasConnector(-1)).toBe(false)
+ assert.strictEqual(station.hasConnector(3), false)
+ assert.strictEqual(station.hasConnector(999), false)
+ assert.strictEqual(station.hasConnector(-1), false)
})
await it('should return connector status for valid connector IDs', () => {
const status1 = station.getConnectorStatus(1)
const status2 = station.getConnectorStatus(2)
- expect(status1).toBeDefined()
- expect(status2).toBeDefined()
+ assert.notStrictEqual(status1, undefined)
+ assert.notStrictEqual(status2, undefined)
})
await it('should return undefined for getConnectorStatus() with invalid connector IDs', () => {
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.getConnectorStatus(999)).toBeUndefined()
- expect(station.getConnectorStatus(-1)).toBeUndefined()
+ assert.strictEqual(station.getConnectorStatus(999), undefined)
+ assert.strictEqual(station.getConnectorStatus(-1), undefined)
})
await it('should correctly count connectors via getNumberOfConnectors()', () => {
station = result.station
// Should return 3, not 4 (connector 0 is excluded from count)
- expect(station.getNumberOfConnectors()).toBe(3)
+ assert.strictEqual(station.getNumberOfConnectors(), 3)
})
await it('should return true for isConnectorAvailable() on operative connectors', () => {
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.isConnectorAvailable(1)).toBe(true)
- expect(station.isConnectorAvailable(2)).toBe(true)
+ assert.strictEqual(station.isConnectorAvailable(1), true)
+ assert.strictEqual(station.isConnectorAvailable(2), true)
})
await it('should return false for isConnectorAvailable() on connector 0', () => {
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.isConnectorAvailable(0)).toBe(false)
+ assert.strictEqual(station.isConnectorAvailable(0), false)
})
await it('should return false for isConnectorAvailable() on non-existing connector', () => {
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.isConnectorAvailable(999)).toBe(false)
+ assert.strictEqual(station.isConnectorAvailable(999), false)
})
})
station = result.station
// Connector 0 always exists and represents the charging station itself
- expect(station.hasConnector(0)).toBe(true)
- expect(station.getConnectorStatus(0)).toBeDefined()
+ assert.strictEqual(station.hasConnector(0), true)
+ assert.notStrictEqual(station.getConnectorStatus(0), undefined)
})
await it('should determine station availability via connector 0 status', () => {
station = result.station
// Initially connector 0 is operative
- expect(station.isChargingStationAvailable()).toBe(true)
+ assert.strictEqual(station.isChargingStationAvailable(), true)
})
})
})
station = result.station
- expect(station.hasEvses).toBe(false)
- expect(station.getNumberOfEvses()).toBe(0)
+ assert.strictEqual(station.hasEvses, false)
+ assert.strictEqual(station.getNumberOfEvses(), 0)
})
await it('should return undefined for getEvseIdByConnectorId() in non-EVSE mode', () => {
})
station = result.station
- expect(station.getEvseIdByConnectorId(1)).toBeUndefined()
- expect(station.getEvseIdByConnectorId(2)).toBeUndefined()
+ assert.strictEqual(station.getEvseIdByConnectorId(1), undefined)
+ assert.strictEqual(station.getEvseIdByConnectorId(2), undefined)
})
})
})
station = result.station
- expect(station.hasEvses).toBe(true)
+ assert.strictEqual(station.hasEvses, true)
})
await it('should return correct EVSE count via getNumberOfEvses() in EVSE mode', () => {
})
station = result.station
- expect(station.getNumberOfEvses()).toBe(1)
+ assert.strictEqual(station.getNumberOfEvses(), 1)
})
await it('should return connector status via getConnectorStatus() in EVSE mode', () => {
const status1 = station.getConnectorStatus(1)
const status2 = station.getConnectorStatus(2)
- expect(status1).toBeDefined()
- expect(status2).toBeDefined()
+ assert.notStrictEqual(status1, undefined)
+ assert.notStrictEqual(status2, undefined)
})
await it('should map connector IDs to EVSE IDs via getEvseIdByConnectorId()', () => {
station = result.station
// In single-EVSE mode, both connectors should map to EVSE 1
- expect(station.getEvseIdByConnectorId(1)).toBe(1)
- expect(station.getEvseIdByConnectorId(2)).toBe(1)
+ assert.strictEqual(station.getEvseIdByConnectorId(1), 1)
+ assert.strictEqual(station.getEvseIdByConnectorId(2), 1)
})
await it('should return undefined for getEvseIdByConnectorId() with invalid connector', () => {
})
station = result.station
- expect(station.getEvseIdByConnectorId(999)).toBeUndefined()
+ assert.strictEqual(station.getEvseIdByConnectorId(999), undefined)
})
await it('should return EVSE status via getEvseStatus() for valid EVSE IDs', () => {
const evseStatus = station.getEvseStatus(1)
- expect(evseStatus).toBeDefined()
- expect(evseStatus?.connectors).toBeDefined()
- expect(evseStatus?.connectors.size).toBeGreaterThan(0)
+ if (evseStatus == null) {
+ assert.fail('Expected evseStatus to be defined')
+ }
+ assert.notStrictEqual(evseStatus.connectors, undefined)
+ assert.ok(evseStatus.connectors.size > 0)
})
await it('should return undefined for getEvseStatus() with invalid EVSE IDs', () => {
})
station = result.station
- expect(station.getEvseStatus(999)).toBeUndefined()
+ assert.strictEqual(station.getEvseStatus(999), undefined)
})
await it('should return true for hasConnector() with connectors in EVSE mode', () => {
})
station = result.station
- expect(station.hasConnector(1)).toBe(true)
- expect(station.hasConnector(2)).toBe(true)
+ assert.strictEqual(station.hasConnector(1), true)
+ assert.strictEqual(station.hasConnector(2), true)
})
await it('should return false for hasConnector() with non-existing connector in EVSE mode', () => {
})
station = result.station
- expect(station.hasConnector(999)).toBe(false)
+ assert.strictEqual(station.hasConnector(999), false)
})
await it('should correctly count connectors in EVSE mode via getNumberOfConnectors()', () => {
station = result.station
// Should return total connectors across all EVSEs
- expect(station.getNumberOfConnectors()).toBe(4)
+ assert.strictEqual(station.getNumberOfConnectors(), 4)
})
})
station = result.station
// Act & Assert
- expect(station.inAcceptedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
- expect(station.inRejectedState()).toBe(false)
- expect(station.inUnknownState()).toBe(false)
+ assert.strictEqual(station.inAcceptedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
+ assert.strictEqual(station.inRejectedState(), false)
+ assert.strictEqual(station.inUnknownState(), false)
})
await it('should return true for inPendingState when boot status is PENDING', () => {
station = result.station
// Act & Assert
- expect(station.inPendingState()).toBe(true)
- expect(station.inAcceptedState()).toBe(false)
- expect(station.inRejectedState()).toBe(false)
- expect(station.inUnknownState()).toBe(false)
+ assert.strictEqual(station.inPendingState(), true)
+ assert.strictEqual(station.inAcceptedState(), false)
+ assert.strictEqual(station.inRejectedState(), false)
+ assert.strictEqual(station.inUnknownState(), false)
})
await it('should return true for inRejectedState when boot status is REJECTED', () => {
station = result.station
// Act & Assert
- expect(station.inRejectedState()).toBe(true)
- expect(station.inAcceptedState()).toBe(false)
- expect(station.inPendingState()).toBe(false)
- expect(station.inUnknownState()).toBe(false)
+ assert.strictEqual(station.inRejectedState(), true)
+ assert.strictEqual(station.inAcceptedState(), false)
+ assert.strictEqual(station.inPendingState(), false)
+ assert.strictEqual(station.inUnknownState(), false)
})
await it('should return true for inUnknownState when boot notification response is null', () => {
}
// Assert - only check inUnknownState
- expect(station.inUnknownState()).toBe(true)
+ assert.strictEqual(station.inUnknownState(), true)
})
await it('should allow state transitions from PENDING to ACCEPTED', () => {
bootNotificationStatus: RegistrationStatusEnumType.PENDING,
})
station = result.station
- expect(station.inPendingState()).toBe(true)
+ assert.strictEqual(station.inPendingState(), true)
// Act - transition from PENDING to ACCEPTED
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
station.bootNotificationResponse!.currentTime = new Date()
// Assert
- expect(station.inAcceptedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
+ assert.strictEqual(station.inAcceptedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
})
await it('should allow state transitions from PENDING to REJECTED', () => {
bootNotificationStatus: RegistrationStatusEnumType.PENDING,
})
station = result.station
- expect(station.inPendingState()).toBe(true)
+ assert.strictEqual(station.inPendingState(), true)
// Act - transition from PENDING to REJECTED
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
station.bootNotificationResponse!.currentTime = new Date()
// Assert
- expect(station.inRejectedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
+ assert.strictEqual(station.inRejectedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
})
})
// Assert
const found = station.getReservationBy('reservationId', 101)
- expect(found).toBeDefined()
- expect(found?.idTag).toBe('test-tag-1')
- expect(found?.connectorId).toBe(1)
+ if (found == null) {
+ assert.fail('Expected reservation to be found')
+ }
+ assert.strictEqual(found.idTag, 'test-tag-1')
+ assert.strictEqual(found.connectorId, 1)
})
await it('should replace existing reservation with new one', async () => {
// Assert - Only second reservation should exist with same ID
const found = station.getReservationBy('reservationId', 201)
- expect(found).toBeDefined()
- expect(found?.idTag).toBe('tag-2')
- expect(found?.connectorId).toBe(2)
+ if (found == null) {
+ assert.fail('Expected reservation to be found')
+ }
+ assert.strictEqual(found.idTag, 'tag-2')
+ assert.strictEqual(found.connectorId, 2)
})
await it('should remove reservation with EXPIRED reason', async () => {
// Assert
const found = station.getReservationBy('reservationId', 301)
- expect(found).toBeUndefined()
+ assert.strictEqual(found, undefined)
})
await it('should remove reservation with REPLACE_EXISTING reason', async () => {
// Assert
const found = station.getReservationBy('reservationId', 401)
- expect(found).toBeUndefined()
+ assert.strictEqual(found, undefined)
})
await it('should query reservation by reservationId', async () => {
const found = station.getReservationBy('reservationId', 501)
// Assert
- expect(found).toBeDefined()
- expect(found?.connectorId).toBe(2)
- expect(found?.idTag).toBe('query-test-id')
+ if (found == null) {
+ assert.fail('Expected reservation to be found')
+ }
+ assert.strictEqual(found.connectorId, 2)
+ assert.strictEqual(found.idTag, 'query-test-id')
})
await it('should query reservation by idTag', async () => {
const found = station.getReservationBy('idTag', 'search-by-tag')
// Assert
- expect(found).toBeDefined()
- expect(found?.reservationId).toBe(601)
- expect(found?.connectorId).toBe(1)
+ if (found == null) {
+ assert.fail('Expected reservation to be found')
+ }
+ assert.strictEqual(found.reservationId, 601)
+ assert.strictEqual(found.connectorId, 1)
})
await it('should query reservation by connectorId', async () => {
const found = station.getReservationBy('connectorId', 2)
// Assert
- expect(found).toBeDefined()
- expect(found?.reservationId).toBe(701)
- expect(found?.idTag).toBe('connector-search')
+ if (found == null) {
+ assert.fail('Expected reservation to be found')
+ }
+ assert.strictEqual(found.reservationId, 701)
+ assert.strictEqual(found.idTag, 'connector-search')
})
await it('should handle isConnectorReservable check with valid reservationId', async () => {
const isReservable = station.isConnectorReservable(801)
// Assert - Should return false since reservation exists
- expect(isReservable).toBe(false)
+ assert.strictEqual(isReservable, false)
})
await it('should handle isConnectorReservable check with non-existent reservationId', () => {
const isReservable = station.isConnectorReservable(999)
// Assert - Should return true since reservation does not exist
- expect(isReservable).toBe(true)
+ assert.strictEqual(isReservable, true)
})
await it('should not allow reservation on connector 0 via isConnectorReservable', () => {
const isReservable = station.isConnectorReservable(901, 'test-tag', 0)
// Assert - Connector 0 should not be reservable
- expect(isReservable).toBe(false)
+ assert.strictEqual(isReservable, false)
})
await it('should handle multiple reservations on different connectors', async () => {
// Assert
const found1 = station.getReservationBy('reservationId', 1001)
const found2 = station.getReservationBy('reservationId', 1002)
- expect(found1).toBeDefined()
- expect(found2).toBeDefined()
- expect(found1?.connectorId).toBe(1)
- expect(found2?.connectorId).toBe(2)
+ assert.notStrictEqual(found1, undefined)
+ assert.notStrictEqual(found2, undefined)
+ assert.strictEqual(found1?.connectorId, 1)
+ assert.strictEqual(found2?.connectorId, 2)
})
})
})
* @file Tests for ChargingStation Lifecycle Operations
* @description Unit tests for charging station start/stop/restart and delete operations
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
const finalStarted = station.started
// Assert
- expect(initialStarted).toBe(false)
- expect(finalStarted).toBe(true)
+ assert.strictEqual(initialStarted, false)
+ assert.strictEqual(finalStarted, true)
})
await it('should not restart when already started', () => {
const stillStarted = station.started
// Assert
- expect(firstStarted).toBe(true)
- expect(stillStarted).toBe(true)
+ assert.strictEqual(firstStarted, true)
+ assert.strictEqual(stillStarted, true)
})
await it('should set starting flag during start()', () => {
// Act & Assert
const initialStarting = station.starting
- expect(initialStarting).toBe(false)
+ assert.strictEqual(initialStarting, false)
// After start() completes, starting should be false
station.start()
- expect(station.starting).toBe(false)
- expect(station.started).toBe(true)
+ assert.strictEqual(station.starting, false)
+ assert.strictEqual(station.started, true)
})
await it('should transition from started to stopped on stop()', async () => {
const result = createMockChargingStation({ connectorsCount: 1 })
station = result.station
station.start()
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
// Act
await station.stop()
// Assert
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
})
await it('should be idempotent when calling stop() on already stopped station', async () => {
const result = createMockChargingStation({ connectorsCount: 1 })
station = result.station
// Station starts in stopped state
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
// Act - call stop on already stopped station
await station.stop()
// Assert - should remain stopped without error
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
})
await it('should set stopping flag during stop()', async () => {
station.start()
// Assert initial state
- expect((station as unknown as { stopping: boolean }).stopping).toBe(false)
+ assert.strictEqual((station as unknown as { stopping: boolean }).stopping, false)
// Act
await station.stop()
// Assert - after stop() completes, stopping should be false
- expect((station as unknown as { stopping: boolean }).stopping).toBe(false)
- expect(station.started).toBe(false)
+ assert.strictEqual((station as unknown as { stopping: boolean }).stopping, false)
+ assert.strictEqual(station.started, false)
})
await it('should clear bootNotificationResponse on stop()', async () => {
const result = createMockChargingStation({ connectorsCount: 1 })
station = result.station
station.start()
- expect(station.bootNotificationResponse).toBeDefined()
+ assert.notStrictEqual(station.bootNotificationResponse, undefined)
// Act
await station.stop()
// Assert - bootNotificationResponse should be deleted
- expect(station.bootNotificationResponse).toBeUndefined()
+ assert.strictEqual(station.bootNotificationResponse, undefined)
})
await it('should be restartable after stop()', async () => {
const result = createMockChargingStation({ connectorsCount: 1 })
station = result.station
station.start()
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
// Act - stop then start again
await station.stop()
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
station.start()
// Assert - should be started again
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
})
await it('should guard against concurrent start operations', () => {
// Act - attempt to start while already starting should be guarded
// The mock start() method resets starting, but this tests the initial state
- expect(station.starting).toBe(true)
+ assert.strictEqual(station.starting, true)
// Assert - the real ChargingStation guards against this
// (mock implementation doesn't fully replicate guard, but state is verified)
// Arrange
const result = createMockChargingStation({ connectorsCount: 2 })
station = result.station
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
// Act - delete while stopped (deleteConfiguration = false to skip file ops)
await station.delete(false)
// Assert - connectors and evses should be cleared
- expect(station.connectors.size).toBe(0)
- expect(station.evses.size).toBe(0)
- expect(station.requests.size).toBe(0)
+ assert.strictEqual(station.connectors.size, 0)
+ assert.strictEqual(station.evses.size, 0)
+ assert.strictEqual(station.requests.size, 0)
})
await it('should stop station before delete() if running', async () => {
const result = createMockChargingStation({ connectorsCount: 1 })
station = result.station
station.start()
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
// Act - delete calls stop internally
await station.delete(false)
// Assert - station should be stopped and cleared
- expect(station.started).toBe(false)
- expect(station.connectors.size).toBe(0)
+ assert.strictEqual(station.started, false)
+ assert.strictEqual(station.connectors.size, 0)
})
await it('should handle delete operation with pending transactions', async () => {
// Start the station
station.start()
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
// Act - Delete station (should stop first)
await station.delete()
// Assert - Station should be stopped and resources cleared
- expect(station.started).toBe(false)
- expect(station.connectors.size).toBe(0)
- expect(station.evses.size).toBe(0)
+ assert.strictEqual(station.started, false)
+ assert.strictEqual(station.connectors.size, 0)
+ assert.strictEqual(station.evses.size, 0)
})
})
})
* @file Tests for ChargingStation Error Recovery and Message Buffering
* @description Unit tests for charging station error handling, reconnection, and message queuing
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
mocks.webSocket.simulateClose(1006, 'Connection lost')
// Assert - WebSocket should be in CLOSED state
- expect(mocks.webSocket.readyState).toBe(3) // CLOSED
+ assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED
})
await it('should not reconnect on normal WebSocket close', () => {
mocks.webSocket.simulateClose(1000, 'Normal closure')
// Assert - WebSocket should be closed
- expect(mocks.webSocket.readyState).toBe(3) // CLOSED
+ assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED
})
await it('should track connection retry count', () => {
const stationWithRetryCount = station as unknown as { wsConnectionRetryCount: number }
// Assert - Initial retry count should be 0
- expect(stationWithRetryCount.wsConnectionRetryCount).toBe(0)
+ assert.strictEqual(stationWithRetryCount.wsConnectionRetryCount, 0)
// Act - Increment retry count manually (simulating reconnection attempt)
stationWithRetryCount.wsConnectionRetryCount = 1
// Assert - Count should be incremented
- expect(stationWithRetryCount.wsConnectionRetryCount).toBe(1)
+ assert.strictEqual(stationWithRetryCount.wsConnectionRetryCount, 1)
})
await it('should support exponential backoff configuration', () => {
station = result.station
// Assert - stationInfo should have reconnect configuration options
- expect(station.stationInfo).toBeDefined()
+ assert.notStrictEqual(station.stationInfo, undefined)
// The actual implementation uses stationInfo.reconnectExponentialDelay
// and stationInfo.autoReconnectMaxRetries for reconnection logic
})
stationWithRetryCount.wsConnectionRetryCount = 0
// Assert
- expect(stationWithRetryCount.wsConnectionRetryCount).toBe(0)
+ assert.strictEqual(stationWithRetryCount.wsConnectionRetryCount, 0)
})
// -------------------------------------------------------------------------
mocks.webSocket.simulateMessage('invalid json')
// Assert - Station should still be operational (not crashed)
- expect(station.connectors.size).toBeGreaterThan(0)
+ assert.ok(station.connectors.size > 0)
})
await it('should handle WebSocket error event gracefully', () => {
mocks.webSocket.simulateError(new Error('Connection refused'))
// Assert - Error event should have been emitted and received
- expect(errorEventReceived).toBe(true)
+ assert.strictEqual(errorEventReceived, true)
})
await it('should reject duplicate message IDs for incoming messages', () => {
])
// Assert - Request with duplicate ID exists
- expect(station.requests.has(messageId)).toBe(true)
+ assert.strictEqual(station.requests.has(messageId), true)
// The actual implementation throws OCPPError with SECURITY_ERROR
// when receiving an incoming message with duplicate message ID
station.requests.clear()
// Assert - No pending request exists
- expect(station.requests.size).toBe(0)
+ assert.strictEqual(station.requests.size, 0)
// The actual implementation throws OCPPError with INTERNAL_ERROR
// when receiving a response for unknown message ID
mocks.webSocket.simulateClose(1006, 'Server unreachable')
// Assert - Station should remain in valid state
- expect(station.connectors.size).toBeGreaterThan(0)
- expect(mocks.webSocket.readyState).toBe(3) // CLOSED
+ assert.ok(station.connectors.size > 0)
+ assert.strictEqual(mocks.webSocket.readyState, 3) // CLOSED
})
await it('should handle boot notification rejected state', () => {
station = result.station
// Assert - Station should report rejected state
- expect(station.inRejectedState()).toBe(true)
- expect(station.inAcceptedState()).toBe(false)
+ assert.strictEqual(station.inRejectedState(), true)
+ assert.strictEqual(station.inAcceptedState(), false)
// Station in rejected state should not initiate messages per OCPP spec (B03.FR.03)
- expect(station.bootNotificationResponse?.status).toBe(RegistrationStatusEnumType.REJECTED)
+ assert.strictEqual(
+ station.bootNotificationResponse?.status,
+ RegistrationStatusEnumType.REJECTED
+ )
})
await it('should maintain connector states during connection failure', () => {
// Assert - Connector state should be preserved
const connector1After = station.getConnectorStatus(1)
- expect(connector1After?.transactionStarted).toBe(true)
- expect(connector1After?.transactionId).toBe(999)
+ assert.strictEqual(connector1After?.transactionStarted, true)
+ assert.strictEqual(connector1After.transactionId, 999)
})
// -------------------------------------------------------------------------
// Assert - Station should be properly cleaned up
// (listenerCount returns 0 in mock implementation)
- expect(station.listenerCount('someEvent')).toBe(0)
+ assert.strictEqual(station.listenerCount('someEvent'), 0)
})
await it('should clear timers on cleanup', () => {
cleanupChargingStation(station)
// Assert - Timer should be cleared
- expect(station.heartbeatSetInterval).toBeUndefined()
+ assert.strictEqual(station.heartbeatSetInterval, undefined)
})
await it('should clear pending requests on cleanup', () => {
cleanupChargingStation(station)
// Assert - Requests should be cleared
- expect(station.requests.size).toBe(0)
+ assert.strictEqual(station.requests.size, 0)
})
})
// Assert - Message should be queued but not sent
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(1)
- expect(stationWithQueue.messageQueue[0]).toBe(testMessage)
- expect(mocks.webSocket.sentMessages.length).toBe(0)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 1)
+ assert.strictEqual(stationWithQueue.messageQueue[0], testMessage)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 0)
})
await it('should send message immediately when WebSocket is open', () => {
// Note: Due to async nature, the message may be sent or buffered depending on timing
// This test verifies the message is queued at minimum
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBeGreaterThanOrEqual(0)
+ assert.ok(stationWithQueue.messageQueue.length >= 0)
})
await it('should flush messages in FIFO order when connection restored', () => {
// Assert - All messages should be buffered
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(3)
- expect(stationWithQueue.messageQueue[0]).toBe(msg1)
- expect(stationWithQueue.messageQueue[1]).toBe(msg2)
- expect(stationWithQueue.messageQueue[2]).toBe(msg3)
- expect(mocks.webSocket.sentMessages.length).toBe(0)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 3)
+ assert.strictEqual(stationWithQueue.messageQueue[0], msg1)
+ assert.strictEqual(stationWithQueue.messageQueue[1], msg2)
+ assert.strictEqual(stationWithQueue.messageQueue[2], msg3)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 0)
})
await it('should preserve message order across multiple buffer operations', () => {
// Assert - Verify FIFO order
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(5)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 5)
for (let i = 0; i < messages.length; i++) {
- expect(stationWithQueue.messageQueue[i]).toBe(messages[i])
+ assert.strictEqual(stationWithQueue.messageQueue[i], messages[i])
}
})
// Assert - All messages should be buffered
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(messageCount)
- expect(mocks.webSocket.sentMessages.length).toBe(0)
+ assert.strictEqual(stationWithQueue.messageQueue.length, messageCount)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 0)
// Verify first and last message are in correct positions
- expect(stationWithQueue.messageQueue[0]).toContain('msg-0')
- expect(stationWithQueue.messageQueue[messageCount - 1]).toContain(
- `msg-${(messageCount - 1).toString()}`
+ assert.ok(stationWithQueue.messageQueue[0].includes('msg-0'))
+ assert.ok(
+ stationWithQueue.messageQueue[messageCount - 1].includes(
+ `msg-${(messageCount - 1).toString()}`
+ )
)
})
// Assert - Message should remain buffered
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(1)
- expect(mocks.webSocket.sentMessages.length).toBe(initialSentCount)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 1)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, initialSentCount)
})
await it('should clear buffer after successful message transmission', () => {
const bufferedCount = stationWithQueue.messageQueue.length
// Assert - Message is buffered
- expect(bufferedCount).toBe(1)
+ assert.strictEqual(bufferedCount, 1)
// Now simulate successful send by manually removing (simulating what sendMessageBuffer does)
if (stationWithQueue.messageQueue.length > 0) {
}
// Assert - Buffer should be cleared
- expect(stationWithQueue.messageQueue.length).toBe(0)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 0)
})
await it('should handle rapid buffer/reconnect cycles without message loss', () => {
// Assert - All messages from all cycles should be buffered in order
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(totalExpectedMessages)
- expect(stationWithQueue.messageQueue[0]).toContain('cycle-0-msg-0')
- expect(stationWithQueue.messageQueue[totalExpectedMessages - 1]).toContain(
- `cycle-${(cycleCount - 1).toString()}-msg-${(messagesPerCycle - 1).toString()}`
+ assert.strictEqual(stationWithQueue.messageQueue.length, totalExpectedMessages)
+ assert.ok(stationWithQueue.messageQueue[0].includes('cycle-0-msg-0'))
+ assert.ok(
+ stationWithQueue.messageQueue[totalExpectedMessages - 1].includes(
+ `cycle-${(cycleCount - 1).toString()}-msg-${(messagesPerCycle - 1).toString()}`
+ )
)
})
})
* @file Tests for ChargingStation Transaction Management
* @description Unit tests for transaction queries, energy meters, and concurrent transaction handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
const connectorId = station.getConnectorIdByTransactionId(12345)
// Assert
- expect(connectorId).toBeUndefined()
+ assert.strictEqual(connectorId, undefined)
})
await it('should return connector id for getConnectorIdByTransactionId with active transaction', () => {
const connectorId = station.getConnectorIdByTransactionId(100)
// Assert
- expect(connectorId).toBe(1)
+ assert.strictEqual(connectorId, 1)
})
await it('should return undefined for getConnectorIdByTransactionId with undefined transactionId', () => {
const connectorId = station.getConnectorIdByTransactionId(undefined)
// Assert
- expect(connectorId).toBeUndefined()
+ assert.strictEqual(connectorId, undefined)
})
await it('should return undefined for getEvseIdByTransactionId in non-EVSE mode', () => {
const evseId = station.getEvseIdByTransactionId(100)
// Assert
- expect(evseId).toBeUndefined()
+ assert.strictEqual(evseId, undefined)
})
await it('should return EVSE id for getEvseIdByTransactionId in EVSE mode with active transaction', () => {
const evseId = station.getEvseIdByTransactionId(200)
// Assert
- expect(evseId).toBe(1)
+ assert.strictEqual(evseId, 1)
})
await it('should return idTag for getTransactionIdTag with active transaction', () => {
const idTag = station.getTransactionIdTag(300)
// Assert
- expect(idTag).toBe('MY-TAG-123')
+ assert.strictEqual(idTag, 'MY-TAG-123')
})
await it('should return undefined for getTransactionIdTag with no matching transaction', () => {
const idTag = station.getTransactionIdTag(999)
// Assert
- expect(idTag).toBeUndefined()
+ assert.strictEqual(idTag, undefined)
})
await it('should return zero for getNumberOfRunningTransactions with no transactions', () => {
const count = station.getNumberOfRunningTransactions()
// Assert
- expect(count).toBe(0)
+ assert.strictEqual(count, 0)
})
await it('should return correct count for getNumberOfRunningTransactions with active transactions', () => {
const count = station.getNumberOfRunningTransactions()
// Assert
- expect(count).toBe(2)
+ assert.strictEqual(count, 2)
})
})
const energy = station.getEnergyActiveImportRegisterByConnectorId(1)
// Assert
- expect(energy).toBe(0)
+ assert.strictEqual(energy, 0)
})
await it('should return energy value for getEnergyActiveImportRegisterByConnectorId with active transaction', () => {
const energy = station.getEnergyActiveImportRegisterByConnectorId(1)
// Assert
- expect(energy).toBe(12500)
+ assert.strictEqual(energy, 12500)
})
await it('should return rounded energy value when rounded=true', () => {
const energy = station.getEnergyActiveImportRegisterByConnectorId(1, true)
// Assert
- expect(energy).toBe(12346)
+ assert.strictEqual(energy, 12346)
})
await it('should return 0 for getEnergyActiveImportRegisterByConnectorId with invalid connector', () => {
const energy = station.getEnergyActiveImportRegisterByConnectorId(99)
// Assert
- expect(energy).toBe(0)
+ assert.strictEqual(energy, 0)
})
await it('should return 0 for getEnergyActiveImportRegisterByTransactionId with no matching transaction', () => {
const energy = station.getEnergyActiveImportRegisterByTransactionId(999)
// Assert
- expect(energy).toBe(0)
+ assert.strictEqual(energy, 0)
})
await it('should return energy for getEnergyActiveImportRegisterByTransactionId with active transaction', () => {
const energy = station.getEnergyActiveImportRegisterByTransactionId(400)
// Assert
- expect(energy).toBe(25000)
+ assert.strictEqual(energy, 25000)
})
})
}
// Act & Assert - Running transactions count
- expect(station.getNumberOfRunningTransactions()).toBe(3)
+ assert.strictEqual(station.getNumberOfRunningTransactions(), 3)
// Act & Assert - Transaction queries
- expect(station.getConnectorIdByTransactionId(100)).toBe(1)
- expect(station.getConnectorIdByTransactionId(101)).toBe(2)
- expect(station.getConnectorIdByTransactionId(102)).toBe(3)
+ assert.strictEqual(station.getConnectorIdByTransactionId(100), 1)
+ assert.strictEqual(station.getConnectorIdByTransactionId(101), 2)
+ assert.strictEqual(station.getConnectorIdByTransactionId(102), 3)
// Act & Assert - Energy meters
- expect(station.getEnergyActiveImportRegisterByTransactionId(100)).toBe(10000)
- expect(station.getEnergyActiveImportRegisterByTransactionId(101)).toBe(20000)
- expect(station.getEnergyActiveImportRegisterByTransactionId(102)).toBe(30000)
+ assert.strictEqual(station.getEnergyActiveImportRegisterByTransactionId(100), 10000)
+ assert.strictEqual(station.getEnergyActiveImportRegisterByTransactionId(101), 20000)
+ assert.strictEqual(station.getEnergyActiveImportRegisterByTransactionId(102), 30000)
// Act & Assert - Id tags
- expect(station.getTransactionIdTag(100)).toBe('TAG-A')
- expect(station.getTransactionIdTag(101)).toBe('TAG-B')
- expect(station.getTransactionIdTag(102)).toBe('TAG-C')
+ assert.strictEqual(station.getTransactionIdTag(100), 'TAG-A')
+ assert.strictEqual(station.getTransactionIdTag(101), 'TAG-B')
+ assert.strictEqual(station.getTransactionIdTag(102), 'TAG-C')
})
await it('should handle transactions across multiple EVSEs', () => {
}
// Act & Assert - Running transactions count
- expect(station.getNumberOfRunningTransactions()).toBe(2)
+ assert.strictEqual(station.getNumberOfRunningTransactions(), 2)
// Act & Assert - EVSE queries
- expect(station.getEvseIdByTransactionId(500)).toBe(1)
- expect(station.getEvseIdByTransactionId(501)).toBe(2)
+ assert.strictEqual(station.getEvseIdByTransactionId(500), 1)
+ assert.strictEqual(station.getEvseIdByTransactionId(501), 2)
// Act & Assert - Connector queries
- expect(station.getConnectorIdByTransactionId(500)).toBe(1)
- expect(station.getConnectorIdByTransactionId(501)).toBe(3)
+ assert.strictEqual(station.getConnectorIdByTransactionId(500), 1)
+ assert.strictEqual(station.getConnectorIdByTransactionId(501), 3)
// Act & Assert - Energy meters
- expect(station.getEnergyActiveImportRegisterByTransactionId(500)).toBe(15000)
- expect(station.getEnergyActiveImportRegisterByTransactionId(501)).toBe(18000)
+ assert.strictEqual(station.getEnergyActiveImportRegisterByTransactionId(500), 15000)
+ assert.strictEqual(station.getEnergyActiveImportRegisterByTransactionId(501), 18000)
})
await it('should correctly count transactions only on connectors > 0', () => {
const count = station.getNumberOfRunningTransactions()
// Assert - Only connector 1 should count
- expect(count).toBe(1)
+ assert.strictEqual(count, 1)
})
await it('should return idTag in EVSE mode for getTransactionIdTag', () => {
const idTag = station.getTransactionIdTag(600)
// Assert
- expect(idTag).toBe('EVSE-MODE-TAG')
+ assert.strictEqual(idTag, 'EVSE-MODE-TAG')
})
await it('should handle rounded energy values for getEnergyActiveImportRegisterByTransactionId', () => {
const rounded = station.getEnergyActiveImportRegisterByTransactionId(700, true)
// Assert
- expect(unrounded).toBe(12345.5)
- expect(rounded).toBe(12346)
+ assert.strictEqual(unrounded, 12345.5)
+ assert.strictEqual(rounded, 12346)
})
})
station.startHeartbeat()
// Assert - heartbeat interval should be created
- expect(station.heartbeatSetInterval).toBeDefined()
- expect(typeof station.heartbeatSetInterval).toBe('object')
+ assert.notStrictEqual(station.heartbeatSetInterval, undefined)
+ assert.strictEqual(typeof station.heartbeatSetInterval, 'object')
})
})
const secondInterval = station.heartbeatSetInterval
// Assert - interval should be different (old cleared, new created)
- expect(secondInterval).toBeDefined()
- expect(typeof secondInterval).toBe('object')
- expect(firstInterval !== secondInterval).toBe(true)
+ assert.notStrictEqual(secondInterval, undefined)
+ assert.strictEqual(typeof secondInterval, 'object')
+ assert.ok(firstInterval !== secondInterval)
})
})
const secondInterval = station.heartbeatSetInterval
// Assert - interval should be same (not restarted)
- expect(firstInterval).toBe(secondInterval)
+ assert.strictEqual(firstInterval, secondInterval)
})
})
// Assert - meter values interval should be created
if (connector1 != null) {
- expect(connector1.transactionSetInterval).toBeDefined()
- expect(typeof connector1.transactionSetInterval).toBe('object')
+ assert.notStrictEqual(connector1.transactionSetInterval, undefined)
+ assert.strictEqual(typeof connector1.transactionSetInterval, 'object')
}
})
})
const secondInterval = connector1?.transactionSetInterval
// Assert - interval should be different
- expect(secondInterval).toBeDefined()
- expect(typeof secondInterval).toBe('object')
- expect(firstInterval !== secondInterval).toBe(true)
+ assert.notStrictEqual(secondInterval, undefined)
+ assert.strictEqual(typeof secondInterval, 'object')
+ assert.ok(firstInterval !== secondInterval)
})
})
station.stopMeterValues(1)
// Assert - interval should be cleared
- expect(connector1?.transactionSetInterval).toBeUndefined()
+ assert.strictEqual(connector1?.transactionSetInterval, undefined)
})
})
// Assert - transaction updated interval should be created
if (connector1 != null) {
- expect(connector1.transactionTxUpdatedSetInterval).toBeDefined()
- expect(typeof connector1.transactionTxUpdatedSetInterval).toBe('object')
+ assert.notStrictEqual(connector1.transactionTxUpdatedSetInterval, undefined)
+ assert.strictEqual(typeof connector1.transactionTxUpdatedSetInterval, 'object')
}
})
})
station.stopTxUpdatedInterval(1)
// Assert - interval should be cleared
- expect(connector1?.transactionTxUpdatedSetInterval).toBeUndefined()
+ assert.strictEqual(connector1?.transactionTxUpdatedSetInterval, undefined)
})
})
})
* - ChargingStation-Transactions.test.ts: transaction handling and energy meters
* - ChargingStation-Configuration.test.ts: boot notification, config persistence, WebSocket, error handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
const result = createMockChargingStation()
const station = result.station
- expect(station).toBeDefined()
- expect(station.connectors.size).toBeGreaterThan(0)
- expect(station.stationInfo).toBeDefined()
+ assert.notStrictEqual(station, undefined)
+ assert.ok(station.connectors.size > 0)
+ assert.notStrictEqual(station.stationInfo, undefined)
cleanupChargingStation(station)
})
const station = result.station
// 5 connectors + connector 0 = 6 total
- expect(station.connectors.size).toBe(6)
- expect(station.hasConnector(5)).toBe(true)
+ assert.strictEqual(station.connectors.size, 6)
+ assert.strictEqual(station.hasConnector(5), true)
cleanupChargingStation(station)
})
})
const station = result.station
- expect(station.hasEvses).toBe(true)
- expect(station.getNumberOfEvses()).toBe(2)
+ assert.strictEqual(station.hasEvses, true)
+ assert.strictEqual(station.getNumberOfEvses(), 2)
cleanupChargingStation(station)
})
const result = createMockChargingStation()
const mocks = result.mocks
- expect(mocks.webSocket).toBeDefined()
- expect(mocks.webSocket).toBeInstanceOf(MockWebSocket)
- expect(mocks.webSocket.readyState).toBe(WebSocketReadyState.OPEN)
+ assert.notStrictEqual(mocks.webSocket, undefined)
+ assert.ok(mocks.webSocket instanceof MockWebSocket)
+ assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.OPEN)
cleanupChargingStation(result.station)
})
const result = createMockChargingStation()
const mocks = result.mocks
- expect(mocks.sharedLRUCache).toBeDefined()
- expect(mocks.sharedLRUCache).toBeInstanceOf(MockSharedLRUCache)
- expect(mocks.idTagsCache).toBeDefined()
- expect(mocks.idTagsCache).toBeInstanceOf(MockIdTagsCache)
+ assert.notStrictEqual(mocks.sharedLRUCache, undefined)
+ assert.ok(mocks.sharedLRUCache instanceof MockSharedLRUCache)
+ assert.notStrictEqual(mocks.idTagsCache, undefined)
+ assert.ok(mocks.idTagsCache instanceof MockIdTagsCache)
cleanupChargingStation(result.station)
})
const acceptedResult = createMockChargingStation({
bootNotificationStatus: RegistrationStatusEnumType.ACCEPTED,
})
- expect(acceptedResult.station.inAcceptedState()).toBe(true)
+ assert.strictEqual(acceptedResult.station.inAcceptedState(), true)
cleanupChargingStation(acceptedResult.station)
// Test PENDING state
const pendingResult = createMockChargingStation({
bootNotificationStatus: RegistrationStatusEnumType.PENDING,
})
- expect(pendingResult.station.inPendingState()).toBe(true)
+ assert.strictEqual(pendingResult.station.inPendingState(), true)
cleanupChargingStation(pendingResult.station)
// Test REJECTED state
const rejectedResult = createMockChargingStation({
bootNotificationStatus: RegistrationStatusEnumType.REJECTED,
})
- expect(rejectedResult.station.inRejectedState()).toBe(true)
+ assert.strictEqual(rejectedResult.station.inRejectedState(), true)
cleanupChargingStation(rejectedResult.station)
})
})
// Start station
station.start()
- expect(station.started).toBe(true)
+ assert.strictEqual(station.started, true)
// Set up transaction
const connector1 = station.getConnectorStatus(1)
}
// Verify transaction
- expect(station.getNumberOfRunningTransactions()).toBe(1)
- expect(station.getTransactionIdTag(TEST_TRANSACTION_ID)).toBe(TEST_ID_TAG)
- expect(station.getEnergyActiveImportRegisterByTransactionId(TEST_TRANSACTION_ID)).toBe(
+ assert.strictEqual(station.getNumberOfRunningTransactions(), 1)
+ assert.strictEqual(station.getTransactionIdTag(TEST_TRANSACTION_ID), TEST_ID_TAG)
+ assert.strictEqual(
+ station.getEnergyActiveImportRegisterByTransactionId(TEST_TRANSACTION_ID),
TEST_TRANSACTION_ENERGY_WH
)
// Stop station
await station.stop()
- expect(station.started).toBe(false)
+ assert.strictEqual(station.started, false)
cleanupChargingStation(station)
station = undefined
// Send WebSocket messages
mocks.webSocket.send('["2","uuid-1","Heartbeat",{}]')
- expect(mocks.webSocket.sentMessages.length).toBe(1)
+ assert.strictEqual(mocks.webSocket.sentMessages.length, 1)
// Simulate connection close
mocks.webSocket.simulateClose(1006, 'Connection lost')
- expect(mocks.webSocket.readyState).toBe(WebSocketReadyState.CLOSED)
+ assert.strictEqual(mocks.webSocket.readyState, WebSocketReadyState.CLOSED)
// Buffer messages while disconnected
station.bufferMessage('["2","uuid-2","StatusNotification",{}]')
const stationWithQueue = station as unknown as { messageQueue: string[] }
- expect(stationWithQueue.messageQueue.length).toBe(1)
+ assert.strictEqual(stationWithQueue.messageQueue.length, 1)
cleanupChargingStation(station)
station = undefined
station = result.station
// Verify EVSE structure
- expect(station.hasEvses).toBe(true)
- expect(station.getEvseIdByConnectorId(1)).toBe(1)
+ assert.strictEqual(station.hasEvses, true)
+ assert.strictEqual(station.getEvseIdByConnectorId(1), 1)
// Add reservation
const reservation = {
// Verify reservation
const found = station.getReservationBy('reservationId', 1)
- expect(found).toBeDefined()
- expect(found?.idTag).toBe('RESERVATION-TAG')
+ assert.notStrictEqual(found, undefined)
+ assert.strictEqual(found?.idTag, 'RESERVATION-TAG')
// Check reservability
- expect(station.isConnectorReservable(1)).toBe(false)
- expect(station.isConnectorReservable(999)).toBe(true)
+ assert.strictEqual(station.isConnectorReservable(1), false)
+ assert.strictEqual(station.isConnectorReservable(999), true)
cleanupChargingStation(station)
station = undefined
station = result.station
// Verify initial state
- expect(station.inPendingState()).toBe(true)
- expect(station.getHeartbeatInterval()).toBe(120000)
+ assert.strictEqual(station.inPendingState(), true)
+ assert.strictEqual(station.getHeartbeatInterval(), 120000)
// Transition to accepted
station.bootNotificationResponse = {
}
// Verify state change
- expect(station.inAcceptedState()).toBe(true)
- expect(station.inPendingState()).toBe(false)
+ assert.strictEqual(station.inAcceptedState(), true)
+ assert.strictEqual(station.inPendingState(), false)
cleanupChargingStation(station)
station = undefined
// Store some data in cache
mocks1.idTagsCache.setIdTags('test-file.json', ['tag1', 'tag2'])
- expect(mocks1.idTagsCache.getIdTags('test-file.json')).toStrictEqual(['tag1', 'tag2'])
+ assert.deepStrictEqual(mocks1.idTagsCache.getIdTags('test-file.json'), ['tag1', 'tag2'])
// Cleanup first station
cleanupChargingStation(result1.station)
// Cache should be fresh (singletons reset in cleanup)
// The resetInstance is called in cleanup, so new getInstance creates fresh instance
- expect(mocks2.idTagsCache.getIdTags('test-file.json')).toBeUndefined()
+ assert.strictEqual(mocks2.idTagsCache.getIdTags('test-file.json'), undefined)
cleanupChargingStation(result2.station)
})
* @file Tests for ConfigurationKeyUtils
* @description Unit tests for OCPP configuration key management utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStationOcppConfiguration } from '../../src/types/index.js'
cs.ocppConfiguration = {} as Partial<ChargingStationOcppConfiguration>
// Act & Assert
- expect(getConfigurationKey(cs, TEST_KEY_1)).toBeUndefined()
+ assert.strictEqual(getConfigurationKey(cs, TEST_KEY_1), undefined)
})
await it('should find existing key (case-sensitive)', () => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(k?.key).toBe(TEST_KEY_1)
- expect(k?.value).toBe(VALUE_A)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.key, TEST_KEY_1)
+ assert.strictEqual(k.value, VALUE_A)
})
await it('should respect case sensitivity (no match)', () => {
addConfigurationKey(cs, MIXED_CASE_KEY, VALUE_A, undefined, { save: false })
// Act & Assert
- expect(getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase())).toBeUndefined()
+ assert.strictEqual(getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase()), undefined)
})
await it('should support caseInsensitive lookup', () => {
const k = getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase(), true)
// Assert
- expect(k?.key).toBe(MIXED_CASE_KEY)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.key, MIXED_CASE_KEY)
})
})
addConfigurationKey(cs, TEST_KEY_1, VALUE_A)
// Assert
- expect(getConfigurationKey(cs, TEST_KEY_1)).toBeUndefined()
+ assert.strictEqual(getConfigurationKey(cs, TEST_KEY_1), undefined)
})
await it('should add new key with default options', () => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(k).toBeDefined()
- expect(k?.value).toBe(VALUE_A)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.value, VALUE_A)
// defaults
- expect(k?.readonly).toBe(false)
- expect(k?.reboot).toBe(false)
- expect(k?.visible).toBe(true)
+ assert.strictEqual(k.readonly, false)
+ assert.strictEqual(k.reboot, false)
+ assert.strictEqual(k.visible, true)
})
await it('should add new key with custom options', () => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(k?.readonly).toBe(true)
- expect(k?.reboot).toBe(true)
- expect(k?.visible).toBe(false)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.readonly, true)
+ assert.strictEqual(k.reboot, true)
+ assert.strictEqual(k.visible, false)
})
await it('should log error and not overwrite value when key exists and overwrite=false', t => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
// value unchanged
- expect(k?.value).toBe(VALUE_A)
+ assert.strictEqual(k.value, VALUE_A)
// options updated only where differing (all provided differ)
- expect(k?.reboot).toBe(true)
- expect(k?.readonly).toBe(true)
- expect(k?.visible).toBe(false)
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(k.reboot, true)
+ assert.strictEqual(k.readonly, true)
+ assert.strictEqual(k.visible, false)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should log error and leave key untouched when identical options & value attempted (overwrite=false)', t => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(k?.value).toBe(VALUE_A)
- expect(k?.readonly).toBe(true)
- expect(k?.reboot).toBe(false)
- expect(k?.visible).toBe(true)
- expect(errorMock.mock.calls.length).toBe(1)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.value, VALUE_A)
+ assert.strictEqual(k.readonly, true)
+ assert.strictEqual(k.reboot, false)
+ assert.strictEqual(k.visible, true)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should overwrite existing key value and options when overwrite=true', () => {
const k = getConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(k?.value).toBe(VALUE_B)
- expect(k?.readonly).toBe(true)
- expect(k?.reboot).toBe(true)
- expect(k?.visible).toBe(false)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.value, VALUE_B)
+ assert.strictEqual(k.readonly, true)
+ assert.strictEqual(k.reboot, true)
+ assert.strictEqual(k.visible, false)
})
await it('should caseInsensitive overwrite update existing differently cased key', () => {
const k = getConfigurationKey(cs, MIXED_CASE_KEY)
// Assert
- expect(k?.value).toBe(VALUE_B)
- expect(k?.readonly).toBe(true)
+ if (k == null) {
+ assert.fail('Expected configuration key to be found')
+ }
+ assert.strictEqual(k.value, VALUE_B)
+ assert.strictEqual(k.readonly, true)
})
await it('should case-insensitive false create separate key with different case', () => {
const second = getConfigurationKey(cs, MIXED_CASE_KEY.toLowerCase())
// Assert
- expect(orig).toBeDefined()
- expect(second).toBeDefined()
- expect(orig).not.toBe(second)
+ assert.notStrictEqual(orig, undefined)
+ assert.notStrictEqual(second, undefined)
+ assert.notStrictEqual(orig, second)
})
await it('should call saveOcppConfiguration when params.save=true (new key)', t => {
addConfigurationKey(cs, TEST_KEY_1, VALUE_A, undefined, { save: true })
// Assert
- expect(saveMock.mock.calls.length).toBe(1)
+ assert.strictEqual(saveMock.mock.calls.length, 1)
})
await it('should call saveOcppConfiguration when overwriting existing key and save=true', t => {
)
// Assert
- expect(saveMock.mock.calls.length).toBe(1)
+ assert.strictEqual(saveMock.mock.calls.length, 1)
})
})
const res = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_A)
// Assert
- expect(res).toBeUndefined()
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(res, undefined)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should return undefined without logging when configurationKey array missing', t => {
const res = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_A)
// Assert
- expect(res).toBeUndefined()
- expect(errorMock.mock.calls.length).toBe(0)
+ assert.strictEqual(res, undefined)
+ assert.strictEqual(errorMock.mock.calls.length, 0)
})
await it('should update existing key value and save', t => {
const updated = setConfigurationKeyValue(cs, TEST_KEY_1, VALUE_B)
// Assert
- expect(updated?.value).toBe(VALUE_B)
- expect(saveMock.mock.calls.length).toBe(1)
+ assert.strictEqual(updated?.value, VALUE_B)
+ assert.strictEqual(saveMock.mock.calls.length, 1)
})
await it('should caseInsensitive value update work', () => {
const updated = setConfigurationKeyValue(cs, MIXED_CASE_KEY.toLowerCase(), VALUE_B, true)
// Assert
- expect(updated?.value).toBe(VALUE_B)
+ assert.strictEqual(updated?.value, VALUE_B)
})
})
const res = deleteConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(res).toBeUndefined()
+ assert.strictEqual(res, undefined)
})
await it('should return undefined when key does not exist', () => {
const res = deleteConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(res).toBeUndefined()
+ assert.strictEqual(res, undefined)
})
await it('should delete existing key and save by default', t => {
const deleted = deleteConfigurationKey(cs, TEST_KEY_1)
// Assert
- expect(Array.isArray(deleted)).toBe(true)
- expect(deleted).toHaveLength(1)
- expect(deleted?.[0].key).toBe(TEST_KEY_1)
- expect(getConfigurationKey(cs, TEST_KEY_1)).toBeUndefined()
- expect(saveMock.mock.calls.length).toBe(1)
+ if (deleted == null) {
+ assert.fail('Expected deleted to be defined')
+ }
+ assert.ok(Array.isArray(deleted))
+ assert.strictEqual(deleted.length, 1)
+ assert.strictEqual(deleted[0].key, TEST_KEY_1)
+ assert.strictEqual(getConfigurationKey(cs, TEST_KEY_1), undefined)
+ assert.strictEqual(saveMock.mock.calls.length, 1)
})
await it('should not save when params.save=false', t => {
const deleted = deleteConfigurationKey(cs, TEST_KEY_1, { save: false })
// Assert
- expect(deleted).toHaveLength(1)
- expect(saveMock.mock.calls.length).toBe(0)
+ if (deleted == null) {
+ assert.fail('Expected deleted to be defined')
+ }
+ assert.strictEqual(deleted.length, 1)
+ assert.strictEqual(saveMock.mock.calls.length, 0)
})
await it('should caseInsensitive deletion remove key with different case', () => {
})
// Assert
- expect(deleted).toHaveLength(1)
- expect(getConfigurationKey(cs, MIXED_CASE_KEY)).toBeUndefined()
+ if (deleted == null) {
+ assert.fail('Expected deleted to be defined')
+ }
+ assert.strictEqual(deleted.length, 1)
+ assert.strictEqual(getConfigurationKey(cs, MIXED_CASE_KEY), undefined)
})
})
const delRes = deleteConfigurationKey(cs, TEST_KEY_1, { save: false })
// Assert
- expect(setRes?.value).toBe(VALUE_B)
- expect(delRes).toHaveLength(1)
- expect(getConfigurationKey(cs, TEST_KEY_1)).toBeUndefined()
+ assert.strictEqual(setRes?.value, VALUE_B)
+ assert.strictEqual(delRes?.length, 1)
+ assert.strictEqual(getConfigurationKey(cs, TEST_KEY_1), undefined)
})
})
})
* @description Unit tests for charging station helper functions and utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import {
hasReservationExpired,
validateStationInfo,
} from '../../src/charging-station/Helpers.js'
-import { BaseError } from '../../src/exception/index.js'
import {
AvailabilityType,
type ChargingStationConfiguration,
}) as Reservation
await it('should return formatted charging station ID with index', () => {
- expect(getChargingStationId(1, chargingStationTemplate)).toBe(
+ assert.strictEqual(
+ getChargingStationId(1, chargingStationTemplate),
`${TEST_CHARGING_STATION_BASE_NAME}-00001`
)
})
await it('should return consistent hash ID for same template and index', () => {
- expect(getHashId(1, chargingStationTemplate)).toBe(
+ assert.strictEqual(
+ getHashId(1, chargingStationTemplate),
'b4b1e8ec4fca79091d99ea9a7ea5901548010e6c0e98be9296f604b9d68734444dfdae73d7d406b6124b42815214d088'
)
})
stationNoInfo.stationInfo = undefined
// Act & Assert
- expect(() => {
- validateStationInfo(stationNoInfo)
- }).toThrow(new BaseError('Missing charging station information'))
+ assert.throws(
+ () => {
+ validateStationInfo(stationNoInfo)
+ },
+ { message: /Missing charging station information/ }
+ )
})
await it('should throw when stationInfo is empty object', () => {
stationEmptyInfo.stationInfo = {} as ChargingStationInfo
// Act & Assert
- expect(() => {
- validateStationInfo(stationEmptyInfo)
- }).toThrow(new BaseError('Missing charging station information'))
+ assert.throws(
+ () => {
+ validateStationInfo(stationEmptyInfo)
+ },
+ { message: /Missing charging station information/ }
+ )
})
await it('should throw when chargingStationId is undefined', () => {
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingId)
- }).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingId)
+ },
+ { message: /Missing chargingStationId in stationInfo properties/ }
+ )
})
await it('should throw when chargingStationId is empty string', () => {
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationEmptyId)
- }).toThrow(new BaseError('Missing chargingStationId in stationInfo properties'))
+ assert.throws(
+ () => {
+ validateStationInfo(stationEmptyId)
+ },
+ { message: /Missing chargingStationId in stationInfo properties/ }
+ )
})
await it('should throw when hashId is undefined', () => {
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingHash)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing hashId in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingHash)
+ },
+ { message: /Missing hashId in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationEmptyHash)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing hashId in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationEmptyHash)
+ },
+ { message: /Missing hashId in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingTemplate)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing templateIndex in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingTemplate)
+ },
+ { message: /Missing templateIndex in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationInvalidTemplate)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Invalid templateIndex value in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationInvalidTemplate)
+ },
+ { message: /Invalid templateIndex value in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingName)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing templateName in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingName)
+ },
+ { message: /Missing templateName in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationEmptyName)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing templateName in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationEmptyName)
+ },
+ { message: /Missing templateName in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingPower)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing maximumPower in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingPower)
+ },
+ { message: /Missing maximumPower in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationInvalidPower)
- }).toThrow(
- new RangeError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Invalid maximumPower value in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationInvalidPower)
+ },
+ { message: /Invalid maximumPower value in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationMissingAmperage)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Missing maximumAmperage in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationMissingAmperage)
+ },
+ { message: /Missing maximumAmperage in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationInvalidAmperage)
- }).toThrow(
- new RangeError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: Invalid maximumAmperage value in stationInfo properties`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationInvalidAmperage)
+ },
+ { message: /Invalid maximumAmperage value in stationInfo properties/ }
)
})
})
// Act & Assert
- expect(() => {
+ assert.doesNotThrow(() => {
validateStationInfo(validStation)
- }).not.toThrow()
+ })
})
await it('should throw for OCPP 2.0 without EVSE configuration', () => {
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationOcpp20)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: OCPP ${stationOcpp20.stationInfo?.ocppVersion ?? 'unknown'} requires at least one EVSE defined in the charging station template/configuration`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationOcpp20)
+ },
+ {
+ message:
+ /requires at least one EVSE defined in the charging station template\/configuration/,
+ }
)
})
})
// Act & Assert
- expect(() => {
- validateStationInfo(stationOcpp201)
- }).toThrow(
- new BaseError(
- `${TEST_CHARGING_STATION_BASE_NAME}-00001: OCPP ${stationOcpp201.stationInfo?.ocppVersion ?? 'unknown'} requires at least one EVSE defined in the charging station template/configuration`
- )
+ assert.throws(
+ () => {
+ validateStationInfo(stationOcpp201)
+ },
+ {
+ message:
+ /requires at least one EVSE defined in the charging station template\/configuration/,
+ }
)
})
const result = checkChargingStationState(stationNotStarted, 'log prefix |')
// Assert
- expect(result).toBe(false)
- expect(warnMock.mock.calls.length).toBe(1)
+ assert.strictEqual(result, false)
+ assert.strictEqual(warnMock.mock.calls.length, 1)
})
await it('should return true when station is starting', t => {
const result = checkChargingStationState(stationStarting, 'log prefix |')
// Assert
- expect(result).toBe(true)
- expect(warnMock.mock.calls.length).toBe(0)
+ assert.strictEqual(result, true)
+ assert.strictEqual(warnMock.mock.calls.length, 0)
})
await it('should return true when station is started', t => {
const result = checkChargingStationState(stationStarted, 'log prefix |')
// Assert
- expect(result).toBe(true)
- expect(warnMock.mock.calls.length).toBe(0)
+ assert.strictEqual(result, true)
+ assert.strictEqual(warnMock.mock.calls.length, 0)
})
await it('should return correct phase rotation value for connector and phase count', () => {
- expect(getPhaseRotationValue(0, 0)).toBe('0.RST')
- expect(getPhaseRotationValue(1, 0)).toBe('1.NotApplicable')
- expect(getPhaseRotationValue(2, 0)).toBe('2.NotApplicable')
- expect(getPhaseRotationValue(0, 1)).toBe('0.NotApplicable')
- expect(getPhaseRotationValue(1, 1)).toBe('1.NotApplicable')
- expect(getPhaseRotationValue(2, 1)).toBe('2.NotApplicable')
- expect(getPhaseRotationValue(0, 2)).toBeUndefined()
- expect(getPhaseRotationValue(1, 2)).toBeUndefined()
- expect(getPhaseRotationValue(2, 2)).toBeUndefined()
- expect(getPhaseRotationValue(0, 3)).toBe('0.RST')
- expect(getPhaseRotationValue(1, 3)).toBe('1.RST')
- expect(getPhaseRotationValue(2, 3)).toBe('2.RST')
+ assert.strictEqual(getPhaseRotationValue(0, 0), '0.RST')
+ assert.strictEqual(getPhaseRotationValue(1, 0), '1.NotApplicable')
+ assert.strictEqual(getPhaseRotationValue(2, 0), '2.NotApplicable')
+ assert.strictEqual(getPhaseRotationValue(0, 1), '0.NotApplicable')
+ assert.strictEqual(getPhaseRotationValue(1, 1), '1.NotApplicable')
+ assert.strictEqual(getPhaseRotationValue(2, 1), '2.NotApplicable')
+ assert.strictEqual(getPhaseRotationValue(0, 2), undefined)
+ assert.strictEqual(getPhaseRotationValue(1, 2), undefined)
+ assert.strictEqual(getPhaseRotationValue(2, 2), undefined)
+ assert.strictEqual(getPhaseRotationValue(0, 3), '0.RST')
+ assert.strictEqual(getPhaseRotationValue(1, 3), '1.RST')
+ assert.strictEqual(getPhaseRotationValue(2, 3), '2.RST')
})
await it('should return -1 for undefined EVSEs and 0 for empty object', () => {
- expect(getMaxNumberOfEvses(undefined)).toBe(-1)
- expect(getMaxNumberOfEvses({})).toBe(0)
+ assert.strictEqual(getMaxNumberOfEvses(undefined), -1)
+ assert.strictEqual(getMaxNumberOfEvses({}), 0)
})
await it('should throw for undefined or empty template', t => {
const errorMock = t.mock.method(logger, 'error')
// Act & Assert
- expect(() => {
- checkTemplate(undefined, 'log prefix |', 'test-template.json')
- }).toThrow(new BaseError('Failed to read charging station template file test-template.json'))
- expect(errorMock.mock.calls.length).toBe(1)
- expect(() => {
- checkTemplate({} as ChargingStationTemplate, 'log prefix |', 'test-template.json')
- }).toThrow(
- new BaseError('Empty charging station information from template file test-template.json')
+ assert.throws(
+ () => {
+ checkTemplate(undefined, 'log prefix |', 'test-template.json')
+ },
+ { message: /Failed to read charging station template file test-template\.json/ }
+ )
+ assert.strictEqual(errorMock.mock.calls.length, 1)
+ assert.throws(
+ () => {
+ checkTemplate({} as ChargingStationTemplate, 'log prefix |', 'test-template.json')
+ },
+ { message: /Empty charging station information from template file test-template\.json/ }
)
- expect(errorMock.mock.calls.length).toBe(2)
+ assert.strictEqual(errorMock.mock.calls.length, 2)
checkTemplate(chargingStationTemplate, 'log prefix |', 'test-template.json')
- expect(warnMock.mock.calls.length).toBe(1)
+ assert.strictEqual(warnMock.mock.calls.length, 1)
})
await it('should throw for undefined or empty configuration', t => {
const errorMock = t.mock.method(logger, 'error')
// Act & Assert
- expect(() => {
- checkConfiguration(undefined, 'log prefix |', 'configuration.json')
- }).toThrow(
- new BaseError('Failed to read charging station configuration file configuration.json')
+ assert.throws(
+ () => {
+ checkConfiguration(undefined, 'log prefix |', 'configuration.json')
+ },
+ { message: /Failed to read charging station configuration file configuration\.json/ }
+ )
+ assert.strictEqual(errorMock.mock.calls.length, 1)
+ assert.throws(
+ () => {
+ checkConfiguration({} as ChargingStationConfiguration, 'log prefix |', 'configuration.json')
+ },
+ { message: /Empty charging station configuration from file configuration\.json/ }
)
- expect(errorMock.mock.calls.length).toBe(1)
- expect(() => {
- checkConfiguration({} as ChargingStationConfiguration, 'log prefix |', 'configuration.json')
- }).toThrow(new BaseError('Empty charging station configuration from file configuration.json'))
- expect(errorMock.mock.calls.length).toBe(2)
+ assert.strictEqual(errorMock.mock.calls.length, 2)
})
await it('should warn and clear status when connector has predefined status', t => {
checkStationInfoConnectorStatus(1, {} as ConnectorStatus, 'log prefix |', 'test-template.json')
// Act & Assert
- expect(warnMock.mock.calls.length).toBe(0)
+ assert.strictEqual(warnMock.mock.calls.length, 0)
const connectorStatus = {
status: ConnectorStatusEnum.Available,
} as ConnectorStatus
checkStationInfoConnectorStatus(1, connectorStatus, 'log prefix |', 'test-template.json')
- expect(warnMock.mock.calls.length).toBe(1)
- expect(connectorStatus.status).toBeUndefined()
+ assert.strictEqual(warnMock.mock.calls.length, 1)
+ assert.strictEqual(connectorStatus.status, undefined)
})
await it('should return Available when no bootStatus is defined', () => {
const connectorStatus = {} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Available
)
})
} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Unavailable
)
})
} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Unavailable
)
})
} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Unavailable
)
})
} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Charging
)
})
} as ConnectorStatus
// Act & Assert
- expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe(
+ assert.strictEqual(
+ getBootConnectorStatus(chargingStation, 1, connectorStatus),
ConnectorStatusEnum.Available
)
})
// Tests for reservation helper functions
await it('should return true when reservation has expired', () => {
- expect(hasReservationExpired(createTestReservation(true))).toBe(true)
+ assert.strictEqual(hasReservationExpired(createTestReservation(true)), true)
})
await it('should return false when reservation is still valid', () => {
- expect(hasReservationExpired(createTestReservation(false))).toBe(false)
+ assert.strictEqual(hasReservationExpired(createTestReservation(false)), false)
})
await it('should return false when connector has no reservation', () => {
const connectorStatus = {} as ConnectorStatus
- expect(hasPendingReservation(connectorStatus)).toBe(false)
+ assert.strictEqual(hasPendingReservation(connectorStatus), false)
})
await it('should return true when connector has valid pending reservation', () => {
const connectorStatus = { reservation: createTestReservation(false) } as ConnectorStatus
- expect(hasPendingReservation(connectorStatus)).toBe(true)
+ assert.strictEqual(hasPendingReservation(connectorStatus), true)
})
await it('should return false when connector reservation has expired', () => {
const connectorStatus = { reservation: createTestReservation(true) } as ConnectorStatus
- expect(hasPendingReservation(connectorStatus)).toBe(false)
+ assert.strictEqual(hasPendingReservation(connectorStatus), false)
})
await it('should return false when no reservations exist (connector mode)', () => {
baseName: TEST_CHARGING_STATION_BASE_NAME,
connectorsCount: 2,
})
- expect(hasPendingReservations(chargingStation)).toBe(false)
+ assert.strictEqual(hasPendingReservations(chargingStation), false)
})
await it('should return true when pending reservation exists (connector mode)', () => {
}
// Act & Assert
- expect(hasPendingReservations(chargingStation)).toBe(true)
+ assert.strictEqual(hasPendingReservations(chargingStation), true)
})
await it('should return false when no reservations exist (EVSE mode)', () => {
})
// Act & Assert
- expect(hasPendingReservations(chargingStation)).toBe(false)
+ assert.strictEqual(hasPendingReservations(chargingStation), false)
})
await it('should return true when pending reservation exists (EVSE mode)', () => {
}
// Act & Assert
- expect(hasPendingReservations(chargingStation)).toBe(true)
+ assert.strictEqual(hasPendingReservations(chargingStation), true)
})
await it('should return false when only expired reservations exist (EVSE mode)', () => {
}
// Act & Assert
- expect(hasPendingReservations(chargingStation)).toBe(false)
+ assert.strictEqual(hasPendingReservations(chargingStation), false)
})
})
* - deleteIdTags — cache and index cleanup
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
const instance1 = IdTagsCache.getInstance()
const instance2 = IdTagsCache.getInstance()
- expect(instance1).toBe(instance2)
+ assert.strictEqual(instance1, instance2)
})
await it('should create new instance after reset', () => {
resetIdTagsCache()
const instance2 = IdTagsCache.getInstance()
- expect(instance1).not.toBe(instance2)
+ assert.notStrictEqual(instance1, instance2)
})
})
const result = cache.getIdTags(file)
- expect(result).toStrictEqual(TEST_ID_TAGS)
+ assert.deepStrictEqual(result, TEST_ID_TAGS)
})
await it('should load id tags from file when cache is empty', () => {
const cache = IdTagsCache.getInstance()
const result = cache.getIdTags(idTagsFile)
- expect(result).toStrictEqual(TEST_ID_TAGS)
+ assert.deepStrictEqual(result, TEST_ID_TAGS)
cache.deleteIdTags(idTagsFile)
} finally {
rmSync(tmpDir, { force: true, recursive: true })
const result = cache.getIdTags('')
- expect(result).toStrictEqual([])
+ assert.deepStrictEqual(result, [])
cache.deleteIdTags('')
})
})
const tag2 = cache.getIdTag(IdTagDistribution.ROUND_ROBIN, station, 1)
const tag3 = cache.getIdTag(IdTagDistribution.ROUND_ROBIN, station, 1)
- expect(tag1).toBe('TAG-001')
- expect(tag2).toBe('TAG-002')
- expect(tag3).toBe('TAG-003')
+ assert.strictEqual(tag1, 'TAG-001')
+ assert.strictEqual(tag2, 'TAG-002')
+ assert.strictEqual(tag3, 'TAG-003')
})
await it('should wrap around when reaching end of tags', () => {
const tag4 = cache.getIdTag(IdTagDistribution.ROUND_ROBIN, station, 1)
- expect(tag4).toBe('TAG-001')
+ assert.strictEqual(tag4, 'TAG-001')
})
})
}
for (const tag of results) {
- expect(TEST_ID_TAGS.includes(tag)).toBe(true)
+ assert.ok(TEST_ID_TAGS.includes(tag))
}
})
})
// index=1, connectorId=1: (1-1 + (1-1)) % 3 = 0 → TAG-001
const tag = cache.getIdTag(IdTagDistribution.CONNECTOR_AFFINITY, station, 1)
- expect(tag).toBe('TAG-001')
+ assert.strictEqual(tag, 'TAG-001')
})
await it('should return different tags for different connectors', () => {
// index=1, connectorId=2: (1-1 + (2-1)) % 3 = 1 → TAG-002
const tag2 = cache.getIdTag(IdTagDistribution.CONNECTOR_AFFINITY, station, 2)
- expect(tag1).toBe('TAG-001')
- expect(tag2).toBe('TAG-002')
+ assert.strictEqual(tag1, 'TAG-001')
+ assert.strictEqual(tag2, 'TAG-002')
})
})
const result = cache.deleteIdTags(file)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
const internal = cache as unknown as IdTagsCacheInternal
- expect(internal.idTagsCaches.has(file)).toBe(false)
+ assert.strictEqual(internal.idTagsCaches.has(file), false)
})
await it('should remove addressable indexes on delete', () => {
const indexKeysBefore = [...internal.idTagsCachesAddressableIndexes.keys()].filter(key =>
key.startsWith(file)
)
- expect(indexKeysBefore.length).toBe(1)
+ assert.strictEqual(indexKeysBefore.length, 1)
cache.deleteIdTags(file)
const indexKeysAfter = [...internal.idTagsCachesAddressableIndexes.keys()].filter(key =>
key.startsWith(file)
)
- expect(indexKeysAfter.length).toBe(0)
+ assert.strictEqual(indexKeysAfter.length, 0)
})
})
})
* - Cache clear
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type {
const instance1 = SharedLRUCache.getInstance()
const instance2 = SharedLRUCache.getInstance()
- expect(instance1).toBe(instance2)
+ assert.strictEqual(instance1, instance2)
})
await it('should create new instance after reset', () => {
resetSharedLRUCache()
const instance2 = SharedLRUCache.getInstance()
- expect(instance1).not.toBe(instance2)
+ assert.notStrictEqual(instance1, instance2)
})
})
cache.setChargingStationTemplate(template)
const retrieved = cache.getChargingStationTemplate('template-hash-1')
- expect(retrieved).toStrictEqual(template)
+ assert.deepStrictEqual(retrieved, template)
})
await it('should return undefined for non-existent template', () => {
const result = cache.getChargingStationTemplate('unknown-hash')
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should report has correctly for templates', () => {
cache.setChargingStationTemplate(template)
- expect(cache.hasChargingStationTemplate('template-hash-2')).toBe(true)
- expect(cache.hasChargingStationTemplate('unknown-hash')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationTemplate('template-hash-2'), true)
+ assert.strictEqual(cache.hasChargingStationTemplate('unknown-hash'), false)
})
await it('should delete a charging station template', () => {
cache.setChargingStationTemplate(template)
cache.deleteChargingStationTemplate('template-hash-3')
- expect(cache.hasChargingStationTemplate('template-hash-3')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationTemplate('template-hash-3'), false)
})
})
cache.setChargingStationConfiguration(config)
const retrieved = cache.getChargingStationConfiguration('config-hash-1')
- expect(retrieved).toStrictEqual(config)
+ assert.deepStrictEqual(retrieved, config)
})
await it('should not cache configuration with empty configurationKey', () => {
cache.setChargingStationConfiguration(config)
- expect(cache.hasChargingStationConfiguration('config-hash-empty-key')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationConfiguration('config-hash-empty-key'), false)
})
await it('should not cache configuration with null stationInfo', () => {
cache.setChargingStationConfiguration(config)
- expect(cache.hasChargingStationConfiguration('config-hash-no-info')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationConfiguration('config-hash-no-info'), false)
})
await it('should not cache configuration with empty configurationHash', () => {
cache.setChargingStationConfiguration(config)
- expect(cache.hasChargingStationConfiguration('')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationConfiguration(''), false)
})
await it('should delete a charging station configuration', () => {
cache.setChargingStationConfiguration(config)
cache.deleteChargingStationConfiguration('config-hash-del')
- expect(cache.hasChargingStationConfiguration('config-hash-del')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationConfiguration('config-hash-del'), false)
})
})
cache.setChargingStationConfiguration(config)
cache.clear()
- expect(cache.hasChargingStationTemplate('template-clear')).toBe(false)
- expect(cache.hasChargingStationConfiguration('config-clear')).toBe(false)
+ assert.strictEqual(cache.hasChargingStationTemplate('template-clear'), false)
+ assert.strictEqual(cache.hasChargingStationConfiguration('config-clear'), false)
})
})
})
--- /dev/null
+/**
+ * @file Tests for OCPP16Constants state machine transitions
+ * @description Unit tests for OCPP 1.6 connector status state machine (§3)
+ */
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import { OCPP16Constants } from '../../../../src/charging-station/ocpp/1.6/OCPP16Constants.js'
+import { OCPP16ChargePointStatus } from '../../../../src/types/ocpp/1.6/ChargePointStatus.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+
+await describe('OCPP16Constants', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await describe('ChargePointStatusChargingStationTransitions', async () => {
+ await it('should have valid station-level transitions array', () => {
+ assert.notStrictEqual(OCPP16Constants.ChargePointStatusChargingStationTransitions, undefined)
+ assert.strictEqual(
+ Array.isArray(OCPP16Constants.ChargePointStatusChargingStationTransitions),
+ true
+ )
+ })
+
+ await it('should contain at least 9 station-level transitions', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ assert.strictEqual(transitions.length, 9)
+ })
+
+ await it('should have transitions with correct structure (from/to properties)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ for (const transition of transitions) {
+ assert.notStrictEqual(transition, undefined)
+ assert.notStrictEqual(transition.to, undefined)
+ if (transition.from !== undefined) {
+ assert.strictEqual(typeof transition.from, 'string')
+ }
+ assert.strictEqual(typeof transition.to, 'string')
+ }
+ })
+
+ await it('should include transition to Available (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInitialAvailable = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Available && t.from === undefined
+ )
+ assert.strictEqual(hasInitialAvailable, true)
+ })
+
+ await it('should include transition to Unavailable (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInitialUnavailable = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Unavailable && t.from === undefined
+ )
+ assert.strictEqual(hasInitialUnavailable, true)
+ })
+
+ await it('should include transition to Faulted (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInitialFaulted = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Faulted && t.from === undefined
+ )
+ assert.strictEqual(hasInitialFaulted, true)
+ })
+
+ await it('should include Available → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should NOT include self-loop transitions (Available → Available)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasSelfLoop = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasSelfLoop, false)
+ })
+
+ await it('should NOT include self-loop transitions (Unavailable → Unavailable)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasSelfLoop = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasSelfLoop, false)
+ })
+
+ await it('should NOT include self-loop transitions (Faulted → Faulted)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasSelfLoop = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasSelfLoop, false)
+ })
+
+ await it('should NOT include invalid transitions (Available → Preparing)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transitions (Available → Charging)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transitions (Available → Reserved)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should be frozen (immutable)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusChargingStationTransitions
+ assert.strictEqual(Object.isFrozen(transitions), true)
+ })
+ })
+
+ await describe('ChargePointStatusConnectorTransitions', async () => {
+ await it('should have valid connector-level transitions array', () => {
+ assert.notStrictEqual(OCPP16Constants.ChargePointStatusConnectorTransitions, undefined)
+ assert.strictEqual(Array.isArray(OCPP16Constants.ChargePointStatusConnectorTransitions), true)
+ })
+
+ await it('should contain 56 connector-level transitions', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ assert.ok(transitions.length >= 56)
+ })
+
+ await it('should have transitions with correct structure', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ for (const transition of transitions) {
+ assert.notStrictEqual(transition, undefined)
+ assert.notStrictEqual(transition.to, undefined)
+ if (transition.from !== undefined) {
+ assert.strictEqual(typeof transition.from, 'string')
+ }
+ assert.strictEqual(typeof transition.to, 'string')
+ }
+ })
+
+ await it('should include transition to Available (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInitial = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Available && t.from === undefined
+ )
+ assert.strictEqual(hasInitial, true)
+ })
+
+ await it('should include transition to Unavailable (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInitial = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Unavailable && t.from === undefined
+ )
+ assert.strictEqual(hasInitial, true)
+ })
+
+ await it('should include transition to Faulted (initial state)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInitial = transitions.some(
+ t => t.to === OCPP16ChargePointStatus.Faulted && t.from === undefined
+ )
+ assert.strictEqual(hasInitial, true)
+ })
+
+ await it('should include Available → Preparing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → Reserved transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Available → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → Finishing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Preparing → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → Finishing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Charging → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → Finishing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEV → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → Finishing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include SuspendedEVSE → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Finishing → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Finishing && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Finishing → Preparing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Finishing && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Finishing → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Finishing &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Finishing → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Finishing && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Reserved → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Reserved && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Reserved → Preparing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Reserved && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Reserved → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Reserved &&
+ t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Reserved → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Reserved && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Preparing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Unavailable → Faulted transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable && t.to === OCPP16ChargePointStatus.Faulted
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Available transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Preparing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Charging transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → SuspendedEV transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → SuspendedEVSE transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Finishing transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Reserved transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should include Faulted → Unavailable transition', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasTransition = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Unavailable
+ )
+ assert.strictEqual(hasTransition, true)
+ })
+
+ await it('should NOT include invalid transition (Available → Finishing)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Preparing → Reserved)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Charging → Preparing)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Charging → Reserved)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Reserved → Charging)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Reserved && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Unavailable → Finishing)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should NOT include invalid transition (Unavailable → Reserved)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ const hasInvalid = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Unavailable &&
+ t.to === OCPP16ChargePointStatus.Reserved
+ )
+ assert.strictEqual(hasInvalid, false)
+ })
+
+ await it('should be frozen (immutable)', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+ assert.strictEqual(Object.isFrozen(transitions), true)
+ })
+ })
+
+ await describe('Connector lifecycle verification', async () => {
+ await it('should support complete charging lifecycle: Available → Preparing → Charging → Finishing → Available', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+
+ const step1 = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Available && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(step1, true)
+
+ const step2 = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Preparing && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(step2, true)
+
+ const step3 = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging && t.to === OCPP16ChargePointStatus.Finishing
+ )
+ assert.strictEqual(step3, true)
+
+ const step4 = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Finishing && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(step4, true)
+ })
+
+ await it('should support suspended state transitions during charging', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+
+ const toSuspendedEV = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging &&
+ t.to === OCPP16ChargePointStatus.SuspendedEV
+ )
+ assert.strictEqual(toSuspendedEV, true)
+
+ const resumeFromEV = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEV &&
+ t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(resumeFromEV, true)
+
+ const toSuspendedEVSE = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Charging &&
+ t.to === OCPP16ChargePointStatus.SuspendedEVSE
+ )
+ assert.strictEqual(toSuspendedEVSE, true)
+
+ const resumeFromEVSE = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.SuspendedEVSE &&
+ t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(resumeFromEVSE, true)
+ })
+
+ await it('should support recovery from Faulted state to any active state', () => {
+ const transitions = OCPP16Constants.ChargePointStatusConnectorTransitions
+
+ const toAvailable = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Available
+ )
+ assert.strictEqual(toAvailable, true)
+
+ const toPreparing = transitions.some(
+ t =>
+ t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Preparing
+ )
+ assert.strictEqual(toPreparing, true)
+
+ const toCharging = transitions.some(
+ t => t.from === OCPP16ChargePointStatus.Faulted && t.to === OCPP16ChargePointStatus.Charging
+ )
+ assert.strictEqual(toCharging, true)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService — ChangeAvailability handler
+ * @description Verifies the ChangeAvailability incoming request handler (§5.3) for
+ * OCPP 1.6 covering whole-station (connectorId=0) and single-connector scenarios,
+ * active transaction scheduling, and invalid connector rejection.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type { TestableOCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+
+import {
+ OCPP16AvailabilityType,
+ type OCPP16ChangeAvailabilityRequest,
+} from '../../../../src/types/index.js'
+import { OCPP16AvailabilityStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import { createOCPP16IncomingRequestTestContext, setMockRequestHandler } from './OCPP16TestUtils.js'
+
+// @spec §5.3 — ChangeAvailability
+
+await describe('OCPP16IncomingRequestService — ChangeAvailability', async () => {
+ let station: ChargingStation
+ let testableService: TestableOCPP16IncomingRequestService
+
+ beforeEach(() => {
+ const ctx = createOCPP16IncomingRequestTestContext()
+ station = ctx.station
+ testableService = ctx.testableService
+
+ // Mock requestHandler so sendAndSetConnectorStatus resolves without error
+ setMockRequestHandler(station, async () => Promise.resolve({}))
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ─── connectorId=0 (all connectors) ──────────────────────────────────
+
+ await describe('connectorId=0 (all connectors)', async () => {
+ await it('should return Accepted when setting all connectors to Operative', async () => {
+ // Arrange
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 0,
+ type: OCPP16AvailabilityType.Operative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.ACCEPTED)
+ })
+
+ await it('should return Accepted when setting all connectors to Inoperative', async () => {
+ // Arrange
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 0,
+ type: OCPP16AvailabilityType.Inoperative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.ACCEPTED)
+ })
+ })
+
+ // ─── connectorId=1 (specific connector) ──────────────────────────────
+
+ await describe('connectorId=1 (specific connector)', async () => {
+ await it('should return Accepted when setting connector to Operative', async () => {
+ // Arrange
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 1,
+ type: OCPP16AvailabilityType.Operative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.ACCEPTED)
+ })
+
+ await it('should return Accepted when setting connector to Inoperative', async () => {
+ // Arrange
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 1,
+ type: OCPP16AvailabilityType.Inoperative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.ACCEPTED)
+ })
+ })
+
+ // ─── Active transaction → Scheduled ──────────────────────────────────
+
+ await it('should return Scheduled when connector has an active transaction', async () => {
+ // Arrange — simulate active transaction on connector 1
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.transactionStarted = true
+ }
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 1,
+ type: OCPP16AvailabilityType.Inoperative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.SCHEDULED)
+ })
+
+ // ─── Invalid connectorId → Rejected ──────────────────────────────────
+
+ await it('should return Rejected for a non-existing connector id', async () => {
+ // Arrange
+ const request: OCPP16ChangeAvailabilityRequest = {
+ connectorId: 99,
+ type: OCPP16AvailabilityType.Operative,
+ }
+
+ // Act
+ const response = await testableService.handleRequestChangeAvailability(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16AvailabilityStatus.REJECTED)
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService Configuration
+ * @description Unit tests for OCPP 1.6 ChangeConfiguration (§5.4) and GetConfiguration (§5.8)
+ * incoming request handlers
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type {
+ ChangeConfigurationRequest,
+ GetConfigurationRequest,
+} from '../../../../src/types/index.js'
+
+import { OCPP16StandardParametersKey } from '../../../../src/types/index.js'
+import { OCPP16ConfigurationStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — Configuration', async () => {
+ let testContext: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ testContext = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ---------------------------------------------------------------------------
+ // ChangeConfiguration (§5.4)
+ // ---------------------------------------------------------------------------
+
+ // @spec §5.4 — TC_030_CS: Change a mutable key → Accepted
+ await it('should accept changing a mutable configuration key', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.MeterValueSampleInterval, '60')
+ const request: ChangeConfigurationRequest = {
+ key: OCPP16StandardParametersKey.MeterValueSampleInterval,
+ value: '30',
+ }
+
+ // Act
+ const response = testableService.handleRequestChangeConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ConfigurationStatus.ACCEPTED)
+ })
+
+ // @spec §5.4 — TC_031_CS: Change a readonly key → Rejected
+ await it('should reject changing a readonly configuration key', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.HeartbeatInterval, '60', true)
+ const request: ChangeConfigurationRequest = {
+ key: OCPP16StandardParametersKey.HeartbeatInterval,
+ value: '30',
+ }
+
+ // Act
+ const response = testableService.handleRequestChangeConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ConfigurationStatus.REJECTED)
+ })
+
+ // @spec §5.4 — TC_032_CS: Change key with reboot: true → RebootRequired
+ await it('should return RebootRequired when changing a key that requires reboot', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, 'RebootKey', 'oldValue')
+ // Manually set reboot flag since upsertConfigurationKey doesn't support it
+ const configKey = station.ocppConfiguration?.configurationKey?.find(k => k.key === 'RebootKey')
+ if (configKey != null) {
+ configKey.reboot = true
+ }
+ const request: ChangeConfigurationRequest = {
+ key: 'RebootKey',
+ value: 'newValue',
+ }
+
+ // Act
+ const response = testableService.handleRequestChangeConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ConfigurationStatus.REBOOT_REQUIRED)
+ })
+
+ // @spec §5.4 — TC_033_CS: Change unknown key → NotSupported
+ await it('should return NotSupported for an unknown configuration key', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: ChangeConfigurationRequest = {
+ key: 'NonExistentKey',
+ value: 'anyValue',
+ }
+
+ // Act
+ const response = testableService.handleRequestChangeConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ConfigurationStatus.NOT_SUPPORTED)
+ })
+
+ // ---------------------------------------------------------------------------
+ // GetConfiguration (§5.8)
+ // ---------------------------------------------------------------------------
+
+ // @spec §5.8 — TC_030_CS: Get all keys (no filter) → returns all visible keys
+ await it('should return all visible keys when no key filter is provided', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.HeartbeatInterval, '60')
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.MeterValueSampleInterval, '30')
+ const request: GetConfigurationRequest = {}
+
+ // Act
+ const response = testableService.handleRequestGetConfiguration(station, request)
+
+ // Assert
+ assert.notStrictEqual(response.configurationKey, undefined)
+ assert.notStrictEqual(response.unknownKey, undefined)
+ assert.ok(response.configurationKey.length >= 2)
+ const heartbeatKey = response.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.HeartbeatInterval as string)
+ )
+ assert.notStrictEqual(heartbeatKey, undefined)
+ assert.strictEqual(heartbeatKey?.value, '60')
+ })
+
+ // @spec §5.8 — TC_031_CS: Get specific existing key → returns matching key
+ await it('should return a specific existing configuration key', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.WebSocketPingInterval, '10')
+ const request: GetConfigurationRequest = {
+ key: [OCPP16StandardParametersKey.WebSocketPingInterval],
+ }
+
+ // Act
+ const response = testableService.handleRequestGetConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.configurationKey.length, 1)
+ assert.strictEqual(
+ response.configurationKey[0].key,
+ OCPP16StandardParametersKey.WebSocketPingInterval
+ )
+ assert.strictEqual(response.configurationKey[0].value, '10')
+ assert.strictEqual(response.unknownKey.length, 0)
+ })
+
+ // @spec §5.8 — TC_032_CS: Get non-existent key → appears in unknownKey list
+ await it('should report a non-existent key in the unknownKey list', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: GetConfigurationRequest = {
+ key: ['CompletelyUnknownKey'],
+ }
+
+ // Act
+ const response = testableService.handleRequestGetConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.configurationKey.length, 0)
+ assert.strictEqual(response.unknownKey.length, 1)
+ assert.strictEqual(response.unknownKey[0], 'CompletelyUnknownKey')
+ })
+
+ // @spec §5.8 — TC_033_CS: Get mix of existing and non-existent keys
+ await it('should return both configurationKey and unknownKey for mixed requests', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.HeartbeatInterval, '45')
+ const request: GetConfigurationRequest = {
+ key: [OCPP16StandardParametersKey.HeartbeatInterval, 'DoesNotExistKey'],
+ }
+
+ // Act
+ const response = testableService.handleRequestGetConfiguration(station, request)
+
+ // Assert
+ assert.strictEqual(response.configurationKey.length, 1)
+ assert.strictEqual(
+ response.configurationKey[0].key,
+ OCPP16StandardParametersKey.HeartbeatInterval
+ )
+ assert.strictEqual(response.configurationKey[0].value, '45')
+ assert.strictEqual(response.unknownKey.length, 1)
+ assert.strictEqual(response.unknownKey[0], 'DoesNotExistKey')
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService firmware handlers
+ * @description Unit tests for OCPP 1.6 GetDiagnostics (§6.1) and UpdateFirmware (§6.4)
+ * incoming request handlers
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { GetDiagnosticsRequest } from '../../../../src/types/index.js'
+
+import { OCPP16StandardParametersKey } from '../../../../src/types/index.js'
+import { OCPP16FirmwareStatus } from '../../../../src/types/ocpp/1.6/Requests.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — Firmware', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // §6.1: GetDiagnostics
+ await describe('handleRequestGetDiagnostics', async () => {
+ // @spec §6.1 — TC_048_CS
+ await it('should return empty response for non-FTP location', async () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,FirmwareManagement'
+ )
+ station.ocppRequestService.requestHandler = (() =>
+ Promise.resolve({})) as typeof station.ocppRequestService.requestHandler
+
+ const request: GetDiagnosticsRequest = {
+ location: 'http://example.com/diagnostics',
+ }
+
+ // Act
+ const response = await testableService.handleRequestGetDiagnostics(station, request)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+
+ // @spec §6.1 — TC_047_CS
+ await it('should return empty response when FirmwareManagement feature profile is not enabled', async () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+
+ const request: GetDiagnosticsRequest = {
+ location: 'ftp://localhost/diagnostics',
+ }
+
+ // Act
+ const response = await testableService.handleRequestGetDiagnostics(station, request)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+
+ await it('should return empty response when SupportedFeatureProfiles key is missing', async () => {
+ // Arrange
+ const { station, testableService } = context
+
+ const request: GetDiagnosticsRequest = {
+ location: 'ftp://localhost/diagnostics',
+ }
+
+ // Act
+ const response = await testableService.handleRequestGetDiagnostics(station, request)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+ })
+
+ // §6.4: UpdateFirmware
+ await describe('handleRequestUpdateFirmware', async () => {
+ // @spec §6.4 — TC_044_CS
+ await it('should return empty response for valid location with immediate retrieve date', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,FirmwareManagement'
+ )
+
+ // Act
+ const response = testableService.handleRequestUpdateFirmware(station, {
+ location: 'ftp://localhost/firmware.bin',
+ retrieveDate: new Date(),
+ })
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+
+ await it('should return empty response when FirmwareManagement feature profile is not enabled', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+
+ // Act
+ const response = testableService.handleRequestUpdateFirmware(station, {
+ location: 'ftp://localhost/firmware.bin',
+ retrieveDate: new Date(),
+ })
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+
+ await it('should return empty response when firmware update is already in progress', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,FirmwareManagement'
+ )
+ if (station.stationInfo != null) {
+ station.stationInfo.firmwareStatus = OCPP16FirmwareStatus.Downloading
+ }
+
+ // Act
+ const response = testableService.handleRequestUpdateFirmware(station, {
+ location: 'ftp://localhost/firmware.bin',
+ retrieveDate: new Date(),
+ })
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(Object.keys(response).length, 0)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService RemoteStartTransaction
+ * @description Unit tests for OCPP 1.6 RemoteStartTransaction incoming request handler (§5.11)
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { RemoteStartTransactionRequest } from '../../../../src/types/index.js'
+
+import { AvailabilityType, GenericStatus } from '../../../../src/types/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — RemoteStartTransaction', async () => {
+ let testContext: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ testContext = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // @spec §5.11 — TC_021_CS: connectorId=0 must be rejected
+ await it('should reject remote start transaction with connectorId=0', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: RemoteStartTransactionRequest = {
+ connectorId: 0,
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ // @spec §5.11 — TC_013_CS: Valid connectorId with available connector
+ await it('should accept remote start transaction with valid connectorId and available connector', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: RemoteStartTransactionRequest = {
+ connectorId: 1,
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.11 — TC_014_CS: All connectors have active transactions, no connectorId specified
+ await it('should reject remote start transaction when all connectors have active transactions', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+
+ // Set all connectors as having active transactions
+ for (let connectorId = 1; connectorId <= station.getNumberOfConnectors(); connectorId++) {
+ const connectorStatus = station.getConnectorStatus(connectorId)
+ if (connectorStatus != null) {
+ connectorStatus.transactionStarted = true
+ connectorStatus.transactionId = connectorId * 100
+ }
+ }
+
+ const request: RemoteStartTransactionRequest = {
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ // @spec §5.11 — TC_015_CS: No connectorId specified, finds first available connector
+ await it('should accept remote start transaction without connectorId when connector is available', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: RemoteStartTransactionRequest = {
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.11 — Connector in Unavailable (Inoperative) status
+ await it('should reject remote start transaction when connector is unavailable', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+
+ // Set connector 1 availability to Inoperative (Unavailable)
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.availability = AvailabilityType.Inoperative
+ }
+
+ const request: RemoteStartTransactionRequest = {
+ connectorId: 1,
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ // @spec §5.11 — Station-level unavailability
+ await it('should reject remote start transaction when charging station is unavailable', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+
+ // Set station-level (connector 0) availability to Inoperative
+ const connector0Status = station.getConnectorStatus(0)
+ if (connector0Status != null) {
+ connector0Status.availability = AvailabilityType.Inoperative
+ }
+
+ const request: RemoteStartTransactionRequest = {
+ connectorId: 1,
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ // @spec §5.11 — Non-existing connector
+ await it('should reject remote start transaction with non-existing connectorId', async () => {
+ // Arrange
+ const { station, testableService } = testContext
+ const request: RemoteStartTransactionRequest = {
+ connectorId: 99,
+ idTag: 'TEST-TAG-001',
+ }
+
+ // Act
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService — RemoteStopTransaction and UnlockConnector
+ * @description Verifies the RemoteStopTransaction (§5.12) and UnlockConnector (§5.17)
+ * incoming request handlers for OCPP 1.6, covering accepted/rejected transaction lookups,
+ * connector unlock with and without active transactions, and invalid connector handling.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type { TestableOCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+
+import { OCPP16UnlockStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { OCPP16AuthorizationStatus } from '../../../../src/types/ocpp/1.6/Transaction.js'
+import { GenericStatus } from '../../../../src/types/ocpp/Common.js'
+import {
+ setupConnectorWithTransaction,
+ standardCleanup,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import { createOCPP16IncomingRequestTestContext, setMockRequestHandler } from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — RemoteStopTransaction and UnlockConnector', async () => {
+ let station: ChargingStation
+ let testableService: TestableOCPP16IncomingRequestService
+
+ beforeEach(() => {
+ const ctx = createOCPP16IncomingRequestTestContext()
+ station = ctx.station
+ testableService = ctx.testableService
+
+ // Mock requestHandler so OCPP requests (StatusNotification, StopTransaction) resolve
+ setMockRequestHandler(station, async () =>
+ Promise.resolve({ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } })
+ )
+
+ // Mock stopTransactionOnConnector — called by UnlockConnector when transaction is active
+ station.stopTransactionOnConnector = async () =>
+ Promise.resolve({ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } })
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ─── RemoteStopTransaction (§5.12) ────────────────────────────────────
+
+ await describe('handleRequestRemoteStopTransaction', async () => {
+ // @spec §5.12 — TC_016_CS
+ await it('should return Accepted when transactionId matches an active connector', () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, { transactionId: 42 })
+
+ // Act
+ const response = testableService.handleRequestRemoteStopTransaction(station, {
+ transactionId: 42,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.12 — TC_020_CS
+ await it('should return Rejected when transactionId does not match any connector', () => {
+ // Act
+ const response = testableService.handleRequestRemoteStopTransaction(station, {
+ transactionId: 99999,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ await it('should return a response with exactly one status property', () => {
+ // Act
+ const response = testableService.handleRequestRemoteStopTransaction(station, {
+ transactionId: 1,
+ })
+
+ // Assert
+ assert.strictEqual(Object.keys(response).length, 1)
+ assert.notStrictEqual(response.status, undefined)
+ })
+ })
+
+ // ─── UnlockConnector (§5.17) ──────────────────────────────────────────
+
+ await describe('handleRequestUnlockConnector', async () => {
+ // @spec §5.17 — TC_026_CS
+ await it('should return Unlocked for valid connectorId with no active transaction', async () => {
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 1,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16UnlockStatus.UNLOCKED)
+ })
+
+ // @spec §5.17 — TC_027_CS
+ await it('should return Unlocked when connector has active transaction and stop succeeds', async () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, { transactionId: 100 })
+
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 1,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16UnlockStatus.UNLOCKED)
+ })
+
+ // @spec §5.17 — TC_029_CS
+ await it('should return NotSupported for connectorId=0', async () => {
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 0,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16UnlockStatus.NOT_SUPPORTED)
+ })
+
+ // @spec §5.17 — TC_037_CS
+ await it('should return NotSupported for non-existent connectorId', async () => {
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 99,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16UnlockStatus.NOT_SUPPORTED)
+ })
+
+ await it('should return UnlockFailed when active transaction stop is rejected', async () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, { transactionId: 200 })
+ station.stopTransactionOnConnector = async () =>
+ Promise.resolve({ idTagInfo: { status: OCPP16AuthorizationStatus.INVALID } })
+
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 1,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16UnlockStatus.UNLOCK_FAILED)
+ })
+
+ await it('should return a response with exactly one status property', async () => {
+ // Act
+ const response = await testableService.handleRequestUnlockConnector(station, {
+ connectorId: 1,
+ })
+
+ // Assert
+ assert.strictEqual(Object.keys(response).length, 1)
+ assert.notStrictEqual(response.status, undefined)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService Reservation handlers
+ * @description Unit tests for OCPP 1.6 ReserveNow (§8.2) and CancelReservation (§8.1)
+ * incoming request handlers
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type {
+ OCPP16CancelReservationRequest,
+ OCPP16ReserveNowRequest,
+} from '../../../../src/types/index.js'
+
+import {
+ GenericStatus,
+ OCPP16AuthorizationStatus,
+ OCPP16ChargePointStatus,
+ OCPP16StandardParametersKey,
+} from '../../../../src/types/index.js'
+import { OCPP16ReservationStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ ReservationFixtures,
+ setMockRequestHandler,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+/**
+ * Enable the Reservation feature profile and mock auth to accept requests.
+ * @param context - Test context with station and service
+ * @param reserveConnectorZeroSupported - Whether connector 0 reservation is supported
+ */
+function enableReservationProfile (
+ context: OCPP16IncomingRequestTestContext,
+ reserveConnectorZeroSupported = false
+): void {
+ const { station } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,Reservation'
+ )
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.ReserveConnectorZeroSupported,
+ reserveConnectorZeroSupported ? 'true' : 'false'
+ )
+ // Mock getReserveConnectorZeroSupported (not on mock station by default)
+ const stationWithReserve = station as ChargingStation & {
+ getReserveConnectorZeroSupported: () => boolean
+ }
+ stationWithReserve.getReserveConnectorZeroSupported = () => reserveConnectorZeroSupported
+ // Mock auth: remote authorization returns Accepted
+ setMockRequestHandler(station, async () =>
+ Promise.resolve({ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } })
+ )
+}
+
+await describe('OCPP16IncomingRequestService — Reservation', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // =========================================================================
+ // ReserveNow (§8.2)
+ // =========================================================================
+
+ await describe('handleRequestReserveNow', async () => {
+ // @spec §8.2 — TC_049_CS
+ await it('should accept reservation for available connector', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const reservation = ReservationFixtures.createReservation(1, 1, 'TEST-TAG-001')
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.ACCEPTED)
+ })
+
+ // @spec §8.2 — TC_050_CS
+ await it('should accept reservation for connectorId=0 when ReserveConnectorZeroSupported is true', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context, true)
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: 0,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId: 10,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.ACCEPTED)
+ })
+
+ // @spec §8.2 — TC_050_CS
+ await it('should reject reservation for connectorId=0 when ReserveConnectorZeroSupported is false', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context, false)
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: 0,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId: 10,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.REJECTED)
+ })
+
+ // @spec §8.2 — TC_052_CS
+ await it('should return occupied when connector has active transaction', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.status = OCPP16ChargePointStatus.Charging
+ }
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: 1,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId: 2,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.OCCUPIED)
+ })
+
+ await it('should reject reservation when feature profile is not enabled', async () => {
+ // Arrange
+ const { station, testableService } = context
+ // Do NOT enable Reservation feature profile
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: 1,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId: 3,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.REJECTED)
+ })
+
+ await it('should reject reservation for non-existing connectorId', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const request: OCPP16ReserveNowRequest = {
+ connectorId: 99,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId: 4,
+ }
+
+ // Act
+ const response = await testableService.handleRequestReserveNow(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ReservationStatus.REJECTED)
+ })
+ })
+
+ // =========================================================================
+ // CancelReservation (§8.1)
+ // =========================================================================
+
+ await describe('handleRequestCancelReservation', async () => {
+ // @spec §8.1 — TC_051_CS
+ await it('should accept cancellation for existing reservation', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+
+ // First create a reservation via ReserveNow
+ const reservation = ReservationFixtures.createReservation(1, 42, 'TEST-TAG-001')
+ const reserveRequest: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+ const reserveResponse = await testableService.handleRequestReserveNow(station, reserveRequest)
+ assert.strictEqual(reserveResponse.status, OCPP16ReservationStatus.ACCEPTED)
+
+ // Act — cancel the reservation
+ const cancelRequest: OCPP16CancelReservationRequest = {
+ reservationId: 42,
+ }
+ const response = await testableService.handleRequestCancelReservation(station, cancelRequest)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ await it('should reject cancellation for non-existent reservation', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const cancelRequest: OCPP16CancelReservationRequest = {
+ reservationId: 999,
+ }
+
+ // Act
+ const response = await testableService.handleRequestCancelReservation(station, cancelRequest)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ await it('should reject cancellation when feature profile is not enabled', async () => {
+ // Arrange
+ const { station, testableService } = context
+ // Do NOT enable Reservation feature profile
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+ const cancelRequest: OCPP16CancelReservationRequest = {
+ reservationId: 1,
+ }
+
+ // Act
+ const response = await testableService.handleRequestCancelReservation(station, cancelRequest)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService Reset
+ * @description Unit tests for OCPP 1.6 Reset incoming request handler (§5.13)
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ResetRequest } from '../../../../src/types/index.js'
+
+import { GenericStatus } from '../../../../src/types/index.js'
+import { ResetType } from '../../../../src/types/ocpp/1.6/Requests.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ ResetFixtures,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — Reset', async () => {
+ let testContext: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ testContext = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // @spec §5.13 — TC_022_CS: Hard reset without active transactions
+ await it('should handle hard reset request without active transactions', () => {
+ // Arrange
+ const { testableService } = testContext
+ const station = ResetFixtures.createStandardStation(0)
+ const resetRequest: ResetRequest = {
+ type: ResetType.HARD,
+ }
+
+ // Act
+ const response = testableService.handleRequestReset(station, resetRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.13 — TC_023_CS: Soft reset without active transactions
+ await it('should handle soft reset request without active transactions', () => {
+ // Arrange
+ const { testableService } = testContext
+ const station = ResetFixtures.createStandardStation(0)
+ const resetRequest: ResetRequest = {
+ type: ResetType.SOFT,
+ }
+
+ // Act
+ const response = testableService.handleRequestReset(station, resetRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.13 — TC_024_CS: Hard reset with active transaction
+ await it('should handle hard reset request with active transaction', () => {
+ // Arrange
+ const { testableService } = testContext
+ const station = ResetFixtures.createStandardStation(1)
+ const connector = station.getConnectorStatus(1)
+ if (connector != null) {
+ connector.transactionStarted = true
+ connector.transactionId = 1
+ }
+
+ const resetRequest: ResetRequest = {
+ type: ResetType.HARD,
+ }
+
+ // Act
+ const response = testableService.handleRequestReset(station, resetRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // @spec §5.13 — TC_025_CS: Soft reset with active transaction
+ await it('should handle soft reset request with active transaction', () => {
+ // Arrange
+ const { testableService } = testContext
+ const station = ResetFixtures.createStandardStation(1)
+ const connector = station.getConnectorStatus(1)
+ if (connector != null) {
+ connector.transactionStarted = true
+ connector.transactionId = 1
+ }
+
+ const resetRequest: ResetRequest = {
+ type: ResetType.SOFT,
+ }
+
+ // Act
+ const response = testableService.handleRequestReset(station, resetRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ })
+
+ // Additional test: Verify response structure
+ await it('should return proper response structure for reset', () => {
+ // Arrange
+ const { testableService } = testContext
+ const station = ResetFixtures.createStandardStation(0)
+ const resetRequest: ResetRequest = {
+ type: ResetType.HARD,
+ }
+
+ // Act
+ const response = testableService.handleRequestReset(station, resetRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.ok([GenericStatus.Accepted, GenericStatus.Rejected].includes(response.status))
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService simple handlers
+ * @description Tests for ClearCache (§5.5) and DataTransfer (§5.6) incoming request handlers
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import { OCPP16DataTransferStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — SimpleHandlers', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // @spec §5.5: ClearCache
+ await describe('handleRequestClearCache', async () => {
+ await it('should return response with status field', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestClearCache(station)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ })
+ })
+
+ // @spec §5.6: DataTransfer
+ await describe('handleRequestDataTransfer', async () => {
+ await it('should return UnknownVendorId status for unknown vendor', () => {
+ // Arrange
+ const { station, testableService } = context
+ const dataTransferRequest = {
+ data: 'test-data',
+ messageId: 'test-msg',
+ vendorId: 'unknown-vendor-xyz',
+ }
+
+ // Act
+ const response = testableService.handleRequestDataTransfer(station, dataTransferRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, OCPP16DataTransferStatus.UNKNOWN_VENDOR_ID)
+ assert.strictEqual(typeof response.status, 'string')
+ })
+
+ await it('should return Accepted status for matching vendor', () => {
+ // Arrange
+ const { station, testableService } = context
+ const matchingVendor = 'test-vendor-match'
+ if (station.stationInfo != null) {
+ station.stationInfo.chargePointVendor = matchingVendor
+ }
+ const dataTransferRequest = {
+ data: 'test-data',
+ messageId: 'test-msg',
+ vendorId: matchingVendor,
+ }
+
+ // Act
+ const response = testableService.handleRequestDataTransfer(station, dataTransferRequest)
+
+ // Assert
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, 'Accepted')
+ assert.strictEqual(typeof response.status, 'string')
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService Smart Charging handlers
+ * @description Unit tests for OCPP 1.6 SetChargingProfile (§9.3), ClearChargingProfile (§9.1),
+ * and GetCompositeSchedule (§9.2) incoming request handlers
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type {
+ OCPP16ClearChargingProfileRequest,
+ OCPP16GetCompositeScheduleRequest,
+ SetChargingProfileRequest,
+} from '../../../../src/types/index.js'
+
+import { GenericStatus, OCPP16StandardParametersKey } from '../../../../src/types/index.js'
+import { OCPP16ChargingProfilePurposeType } from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+import {
+ OCPP16ChargingProfileStatus,
+ OCPP16ClearChargingProfileStatus,
+} from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ ChargingProfileFixtures,
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — SmartCharging', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ============================================================================
+ // SetChargingProfile (§9.3)
+ // ============================================================================
+
+ await describe('handleRequestSetChargingProfile', async () => {
+ // @spec §9.3 — TC_053_CS
+ await it('should accept a valid TxDefaultProfile on a valid connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createTxDefaultProfile()
+ const request: SetChargingProfileRequest = {
+ connectorId: 1,
+ csChargingProfiles: profile,
+ }
+
+ // Act
+ const response = testableService.handleRequestSetChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ChargingProfileStatus.ACCEPTED)
+ })
+
+ await it('should accept a ChargePointMaxProfile on connector 0', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createChargePointMaxProfile()
+ const request: SetChargingProfileRequest = {
+ connectorId: 0,
+ csChargingProfiles: profile,
+ }
+
+ // Act
+ const response = testableService.handleRequestSetChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ChargingProfileStatus.ACCEPTED)
+ })
+
+ // @spec §9.3 — TC_058_CS
+ await it('should reject a profile for a non-existing connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createTxDefaultProfile()
+ const request: SetChargingProfileRequest = {
+ connectorId: 99,
+ csChargingProfiles: profile,
+ }
+
+ // Act
+ const response = testableService.handleRequestSetChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ChargingProfileStatus.REJECTED)
+ })
+
+ await it('should reject a ChargePointMaxProfile on a non-zero connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createChargePointMaxProfile()
+ const request: SetChargingProfileRequest = {
+ connectorId: 1,
+ csChargingProfiles: profile,
+ }
+
+ // Act
+ const response = testableService.handleRequestSetChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ChargingProfileStatus.REJECTED)
+ })
+
+ await it('should return NotSupported when SmartCharging feature profile is not enabled', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+ const profile = ChargingProfileFixtures.createTxDefaultProfile()
+ const request: SetChargingProfileRequest = {
+ connectorId: 1,
+ csChargingProfiles: profile,
+ }
+
+ // Act
+ const response = testableService.handleRequestSetChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ChargingProfileStatus.NOT_SUPPORTED)
+ })
+ })
+
+ // ============================================================================
+ // ClearChargingProfile (§9.1)
+ // ============================================================================
+
+ await describe('handleRequestClearChargingProfile', async () => {
+ // @spec §9.1 — TC_055_CS
+ await it('should accept clearing profiles by profile ID', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createTxDefaultProfile(10)
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.chargingProfiles = [profile]
+ }
+ const request: OCPP16ClearChargingProfileRequest = {
+ id: 10,
+ }
+
+ // Act
+ const response = testableService.handleRequestClearChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ClearChargingProfileStatus.ACCEPTED)
+ })
+
+ // @spec §9.1 — TC_056_CS
+ await it('should accept clearing profiles by purpose and stack level', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createTxDefaultProfile(1, 0)
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.chargingProfiles = [profile]
+ }
+ const request: OCPP16ClearChargingProfileRequest = {
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE,
+ stackLevel: 0,
+ }
+
+ // Act
+ const response = testableService.handleRequestClearChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ClearChargingProfileStatus.ACCEPTED)
+ })
+
+ await it('should return Unknown when no matching profiles exist', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ // Ensure no profiles on any connector
+ for (const [connectorId] of station.connectors.entries()) {
+ const connectorStatus = station.getConnectorStatus(connectorId)
+ if (connectorStatus != null) {
+ connectorStatus.chargingProfiles = []
+ }
+ }
+ const request: OCPP16ClearChargingProfileRequest = {
+ id: 999,
+ }
+
+ // Act
+ const response = testableService.handleRequestClearChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ClearChargingProfileStatus.UNKNOWN)
+ })
+
+ await it('should return Unknown when SmartCharging feature profile is not enabled', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+ const request: OCPP16ClearChargingProfileRequest = {
+ id: 1,
+ }
+
+ // Act
+ const response = testableService.handleRequestClearChargingProfile(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16ClearChargingProfileStatus.UNKNOWN)
+ })
+ })
+
+ // ============================================================================
+ // GetCompositeSchedule (§9.2)
+ // ============================================================================
+
+ await describe('handleRequestGetCompositeSchedule', async () => {
+ // @spec §9.2 — TC_054_CS
+ await it('should return Accepted with schedule when profiles exist on connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const profile = ChargingProfileFixtures.createTxDefaultProfile()
+ profile.chargingSchedule.startSchedule = new Date()
+ profile.chargingSchedule.duration = 3600
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.chargingProfiles = [profile]
+ }
+ const request: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 1,
+ duration: 3600,
+ }
+
+ // Act
+ const response = testableService.handleRequestGetCompositeSchedule(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ assert.strictEqual(response.connectorId, 1)
+ assert.notStrictEqual(response.chargingSchedule, undefined)
+ assert.notStrictEqual(response.scheduleStart, undefined)
+ })
+
+ await it('should return Rejected for a non-existing connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const request: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 99,
+ duration: 3600,
+ }
+
+ // Act
+ const response = testableService.handleRequestGetCompositeSchedule(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ await it('should return Rejected for connector 0', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ const request: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 0,
+ duration: 3600,
+ }
+
+ // Act
+ const response = testableService.handleRequestGetCompositeSchedule(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ await it('should return Rejected when no profiles are set on connector', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ // Ensure no profiles on connector 1 or connector 0
+ const connector1Status = station.getConnectorStatus(1)
+ if (connector1Status != null) {
+ connector1Status.chargingProfiles = []
+ }
+ const connector0Status = station.getConnectorStatus(0)
+ if (connector0Status != null) {
+ connector0Status.chargingProfiles = []
+ }
+ const request: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 1,
+ duration: 3600,
+ }
+
+ // Act
+ const response = testableService.handleRequestGetCompositeSchedule(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+
+ await it('should return Rejected when SmartCharging feature profile is not enabled', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+ const request: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 1,
+ duration: 3600,
+ }
+
+ // Act
+ const response = testableService.handleRequestGetCompositeSchedule(station, request)
+
+ // Assert
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16IncomingRequestService TriggerMessage handler
+ * @description Tests for TriggerMessage (§10.1) incoming request handler covering
+ * accepted triggers, unimplemented triggers, and feature profile validation
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import {
+ OCPP16MessageTrigger,
+ OCPP16StandardParametersKey,
+ OCPP16TriggerMessageStatus,
+} from '../../../../src/types/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16IncomingRequestService — TriggerMessage', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ upsertConfigurationKey(
+ context.station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,RemoteTrigger'
+ )
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // @spec §10.1 — TC_061_CS
+ await describe('BootNotification trigger', async () => {
+ await it('should return Accepted for BootNotification trigger', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: OCPP16MessageTrigger.BootNotification,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.ACCEPTED)
+ })
+ })
+
+ // @spec §10.1 — TC_062_CS
+ await describe('Heartbeat trigger', async () => {
+ await it('should return Accepted for Heartbeat trigger', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: OCPP16MessageTrigger.Heartbeat,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.ACCEPTED)
+ })
+ })
+
+ await describe('StatusNotification trigger', async () => {
+ await it('should return Accepted for StatusNotification trigger with connectorId', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ connectorId: 1,
+ requestedMessage: OCPP16MessageTrigger.StatusNotification,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.ACCEPTED)
+ })
+ })
+
+ await describe('MeterValues trigger', async () => {
+ await it('should return NotImplemented for MeterValues trigger', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ connectorId: 1,
+ requestedMessage: OCPP16MessageTrigger.MeterValues,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.NOT_IMPLEMENTED)
+ })
+ })
+
+ await describe('DiagnosticsStatusNotification trigger', async () => {
+ await it('should return NotImplemented for DiagnosticsStatusNotification trigger', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: OCPP16MessageTrigger.DiagnosticsStatusNotification,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.NOT_IMPLEMENTED)
+ })
+ })
+
+ await describe('FirmwareStatusNotification trigger', async () => {
+ await it('should return NotImplemented for FirmwareStatusNotification trigger', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: OCPP16MessageTrigger.FirmwareStatusNotification,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.NOT_IMPLEMENTED)
+ })
+ })
+
+ await describe('unsupported requestedMessage', async () => {
+ await it('should return NotImplemented for unknown requestedMessage value', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: 'UnknownMessage' as OCPP16MessageTrigger,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.NOT_IMPLEMENTED)
+ })
+ })
+
+ await describe('feature profile not enabled', async () => {
+ await it('should return NotImplemented when RemoteTrigger profile is not enabled', () => {
+ // Arrange
+ const { station, testableService } = context
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.SupportedFeatureProfiles, 'Core')
+
+ // Act
+ const response = testableService.handleRequestTriggerMessage(station, {
+ requestedMessage: OCPP16MessageTrigger.BootNotification,
+ })
+
+ // Assert
+ assert.strictEqual(response.status, OCPP16TriggerMessageStatus.NOT_IMPLEMENTED)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP 1.6 Charging Profile Management — Integration
+ * @see OCPP 1.6 — §9.3 SetChargingProfile, §9.1 ClearChargingProfile, §9.2 GetCompositeSchedule
+ * @description Multi-step integration tests verifying roundtrip flows across SetChargingProfile,
+ * ClearChargingProfile, and GetCompositeSchedule handlers for OCPP 1.6 Smart Charging
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type {
+ OCPP16ClearChargingProfileRequest,
+ OCPP16GetCompositeScheduleRequest,
+ SetChargingProfileRequest,
+} from '../../../../src/types/index.js'
+import type { OCPP16ChargingProfile } from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+
+import { GenericStatus, OCPP16StandardParametersKey } from '../../../../src/types/index.js'
+import {
+ OCPP16ChargingProfileKindType,
+ OCPP16ChargingProfilePurposeType,
+ OCPP16ChargingRateUnitType,
+} from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+import {
+ OCPP16ChargingProfileStatus,
+ OCPP16ClearChargingProfileStatus,
+} from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ ChargingProfileFixtures,
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16 Integration — Charging Profile Management', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ upsertConfigurationKey(
+ context.station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,SmartCharging'
+ )
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ============================================================================
+ // Set → Get Roundtrip
+ // ============================================================================
+
+ await it('should return composite schedule matching a set TxDefaultProfile', () => {
+ // Arrange
+ const { station, testableService } = context
+ const profile = ChargingProfileFixtures.createTxDefaultProfile(1, 0)
+ profile.chargingSchedule.startSchedule = new Date()
+ profile.chargingSchedule.duration = 3600
+
+ const setRequest: SetChargingProfileRequest = {
+ connectorId: 1,
+ csChargingProfiles: profile,
+ }
+
+ // Act — Step 1: Set the charging profile
+ const setResponse = testableService.handleRequestSetChargingProfile(station, setRequest)
+
+ // Assert — Step 1: Profile accepted
+ assert.strictEqual(setResponse.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Act — Step 2: Get composite schedule
+ const getRequest: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 1,
+ duration: 3600,
+ }
+ const getResponse = testableService.handleRequestGetCompositeSchedule(station, getRequest)
+
+ // Assert — Step 2: Schedule returned from the set profile
+ assert.strictEqual(getResponse.status, GenericStatus.Accepted)
+ assert.strictEqual(getResponse.connectorId, 1)
+ assert.notStrictEqual(getResponse.chargingSchedule, undefined)
+ if (getResponse.chargingSchedule == null) {
+ assert.fail('Expected chargingSchedule to be defined')
+ }
+ assert.strictEqual(
+ getResponse.chargingSchedule.chargingRateUnit,
+ OCPP16ChargingRateUnitType.AMPERE
+ )
+ assert.notStrictEqual(getResponse.chargingSchedule.chargingSchedulePeriod, undefined)
+ assert.notStrictEqual(getResponse.scheduleStart, undefined)
+ })
+
+ // ============================================================================
+ // Set → Clear → Get Roundtrip
+ // ============================================================================
+
+ await it('should return Rejected from GetCompositeSchedule after clearing a set profile', () => {
+ // Arrange
+ const { station, testableService } = context
+ const profile = ChargingProfileFixtures.createTxDefaultProfile(10, 0)
+ profile.chargingSchedule.startSchedule = new Date()
+ profile.chargingSchedule.duration = 3600
+
+ // Ensure connector 0 has no profiles so only connector 1 matters
+ const connector0 = station.getConnectorStatus(0)
+ if (connector0 != null) {
+ connector0.chargingProfiles = []
+ }
+
+ // Act — Step 1: Set the profile on connector 1
+ const setResponse = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: profile,
+ })
+ assert.strictEqual(setResponse.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Act — Step 2: Clear by connector ID
+ const clearRequest: OCPP16ClearChargingProfileRequest = {
+ connectorId: 1,
+ }
+ const clearResponse = testableService.handleRequestClearChargingProfile(station, clearRequest)
+
+ // Assert — Step 2: Clear accepted
+ assert.strictEqual(clearResponse.status, OCPP16ClearChargingProfileStatus.ACCEPTED)
+
+ // Act — Step 3: Get composite schedule should now be Rejected (no profiles)
+ const getRequest: OCPP16GetCompositeScheduleRequest = {
+ connectorId: 1,
+ duration: 3600,
+ }
+ const getResponse = testableService.handleRequestGetCompositeSchedule(station, getRequest)
+
+ // Assert — Step 3: Rejected since all profiles were cleared
+ assert.strictEqual(getResponse.status, GenericStatus.Rejected)
+ })
+
+ // ============================================================================
+ // Multiple Profiles with Different Stack Levels → GetCompositeSchedule
+ // ============================================================================
+
+ await it('should return Accepted composite schedule with multiple profiles at different stack levels', () => {
+ // Arrange
+ const { station, testableService } = context
+ const profileLow = ChargingProfileFixtures.createTxDefaultProfile(1, 0)
+ profileLow.chargingSchedule.startSchedule = new Date()
+ profileLow.chargingSchedule.duration = 3600
+
+ const profileHigh = ChargingProfileFixtures.createTxDefaultProfile(2, 1)
+ profileHigh.chargingSchedule.startSchedule = new Date()
+ profileHigh.chargingSchedule.duration = 3600
+ profileHigh.chargingSchedule.chargingSchedulePeriod = [{ limit: 20, startPeriod: 0 }]
+
+ // Act — Set both profiles
+ const setLow = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: profileLow,
+ })
+ const setHigh = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: profileHigh,
+ })
+
+ // Assert — Both accepted
+ assert.strictEqual(setLow.status, OCPP16ChargingProfileStatus.ACCEPTED)
+ assert.strictEqual(setHigh.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Verify both profiles are stored
+ const connectorStatus = station.getConnectorStatus(1)
+ assert.strictEqual(connectorStatus?.chargingProfiles?.length, 2)
+
+ // Act — Get composite schedule
+ const getResponse = testableService.handleRequestGetCompositeSchedule(station, {
+ connectorId: 1,
+ duration: 3600,
+ })
+
+ // Assert — Accepted with valid schedule
+ assert.strictEqual(getResponse.status, GenericStatus.Accepted)
+ assert.strictEqual(getResponse.connectorId, 1)
+ assert.notStrictEqual(getResponse.chargingSchedule, undefined)
+ })
+
+ // ============================================================================
+ // Replace Profile (Same stackLevel + Purpose)
+ // ============================================================================
+
+ await it('should replace a profile with same stackLevel and purpose, keeping only one profile', () => {
+ // Arrange
+ const { station, testableService } = context
+ const originalProfile = ChargingProfileFixtures.createTxDefaultProfile(1, 0)
+ originalProfile.chargingSchedule.startSchedule = new Date()
+ originalProfile.chargingSchedule.duration = 3600
+ originalProfile.chargingSchedule.chargingSchedulePeriod = [{ limit: 32, startPeriod: 0 }]
+
+ const replacementProfile: SetChargingProfileRequest['csChargingProfiles'] = {
+ chargingProfileId: 5,
+ chargingProfileKind: OCPP16ChargingProfileKindType.ABSOLUTE,
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE,
+ chargingSchedule: {
+ chargingRateUnit: OCPP16ChargingRateUnitType.AMPERE,
+ chargingSchedulePeriod: [{ limit: 16, startPeriod: 0 }],
+ duration: 3600,
+ startSchedule: new Date(),
+ },
+ stackLevel: 0,
+ }
+
+ // Act — Step 1: Set original
+ const setOriginal = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: originalProfile,
+ })
+ assert.strictEqual(setOriginal.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Act — Step 2: Set replacement (same stackLevel=0, same purpose=TxDefaultProfile)
+ const setReplacement = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: replacementProfile,
+ })
+ assert.strictEqual(setReplacement.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Assert — Only one profile stored (replacement overwrote original)
+ const connectorStatus = station.getConnectorStatus(1)
+ assert.strictEqual(connectorStatus?.chargingProfiles?.length, 1)
+ assert.strictEqual(connectorStatus.chargingProfiles[0].chargingProfileId, 5)
+ const storedProfile = connectorStatus.chargingProfiles[0] as OCPP16ChargingProfile | undefined
+ assert.strictEqual(storedProfile?.chargingSchedule.chargingSchedulePeriod[0].limit, 16)
+ })
+
+ // ============================================================================
+ // Clear by Purpose — Only Matching Profiles Cleared
+ // ============================================================================
+
+ await it('should clear only profiles matching the specified purpose when clearing by purpose', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Set a TxDefaultProfile on connector 1 (stackLevel 0)
+ const txDefaultProfile = ChargingProfileFixtures.createTxDefaultProfile(1, 0)
+ txDefaultProfile.chargingSchedule.startSchedule = new Date()
+ txDefaultProfile.chargingSchedule.duration = 3600
+
+ // Set a different profile with a different purpose — ChargePointMaxProfile on connector 0
+ const chargePointMaxProfile = ChargingProfileFixtures.createChargePointMaxProfile(2)
+ chargePointMaxProfile.chargingSchedule.startSchedule = new Date()
+ chargePointMaxProfile.chargingSchedule.duration = 3600
+
+ // Set TxDefaultProfile on connector 1
+ const setTxDefault = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: txDefaultProfile,
+ })
+ assert.strictEqual(setTxDefault.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Set ChargePointMaxProfile on connector 0
+ const setChargePointMax = testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 0,
+ csChargingProfiles: chargePointMaxProfile,
+ })
+ assert.strictEqual(setChargePointMax.status, OCPP16ChargingProfileStatus.ACCEPTED)
+
+ // Verify both connectors have profiles
+ assert.strictEqual(station.getConnectorStatus(1)?.chargingProfiles?.length, 1)
+ assert.strictEqual(station.getConnectorStatus(0)?.chargingProfiles?.length, 1)
+
+ // Act — Clear only TxDefaultProfile purpose (no connectorId specified → scans all connectors)
+ const clearRequest: OCPP16ClearChargingProfileRequest = {
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE,
+ }
+ const clearResponse = testableService.handleRequestClearChargingProfile(station, clearRequest)
+
+ // Assert — Clear accepted (TxDefaultProfile found and cleared)
+ assert.strictEqual(clearResponse.status, OCPP16ClearChargingProfileStatus.ACCEPTED)
+
+ // Assert — TxDefaultProfile cleared from connector 1
+ assert.strictEqual(station.getConnectorStatus(1)?.chargingProfiles?.length, 0)
+
+ // Assert — ChargePointMaxProfile on connector 0 is untouched
+ assert.strictEqual(station.getConnectorStatus(0)?.chargingProfiles?.length, 1)
+ assert.strictEqual(station.getConnectorStatus(0)?.chargingProfiles?.[0].chargingProfileId, 2)
+ })
+
+ // ============================================================================
+ // Clear by Profile ID → Verify Specific Profile Removed
+ // ============================================================================
+
+ await it('should clear only the profile with matching ID when clearing by ID', () => {
+ // Arrange
+ const { station, testableService } = context
+
+ // Set two TxDefaultProfiles with different IDs and stack levels on connector 1
+ const profileA = ChargingProfileFixtures.createTxDefaultProfile(10, 0)
+ profileA.chargingSchedule.startSchedule = new Date()
+ profileA.chargingSchedule.duration = 3600
+
+ const profileB = ChargingProfileFixtures.createTxDefaultProfile(20, 1)
+ profileB.chargingSchedule.startSchedule = new Date()
+ profileB.chargingSchedule.duration = 3600
+ profileB.chargingSchedule.chargingSchedulePeriod = [{ limit: 24, startPeriod: 0 }]
+
+ testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: profileA,
+ })
+ testableService.handleRequestSetChargingProfile(station, {
+ connectorId: 1,
+ csChargingProfiles: profileB,
+ })
+
+ // Verify both stored
+ assert.strictEqual(station.getConnectorStatus(1)?.chargingProfiles?.length, 2)
+
+ // Act — Clear only profile with ID 10
+ const clearResponse = testableService.handleRequestClearChargingProfile(station, {
+ id: 10,
+ })
+
+ // Assert — Clear accepted
+ assert.strictEqual(clearResponse.status, OCPP16ClearChargingProfileStatus.ACCEPTED)
+
+ // Assert — Only profile B remains
+ const remaining = station.getConnectorStatus(1)?.chargingProfiles
+ assert.strictEqual(remaining?.length, 1)
+ assert.strictEqual(remaining[0].chargingProfileId, 20)
+
+ // Verify composite schedule still works with remaining profile
+ const getResponse = testableService.handleRequestGetCompositeSchedule(station, {
+ connectorId: 1,
+ duration: 3600,
+ })
+ assert.strictEqual(getResponse.status, GenericStatus.Accepted)
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16 Integration — Configuration Management
+ * @see OCPP 1.6 — §5.4 ChangeConfiguration, §5.8 GetConfiguration
+ * @description Multi-step integration tests verifying ChangeConfiguration → GetConfiguration
+ * roundtrips for OCPP 1.6 configuration management flows
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type {
+ ChangeConfigurationRequest,
+ GetConfigurationRequest,
+} from '../../../../src/types/index.js'
+
+import { OCPP16StandardParametersKey } from '../../../../src/types/index.js'
+import { OCPP16ConfigurationStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16 Integration — Configuration Management', async () => {
+ let testContext: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ testContext = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ---------------------------------------------------------------------------
+ // 1. Change → Get roundtrip
+ // ---------------------------------------------------------------------------
+
+ await it('should reflect changed value when retrieving a mutable key after ChangeConfiguration', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.MeterValueSampleInterval, '60')
+ const changeRequest: ChangeConfigurationRequest = {
+ key: OCPP16StandardParametersKey.MeterValueSampleInterval,
+ value: '15',
+ }
+
+ // Act — Change
+ const changeResponse = testableService.handleRequestChangeConfiguration(station, changeRequest)
+
+ // Assert — Change accepted
+ assert.strictEqual(changeResponse.status, OCPP16ConfigurationStatus.ACCEPTED)
+
+ // Act — Get
+ const getRequest: GetConfigurationRequest = {
+ key: [OCPP16StandardParametersKey.MeterValueSampleInterval],
+ }
+ const getResponse = testableService.handleRequestGetConfiguration(station, getRequest)
+
+ // Assert — Value matches what was set
+ assert.strictEqual(getResponse.configurationKey.length, 1)
+ assert.strictEqual(
+ getResponse.configurationKey[0].key,
+ OCPP16StandardParametersKey.MeterValueSampleInterval
+ )
+ assert.strictEqual(getResponse.configurationKey[0].value, '15')
+ assert.strictEqual(getResponse.unknownKey.length, 0)
+ })
+
+ // ---------------------------------------------------------------------------
+ // 2. Multiple key changes → GetConfiguration (all)
+ // ---------------------------------------------------------------------------
+
+ await it('should reflect all changed values when getting configuration after multiple changes', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.MeterValueSampleInterval, '60')
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.WebSocketPingInterval, '30')
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.ConnectionTimeOut, '120')
+
+ // Act — Change multiple keys
+ const change1 = testableService.handleRequestChangeConfiguration(station, {
+ key: OCPP16StandardParametersKey.MeterValueSampleInterval,
+ value: '10',
+ })
+ const change2 = testableService.handleRequestChangeConfiguration(station, {
+ key: OCPP16StandardParametersKey.WebSocketPingInterval,
+ value: '5',
+ })
+ const change3 = testableService.handleRequestChangeConfiguration(station, {
+ key: OCPP16StandardParametersKey.ConnectionTimeOut,
+ value: '60',
+ })
+
+ // Assert — All changes accepted
+ assert.strictEqual(change1.status, OCPP16ConfigurationStatus.ACCEPTED)
+ assert.strictEqual(change2.status, OCPP16ConfigurationStatus.ACCEPTED)
+ assert.strictEqual(change3.status, OCPP16ConfigurationStatus.ACCEPTED)
+
+ // Act — Get all keys
+ const getResponse = testableService.handleRequestGetConfiguration(station, {})
+
+ // Assert — All changed values reflected
+ const meterKey = getResponse.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.MeterValueSampleInterval as string)
+ )
+ const wsKey = getResponse.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.WebSocketPingInterval as string)
+ )
+ const connKey = getResponse.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.ConnectionTimeOut as string)
+ )
+ assert.notStrictEqual(meterKey, undefined)
+ assert.strictEqual(meterKey?.value, '10')
+ assert.notStrictEqual(wsKey, undefined)
+ assert.strictEqual(wsKey?.value, '5')
+ assert.notStrictEqual(connKey, undefined)
+ assert.strictEqual(connKey?.value, '60')
+ })
+
+ // ---------------------------------------------------------------------------
+ // 3. Readonly key protection
+ // ---------------------------------------------------------------------------
+
+ await it('should reject changing a readonly key and preserve original value on retrieval', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.HeartbeatInterval, '60', true)
+
+ // Act — Attempt to change readonly key
+ const changeRequest: ChangeConfigurationRequest = {
+ key: OCPP16StandardParametersKey.HeartbeatInterval,
+ value: '999',
+ }
+ const changeResponse = testableService.handleRequestChangeConfiguration(station, changeRequest)
+
+ // Assert — Rejected
+ assert.strictEqual(changeResponse.status, OCPP16ConfigurationStatus.REJECTED)
+
+ // Act — Verify value unchanged via GetConfiguration
+ const getResponse = testableService.handleRequestGetConfiguration(station, {
+ key: [OCPP16StandardParametersKey.HeartbeatInterval],
+ })
+
+ // Assert — Original value preserved
+ assert.strictEqual(getResponse.configurationKey.length, 1)
+ assert.strictEqual(getResponse.configurationKey[0].value, '60')
+ assert.strictEqual(getResponse.configurationKey[0].readonly, true)
+ })
+
+ // ---------------------------------------------------------------------------
+ // 4. RebootRequired key
+ // ---------------------------------------------------------------------------
+
+ await it('should return RebootRequired and update value for a key with reboot flag', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, 'RebootRequiredKey', 'oldValue')
+ const configKey = station.ocppConfiguration?.configurationKey?.find(
+ k => k.key === 'RebootRequiredKey'
+ )
+ if (configKey != null) {
+ configKey.reboot = true
+ }
+
+ // Act — Change the reboot-requiring key
+ const changeResponse = testableService.handleRequestChangeConfiguration(station, {
+ key: 'RebootRequiredKey',
+ value: 'newValue',
+ })
+
+ // Assert — RebootRequired returned
+ assert.strictEqual(changeResponse.status, OCPP16ConfigurationStatus.REBOOT_REQUIRED)
+
+ // Act — Verify value was actually updated despite reboot being needed
+ const getResponse = testableService.handleRequestGetConfiguration(station, {
+ key: ['RebootRequiredKey'],
+ })
+
+ // Assert — Value updated
+ assert.strictEqual(getResponse.configurationKey.length, 1)
+ assert.strictEqual(getResponse.configurationKey[0].key, 'RebootRequiredKey')
+ assert.strictEqual(getResponse.configurationKey[0].value, 'newValue')
+ })
+
+ // ---------------------------------------------------------------------------
+ // 5. Unknown key
+ // ---------------------------------------------------------------------------
+
+ await it('should return NotSupported for unknown key and not add it to configuration', () => {
+ // Arrange
+ const { station, testableService } = testContext
+
+ // Act — Attempt to change a key that does not exist in configuration
+ const changeResponse = testableService.handleRequestChangeConfiguration(station, {
+ key: 'CompletelyUnknownConfigKey',
+ value: 'someValue',
+ })
+
+ // Assert — NotSupported
+ assert.strictEqual(changeResponse.status, OCPP16ConfigurationStatus.NOT_SUPPORTED)
+
+ // Act — Verify key is not in configuration
+ const getResponse = testableService.handleRequestGetConfiguration(station, {
+ key: ['CompletelyUnknownConfigKey'],
+ })
+
+ // Assert — Key appears in unknownKey list, not in configurationKey
+ assert.strictEqual(getResponse.configurationKey.length, 0)
+ assert.strictEqual(getResponse.unknownKey.length, 1)
+ assert.strictEqual(getResponse.unknownKey[0], 'CompletelyUnknownConfigKey')
+ })
+
+ // ---------------------------------------------------------------------------
+ // 6. Get all keys after multiple changes
+ // ---------------------------------------------------------------------------
+
+ await it('should return all visible keys including changed ones when getting all configuration', () => {
+ // Arrange
+ const { station, testableService } = testContext
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.HeartbeatInterval, '30')
+ upsertConfigurationKey(station, OCPP16StandardParametersKey.MeterValueSampleInterval, '60')
+ upsertConfigurationKey(station, 'VendorCustomKey', 'initial')
+
+ // Act — Change some keys
+ testableService.handleRequestChangeConfiguration(station, {
+ key: OCPP16StandardParametersKey.MeterValueSampleInterval,
+ value: '20',
+ })
+ testableService.handleRequestChangeConfiguration(station, {
+ key: 'VendorCustomKey',
+ value: 'updated',
+ })
+
+ // Act — Get all keys (no filter)
+ const getResponse = testableService.handleRequestGetConfiguration(station, {})
+
+ // Assert — All visible keys returned with correct values
+ assert.ok(getResponse.configurationKey.length >= 3)
+ assert.strictEqual(getResponse.unknownKey.length, 0)
+
+ const heartbeat = getResponse.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.HeartbeatInterval as string)
+ )
+ const meterInterval = getResponse.configurationKey.find(
+ k => k.key === (OCPP16StandardParametersKey.MeterValueSampleInterval as string)
+ )
+ const vendorKey = getResponse.configurationKey.find(k => k.key === 'VendorCustomKey')
+
+ assert.notStrictEqual(heartbeat, undefined)
+ assert.strictEqual(heartbeat?.value, '30')
+ assert.notStrictEqual(meterInterval, undefined)
+ assert.strictEqual(meterInterval?.value, '20')
+ assert.notStrictEqual(vendorKey, undefined)
+ assert.strictEqual(vendorKey?.value, 'updated')
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP 1.6 Reservation integration flows
+ * @module OCPP 1.6 — §8.2 ReserveNow, §8.1 CancelReservation, §5.11 RemoteStartTransaction
+ * @description Multi-step integration tests for reservation lifecycle:
+ * ReserveNow → CancelReservation and ReserveNow → RemoteStartTransaction flows.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type {
+ OCPP16CancelReservationRequest,
+ OCPP16ReserveNowRequest,
+ RemoteStartTransactionRequest,
+} from '../../../../src/types/index.js'
+
+import {
+ GenericStatus,
+ OCPP16AuthorizationStatus,
+ OCPP16StandardParametersKey,
+} from '../../../../src/types/index.js'
+import { OCPP16ReservationStatus } from '../../../../src/types/ocpp/1.6/Responses.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16IncomingRequestTestContext,
+ type OCPP16IncomingRequestTestContext,
+ ReservationFixtures,
+ setMockRequestHandler,
+ upsertConfigurationKey,
+} from './OCPP16TestUtils.js'
+
+/**
+ * Enable the Reservation feature profile and mock auth to accept requests.
+ * @param context - Test context with station and service
+ */
+function enableReservationProfile (context: OCPP16IncomingRequestTestContext): void {
+ const { station } = context
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ 'Core,Reservation'
+ )
+ upsertConfigurationKey(
+ station,
+ OCPP16StandardParametersKey.ReserveConnectorZeroSupported,
+ 'false'
+ )
+ const stationWithReserve = station as ChargingStation & {
+ getReserveConnectorZeroSupported: () => boolean
+ }
+ stationWithReserve.getReserveConnectorZeroSupported = () => false
+ // Mock auth: remote authorization returns Accepted
+ setMockRequestHandler(station, async () =>
+ Promise.resolve({ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } })
+ )
+}
+
+await describe('OCPP16 Integration — Reservation Flow', async () => {
+ let context: OCPP16IncomingRequestTestContext
+
+ beforeEach(() => {
+ context = createOCPP16IncomingRequestTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ===========================================================================
+ // Reserve → Cancel
+ // ===========================================================================
+
+ await describe('ReserveNow → CancelReservation', async () => {
+ await it('should reserve then cancel, restoring connector to available', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const reservation = ReservationFixtures.createReservation(1, 100, 'TAG-RESERVE-CANCEL')
+ const reserveRequest: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+
+ // Act — reserve
+ const reserveResponse = await testableService.handleRequestReserveNow(station, reserveRequest)
+
+ // Assert — reservation accepted and stored
+ assert.strictEqual(reserveResponse.status, OCPP16ReservationStatus.ACCEPTED)
+ const connectorAfterReserve = station.getConnectorStatus(1)
+ if (connectorAfterReserve == null) {
+ assert.fail('Expected connector to be defined after reserve')
+ }
+ if (connectorAfterReserve.reservation == null) {
+ assert.fail('Expected reservation to be defined after reserve')
+ }
+ assert.strictEqual(connectorAfterReserve.reservation.reservationId, 100)
+ assert.strictEqual(connectorAfterReserve.reservation.idTag, 'TAG-RESERVE-CANCEL')
+
+ // Act — cancel
+ const cancelRequest: OCPP16CancelReservationRequest = { reservationId: 100 }
+ const cancelResponse = await testableService.handleRequestCancelReservation(
+ station,
+ cancelRequest
+ )
+
+ // Assert — cancellation accepted and reservation cleared
+ assert.strictEqual(cancelResponse.status, GenericStatus.Accepted)
+ const connectorAfterCancel = station.getConnectorStatus(1)
+ assert.notStrictEqual(connectorAfterCancel, undefined)
+ assert.strictEqual(connectorAfterCancel?.reservation, undefined)
+ })
+ })
+
+ // ===========================================================================
+ // Reserve → Start (same connector)
+ // ===========================================================================
+
+ await describe('ReserveNow → RemoteStartTransaction', async () => {
+ await it('should accept remote start on a reserved connector with matching idTag', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const idTag = 'TAG-RESERVE-START'
+ const reservation = ReservationFixtures.createReservation(1, 200, idTag)
+ const reserveRequest: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+
+ // Act — reserve
+ const reserveResponse = await testableService.handleRequestReserveNow(station, reserveRequest)
+ assert.strictEqual(reserveResponse.status, OCPP16ReservationStatus.ACCEPTED)
+
+ // Act — remote start on same connector with matching idTag
+ const startRequest: RemoteStartTransactionRequest = {
+ connectorId: 1,
+ idTag,
+ }
+ const startResponse = await testableService.handleRequestRemoteStartTransaction(
+ station,
+ startRequest
+ )
+
+ // Assert — remote start accepted on the reserved connector
+ assert.strictEqual(startResponse.status, GenericStatus.Accepted)
+ })
+ })
+
+ // ===========================================================================
+ // Reserve → Start on different connector
+ // ===========================================================================
+
+ await describe('ReserveNow connector 1 → RemoteStartTransaction connector 2', async () => {
+ await it('should preserve reservation on connector 1 when starting on connector 2', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const reservation = ReservationFixtures.createReservation(1, 300, 'TAG-CONN1')
+ const reserveRequest: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+
+ // Act — reserve connector 1
+ const reserveResponse = await testableService.handleRequestReserveNow(station, reserveRequest)
+ assert.strictEqual(reserveResponse.status, OCPP16ReservationStatus.ACCEPTED)
+
+ // Act — remote start on connector 2 (different connector)
+ const startRequest: RemoteStartTransactionRequest = {
+ connectorId: 2,
+ idTag: 'TAG-CONN2',
+ }
+ const startResponse = await testableService.handleRequestRemoteStartTransaction(
+ station,
+ startRequest
+ )
+
+ // Assert — start accepted on connector 2
+ assert.strictEqual(startResponse.status, GenericStatus.Accepted)
+
+ // Assert — connector 1 reservation unchanged
+ const connector1 = station.getConnectorStatus(1)
+ if (connector1 == null) {
+ assert.fail('Expected connector 1 to be defined')
+ }
+ if (connector1.reservation == null) {
+ assert.fail('Expected reservation to be defined on connector 1')
+ }
+ assert.strictEqual(connector1.reservation.reservationId, 300)
+ assert.strictEqual(connector1.reservation.idTag, 'TAG-CONN1')
+
+ // Assert — connector 2 has no reservation
+ const connector2 = station.getConnectorStatus(2)
+ assert.notStrictEqual(connector2, undefined)
+ assert.strictEqual(connector2?.reservation, undefined)
+ })
+ })
+
+ // ===========================================================================
+ // Double reservation on same connector
+ // ===========================================================================
+
+ await describe('Double ReserveNow on same connector', async () => {
+ await it('should replace existing reservation when new reservation is added', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const firstReservation = ReservationFixtures.createReservation(1, 400, 'TAG-FIRST')
+ const firstRequest: OCPP16ReserveNowRequest = {
+ connectorId: firstReservation.connectorId,
+ expiryDate: firstReservation.expiryDate,
+ idTag: firstReservation.idTag,
+ reservationId: firstReservation.reservationId,
+ }
+
+ // Act — first reservation
+ const firstResponse = await testableService.handleRequestReserveNow(station, firstRequest)
+ assert.strictEqual(firstResponse.status, OCPP16ReservationStatus.ACCEPTED)
+
+ // Verify first reservation stored
+ const connectorAfterFirst = station.getConnectorStatus(1)
+ assert.strictEqual(connectorAfterFirst?.reservation?.reservationId, 400)
+
+ // Act — second reservation with different ID on same connector
+ const secondReservation = ReservationFixtures.createReservation(1, 401, 'TAG-SECOND')
+ const secondRequest: OCPP16ReserveNowRequest = {
+ connectorId: secondReservation.connectorId,
+ expiryDate: secondReservation.expiryDate,
+ idTag: secondReservation.idTag,
+ reservationId: secondReservation.reservationId,
+ }
+ const secondResponse = await testableService.handleRequestReserveNow(station, secondRequest)
+
+ // Assert — second reservation accepted, replaces first
+ assert.strictEqual(secondResponse.status, OCPP16ReservationStatus.ACCEPTED)
+ const connectorAfterSecond = station.getConnectorStatus(1)
+ if (connectorAfterSecond == null) {
+ assert.fail('Expected connector to be defined after second reservation')
+ }
+ if (connectorAfterSecond.reservation == null) {
+ assert.fail('Expected reservation to be defined after second reservation')
+ }
+ assert.strictEqual(connectorAfterSecond.reservation.reservationId, 401)
+ assert.strictEqual(connectorAfterSecond.reservation.idTag, 'TAG-SECOND')
+ })
+ })
+
+ // ===========================================================================
+ // Reserve → Cancel non-existent (wrong ID)
+ // ===========================================================================
+
+ await describe('ReserveNow → CancelReservation with wrong ID', async () => {
+ await it('should reject cancel with wrong ID and preserve original reservation', async () => {
+ // Arrange
+ const { station, testableService } = context
+ enableReservationProfile(context)
+ const reservation = ReservationFixtures.createReservation(1, 500, 'TAG-KEEP')
+ const reserveRequest: OCPP16ReserveNowRequest = {
+ connectorId: reservation.connectorId,
+ expiryDate: reservation.expiryDate,
+ idTag: reservation.idTag,
+ reservationId: reservation.reservationId,
+ }
+
+ // Act — create reservation
+ const reserveResponse = await testableService.handleRequestReserveNow(station, reserveRequest)
+ assert.strictEqual(reserveResponse.status, OCPP16ReservationStatus.ACCEPTED)
+
+ // Act — cancel with wrong reservation ID
+ const cancelRequest: OCPP16CancelReservationRequest = { reservationId: 999 }
+ const cancelResponse = await testableService.handleRequestCancelReservation(
+ station,
+ cancelRequest
+ )
+
+ // Assert — cancellation rejected
+ assert.strictEqual(cancelResponse.status, GenericStatus.Rejected)
+
+ // Assert — original reservation still intact
+ const connector = station.getConnectorStatus(1)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ if (connector.reservation == null) {
+ assert.fail('Expected reservation to be defined')
+ }
+ assert.strictEqual(connector.reservation.reservationId, 500)
+ assert.strictEqual(connector.reservation.idTag, 'TAG-KEEP')
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP 1.6 integration — Transaction lifecycle
+ * @module OCPP 1.6 — §5.11 RemoteStartTransaction, §5.12 RemoteStopTransaction,
+ * §5.14 StartTransaction (response), §5.16 StopTransaction (response)
+ * @description Multi-step integration tests crossing IncomingRequestService, RequestService,
+ * and ResponseService boundaries for the OCPP 1.6 transaction lifecycle.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type { TestableOCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+import type { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import type {
+ RemoteStartTransactionRequest,
+ RemoteStopTransactionRequest,
+} from '../../../../src/types/ocpp/1.6/Requests.js'
+import type {
+ OCPP16StartTransactionRequest,
+ OCPP16StartTransactionResponse,
+ OCPP16StopTransactionRequest,
+ OCPP16StopTransactionResponse,
+} from '../../../../src/types/ocpp/1.6/Transaction.js'
+
+import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+import { OCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.js'
+import { OCPP16ResponseService as OCPP16ResponseServiceClass } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import {
+ AvailabilityType,
+ GenericStatus,
+ OCPP16ChargePointStatus,
+ OCPP16MeterValueUnit,
+ OCPPVersion,
+} from '../../../../src/types/index.js'
+import { OCPP16RequestCommand } from '../../../../src/types/ocpp/1.6/Requests.js'
+import { OCPP16AuthorizationStatus } from '../../../../src/types/ocpp/1.6/Transaction.js'
+import { Constants } from '../../../../src/utils/index.js'
+import {
+ setupConnectorWithTransaction,
+ standardCleanup,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
+import { createMockChargingStation } from '../../ChargingStationTestUtils.js'
+
+/**
+ * Creates a shared station configured for cross-service integration tests,
+ * along with both IncomingRequest and Response service contexts.
+ * @returns Integration context with station, testable incoming request service, and response service
+ */
+function createIntegrationContext (): {
+ responseService: OCPP16ResponseService
+ station: ChargingStation
+ testableService: TestableOCPP16IncomingRequestService
+} {
+ const { station } = createMockChargingStation({
+ baseName: TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount: 2,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ ocppRequestService: {
+ requestHandler: () => Promise.resolve({}),
+ },
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+
+ // IncomingRequest service (handles RemoteStart/Stop from CSMS)
+ const incomingRequestService = new OCPP16IncomingRequestService()
+ const testableService = createTestableIncomingRequestService(incomingRequestService)
+
+ // Response service (handles StartTransaction/StopTransaction responses from CSMS)
+ const responseService = new OCPP16ResponseServiceClass()
+
+ // Mock meter value start/stop to avoid real timer setup
+ station.startMeterValues = (_connectorId: number, _interval: number) => {
+ /* noop */
+ }
+ station.stopMeterValues = (_connectorId: number) => {
+ /* noop */
+ }
+
+ // Add MeterValues template required by buildTransactionBeginMeterValue
+ for (const [connectorId] of station.connectors) {
+ if (connectorId > 0) {
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector != null) {
+ connector.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+ }
+ }
+ }
+
+ return { responseService, station, testableService }
+}
+
+await describe('OCPP16 Integration — Transaction Lifecycle', async () => {
+ let station: ChargingStation
+ let testableService: TestableOCPP16IncomingRequestService
+ let responseService: OCPP16ResponseService
+
+ beforeEach(() => {
+ const ctx = createIntegrationContext()
+ station = ctx.station
+ testableService = ctx.testableService
+ responseService = ctx.responseService
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ─── Happy path: RemoteStart → StartTransaction → StopTransaction ────
+
+ await it('should complete full transaction lifecycle: RemoteStart → StartTransaction accepted → StopTransaction', async () => {
+ const connectorId = 1
+ const transactionId = 42
+ const idTag = 'TEST-TAG-001'
+
+ // Step 1: RemoteStartTransaction — CSMS asks station to start charging
+ const remoteStartRequest: RemoteStartTransactionRequest = {
+ connectorId,
+ idTag,
+ }
+ const remoteStartResponse = await testableService.handleRequestRemoteStartTransaction(
+ station,
+ remoteStartRequest
+ )
+
+ assert.strictEqual(remoteStartResponse.status, GenericStatus.Accepted)
+
+ // Step 2: StartTransaction response — CSMS accepts the transaction
+ const startTxRequest: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag,
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const startTxResponse: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId,
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ startTxResponse,
+ startTxRequest
+ )
+
+ // Verify connector state after StartTransaction accepted
+ const connectorAfterStart = station.getConnectorStatus(connectorId)
+ if (connectorAfterStart == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorAfterStart.transactionStarted, true)
+ assert.strictEqual(connectorAfterStart.transactionId, transactionId)
+ assert.strictEqual(connectorAfterStart.transactionIdTag, idTag)
+
+ // Step 3: StopTransaction response — transaction ends
+ const stopTxRequest: OCPP16StopTransactionRequest = {
+ meterStop: 1000,
+ timestamp: new Date(),
+ transactionId,
+ }
+ const stopTxResponse: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ stopTxResponse,
+ stopTxRequest
+ )
+
+ // Verify connector state is reset after StopTransaction
+ const connectorAfterStop = station.getConnectorStatus(connectorId)
+ if (connectorAfterStop == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorAfterStop.transactionStarted, false)
+ assert.strictEqual(connectorAfterStop.transactionId, undefined)
+ assert.strictEqual(connectorAfterStop.transactionIdTag, undefined)
+ })
+
+ // ─── Remote stop path ────────────────────────────────────────────────
+
+ await it('should accept RemoteStopTransaction for an active transaction', () => {
+ const connectorId = 1
+ const transactionId = 100
+
+ // Arrange: set up an active transaction using lifecycle helper
+ setupConnectorWithTransaction(station, connectorId, { transactionId })
+
+ // Act: RemoteStopTransaction
+ const remoteStopRequest: RemoteStopTransactionRequest = {
+ transactionId,
+ }
+ const remoteStopResponse = testableService.handleRequestRemoteStopTransaction(
+ station,
+ remoteStopRequest
+ )
+
+ // Assert: remote stop is accepted
+ assert.strictEqual(remoteStopResponse.status, GenericStatus.Accepted)
+ })
+
+ await it('should reject RemoteStopTransaction for a non-existing transaction', () => {
+ // Act: RemoteStopTransaction with unknown transactionId
+ const remoteStopRequest: RemoteStopTransactionRequest = {
+ transactionId: 999,
+ }
+ const remoteStopResponse = testableService.handleRequestRemoteStopTransaction(
+ station,
+ remoteStopRequest
+ )
+
+ // Assert: remote stop is rejected
+ assert.strictEqual(remoteStopResponse.status, GenericStatus.Rejected)
+ })
+
+ // ─── Authorization failure path ──────────────────────────────────────
+
+ await it('should reject RemoteStartTransaction when connector is unavailable and verify no transaction started', async () => {
+ const connectorId = 1
+
+ // Arrange: make connector unavailable
+ const connectorStatus = station.getConnectorStatus(connectorId)
+ if (connectorStatus != null) {
+ connectorStatus.availability = AvailabilityType.Inoperative
+ }
+
+ // Act
+ const request: RemoteStartTransactionRequest = {
+ connectorId,
+ idTag: 'TEST-TAG-001',
+ }
+ const response = await testableService.handleRequestRemoteStartTransaction(station, request)
+
+ // Assert: rejected, no transaction started
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ if (connectorStatus == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorStatus.transactionStarted, false)
+ assert.strictEqual(connectorStatus.transactionId, undefined)
+ })
+
+ // ─── Transaction rejection path ──────────────────────────────────────
+
+ await it('should reset connector when StartTransaction response has Blocked status', async () => {
+ const connectorId = 1
+
+ // Arrange
+ const startTxRequest: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'BLOCKED-TAG',
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const startTxResponse: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.BLOCKED },
+ transactionId: 99,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ startTxResponse,
+ startTxRequest
+ )
+
+ // Assert: connector should be reset, no active transaction
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ })
+
+ // ─── State consistency ───────────────────────────────────────────────
+
+ await it('should return connector to Available status after full transaction cycle', async () => {
+ const connectorId = 1
+ const transactionId = 55
+ const idTag = 'TEST-TAG-002'
+
+ // Verify initial state
+ const connectorBefore = station.getConnectorStatus(connectorId)
+ assert.strictEqual(connectorBefore?.status, OCPP16ChargePointStatus.Available)
+
+ // Step 1: RemoteStart accepted
+ const remoteStartResponse = await testableService.handleRequestRemoteStartTransaction(station, {
+ connectorId,
+ idTag,
+ })
+ assert.strictEqual(remoteStartResponse.status, GenericStatus.Accepted)
+
+ // Step 2: StartTransaction accepted — connector moves to Charging
+ const startTxRequest: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag,
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const startTxResponse: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId,
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ startTxResponse,
+ startTxRequest
+ )
+
+ const connectorDuringTx = station.getConnectorStatus(connectorId)
+ if (connectorDuringTx == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorDuringTx.transactionStarted, true)
+ assert.strictEqual(connectorDuringTx.status, OCPP16ChargePointStatus.Charging)
+
+ // Step 3: StopTransaction — connector returns to Available
+ const stopTxRequest: OCPP16StopTransactionRequest = {
+ meterStop: 5000,
+ timestamp: new Date(),
+ transactionId,
+ }
+ const stopTxResponse: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ stopTxResponse,
+ stopTxRequest
+ )
+
+ // Verify: connector is back to Available with no active transaction
+ const connectorAfter = station.getConnectorStatus(connectorId)
+ if (connectorAfter == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorAfter.status, OCPP16ChargePointStatus.Available)
+ assert.strictEqual(connectorAfter.transactionStarted, false)
+ assert.strictEqual(connectorAfter.transactionId, undefined)
+ assert.strictEqual(connectorAfter.transactionIdTag, undefined)
+ })
+
+ // ─── Cross-service: RemoteStop with active transaction ───────────────
+
+ await it('should accept RemoteStop then complete StopTransaction lifecycle for active transaction', async () => {
+ const connectorId = 1
+ const transactionId = 200
+ const idTag = 'TEST-TAG-003'
+
+ // Arrange: start a full transaction via response service
+ const startTxRequest: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag,
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const startTxResponse: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId,
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ startTxResponse,
+ startTxRequest
+ )
+
+ // Verify transaction is active
+ const connectorDuring = station.getConnectorStatus(connectorId)
+ if (connectorDuring == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorDuring.transactionStarted, true)
+ assert.strictEqual(connectorDuring.transactionId, transactionId)
+
+ // Act: RemoteStopTransaction via incoming request service
+ const remoteStopResponse = testableService.handleRequestRemoteStopTransaction(station, {
+ transactionId,
+ })
+ assert.strictEqual(remoteStopResponse.status, GenericStatus.Accepted)
+
+ // Act: Complete the stop via response service
+ const stopTxRequest: OCPP16StopTransactionRequest = {
+ meterStop: 3000,
+ timestamp: new Date(),
+ transactionId,
+ }
+ const stopTxResponse: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ stopTxResponse,
+ stopTxRequest
+ )
+
+ // Assert: connector fully reset
+ const connectorAfter = station.getConnectorStatus(connectorId)
+ if (connectorAfter == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorAfter.transactionStarted, false)
+ assert.strictEqual(connectorAfter.transactionId, undefined)
+ assert.strictEqual(connectorAfter.status, OCPP16ChargePointStatus.Available)
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16RequestService buildRequestPayload
+ * @see OCPP 1.6 — §4.1 BootNotification, §4.2 Authorize, §4.9 DataTransfer,
+ * §4.8 StatusNotification, §4.10 Heartbeat, §4.3 StartTransaction, §4.4 StopTransaction,
+ * §4.7 MeterValues, §6.2 DiagnosticsStatusNotification, §6.5 FirmwareStatusNotification
+ * @description Unit tests for OCPP 1.6 request payload construction across all 10 request commands
+ */
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/index.js'
+import type { TestableOCPP16RequestService } from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+
+import {
+ OCPP16ChargePointErrorCode,
+ OCPP16ChargePointStatus,
+ type OCPP16DataTransferRequest,
+ OCPP16DiagnosticsStatus,
+ type OCPP16DiagnosticsStatusNotificationRequest,
+ OCPP16FirmwareStatus,
+ type OCPP16FirmwareStatusNotificationRequest,
+ type OCPP16MeterValuesRequest,
+ OCPP16MeterValueUnit,
+ OCPP16RequestCommand,
+ type OCPP16StartTransactionRequest,
+ type OCPP16StatusNotificationRequest,
+ type OCPP16StopTransactionRequest,
+} from '../../../../src/types/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import { createOCPP16RequestTestContext } from './OCPP16TestUtils.js'
+
+await describe('OCPP16RequestService — buildRequestPayload', async () => {
+ let testableRequestService: TestableOCPP16RequestService
+ let station: ChargingStation
+
+ beforeEach(() => {
+ const context = createOCPP16RequestTestContext()
+ testableRequestService = context.testableRequestService
+ station = context.station
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ---- AUTHORIZE ----
+ await describe('AUTHORIZE', async () => {
+ await it('should build Authorize payload with default idTag when none provided', () => {
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.AUTHORIZE,
+ {}
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual((payload as { idTag: string }).idTag, '00000000')
+ })
+
+ await it('should build Authorize payload with provided idTag overriding default', () => {
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.AUTHORIZE,
+ { idTag: 'MY-TAG-001' }
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual((payload as { idTag: string }).idTag, 'MY-TAG-001')
+ })
+ })
+
+ // ---- BOOT_NOTIFICATION ----
+ await it('should build BootNotification payload passing params through', () => {
+ const params = {
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ firmwareVersion: '1.0.0',
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.BOOT_NOTIFICATION,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.deepStrictEqual(payload, params)
+ })
+
+ // ---- DATA_TRANSFER ----
+ await it('should build DataTransfer payload passing params through', () => {
+ const params: OCPP16DataTransferRequest = {
+ data: 'test-data',
+ vendorId: 'TestVendor',
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.DATA_TRANSFER,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual((payload as OCPP16DataTransferRequest).vendorId, 'TestVendor')
+ assert.strictEqual((payload as OCPP16DataTransferRequest).data, 'test-data')
+ })
+
+ // ---- DIAGNOSTICS_STATUS_NOTIFICATION ----
+ await it('should build DiagnosticsStatusNotification payload passing params through', () => {
+ const params: OCPP16DiagnosticsStatusNotificationRequest = {
+ status: OCPP16DiagnosticsStatus.Uploading,
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(
+ (payload as OCPP16DiagnosticsStatusNotificationRequest).status,
+ OCPP16DiagnosticsStatus.Uploading
+ )
+ })
+
+ // ---- FIRMWARE_STATUS_NOTIFICATION ----
+ await it('should build FirmwareStatusNotification payload passing params through', () => {
+ const params: OCPP16FirmwareStatusNotificationRequest = {
+ status: OCPP16FirmwareStatus.Downloaded,
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(
+ (payload as OCPP16FirmwareStatusNotificationRequest).status,
+ OCPP16FirmwareStatus.Downloaded
+ )
+ })
+
+ // ---- HEARTBEAT ----
+ await it('should build Heartbeat payload as empty object', () => {
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.HEARTBEAT
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(Object.keys(payload as object).length, 0)
+ })
+
+ // ---- METER_VALUES ----
+ await it('should build MeterValues payload passing params through', () => {
+ const params: OCPP16MeterValuesRequest = {
+ connectorId: 1,
+ meterValue: [{ sampledValue: [{ value: '1000' }], timestamp: new Date() }],
+ transactionId: 1,
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.METER_VALUES,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual((payload as OCPP16MeterValuesRequest).connectorId, 1)
+ assert.strictEqual((payload as OCPP16MeterValuesRequest).transactionId, 1)
+ })
+
+ // ---- START_TRANSACTION ----
+ await describe('START_TRANSACTION', async () => {
+ await it('should build StartTransaction payload with meterStart and timestamp', () => {
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 0
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ { connectorId: 1, idTag: 'TEST-TAG-001' }
+ ) as OCPP16StartTransactionRequest
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.connectorId, 1)
+ assert.strictEqual(payload.idTag, 'TEST-TAG-001')
+ assert.strictEqual(typeof payload.meterStart, 'number')
+ assert.notStrictEqual(payload.timestamp, undefined)
+ })
+
+ await it('should build StartTransaction payload with meterStart from connector energy reading', () => {
+ // Arrange — set energy register value on connector 1
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 1500
+ }
+
+ // Act
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ { connectorId: 1, idTag: 'ENERGY-TAG' }
+ ) as OCPP16StartTransactionRequest
+
+ // Assert
+ assert.strictEqual(payload.meterStart, 1500)
+ assert.strictEqual(payload.idTag, 'ENERGY-TAG')
+ })
+ })
+
+ // ---- STATUS_NOTIFICATION ----
+ await it('should build StatusNotification payload passing params through', () => {
+ const params: OCPP16StatusNotificationRequest = {
+ connectorId: 1,
+ errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
+ status: OCPP16ChargePointStatus.Available,
+ }
+
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ params
+ )
+
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual((payload as OCPP16StatusNotificationRequest).connectorId, 1)
+ assert.strictEqual(
+ (payload as OCPP16StatusNotificationRequest).errorCode,
+ OCPP16ChargePointErrorCode.NO_ERROR
+ )
+ assert.strictEqual(
+ (payload as OCPP16StatusNotificationRequest).status,
+ OCPP16ChargePointStatus.Available
+ )
+ })
+
+ // ---- STOP_TRANSACTION ----
+ await describe('STOP_TRANSACTION', async () => {
+ await it('should build StopTransaction payload with meterStop, timestamp, and idTag from transaction', () => {
+ // Arrange — set up an active transaction on connector 1
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.transactionId = 42
+ connectorStatus.transactionIdTag = 'STOP-TAG-001'
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 5000
+ }
+
+ // Act
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ { transactionId: 42 }
+ ) as OCPP16StopTransactionRequest
+
+ // Assert
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.transactionId, 42)
+ assert.strictEqual(payload.meterStop, 5000)
+ assert.strictEqual(payload.idTag, 'STOP-TAG-001')
+ assert.notStrictEqual(payload.timestamp, undefined)
+ })
+
+ await it('should build StopTransaction payload with transactionData when enabled', () => {
+ // Arrange — enable transactionDataMeterValues and set up transaction with MeterValues template
+ if (station.stationInfo != null) {
+ station.stationInfo.transactionDataMeterValues = true
+ }
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.transactionId = 99
+ connectorStatus.transactionIdTag = 'DATA-TAG'
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 3000
+ connectorStatus.transactionBeginMeterValue = {
+ sampledValue: [{ value: '0' }],
+ timestamp: new Date(),
+ }
+ connectorStatus.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+ }
+
+ // Act
+ const payload = testableRequestService.buildRequestPayload(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ { transactionId: 99 }
+ ) as OCPP16StopTransactionRequest
+
+ // Assert
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.transactionId, 99)
+ assert.strictEqual(payload.meterStop, 3000)
+ assert.notStrictEqual(payload.transactionData, undefined)
+ assert.strictEqual(Array.isArray(payload.transactionData), true)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16ResponseService BootNotification and Authorize response handlers
+ * @description Verifies correct handling of BootNotification (§4.1) and Authorize (§4.2) responses
+ *
+ * Covers:
+ * - §4.1 BootNotificationResponse: Accepted/Pending/Rejected status handling
+ * - §4.1 HeartbeatInterval configuration key update from response interval
+ * - §4.2 AuthorizeResponse: Accepted idTagInfo sets idTagAuthorized=true
+ * - §4.2 AuthorizeResponse: Non-accepted idTagInfo sets idTagAuthorized=false
+ * - §4.2 AuthorizeResponse: No matching connector leaves state unchanged
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
+
+import type {
+ OCPP16AuthorizeRequest,
+ OCPP16AuthorizeResponse,
+} from '../../../../src/types/ocpp/1.6/Transaction.js'
+
+import {
+ ChargingStationEvents,
+ OCPP16AuthorizationStatus,
+ type OCPP16BootNotificationResponse,
+ OCPP16RequestCommand,
+ OCPP16StandardParametersKey,
+ RegistrationStatusEnumType,
+} from '../../../../src/types/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import {
+ createOCPP16ResponseTestContext,
+ dispatchResponse,
+ type OCPP16ResponseTestContext,
+} from './OCPP16TestUtils.js'
+
+await describe('OCPP16ResponseService — BootNotification and Authorize', async () => {
+ let ctx: OCPP16ResponseTestContext
+
+ beforeEach(() => {
+ mock.timers.enable({ apis: ['setInterval', 'setTimeout'] })
+ ctx = createOCPP16ResponseTestContext()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ /**
+ * Helper to dispatch a BootNotificationResponse through the public responseHandler.
+ * @param payload - The BootNotificationResponse payload to dispatch
+ */
+ async function dispatchBootNotification (payload: OCPP16BootNotificationResponse): Promise<void> {
+ await dispatchResponse(
+ ctx.responseService,
+ ctx.station,
+ OCPP16RequestCommand.BOOT_NOTIFICATION,
+ payload
+ )
+ }
+
+ /**
+ * Helper to dispatch an AuthorizeResponse through the public responseHandler.
+ * @param payload - The AuthorizeResponse payload to dispatch
+ * @param requestPayload - The original AuthorizeRequest (contains idTag)
+ */
+ async function dispatchAuthorize (
+ payload: OCPP16AuthorizeResponse,
+ requestPayload: OCPP16AuthorizeRequest
+ ): Promise<void> {
+ await dispatchResponse(
+ ctx.responseService,
+ ctx.station,
+ OCPP16RequestCommand.AUTHORIZE,
+ payload,
+ requestPayload
+ )
+ }
+
+ // ============================================================================
+ // §4.1 — BootNotification Response
+ // ============================================================================
+
+ // @spec §4.1 — TC_001_CS
+ await it('should store response and emit accepted event for Accepted status', async () => {
+ const emitSpy = mock.method(ctx.station, 'emitChargingStationEvent')
+ const payload: OCPP16BootNotificationResponse = {
+ currentTime: new Date(),
+ interval: 60,
+ status: RegistrationStatusEnumType.ACCEPTED,
+ }
+
+ await dispatchBootNotification(payload)
+
+ assert.strictEqual(ctx.station.bootNotificationResponse, payload)
+ assert.strictEqual(ctx.station.inAcceptedState(), true)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.accepted)
+ })
+
+ // @spec §4.1 — TC_002_CS
+ await it('should store response and emit pending event for Pending status', async () => {
+ const emitSpy = mock.method(ctx.station, 'emitChargingStationEvent')
+ const payload: OCPP16BootNotificationResponse = {
+ currentTime: new Date(),
+ interval: 30,
+ status: RegistrationStatusEnumType.PENDING,
+ }
+
+ await dispatchBootNotification(payload)
+
+ assert.strictEqual(ctx.station.bootNotificationResponse, payload)
+ assert.strictEqual(ctx.station.inPendingState(), true)
+ assert.strictEqual(ctx.station.inAcceptedState(), false)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.pending)
+ })
+
+ await it('should store response and emit rejected event for Rejected status', async () => {
+ const emitSpy = mock.method(ctx.station, 'emitChargingStationEvent')
+ const payload: OCPP16BootNotificationResponse = {
+ currentTime: new Date(),
+ interval: 0,
+ status: RegistrationStatusEnumType.REJECTED,
+ }
+
+ await dispatchBootNotification(payload)
+
+ assert.strictEqual(ctx.station.bootNotificationResponse, payload)
+ assert.strictEqual(ctx.station.inRejectedState(), true)
+ assert.strictEqual(ctx.station.inAcceptedState(), false)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.rejected)
+ })
+
+ await it('should update HeartbeatInterval configuration key from response interval', async () => {
+ const payload: OCPP16BootNotificationResponse = {
+ currentTime: new Date(),
+ interval: 120,
+ status: RegistrationStatusEnumType.ACCEPTED,
+ }
+
+ await dispatchBootNotification(payload)
+
+ const configKeys = ctx.station.ocppConfiguration?.configurationKey
+ assert.notStrictEqual(configKeys, undefined)
+
+ const heartbeatKey = configKeys?.find(
+ k => k.key === (OCPP16StandardParametersKey.HeartbeatInterval as string)
+ )
+ assert.notStrictEqual(heartbeatKey, undefined)
+ assert.strictEqual(heartbeatKey?.value, '120')
+
+ // Handler also sets the variant HeartBeatInterval (hidden)
+ const heartBeatKey = configKeys?.find(
+ k => k.key === (OCPP16StandardParametersKey.HeartBeatInterval as string)
+ )
+ assert.notStrictEqual(heartBeatKey, undefined)
+ assert.strictEqual(heartBeatKey?.value, '120')
+ })
+
+ // ============================================================================
+ // §4.2 — Authorize Response
+ // ============================================================================
+
+ // @spec §4.2 — TC_003_CS
+ await it('should set idTagAuthorized to true when idTagInfo status is Accepted', async () => {
+ // Arrange — set authorizeIdTag on connector 1
+ const connectorStatus = ctx.station.getConnectorStatus(1)
+ if (connectorStatus == null) {
+ assert.fail('Expected connector status to be defined')
+ }
+ connectorStatus.authorizeIdTag = 'TEST_TAG'
+
+ // Act
+ await dispatchAuthorize(
+ { idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } },
+ { idTag: 'TEST_TAG' }
+ )
+
+ // Assert
+ assert.strictEqual(connectorStatus.idTagAuthorized, true)
+ assert.strictEqual(connectorStatus.authorizeIdTag, 'TEST_TAG')
+ })
+
+ // @spec §4.2 — TC_010_CS
+ await it('should set idTagAuthorized to false and clear authorizeIdTag for non-Accepted status', async () => {
+ // Arrange — set authorizeIdTag on connector 1
+ const connectorStatus = ctx.station.getConnectorStatus(1)
+ if (connectorStatus == null) {
+ assert.fail('Expected connector status to be defined')
+ }
+ connectorStatus.authorizeIdTag = 'TEST_TAG'
+
+ // Act — Blocked status
+ await dispatchAuthorize(
+ { idTagInfo: { status: OCPP16AuthorizationStatus.BLOCKED } },
+ { idTag: 'TEST_TAG' }
+ )
+
+ // Assert
+ assert.strictEqual(connectorStatus.idTagAuthorized, false)
+ assert.strictEqual(connectorStatus.authorizeIdTag, undefined)
+ })
+
+ await it('should not change connector state when no connector matches authorizeIdTag', async () => {
+ // Arrange — connector 1 has no authorizeIdTag matching the request
+ const connectorStatus = ctx.station.getConnectorStatus(1)
+ assert.notStrictEqual(connectorStatus, undefined)
+ const originalIdTagAuthorized = connectorStatus?.idTagAuthorized
+
+ // Act — no connector has authorizeIdTag === 'UNKNOWN_TAG'
+ await dispatchAuthorize(
+ { idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED } },
+ { idTag: 'UNKNOWN_TAG' }
+ )
+
+ // Assert — no state change
+ assert.strictEqual(connectorStatus?.idTagAuthorized, originalIdTagAuthorized)
+ assert.strictEqual(connectorStatus?.authorizeIdTag, undefined)
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16ResponseService simple response handlers
+ * @module OCPP 1.6 — §4.9 DataTransfer, §6.2 DiagnosticsStatusNotification,
+ * §6.5 FirmwareStatusNotification, §4.10 Heartbeat, §4.7 MeterValues, §4.8 StatusNotification
+ * @description Verifies DataTransfer, DiagnosticsStatusNotification, FirmwareStatusNotification,
+ * Heartbeat, MeterValues, and StatusNotification response handling
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { MockChargingStation } from '../../ChargingStationTestUtils.js'
+
+import { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import {
+ type OCPP16DataTransferResponse,
+ OCPP16DataTransferStatus,
+ type OCPP16DiagnosticsStatusNotificationResponse,
+ type OCPP16FirmwareStatusNotificationResponse,
+ type OCPP16HeartbeatResponse,
+ type OCPP16MeterValuesResponse,
+ OCPP16RequestCommand,
+ type OCPP16StatusNotificationResponse,
+ OCPPVersion,
+} from '../../../../src/types/index.js'
+import { Constants } from '../../../../src/utils/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
+import { createMockChargingStation } from '../../ChargingStationTestUtils.js'
+import { dispatchResponse } from './OCPP16TestUtils.js'
+
+/**
+ * Create a mock station suitable for simple response handler tests.
+ * Uses ocppStrictCompliance: false to bypass AJV validation.
+ * @returns A mock station configured for simple handler tests
+ */
+function createSimpleHandlerStation (): MockChargingStation {
+ const { station } = createMockChargingStation({
+ baseName: TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount: 1,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+ return station as MockChargingStation
+}
+
+await describe('OCPP16ResponseService — SimpleHandlers', async () => {
+ let responseService: OCPP16ResponseService
+ let mockStation: MockChargingStation
+
+ beforeEach(() => {
+ responseService = new OCPP16ResponseService()
+ mockStation = createSimpleHandlerStation()
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await describe('DataTransfer response handler', async () => {
+ await it('should handle DataTransfer response without throwing', async () => {
+ const payload: OCPP16DataTransferResponse = { status: OCPP16DataTransferStatus.ACCEPTED }
+ await assert.doesNotReject(
+ dispatchResponse(responseService, mockStation, OCPP16RequestCommand.DATA_TRANSFER, payload)
+ )
+ })
+ })
+
+ await describe('DiagnosticsStatusNotification response handler', async () => {
+ await it('should handle DiagnosticsStatusNotification response without throwing', async () => {
+ const payload: OCPP16DiagnosticsStatusNotificationResponse = {}
+ await assert.doesNotReject(
+ dispatchResponse(
+ responseService,
+ mockStation,
+ OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
+ payload
+ )
+ )
+ })
+ })
+
+ await describe('FirmwareStatusNotification response handler', async () => {
+ await it('should handle FirmwareStatusNotification response without throwing', async () => {
+ const payload: OCPP16FirmwareStatusNotificationResponse = {}
+ await assert.doesNotReject(
+ dispatchResponse(
+ responseService,
+ mockStation,
+ OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
+ payload
+ )
+ )
+ })
+ })
+
+ await describe('Heartbeat response handler', async () => {
+ await it('should handle Heartbeat response without throwing', async () => {
+ const payload: OCPP16HeartbeatResponse = { currentTime: new Date() }
+ await assert.doesNotReject(
+ dispatchResponse(responseService, mockStation, OCPP16RequestCommand.HEARTBEAT, payload)
+ )
+ })
+ })
+
+ await describe('MeterValues response handler', async () => {
+ await it('should handle MeterValues response without throwing', async () => {
+ const payload: OCPP16MeterValuesResponse = {}
+ await assert.doesNotReject(
+ dispatchResponse(responseService, mockStation, OCPP16RequestCommand.METER_VALUES, payload)
+ )
+ })
+ })
+
+ await describe('StatusNotification response handler', async () => {
+ await it('should handle StatusNotification response without throwing', async () => {
+ const payload: OCPP16StatusNotificationResponse = {}
+ await assert.doesNotReject(
+ dispatchResponse(
+ responseService,
+ mockStation,
+ OCPP16RequestCommand.STATUS_NOTIFICATION,
+ payload
+ )
+ )
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16ResponseService — StartTransaction and StopTransaction
+ * @description Verifies the StartTransaction (§5.14) and StopTransaction (§5.16)
+ * response handlers for OCPP 1.6, covering accepted/rejected authorization flows,
+ * reservation handling, connector state mutations, and transaction lifecycle.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, beforeEach, describe, it } from 'node:test'
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import type {
+ OCPP16StartTransactionRequest,
+ OCPP16StartTransactionResponse,
+ OCPP16StopTransactionRequest,
+ OCPP16StopTransactionResponse,
+} from '../../../../src/types/ocpp/1.6/Transaction.js'
+
+import { OCPP16MeterValueUnit } from '../../../../src/types/index.js'
+import { OCPP16RequestCommand } from '../../../../src/types/ocpp/1.6/Requests.js'
+import { OCPP16AuthorizationStatus } from '../../../../src/types/ocpp/1.6/Transaction.js'
+import {
+ setupConnectorWithTransaction,
+ standardCleanup,
+} from '../../../helpers/TestLifecycleHelpers.js'
+import { createOCPP16ResponseTestContext, setMockRequestHandler } from './OCPP16TestUtils.js'
+
+await describe('OCPP16ResponseService — StartTransaction and StopTransaction', async () => {
+ let station: ChargingStation
+ let responseService: OCPP16ResponseService
+
+ beforeEach(() => {
+ const ctx = createOCPP16ResponseTestContext()
+ station = ctx.station
+ responseService = ctx.responseService
+
+ // Mock requestHandler so OCPP requests (StatusNotification, MeterValues) resolve
+ setMockRequestHandler(station, async () => Promise.resolve({}))
+
+ // Mock startMeterValues/stopMeterValues to avoid real timer setup
+ station.startMeterValues = (_connectorId: number, _interval: number) => {
+ /* noop */
+ }
+ station.stopMeterValues = (_connectorId: number) => {
+ /* noop */
+ }
+
+ // Add MeterValues template required by buildTransactionBeginMeterValue
+ for (const [connectorId] of station.connectors) {
+ if (connectorId > 0) {
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector != null) {
+ connector.MeterValues = [{ unit: OCPP16MeterValueUnit.WATT_HOUR, value: '0' }]
+ }
+ }
+ }
+ })
+
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ─── handleResponseStartTransaction (§5.14) ──────────────────────────
+
+ await describe('handleResponseStartTransaction', async () => {
+ // @spec §5.14 — TC_003_CS
+ await it('should store transactionId on connector when idTagInfo is Accepted', async () => {
+ // Arrange
+ const connectorId = 1
+ const transactionId = 42
+ const requestPayload: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'TEST-TAG-001',
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const responsePayload: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionId, transactionId)
+ assert.strictEqual(connector.transactionStarted, true)
+ assert.strictEqual(connector.transactionIdTag, 'TEST-TAG-001')
+ assert.strictEqual(connector.transactionEnergyActiveImportRegisterValue, 0)
+ })
+
+ // @spec §5.14 — TC_004_CS
+ await it('should reset connector when idTagInfo is not Accepted', async () => {
+ // Arrange
+ const connectorId = 1
+ const requestPayload: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'TEST-TAG-001',
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const responsePayload: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.BLOCKED },
+ transactionId: 99,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert — connector should be reset (no transactionId)
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ })
+
+ // @spec §5.14 — TC_010_CS
+ await it('should clear reservation after accepted start with reservationId', async () => {
+ // Arrange
+ const connectorId = 1
+ const reservationId = 5
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector != null) {
+ connector.reservation = {
+ connectorId,
+ expiryDate: new Date(Date.now() + 3600000),
+ idTag: 'TEST-TAG-001',
+ reservationId,
+ }
+ }
+ const requestPayload: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'TEST-TAG-001',
+ meterStart: 0,
+ reservationId,
+ timestamp: new Date(),
+ }
+ const responsePayload: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId: 100,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert — reservation should be cleared
+ const connectorAfter = station.getConnectorStatus(connectorId)
+ if (connectorAfter == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connectorAfter.reservation, undefined)
+ assert.strictEqual(connectorAfter.transactionId, 100)
+ assert.strictEqual(connectorAfter.transactionStarted, true)
+ })
+
+ await it('should set transactionStarted and transactionStart on Accepted response', async () => {
+ // Arrange
+ const connectorId = 1
+ const requestTimestamp = new Date('2025-01-01T12:00:00Z')
+ const requestPayload: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'TEST-TAG-001',
+ meterStart: 500,
+ timestamp: requestTimestamp,
+ }
+ const responsePayload: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ transactionId: 7,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, true)
+ assert.deepStrictEqual(connector.transactionStart, requestTimestamp)
+ })
+
+ await it('should reset connector on rejected with Invalid status', async () => {
+ // Arrange
+ const connectorId = 1
+ const requestPayload: OCPP16StartTransactionRequest = {
+ connectorId,
+ idTag: 'INVALID-TAG',
+ meterStart: 0,
+ timestamp: new Date(),
+ }
+ const responsePayload: OCPP16StartTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.INVALID },
+ transactionId: 55,
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.START_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert — connector should be reset
+ const connector = station.getConnectorStatus(connectorId)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ assert.strictEqual(connector.transactionIdTag, undefined)
+ })
+ })
+
+ // ─── handleResponseStopTransaction (§5.16) ───────────────────────────
+
+ await describe('handleResponseStopTransaction', async () => {
+ // @spec §5.16 — TC_068_CS
+ await it('should reset connector and log when idTagInfo is present', async () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, { transactionId: 200 })
+ const requestPayload: OCPP16StopTransactionRequest = {
+ meterStop: 1000,
+ timestamp: new Date(),
+ transactionId: 200,
+ }
+ const responsePayload: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert — connector should be reset after stop
+ const connector = station.getConnectorStatus(1)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ })
+
+ // @spec §5.16 — TC_072_CS
+ await it('should reset connector without error when idTagInfo is absent', async () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, { transactionId: 300 })
+ const requestPayload: OCPP16StopTransactionRequest = {
+ meterStop: 2000,
+ timestamp: new Date(),
+ transactionId: 300,
+ }
+ const responsePayload: OCPP16StopTransactionResponse = {}
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert — connector should still be reset
+ const connector = station.getConnectorStatus(1)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ })
+
+ await it('should clear transactionIdTag and energy register after stop', async () => {
+ // Arrange
+ setupConnectorWithTransaction(station, 1, {
+ energyImport: 5000,
+ idTag: 'MY-TAG',
+ transactionId: 400,
+ })
+ const requestPayload: OCPP16StopTransactionRequest = {
+ meterStop: 5000,
+ timestamp: new Date(),
+ transactionId: 400,
+ }
+ const responsePayload: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ // Act
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+
+ // Assert
+ const connector = station.getConnectorStatus(1)
+ if (connector == null) {
+ assert.fail('Expected connector to be defined')
+ }
+ assert.strictEqual(connector.transactionStarted, false)
+ assert.strictEqual(connector.transactionId, undefined)
+ assert.strictEqual(connector.transactionIdTag, undefined)
+ assert.strictEqual(connector.transactionEnergyActiveImportRegisterValue, 0)
+ assert.strictEqual(connector.transactionRemoteStarted, false)
+ })
+
+ await it('should not throw when transactionId does not match any connector', async () => {
+ // Arrange — no active transaction on any connector
+ const requestPayload: OCPP16StopTransactionRequest = {
+ meterStop: 0,
+ timestamp: new Date(),
+ transactionId: 99999,
+ }
+ const responsePayload: OCPP16StopTransactionResponse = {
+ idTagInfo: { status: OCPP16AuthorizationStatus.ACCEPTED },
+ }
+
+ // Act & Assert — should not throw, just log error and return
+ await responseService.responseHandler(
+ station,
+ OCPP16RequestCommand.STOP_TRANSACTION,
+ responsePayload,
+ requestPayload
+ )
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP 1.6 JSON schema validation
+ * @module OCPP 1.6 — §4.1 BootNotification, §5.11 RemoteStartTransaction, §9.3 SetChargingProfile,
+ * §5.13 Reset, §5.3 ChangeAvailability (representative schema coverage)
+ * @description Verifies that OCPP 1.6 JSON schemas correctly validate and reject payloads
+ * when compiled with AJV. Tests representative command pairs (request + response schemas).
+ */
+
+import _Ajv, { type ValidateFunction } from 'ajv'
+import _ajvFormats from 'ajv-formats'
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { join } from 'node:path'
+import { afterEach, describe, it } from 'node:test'
+import { fileURLToPath } from 'node:url'
+
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+
+const AjvConstructor = _Ajv.default
+const ajvFormats = _ajvFormats.default
+
+/** Absolute path to OCPP 1.6 JSON schemas, resolved relative to this test file. */
+const SCHEMA_DIR = join(
+ fileURLToPath(new URL('.', import.meta.url)),
+ '../../../../src/assets/json-schemas/ocpp/1.6'
+)
+
+/**
+ * Load a schema from the OCPP 1.6 schema directory and return parsed JSON.
+ * @param filename - Schema filename (e.g. 'BootNotification.json')
+ * @returns Parsed JSON schema object
+ */
+function loadSchema (filename: string): Record<string, unknown> {
+ return JSON.parse(readFileSync(join(SCHEMA_DIR, filename), 'utf8')) as Record<string, unknown>
+}
+
+/**
+ * Create an AJV validator for the given schema file.
+ * @param schemaFile - Schema filename (e.g. 'BootNotification.json')
+ * @returns Compiled AJV validate function
+ */
+function makeValidator (schemaFile: string): ValidateFunction {
+ const ajv = new AjvConstructor({ keywords: ['javaType'], multipleOfPrecision: 2, strict: false })
+ ajvFormats(ajv)
+ return ajv.compile(loadSchema(schemaFile))
+}
+
+await describe('OCPP16SchemaValidation', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await describe('BootNotification schema validation', async () => {
+ await it('should compile BootNotification request schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('BootNotification.json')
+ })
+ })
+
+ await it('should compile BootNotificationResponse schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('BootNotificationResponse.json')
+ })
+ })
+
+ await it('should fail validation when BootNotification is missing required chargePointModel', () => {
+ const validate = makeValidator('BootNotification.json')
+ assert.strictEqual(validate({ chargePointVendor: 'TestVendor' }), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasMissingProp = validate.errors?.some(
+ e =>
+ e.keyword === 'required' &&
+ (e.params as { missingProperty?: string }).missingProperty === 'chargePointModel'
+ )
+ assert.strictEqual(hasMissingProp, true)
+ })
+
+ await it('should pass validation when BootNotification has required fields', () => {
+ const validate = makeValidator('BootNotification.json')
+ const valid = validate({
+ chargePointModel: 'TestModel',
+ chargePointVendor: 'TestVendor',
+ })
+ assert.strictEqual(valid, true)
+ })
+
+ await it('should pass validation when BootNotificationResponse has valid status', () => {
+ const validate = makeValidator('BootNotificationResponse.json')
+ const valid = validate({
+ currentTime: '2025-03-10T12:00:00Z',
+ interval: 60,
+ status: 'Accepted',
+ })
+ assert.strictEqual(valid, true)
+ })
+ })
+
+ await describe('Authorize schema validation', async () => {
+ await it('should compile Authorize request schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('Authorize.json')
+ })
+ })
+
+ await it('should compile AuthorizeResponse schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('AuthorizeResponse.json')
+ })
+ })
+
+ await it('should fail validation when Authorize is missing required idTag', () => {
+ const validate = makeValidator('Authorize.json')
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasMissingProp = validate.errors?.some(
+ e =>
+ e.keyword === 'required' &&
+ (e.params as { missingProperty?: string }).missingProperty === 'idTag'
+ )
+ assert.strictEqual(hasMissingProp, true)
+ })
+
+ await it('should pass validation when Authorize has valid idTag', () => {
+ const validate = makeValidator('Authorize.json')
+ const valid = validate({ idTag: 'TEST-TAG-001' })
+ assert.strictEqual(valid, true)
+ })
+
+ await it('should pass validation when AuthorizeResponse has valid status', () => {
+ const validate = makeValidator('AuthorizeResponse.json')
+ const valid = validate({ idTagInfo: { status: 'Accepted' } })
+ assert.strictEqual(valid, true)
+ })
+ })
+
+ await describe('StartTransaction schema validation', async () => {
+ await it('should compile StartTransaction request schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('StartTransaction.json')
+ })
+ })
+
+ await it('should compile StartTransactionResponse schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('StartTransactionResponse.json')
+ })
+ })
+
+ await it('should fail validation when StartTransaction is missing required connectorId', () => {
+ const validate = makeValidator('StartTransaction.json')
+ assert.strictEqual(validate({ idTag: 'TEST-TAG', timestamp: '2025-03-10T12:00:00Z' }), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasMissingProp = validate.errors?.some(
+ e =>
+ e.keyword === 'required' &&
+ (e.params as { missingProperty?: string }).missingProperty === 'connectorId'
+ )
+ assert.strictEqual(hasMissingProp, true)
+ })
+
+ await it('should pass validation when StartTransaction has all required fields', () => {
+ const validate = makeValidator('StartTransaction.json')
+ const valid = validate({
+ connectorId: 1,
+ idTag: 'TEST-TAG-001',
+ meterStart: 0,
+ timestamp: '2025-03-10T12:00:00Z',
+ })
+ assert.strictEqual(valid, true)
+ })
+
+ await it('should pass validation when StartTransactionResponse has valid transactionId', () => {
+ const validate = makeValidator('StartTransactionResponse.json')
+ const valid = validate({
+ idTagInfo: { status: 'Accepted' },
+ transactionId: 123,
+ })
+ assert.strictEqual(valid, true)
+ })
+ })
+
+ await describe('Reset schema validation', async () => {
+ await it('should compile Reset request schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('Reset.json')
+ })
+ })
+
+ await it('should compile ResetResponse schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('ResetResponse.json')
+ })
+ })
+
+ await it('should fail validation when Reset is missing required type', () => {
+ const validate = makeValidator('Reset.json')
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasMissingProp = validate.errors?.some(
+ e =>
+ e.keyword === 'required' &&
+ (e.params as { missingProperty?: string }).missingProperty === 'type'
+ )
+ assert.strictEqual(hasMissingProp, true)
+ })
+
+ await it('should fail validation when Reset has invalid type enum value', () => {
+ const validate = makeValidator('Reset.json')
+ assert.strictEqual(validate({ type: 'InvalidType' }), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasEnumError = validate.errors?.some(e => e.keyword === 'enum')
+ assert.strictEqual(hasEnumError, true)
+ })
+
+ await it('should pass validation when Reset has valid type', () => {
+ const validate = makeValidator('Reset.json')
+ const valid = validate({ type: 'Hard' })
+ assert.strictEqual(valid, true)
+ })
+
+ await it('should pass validation when ResetResponse has valid status', () => {
+ const validate = makeValidator('ResetResponse.json')
+ const valid = validate({ status: 'Accepted' })
+ assert.strictEqual(valid, true)
+ })
+ })
+
+ await describe('SetChargingProfile schema validation', async () => {
+ await it('should compile SetChargingProfile request schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('SetChargingProfile.json')
+ })
+ })
+
+ await it('should compile SetChargingProfileResponse schema without error', () => {
+ assert.doesNotThrow(() => {
+ makeValidator('SetChargingProfileResponse.json')
+ })
+ })
+
+ await it('should fail validation when SetChargingProfile is missing required connectorId', () => {
+ const validate = makeValidator('SetChargingProfile.json')
+ assert.strictEqual(validate({ csChargingProfiles: {} }), false)
+ assert.notStrictEqual(validate.errors, undefined)
+ const hasMissingProp = validate.errors?.some(
+ e =>
+ e.keyword === 'required' &&
+ (e.params as { missingProperty?: string }).missingProperty === 'connectorId'
+ )
+ assert.strictEqual(hasMissingProp, true)
+ })
+
+ await it('should pass validation when SetChargingProfile has valid structure', () => {
+ const validate = makeValidator('SetChargingProfile.json')
+ const valid = validate({
+ connectorId: 1,
+ csChargingProfiles: {
+ chargingProfileId: 1,
+ chargingProfileKind: 'Absolute',
+ chargingProfilePurpose: 'ChargePointMaxProfile',
+ chargingSchedule: {
+ chargingRateUnit: 'A',
+ chargingSchedulePeriod: [{ limit: 32, startPeriod: 0 }],
+ },
+ stackLevel: 0,
+ },
+ })
+ assert.strictEqual(valid, true)
+ })
+
+ await it('should pass validation when SetChargingProfileResponse has valid status', () => {
+ const validate = makeValidator('SetChargingProfileResponse.json')
+ const valid = validate({ status: 'Accepted' })
+ assert.strictEqual(valid, true)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file Tests for OCPP16ServiceUtils pure utility functions
+ * @module OCPP 1.6 — §4.7 MeterValues (meter value building), §9.3 SetChargingProfile
+ * (charging profile management), §3 ChargePoint status (connector status transitions)
+ * @description Verifies pure static methods on OCPP16ServiceUtils: meter value building,
+ * charging profile management, feature profile checking, and command support checks.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js'
+import { OCPPServiceUtils } from '../../../../src/charging-station/ocpp/OCPPServiceUtils.js'
+import {
+ type OCPP16ChargingProfile,
+ OCPP16ChargingProfilePurposeType,
+ OCPP16ChargingRateUnitType,
+ type OCPP16ChargingSchedule,
+ type OCPP16ClearChargingProfileRequest,
+ OCPP16IncomingRequestCommand,
+ type OCPP16MeterValue,
+ OCPP16MeterValueContext,
+ OCPP16MeterValueMeasurand,
+ OCPP16MeterValueUnit,
+ OCPP16RequestCommand,
+ OCPP16StandardParametersKey,
+ OCPP16SupportedFeatureProfiles,
+ OCPPVersion,
+} from '../../../../src/types/index.js'
+import { OCPP16ChargingProfileKindType } from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+import { createMockChargingStation } from '../../helpers/StationHelpers.js'
+import { createCommandsSupport, createMeterValuesTemplate } from './OCPP16TestUtils.js'
+
+await describe('OCPP16ServiceUtils — pure functions', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ // ─── buildTransactionBeginMeterValue ───────────────────────────────────
+
+ await describe('buildTransactionBeginMeterValue', async () => {
+ await it('should return a meter value with Transaction.Begin context when template exists', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, 1000)
+
+ // Assert
+ assert.notStrictEqual(meterValue, undefined)
+ assert.ok(meterValue.timestamp instanceof Date)
+ assert.strictEqual(Array.isArray(meterValue.sampledValue), true)
+ assert.strictEqual(meterValue.sampledValue.length, 1)
+ assert.strictEqual(
+ meterValue.sampledValue[0].context,
+ OCPP16MeterValueContext.TRANSACTION_BEGIN
+ )
+ })
+
+ await it('should apply Wh unit divider of 1 for meterStart', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, 5000)
+
+ // Assert — Wh divider is 1, so value = 5000 / 1 = 5000
+ assert.strictEqual(meterValue.sampledValue[0].value, '5000')
+ })
+
+ await it('should apply kWh unit divider of 1000 for meterStart', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.KILO_WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, 5000)
+
+ // Assert — kWh divider is 1000, so value = 5000 / 1000 = 5
+ assert.strictEqual(meterValue.sampledValue[0].value, '5')
+ })
+
+ await it('should use meterStart 0 when undefined', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, undefined)
+
+ // Assert — undefined meterStart defaults to 0
+ assert.strictEqual(meterValue.sampledValue[0].value, '0')
+ })
+
+ await it('should throw when MeterValues template is empty (missing default measurand)', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+
+ assert.throws(
+ () => {
+ OCPP16ServiceUtils.buildTransactionBeginMeterValue(station, 1, 100)
+ },
+ { message: /Missing MeterValues for default measurand/ }
+ )
+ })
+ })
+
+ // ─── buildTransactionDataMeterValues ───────────────────────────────────
+
+ await describe('buildTransactionDataMeterValues', async () => {
+ await it('should return array containing both begin and end meter values', () => {
+ // Arrange
+ const beginMeterValue: OCPP16MeterValue = {
+ sampledValue: [{ context: OCPP16MeterValueContext.TRANSACTION_BEGIN, value: '0' }],
+ timestamp: new Date('2025-01-01T00:00:00Z'),
+ } as OCPP16MeterValue
+ const endMeterValue: OCPP16MeterValue = {
+ sampledValue: [{ context: OCPP16MeterValueContext.TRANSACTION_END, value: '100' }],
+ timestamp: new Date('2025-01-01T01:00:00Z'),
+ } as OCPP16MeterValue
+
+ // Act
+ const result = OCPP16ServiceUtils.buildTransactionDataMeterValues(
+ beginMeterValue,
+ endMeterValue
+ )
+
+ // Assert
+ assert.strictEqual(result.length, 2)
+ assert.strictEqual(result[0], beginMeterValue)
+ assert.strictEqual(result[1], endMeterValue)
+ })
+
+ await it('should return a new array instance', () => {
+ const beginMeterValue: OCPP16MeterValue = {
+ sampledValue: [],
+ timestamp: new Date(),
+ } as OCPP16MeterValue
+ const endMeterValue: OCPP16MeterValue = {
+ sampledValue: [],
+ timestamp: new Date(),
+ } as OCPP16MeterValue
+
+ const result1 = OCPP16ServiceUtils.buildTransactionDataMeterValues(
+ beginMeterValue,
+ endMeterValue
+ )
+ const result2 = OCPP16ServiceUtils.buildTransactionDataMeterValues(
+ beginMeterValue,
+ endMeterValue
+ )
+
+ // Different array instances
+ assert.notStrictEqual(result1, result2)
+ })
+ })
+
+ // ─── buildTransactionEndMeterValue ─────────────────────────────────────
+
+ await describe('buildTransactionEndMeterValue', async () => {
+ await it('should return a meter value with Transaction.End context', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPPServiceUtils.buildTransactionEndMeterValue(station, 1, 10000)
+
+ // Assert
+ assert.notStrictEqual(meterValue, undefined)
+ assert.ok(meterValue.timestamp instanceof Date)
+ assert.strictEqual(meterValue.sampledValue.length, 1)
+ assert.strictEqual(
+ meterValue.sampledValue[0].context,
+ OCPP16MeterValueContext.TRANSACTION_END
+ )
+ })
+
+ await it('should apply kWh unit divider for end meter value', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { ocppVersion: OCPPVersion.VERSION_16 },
+ })
+ const connectorStatus = station.getConnectorStatus(1)
+ if (connectorStatus != null) {
+ connectorStatus.MeterValues = createMeterValuesTemplate([
+ {
+ measurand: OCPP16MeterValueMeasurand.ENERGY_ACTIVE_IMPORT_REGISTER,
+ unit: OCPP16MeterValueUnit.KILO_WATT_HOUR,
+ value: '0',
+ },
+ ])
+ }
+
+ // Act
+ const meterValue = OCPPServiceUtils.buildTransactionEndMeterValue(station, 1, 3000)
+
+ // Assert — kWh divider: 3000 / 1000 = 3
+ assert.strictEqual(meterValue.sampledValue[0].value, '3')
+ })
+ })
+
+ // ─── clearChargingProfiles ──────────────────────────────────────────────
+
+ await describe('clearChargingProfiles', async () => {
+ /**
+ * Creates a minimal OCPP16ChargingProfile fixture.
+ * @param id - Profile ID
+ * @param purpose - Profile purpose type
+ * @param stackLevel - Stack level
+ * @returns Charging profile fixture
+ */
+ function makeProfile (
+ id: number,
+ purpose: OCPP16ChargingProfilePurposeType,
+ stackLevel: number
+ ): OCPP16ChargingProfile {
+ return {
+ chargingProfileId: id,
+ chargingProfileKind: OCPP16ChargingProfileKindType.ABSOLUTE,
+ chargingProfilePurpose: purpose,
+ chargingSchedule: {
+ chargingRateUnit: OCPP16ChargingRateUnitType.WATT,
+ chargingSchedulePeriod: [{ limit: 1000, startPeriod: 0 }],
+ },
+ stackLevel,
+ } as OCPP16ChargingProfile
+ }
+
+ await it('should return false for undefined profiles array', () => {
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const payload: OCPP16ClearChargingProfileRequest = { id: 1 }
+
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, undefined)
+
+ assert.strictEqual(result, false)
+ })
+
+ await it('should return false for empty profiles array', () => {
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const payload: OCPP16ClearChargingProfileRequest = { id: 1 }
+
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, [])
+
+ assert.strictEqual(result, false)
+ })
+
+ await it('should clear profile matching by id', () => {
+ // Arrange
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const profiles = [
+ makeProfile(1, OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE, 0),
+ makeProfile(2, OCPP16ChargingProfilePurposeType.TX_PROFILE, 1),
+ ]
+ const payload: OCPP16ClearChargingProfileRequest = { id: 1 }
+
+ // Act
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, profiles)
+
+ // Assert
+ assert.strictEqual(result, true)
+ // Profile with id 1 should be removed
+ assert.strictEqual(profiles.length, 1)
+ assert.strictEqual(profiles[0].chargingProfileId, 2)
+ })
+
+ await it('should clear profile matching by purpose', () => {
+ // Arrange
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const profiles = [
+ makeProfile(1, OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE, 0),
+ makeProfile(2, OCPP16ChargingProfilePurposeType.TX_PROFILE, 1),
+ ]
+ const payload: OCPP16ClearChargingProfileRequest = {
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_PROFILE,
+ }
+
+ // Act
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, profiles)
+
+ // Assert
+ assert.strictEqual(result, true)
+ assert.strictEqual(profiles.length, 1)
+ assert.strictEqual(
+ profiles[0].chargingProfilePurpose,
+ OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE
+ )
+ })
+
+ await it('should clear profile matching by stackLevel when purpose is null', () => {
+ // Arrange
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const profiles = [
+ makeProfile(1, OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE, 0),
+ makeProfile(2, OCPP16ChargingProfilePurposeType.TX_PROFILE, 5),
+ ]
+ const payload: OCPP16ClearChargingProfileRequest = { stackLevel: 5 }
+
+ // Act
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, profiles)
+
+ // Assert
+ assert.strictEqual(result, true)
+ assert.strictEqual(profiles.length, 1)
+ assert.strictEqual(profiles[0].chargingProfileId, 1)
+ })
+
+ await it('should return false when no profiles match', () => {
+ // Arrange
+ const { station } = createMockChargingStation({ ocppVersion: OCPPVersion.VERSION_16 })
+ const profiles = [makeProfile(1, OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE, 0)]
+ const payload: OCPP16ClearChargingProfileRequest = { id: 99 }
+
+ // Act
+ const result = OCPP16ServiceUtils.clearChargingProfiles(station, payload, profiles)
+
+ // Assert
+ assert.strictEqual(result, false)
+ assert.strictEqual(profiles.length, 1)
+ })
+ })
+
+ // ─── composeChargingSchedules ──────────────────────────────────────────
+
+ await describe('composeChargingSchedules', async () => {
+ /**
+ * Creates a minimal OCPP16ChargingSchedule fixture.
+ * @param startSeconds - Start offset in seconds from epoch
+ * @param durationSeconds - Duration in seconds
+ * @param limit - Power limit in watts
+ * @returns Charging schedule fixture
+ */
+ function makeSchedule (
+ startSeconds: number,
+ durationSeconds: number,
+ limit: number
+ ): OCPP16ChargingSchedule {
+ const start = new Date(Date.UTC(2025, 0, 1, 0, 0, startSeconds))
+ return {
+ chargingRateUnit: OCPP16ChargingRateUnitType.WATT,
+ chargingSchedulePeriod: [{ limit, startPeriod: 0 }],
+ duration: durationSeconds,
+ startSchedule: start,
+ } as OCPP16ChargingSchedule
+ }
+
+ await it('should return undefined when both schedules are undefined', () => {
+ const compositeInterval = {
+ end: new Date(Date.UTC(2025, 0, 1, 1, 0, 0)),
+ start: new Date(Date.UTC(2025, 0, 1, 0, 0, 0)),
+ }
+
+ const result = OCPP16ServiceUtils.composeChargingSchedules(
+ undefined,
+ undefined,
+ compositeInterval
+ )
+
+ assert.strictEqual(result, undefined)
+ })
+
+ await it('should return higher schedule when lower is undefined', () => {
+ const compositeInterval = {
+ end: new Date(Date.UTC(2025, 0, 1, 1, 0, 0)),
+ start: new Date(Date.UTC(2025, 0, 1, 0, 0, 0)),
+ }
+ const higher = makeSchedule(0, 3600, 11000)
+
+ const result = OCPP16ServiceUtils.composeChargingSchedules(
+ higher,
+ undefined,
+ compositeInterval
+ )
+
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.chargingSchedulePeriod[0].limit, 11000)
+ })
+
+ await it('should return lower schedule when higher is undefined', () => {
+ const compositeInterval = {
+ end: new Date(Date.UTC(2025, 0, 1, 1, 0, 0)),
+ start: new Date(Date.UTC(2025, 0, 1, 0, 0, 0)),
+ }
+ const lower = makeSchedule(0, 3600, 7000)
+
+ const result = OCPP16ServiceUtils.composeChargingSchedules(
+ undefined,
+ lower,
+ compositeInterval
+ )
+
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.chargingSchedulePeriod[0].limit, 7000)
+ })
+
+ await it('should compose non-overlapping schedules', () => {
+ // Arrange — Higher: 0..1800s, Lower: 1800..3600s — non-overlapping
+ const compositeInterval = {
+ end: new Date(Date.UTC(2025, 0, 1, 1, 0, 0)),
+ start: new Date(Date.UTC(2025, 0, 1, 0, 0, 0)),
+ }
+ const higher = makeSchedule(0, 1800, 11000)
+ const lower = makeSchedule(1800, 1800, 7000)
+
+ // Act
+ const result = OCPP16ServiceUtils.composeChargingSchedules(higher, lower, compositeInterval)
+
+ // Assert
+ assert.notStrictEqual(result, undefined)
+ if (result == null) {
+ assert.fail('Expected result to be defined')
+ }
+ assert.strictEqual(result.chargingSchedulePeriod.length, 2)
+ // Should be sorted by startPeriod
+ const periods = result.chargingSchedulePeriod
+ assert.ok(periods[0].startPeriod <= periods[1].startPeriod)
+ })
+ })
+
+ // ─── checkFeatureProfile ───────────────────────────────────────────────
+
+ await describe('checkFeatureProfile', async () => {
+ await it('should return true when feature profile is in configuration', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppConfiguration: {
+ configurationKey: [
+ {
+ key: OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ readonly: true,
+ value: 'Core,SmartCharging',
+ },
+ ],
+ },
+ ocppVersion: OCPPVersion.VERSION_16,
+ })
+
+ // Act
+ const result = OCPP16ServiceUtils.checkFeatureProfile(
+ station,
+ OCPP16SupportedFeatureProfiles.SmartCharging,
+ OCPP16RequestCommand.METER_VALUES
+ )
+
+ // Assert
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return false when feature profile is not in configuration', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppConfiguration: {
+ configurationKey: [
+ {
+ key: OCPP16StandardParametersKey.SupportedFeatureProfiles,
+ readonly: true,
+ value: 'Core',
+ },
+ ],
+ },
+ ocppVersion: OCPPVersion.VERSION_16,
+ })
+
+ // Act
+ const result = OCPP16ServiceUtils.checkFeatureProfile(
+ station,
+ OCPP16SupportedFeatureProfiles.SmartCharging,
+ OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
+ )
+
+ // Assert
+ assert.strictEqual(result, false)
+ })
+
+ await it('should return false when SupportedFeatureProfiles key is missing', () => {
+ // Arrange
+ const { station } = createMockChargingStation({
+ ocppConfiguration: { configurationKey: [] },
+ ocppVersion: OCPPVersion.VERSION_16,
+ })
+
+ // Act
+ const result = OCPP16ServiceUtils.checkFeatureProfile(
+ station,
+ OCPP16SupportedFeatureProfiles.Reservation,
+ OCPP16IncomingRequestCommand.RESERVE_NOW
+ )
+
+ // Assert
+ assert.strictEqual(result, false)
+ })
+ })
+
+ // ─── isRequestCommandSupported ──────────────────────────────────────────
+
+ await describe('isRequestCommandSupported', async () => {
+ await it('should return true when commandsSupport is not defined', () => {
+ // Arrange — no commandsSupport means all commands supported
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { commandsSupport: undefined },
+ })
+
+ // Act
+ const result = OCPP16ServiceUtils.isRequestCommandSupported(
+ station,
+ OCPP16RequestCommand.HEARTBEAT
+ )
+
+ // Assert
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return true when command is explicitly enabled', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: {
+ commandsSupport: createCommandsSupport({
+ incomingCommands: {},
+ outgoingCommands: {
+ [OCPP16RequestCommand.HEARTBEAT]: true,
+ },
+ }),
+ },
+ })
+
+ const result = OCPP16ServiceUtils.isRequestCommandSupported(
+ station,
+ OCPP16RequestCommand.HEARTBEAT
+ )
+
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return false when command is explicitly disabled', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: {
+ commandsSupport: createCommandsSupport({
+ incomingCommands: {},
+ outgoingCommands: {
+ [OCPP16RequestCommand.HEARTBEAT]: false,
+ },
+ }),
+ },
+ })
+
+ const result = OCPP16ServiceUtils.isRequestCommandSupported(
+ station,
+ OCPP16RequestCommand.HEARTBEAT
+ )
+
+ assert.strictEqual(result, false)
+ })
+ })
+
+ // ─── isIncomingRequestCommandSupported ──────────────────────────────────
+
+ await describe('isIncomingRequestCommandSupported', async () => {
+ await it('should return true when incomingCommands is not defined', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: { commandsSupport: undefined },
+ })
+
+ const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
+ station,
+ OCPP16IncomingRequestCommand.RESET
+ )
+
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return true when incoming command is explicitly enabled', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: {
+ commandsSupport: createCommandsSupport({
+ incomingCommands: {
+ [OCPP16IncomingRequestCommand.RESET]: true,
+ },
+ }),
+ },
+ })
+
+ const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
+ station,
+ OCPP16IncomingRequestCommand.RESET
+ )
+
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return false when incoming command is explicitly disabled', () => {
+ const { station } = createMockChargingStation({
+ ocppVersion: OCPPVersion.VERSION_16,
+ stationInfo: {
+ commandsSupport: createCommandsSupport({
+ incomingCommands: {
+ [OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION]: false,
+ },
+ }),
+ },
+ })
+
+ const result = OCPP16ServiceUtils.isIncomingRequestCommandSupported(
+ station,
+ OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION
+ )
+
+ assert.strictEqual(result, false)
+ })
+ })
+
+ // ─── isConfigurationKeyVisible ─────────────────────────────────────────
+
+ await describe('isConfigurationKeyVisible', async () => {
+ await it('should return true when visible is undefined', () => {
+ const result = OCPP16ServiceUtils.isConfigurationKeyVisible({
+ key: 'TestKey',
+ readonly: false,
+ value: 'TestValue',
+ })
+
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return true when visible is true', () => {
+ const result = OCPP16ServiceUtils.isConfigurationKeyVisible({
+ key: 'TestKey',
+ readonly: false,
+ value: 'TestValue',
+ visible: true,
+ })
+
+ assert.strictEqual(result, true)
+ })
+
+ await it('should return false when visible is false', () => {
+ const result = OCPP16ServiceUtils.isConfigurationKeyVisible({
+ key: 'TestKey',
+ readonly: false,
+ value: 'TestValue',
+ visible: false,
+ })
+
+ assert.strictEqual(result, false)
+ })
+ })
+})
--- /dev/null
+/**
+ * @file OCPP 1.6 test utilities, fixtures, and mock helpers
+ * @description Provides context factories, charging profile fixtures, reservation fixtures,
+ * and configuration key helpers for OCPP 1.6 unit and integration tests.
+ */
+
+import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
+import type { ChargingStationInfo } from '../../../../src/types/ChargingStationInfo.js'
+import type { ConfigurationKey } from '../../../../src/types/ChargingStationOcppConfiguration.js'
+import type { JsonObject } from '../../../../src/types/JsonType.js'
+import type { SampledValueTemplate } from '../../../../src/types/MeasurandPerPhaseSampledValueTemplates.js'
+import type {
+ OCPP16ChargingProfile,
+ OCPP16ChargingSchedulePeriod,
+} from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+import type { OCPP16SampledValue } from '../../../../src/types/ocpp/1.6/MeterValues.js'
+import type { IncomingRequestCommand, RequestCommand } from '../../../../src/types/ocpp/Requests.js'
+
+import {
+ createTestableIncomingRequestService,
+ createTestableOCPP16RequestService,
+ type TestableOCPP16IncomingRequestService,
+ type TestableOCPP16RequestService,
+} from '../../../../src/charging-station/ocpp/1.6/__testable__/index.js'
+import { OCPP16IncomingRequestService } from '../../../../src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.js'
+import { OCPP16RequestService } from '../../../../src/charging-station/ocpp/1.6/OCPP16RequestService.js'
+import { OCPP16ResponseService } from '../../../../src/charging-station/ocpp/1.6/OCPP16ResponseService.js'
+import {
+ OCPP16ChargingProfilePurposeType,
+ OCPP16ChargingRateUnitType,
+ type OCPP16RequestCommand,
+ OCPP16StandardParametersKey,
+ OCPPVersion,
+} from '../../../../src/types/index.js'
+import { OCPP16ChargingProfileKindType } from '../../../../src/types/ocpp/1.6/ChargingProfile.js'
+import { Constants } from '../../../../src/utils/index.js'
+import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
+import {
+ createMockChargingStation,
+ type MockChargingStation,
+ type MockOCPPRequestService,
+} from '../../ChargingStationTestUtils.js'
+
+// ============================================================================
+// Test Context Types
+// ============================================================================
+
+export interface OCPP16IncomingRequestTestContext {
+ readonly incomingRequestService: OCPP16IncomingRequestService
+ readonly station: ChargingStation
+ readonly testableService: TestableOCPP16IncomingRequestService
+}
+
+export interface OCPP16IncomingRequestTestContextOptions {
+ readonly baseName?: string
+ readonly connectorsCount?: number
+ readonly stationInfo?: Record<string, unknown>
+}
+
+export interface OCPP16RequestTestContext {
+ readonly requestService: OCPP16RequestService
+ readonly station: ChargingStation
+ readonly testableRequestService: TestableOCPP16RequestService
+}
+
+export interface OCPP16RequestTestContextOptions {
+ readonly baseName?: string
+ readonly stationInfo?: Record<string, unknown>
+}
+
+export interface OCPP16ResponseTestContext {
+ readonly responseService: OCPP16ResponseService
+ readonly station: ChargingStation
+}
+
+export interface OCPP16ResponseTestContextOptions {
+ readonly baseName?: string
+ readonly stationInfo?: Record<string, unknown>
+}
+
+// ============================================================================
+// Type Cast Helpers
+// ============================================================================
+
+/**
+ * Create a `commandsSupport` object compatible with `ChargingStationInfo` from partial records.
+ * Encapsulates the casts from `Partial<Record<...>>` to the full `Record<...>` required by the type.
+ * @param config - Partial incoming and outgoing command support maps
+ * @param config.incomingCommands - Partial map of incoming request command support
+ * @param config.outgoingCommands - Partial map of outgoing request command support
+ * @returns A `commandsSupport` value suitable for `stationInfo`
+ */
+export function createCommandsSupport (config: {
+ incomingCommands?: Record<string, boolean>
+ outgoingCommands?: Record<string, boolean>
+}): NonNullable<ChargingStationInfo['commandsSupport']> {
+ return {
+ incomingCommands: (config.incomingCommands ?? {}) as unknown as Record<
+ IncomingRequestCommand,
+ boolean
+ >,
+ ...(config.outgoingCommands != null && {
+ outgoingCommands: config.outgoingCommands as unknown as Record<RequestCommand, boolean>,
+ }),
+ }
+}
+
+/**
+ * Create a `SampledValueTemplate[]` from OCPP 1.6 sampled value entries.
+ * Encapsulates the type widening from `OCPP16SampledValue` to the union-based
+ * `SampledValueTemplate` (`(OCPP16SampledValue | OCPP20SampledValue) & { fluctuationPercent?; minimumValue? }`).
+ * @param entries - Array of OCPP 1.6 sampled value objects
+ * @returns The entries typed as `SampledValueTemplate[]`
+ */
+export function createMeterValuesTemplate (entries: OCPP16SampledValue[]): SampledValueTemplate[] {
+ return entries as unknown as SampledValueTemplate[]
+}
+
+/**
+ * Create a standard OCPP 1.6 incoming request test context with service,
+ * testable wrapper, and mock charging station.
+ * @param options - Optional overrides for base name, connectors, and station info
+ * @returns OCPP16IncomingRequestTestContext with all objects needed for testing
+ */
+export function createOCPP16IncomingRequestTestContext (
+ options: OCPP16IncomingRequestTestContextOptions = {}
+): OCPP16IncomingRequestTestContext {
+ const {
+ baseName = TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount = 2,
+ stationInfo = {},
+ } = options
+
+ const incomingRequestService = new OCPP16IncomingRequestService()
+ const testableService = createTestableIncomingRequestService(incomingRequestService)
+ const { station } = createMockChargingStation({
+ baseName,
+ connectorsCount,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ ...stationInfo,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+
+ return { incomingRequestService, station, testableService }
+}
+
+/**
+ * Create a standard OCPP 1.6 request test context with response service,
+ * request service, testable wrapper, and mock charging station.
+ * @param options - Optional overrides for base name and station info
+ * @returns OCPP16RequestTestContext with all objects needed for testing
+ */
+export function createOCPP16RequestTestContext (
+ options: OCPP16RequestTestContextOptions = {}
+): OCPP16RequestTestContext {
+ const { baseName = TEST_CHARGING_STATION_BASE_NAME, stationInfo = {} } = options
+
+ const mockResponseService = new OCPP16ResponseService()
+ const requestService = new OCPP16RequestService(mockResponseService)
+ const testableRequestService = createTestableOCPP16RequestService(requestService)
+ const { station } = createMockChargingStation({
+ baseName,
+ connectorsCount: 2,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ ...stationInfo,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+
+ return { requestService, station, testableRequestService }
+}
+
+// ============================================================================
+// Context Factories
+// ============================================================================
+
+/**
+ * Create a standard OCPP 1.6 response test context with response service
+ * and mock charging station.
+ * @param options - Optional overrides for base name and station info
+ * @returns OCPP16ResponseTestContext with all objects needed for testing
+ */
+export function createOCPP16ResponseTestContext (
+ options: OCPP16ResponseTestContextOptions = {}
+): OCPP16ResponseTestContext {
+ const { baseName = TEST_CHARGING_STATION_BASE_NAME, stationInfo = {} } = options
+
+ const responseService = new OCPP16ResponseService()
+ const { station } = createMockChargingStation({
+ baseName,
+ connectorsCount: 2,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ ...stationInfo,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+
+ return { responseService, station }
+}
+
+/**
+ * Create a pre-configured mock station for OCPP 1.6 tests.
+ * Default configuration: 2 connectors, OCPP 1.6, 5s reset time.
+ * @param options - Optional overrides for station configuration
+ * @returns MockChargingStation ready for testing
+ */
+export function createStandardStation (
+ options: OCPP16IncomingRequestTestContextOptions = {}
+): MockChargingStation {
+ const {
+ baseName = TEST_CHARGING_STATION_BASE_NAME,
+ connectorsCount = 2,
+ stationInfo = {},
+ } = options
+
+ const { station } = createMockChargingStation({
+ baseName,
+ connectorsCount,
+ heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+ stationInfo: {
+ ocppStrictCompliance: false,
+ ocppVersion: OCPPVersion.VERSION_16,
+ resetTime: 5000,
+ ...stationInfo,
+ },
+ websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+ })
+
+ return station as MockChargingStation
+}
+
+/**
+ * Dispatch an OCPP 1.6 response through the public `responseHandler`, encapsulating
+ * the `as unknown as` casts needed to satisfy the generic `JsonType` parameters.
+ * @param responseService - The OCPP 1.6 response service instance
+ * @param station - Charging station context
+ * @param command - The OCPP 1.6 request command the response belongs to
+ * @param payload - Response payload (specific OCPP type widened to JsonObject)
+ * @param requestPayload - Original request payload (defaults to empty object)
+ */
+export async function dispatchResponse (
+ responseService: OCPP16ResponseService,
+ station: ChargingStation,
+ command: OCPP16RequestCommand,
+ payload: JsonObject,
+ requestPayload: JsonObject = {}
+): Promise<void> {
+ await responseService.responseHandler(
+ station,
+ command,
+ payload as unknown as Parameters<OCPP16ResponseService['responseHandler']>[2],
+ requestPayload as unknown as Parameters<OCPP16ResponseService['responseHandler']>[3]
+ )
+}
+
+/**
+ * Reset connector transaction state for all connectors in the charging station.
+ * Ensures test isolation by clearing any transaction state from previous tests.
+ * @param chargingStation - Charging station instance whose connector state should be reset
+ */
+export function resetConnectorTransactionState (chargingStation: ChargingStation): void {
+ for (const [connectorId, connectorStatus] of chargingStation.connectors.entries()) {
+ if (connectorId === 0) continue
+ connectorStatus.transactionStarted = false
+ connectorStatus.transactionId = undefined
+ connectorStatus.transactionIdTag = undefined
+ connectorStatus.transactionStart = undefined
+ connectorStatus.transactionEnergyActiveImportRegisterValue = 0
+ connectorStatus.transactionRemoteStarted = false
+ connectorStatus.chargingProfiles = []
+ }
+}
+
+// ============================================================================
+// Connector Transaction State Helpers
+// ============================================================================
+
+/**
+ * Reset interval-related configuration keys to their canonical defaults after tests that modify them.
+ * Specifically resets MeterValueSampleInterval and HeartbeatInterval to default values.
+ * @param chargingStation - Charging station test instance whose interval configuration is reset
+ */
+export function resetLimits (chargingStation: ChargingStation) {
+ upsertConfigurationKey(
+ chargingStation,
+ OCPP16StandardParametersKey.MeterValueSampleInterval,
+ '60'
+ )
+ upsertConfigurationKey(
+ chargingStation,
+ OCPP16StandardParametersKey.HeartbeatInterval,
+ Constants.DEFAULT_HEARTBEAT_INTERVAL.toString()
+ )
+}
+
+// ============================================================================
+// Configuration Helpers
+// ============================================================================
+
+/**
+ * Set the mock request handler on a charging station's OCPP request service.
+ * Encapsulates the `as unknown as MockOCPPRequestService` cast in one place.
+ * @param station - Charging station whose request service to mock
+ * @param handler - Async handler function to assign
+ */
+export function setMockRequestHandler (
+ station: ChargingStation,
+ handler: (...args: unknown[]) => Promise<unknown>
+): void {
+ ;(station.ocppRequestService as unknown as MockOCPPRequestService).requestHandler = handler
+}
+
+/**
+ * Upsert a configuration key with provided value and readonly flag (default false).
+ * @param chargingStation - Charging station instance
+ * @param key - Configuration key name
+ * @param value - Configuration key value as string
+ * @param readonly - Whether the key is read-only (default false)
+ */
+export function upsertConfigurationKey (
+ chargingStation: ChargingStation,
+ key: string,
+ value: string,
+ readonly = false
+) {
+ const configKeys = ensureConfig(chargingStation)
+ const configKey = configKeys.find(k => k.key === key)
+ if (configKey) {
+ configKey.value = value
+ if (readonly) configKey.readonly = readonly
+ } else {
+ configKeys.push({ key, readonly, value })
+ }
+}
+
+/**
+ * Ensure configuration key array exists on the charging station.
+ * @param chargingStation - Charging station instance to ensure configuration for
+ * @returns The configuration key array
+ */
+function ensureConfig (chargingStation: ChargingStation): ConfigurationKey[] {
+ chargingStation.ocppConfiguration ??= { configurationKey: [] }
+ chargingStation.ocppConfiguration.configurationKey ??= []
+ return chargingStation.ocppConfiguration.configurationKey
+}
+
+// ============================================================================
+// Fixture Factories
+// ============================================================================
+
+export const ChargingProfileFixtures = {
+ createChargePointMaxProfile: (
+ chargingProfileId = 3,
+ periods: OCPP16ChargingSchedulePeriod[] = [{ limit: 32, startPeriod: 0 }]
+ ): OCPP16ChargingProfile => ({
+ chargingProfileId,
+ chargingProfileKind: OCPP16ChargingProfileKindType.ABSOLUTE,
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE,
+ chargingSchedule: {
+ chargingRateUnit: OCPP16ChargingRateUnitType.AMPERE,
+ chargingSchedulePeriod: periods,
+ },
+ stackLevel: 0,
+ }),
+
+ createTxDefaultProfile: (chargingProfileId = 1, stackLevel = 0): OCPP16ChargingProfile => ({
+ chargingProfileId,
+ chargingProfileKind: OCPP16ChargingProfileKindType.ABSOLUTE,
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_DEFAULT_PROFILE,
+ chargingSchedule: {
+ chargingRateUnit: OCPP16ChargingRateUnitType.AMPERE,
+ chargingSchedulePeriod: [{ limit: 32, startPeriod: 0 }],
+ },
+ stackLevel,
+ }),
+
+ createTxProfile: (chargingProfileId = 2, transactionId?: number): OCPP16ChargingProfile => ({
+ chargingProfileId,
+ chargingProfileKind: OCPP16ChargingProfileKindType.RELATIVE,
+ chargingProfilePurpose: OCPP16ChargingProfilePurposeType.TX_PROFILE,
+ chargingSchedule: {
+ chargingRateUnit: OCPP16ChargingRateUnitType.AMPERE,
+ chargingSchedulePeriod: [{ limit: 16, startPeriod: 0 }],
+ },
+ stackLevel: 0,
+ ...(transactionId != null && { transactionId }),
+ }),
+} as const
+
+export const ReservationFixtures = {
+ createReservation: (
+ connectorId = 1,
+ reservationId = 1,
+ idTag = 'TEST-TAG-001',
+ expiryDate = new Date(Date.now() + 3600000)
+ ) => ({
+ connectorId,
+ expiryDate,
+ idTag,
+ reservationId,
+ }),
+} as const
+
+export const ResetFixtures = {
+ createStandardStation: (runningTransactions = 0): MockChargingStation => {
+ const station = createStandardStation({ stationInfo: { resetTime: 5000 } })
+ station.getNumberOfRunningTransactions = () => runningTransactions
+ station.reset = () => Promise.resolve()
+ return station
+ },
+
+ createStationWithTransaction: (): MockChargingStation => {
+ return ResetFixtures.createStandardStation(1)
+ },
+} as const
+
+export const TransactionFixtures = {
+ createStartTransactionParams: (connectorId = 1, idTag = 'TEST-TAG-001') => ({
+ connectorId,
+ idTag,
+ }),
+
+ createStopTransactionParams: (transactionId = 1) => ({
+ transactionId,
+ }),
+} as const
* @description Unit tests for OCPP 2.0 certificate management and validation
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { rm } from 'node:fs/promises'
import { afterEach, beforeEach, describe, it } from 'node:test'
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- kept for future assertions
const _EXPECTED_HASH_DATA = {
hashAlgorithm: HashAlgorithmEnumType.SHA256,
- issuerKeyHash: expect.stringMatching(/^[a-fA-F0-9]+$/),
- issuerNameHash: expect.stringMatching(/^[a-fA-F0-9]+$/),
- serialNumber: expect.any(String),
+ issuerKeyHash: /^[a-fA-F0-9]+$/,
+ issuerNameHash: /^[a-fA-F0-9]+$/,
+ serialNumber: '<any-string>',
}
await describe('I02-I04 - ISO15118 Certificate Management', async () => {
VALID_PEM_CERTIFICATE_EXTENDED
)
- expect(result).toBeDefined()
- expect(result.success).toBe(true)
- expect(result.filePath).toContain(TEST_STATION_HASH_ID)
- expect(result.filePath).toContain('certs')
- expect(result.filePath).toMatch(/\.pem$/)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.success, true)
+ if (result.filePath == null) {
+ assert.fail('Expected filePath to be defined')
+ }
+ assert.ok(result.filePath.includes(TEST_STATION_HASH_ID))
+ assert.ok(result.filePath.includes('certs'))
+ assert.match(result.filePath, /\.pem$/)
})
await it('should reject invalid PEM certificate without BEGIN/END markers', async () => {
INVALID_PEM_CERTIFICATE_MISSING_MARKERS
)
- expect(result).toBeDefined()
- expect(result.success).toBe(false)
- expect(result.error).toContain('Invalid PEM format')
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.success, false)
+ if (result.error == null) {
+ assert.fail('Expected error to be defined')
+ }
+ assert.ok(result.error.includes('Invalid PEM format'))
})
await it('should reject empty certificate data', async () => {
EMPTY_PEM_CERTIFICATE
)
- expect(result).toBeDefined()
- expect(result.success).toBe(false)
- expect(result.error).toBeDefined()
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.success, false)
+ assert.notStrictEqual(result.error, undefined)
})
await it('should create certificate directory structure if not exists', async () => {
VALID_PEM_CERTIFICATE_EXTENDED
)
- expect(result).toBeDefined()
- expect(result.success).toBe(true)
- expect(result.filePath).toContain('V2GRootCertificate')
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.success, true)
+ if (result.filePath == null) {
+ assert.fail('Expected filePath to be defined')
+ }
+ assert.ok(result.filePath.includes('V2GRootCertificate'))
})
})
const result = await manager.deleteCertificate(TEST_STATION_HASH_ID, hashData)
- expect(result).toBeDefined()
- expect(result.status).toBeDefined()
- expect(['Accepted', 'NotFound', 'Failed']).toContain(result.status)
+ assert.notStrictEqual(result, undefined)
+ assert.notStrictEqual(result.status, undefined)
+ assert.ok(['Accepted', 'Failed', 'NotFound'].includes(result.status))
})
await it('should return NotFound for non-existent certificate', async () => {
const result = await manager.deleteCertificate(TEST_STATION_HASH_ID, hashData)
- expect(result).toBeDefined()
- expect(result.status).toBe('NotFound')
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.status, 'NotFound')
})
await it('should handle filesystem errors gracefully', async () => {
const result = await manager.deleteCertificate('invalid-station-id', hashData)
- expect(result).toBeDefined()
- expect(['NotFound', 'Failed']).toContain(result.status)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(['Failed', 'NotFound'].includes(result.status))
})
})
await it('should return list of installed certificates for station', async () => {
const result = await manager.getInstalledCertificates(TEST_STATION_HASH_ID)
- expect(result).toBeDefined()
- expect(Array.isArray(result.certificateHashDataChain)).toBe(true)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(Array.isArray(result.certificateHashDataChain))
})
await it('should filter certificates by type when filter provided', async () => {
const filterTypes = [InstallCertificateUseEnumType.CSMSRootCertificate]
const result = await manager.getInstalledCertificates(TEST_STATION_HASH_ID, filterTypes)
- expect(result).toBeDefined()
- expect(Array.isArray(result.certificateHashDataChain)).toBe(true)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(Array.isArray(result.certificateHashDataChain))
})
await it('should return empty list when no certificates installed', async () => {
const result = await manager.getInstalledCertificates('empty-station-hash-id')
- expect(result).toBeDefined()
- expect(result.certificateHashDataChain).toHaveLength(0)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.certificateHashDataChain.length, 0)
})
await it('should support multiple certificate type filters', async () => {
]
const result = await manager.getInstalledCertificates(TEST_STATION_HASH_ID, filterTypes)
- expect(result).toBeDefined()
- expect(Array.isArray(result.certificateHashDataChain)).toBe(true)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(Array.isArray(result.certificateHashDataChain))
})
})
await it('should compute hash data for valid PEM certificate', () => {
const hashData = manager.computeCertificateHash(VALID_PEM_CERTIFICATE_EXTENDED)
- expect(hashData).toBeDefined()
- expect(hashData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA256)
- expect(hashData.issuerNameHash).toBeDefined()
- expect(typeof hashData.issuerNameHash).toBe('string')
- expect(hashData.issuerKeyHash).toBeDefined()
- expect(typeof hashData.issuerKeyHash).toBe('string')
- expect(hashData.serialNumber).toBeDefined()
- expect(typeof hashData.serialNumber).toBe('string')
+ assert.notStrictEqual(hashData, undefined)
+ assert.strictEqual(hashData.hashAlgorithm, HashAlgorithmEnumType.SHA256)
+ assert.notStrictEqual(hashData.issuerNameHash, undefined)
+ assert.strictEqual(typeof hashData.issuerNameHash, 'string')
+ assert.notStrictEqual(hashData.issuerKeyHash, undefined)
+ assert.strictEqual(typeof hashData.issuerKeyHash, 'string')
+ assert.notStrictEqual(hashData.serialNumber, undefined)
+ assert.strictEqual(typeof hashData.serialNumber, 'string')
})
await it('should return hex-encoded hash values', () => {
const hashData = manager.computeCertificateHash(VALID_PEM_CERTIFICATE_EXTENDED)
const hexPattern = /^[a-fA-F0-9]+$/
- expect(hashData.issuerNameHash).toMatch(hexPattern)
- expect(hashData.issuerKeyHash).toMatch(hexPattern)
+ assert.match(hashData.issuerNameHash, hexPattern)
+ assert.match(hashData.issuerKeyHash, hexPattern)
})
await it('should throw error for invalid PEM certificate', () => {
- expect(() => {
+ assert.throws(() => {
manager.computeCertificateHash(INVALID_PEM_CERTIFICATE_MISSING_MARKERS)
- }).toThrow()
+ })
})
await it('should throw error for empty certificate', () => {
- expect(() => {
+ assert.throws(() => {
manager.computeCertificateHash(EMPTY_PEM_CERTIFICATE)
- }).toThrow()
+ })
})
await it('should support SHA384 hash algorithm', () => {
HashAlgorithmEnumType.SHA384
)
- expect(hashData).toBeDefined()
- expect(hashData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA384)
+ assert.notStrictEqual(hashData, undefined)
+ assert.strictEqual(hashData.hashAlgorithm, HashAlgorithmEnumType.SHA384)
})
await it('should support SHA512 hash algorithm', () => {
HashAlgorithmEnumType.SHA512
)
- expect(hashData).toBeDefined()
- expect(hashData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA512)
+ assert.notStrictEqual(hashData, undefined)
+ assert.strictEqual(hashData.hashAlgorithm, HashAlgorithmEnumType.SHA512)
})
})
await it('should return true for valid PEM certificate', () => {
const isValid = manager.validateCertificateFormat(VALID_PEM_CERTIFICATE_EXTENDED)
- expect(isValid).toBe(true)
+ assert.strictEqual(isValid, true)
})
await it('should return false for certificate without BEGIN marker', () => {
const isValid = manager.validateCertificateFormat(INVALID_PEM_CERTIFICATE_MISSING_MARKERS)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should return false for certificate with wrong markers', () => {
const isValid = manager.validateCertificateFormat(INVALID_PEM_WRONG_MARKERS)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should return false for empty string', () => {
const isValid = manager.validateCertificateFormat(EMPTY_PEM_CERTIFICATE)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should return false for null/undefined input', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing invalid null input
- expect(manager.validateCertificateFormat(null as any)).toBe(false)
+ assert.strictEqual(manager.validateCertificateFormat(null as any), false)
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing invalid undefined input
- expect(manager.validateCertificateFormat(undefined as any)).toBe(false)
+ assert.strictEqual(manager.validateCertificateFormat(undefined as any), false)
})
await it('should return true for certificate with extra whitespace', () => {
const isValid = manager.validateCertificateFormat(pemWithWhitespace)
- expect(isValid).toBe(true)
+ assert.strictEqual(isValid, true)
})
})
await it('should return correct file path for certificate', () => {
const path = manager.getCertificatePath(TEST_STATION_HASH_ID, TEST_CERT_TYPE, 'SERIAL-12345')
- expect(path).toBeDefined()
- expect(path).toContain(TEST_STATION_HASH_ID)
- expect(path).toContain('certs')
- expect(path).toContain('CSMSRootCertificate')
- expect(path).toContain('SERIAL-12345')
- expect(path).toMatch(/\.pem$/)
+ assert.notStrictEqual(path, undefined)
+ assert.ok(path.includes(TEST_STATION_HASH_ID))
+ assert.ok(path.includes('certs'))
+ assert.ok(path.includes('CSMSRootCertificate'))
+ assert.ok(path.includes('SERIAL-12345'))
+ assert.match(path, /\.pem$/)
})
await it('should handle special characters in serial number', () => {
'SERIAL:ABC/123'
)
- expect(path).toBeDefined()
+ assert.notStrictEqual(path, undefined)
const filename = path.split('/').pop()
- expect(filename).not.toContain(':')
- expect(filename).not.toContain('/')
+ if (filename == null) {
+ assert.fail('Expected filename to be defined')
+ }
+ assert.ok(!filename.includes(':'))
+ assert.ok(!filename.includes('/'))
})
await it('should return different paths for different certificate types', () => {
'SERIAL-001'
)
- expect(csmsPath).not.toBe(v2gPath)
- expect(csmsPath).toContain('CSMSRootCertificate')
- expect(v2gPath).toContain('V2GRootCertificate')
+ assert.notStrictEqual(csmsPath, v2gPath)
+ assert.ok(csmsPath.includes('CSMSRootCertificate'))
+ assert.ok(v2gPath.includes('V2GRootCertificate'))
})
await it('should return path following project convention', () => {
const path = manager.getCertificatePath(TEST_STATION_HASH_ID, TEST_CERT_TYPE, 'SERIAL-12345')
- expect(path).toMatch(/configurations/)
- expect(path).toMatch(/certs/)
- expect(path).toMatch(/\.pem$/)
+ assert.match(path, /configurations/)
+ assert.match(path, /certs/)
+ assert.match(path, /\.pem$/)
})
})
manager.getInstalledCertificates(TEST_STATION_HASH_ID),
])
- expect(results).toHaveLength(3)
+ assert.strictEqual(results.length, 3)
results.forEach(result => {
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
})
const result = await manager.storeCertificate(TEST_STATION_HASH_ID, TEST_CERT_TYPE, longChain)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should sanitize station hash ID for filesystem safety', () => {
const path = manager.getCertificatePath(maliciousHashId, TEST_CERT_TYPE, 'SERIAL-001')
- expect(path).not.toContain('..')
+ assert.ok(!path.includes('..'))
})
})
})
* @description Unit tests for OCPP 2.0 CertificateSigned command handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.strictEqual(response.status, GenericStatus.Accepted)
})
await it('should accept single certificate (no chain)', async () => {
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
- expect(typeof response.statusInfo?.reasonCode).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
+ assert.strictEqual(typeof response.statusInfo?.reasonCode, 'string')
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
// Verify closeWSConnection was called to trigger reconnect
- expect(mockCloseWSConnection.mock.calls.length).toBeGreaterThan(0)
+ assert.ok(mockCloseWSConnection.mock.calls.length > 0)
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
// Verify storeCertificate was called
- expect(mockCertManager.storeCertificate.mock.calls.length).toBeGreaterThan(0)
+ assert.ok(mockCertManager.storeCertificate.mock.calls.length > 0)
// Verify closeWSConnection was NOT called for V2GCertificate
- expect(mockCloseWSConnection.mock.calls.length).toBe(0)
+ assert.strictEqual(mockCloseWSConnection.mock.calls.length, 0)
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(stationWithoutCertManager, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe('InternalError')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, 'InternalError')
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
})
await it('should return Rejected status when storage throws error', async () => {
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
})
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
// status is required
- expect(response.status).toBeDefined()
- expect([GenericStatus.Accepted, GenericStatus.Rejected]).toContain(response.status)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok([GenericStatus.Accepted, GenericStatus.Rejected].includes(response.status))
// statusInfo is optional but if present must have reasonCode
if (response.statusInfo != null) {
- expect(response.statusInfo.reasonCode).toBeDefined()
- expect(typeof response.statusInfo.reasonCode).toBe('string')
+ assert.notStrictEqual(response.statusInfo.reasonCode, undefined)
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
if (response.statusInfo.additionalInfo != null) {
- expect(typeof response.statusInfo.additionalInfo).toBe('string')
+ assert.strictEqual(typeof response.statusInfo.additionalInfo, 'string')
}
}
// customData is optional but if present must have vendorId
if (response.customData != null) {
- expect(response.customData.vendorId).toBeDefined()
- expect(typeof response.customData.vendorId).toBe('string')
+ assert.notStrictEqual(response.customData.vendorId, undefined)
+ assert.strictEqual(typeof response.customData.vendorId, 'string')
}
})
const response: OCPP20CertificateSignedResponse =
await testableService.handleRequestCertificateSigned(station, request)
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
- expect(typeof response.statusInfo?.reasonCode).toBe('string')
- expect(response.statusInfo?.reasonCode.length).toBeGreaterThan(0)
- expect(response.statusInfo?.reasonCode.length).toBeLessThanOrEqual(20)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
+ assert.ok(response.statusInfo.reasonCode.length > 0)
+ assert.ok(response.statusInfo.reasonCode.length <= 20)
})
})
})
* @description Unit tests for OCPP 2.0 ClearCache command handling (C11)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
await it('should handle ClearCache request successfully', async () => {
const response = await testableService.handleRequestClearCache(station)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
- expect([GenericStatus.Accepted, GenericStatus.Rejected]).toContain(response.status)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.ok([GenericStatus.Accepted, GenericStatus.Rejected].includes(response.status))
})
// FR: C11.FR.02 - Return correct status based on cache clearing result
await it('should return correct status based on cache clearing result', async () => {
const response = await testableService.handleRequestClearCache(station)
- expect(response).toBeDefined()
- expect(response.status).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
// Should be either Accepted or Rejected based on cache state
- expect([GenericStatus.Accepted, GenericStatus.Rejected]).toContain(response.status)
+ assert.ok([GenericStatus.Accepted, GenericStatus.Rejected].includes(response.status))
})
// CLR-001: Verify Authorization Cache is cleared (not IdTagsCache)
try {
const response = await testableService.handleRequestClearCache(station)
- expect(clearCacheCalled).toBe(true)
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.strictEqual(clearCacheCalled, true)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
try {
await testableService.handleRequestClearCache(station)
- expect(deleteIdTagsCalled).toBe(false)
+ assert.strictEqual(deleteIdTagsCalled, false)
} finally {
// Restore original method
Object.assign(station.idTagsCache, { deleteIdTags: originalDeleteIdTags })
try {
const response = await testableService.handleRequestClearCache(station)
- expect(response.status).toBe(GenericStatus.Rejected)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
try {
const response = await testableService.handleRequestClearCache(station)
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
try {
const response = await testableService.handleRequestClearCache(station)
- expect(response.status).toBe(GenericStatus.Rejected)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
await testableService.handleRequestClearCache(station)
// clearCache should NOT be called when cache is disabled
- expect(clearCacheAttempted).toBe(false)
+ assert.strictEqual(clearCacheAttempted, false)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
const response = await testableService.handleRequestClearCache(station)
// Per C11.FR.05: SHALL return Rejected if CS does not support Authorization Cache
- expect(response.status).toBe(GenericStatus.Rejected)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
} finally {
// Restore original factory method
Object.assign(OCPPAuthServiceFactory, { getInstance: originalGetInstance })
* @description Unit tests for OCPP 2.0 DeleteCertificate command handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should accept deletion with SHA384 hash algorithm', async () => {
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should accept deletion with SHA512 hash algorithm', async () => {
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
})
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(DeleteCertificateStatusEnumType.NotFound)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.NotFound)
})
})
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Failed)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Failed)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
})
await it('should return Failed with InternalError when certificateManager is missing', async () => {
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(stationWithoutCertManager, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Failed)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.InternalError)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Failed)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.InternalError)
})
})
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
- expect(response.status).toBeDefined()
- expect([
- DeleteCertificateStatusEnumType.Accepted,
- DeleteCertificateStatusEnumType.NotFound,
- DeleteCertificateStatusEnumType.Failed,
- ]).toContain(response.status)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [
+ DeleteCertificateStatusEnumType.Accepted,
+ DeleteCertificateStatusEnumType.Failed,
+ DeleteCertificateStatusEnumType.NotFound,
+ ].includes(response.status)
+ )
if (response.statusInfo != null) {
- expect(response.statusInfo.reasonCode).toBeDefined()
- expect(typeof response.statusInfo.reasonCode).toBe('string')
+ assert.notStrictEqual(response.statusInfo.reasonCode, undefined)
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
if (response.statusInfo.additionalInfo != null) {
- expect(typeof response.statusInfo.additionalInfo).toBe('string')
+ assert.strictEqual(typeof response.statusInfo.additionalInfo, 'string')
}
}
if (response.customData != null) {
- expect(response.customData.vendorId).toBeDefined()
- expect(typeof response.customData.vendorId).toBe('string')
+ assert.notStrictEqual(response.customData.vendorId, undefined)
+ assert.strictEqual(typeof response.customData.vendorId, 'string')
}
})
const response: OCPP20DeleteCertificateResponse =
await testableService.handleRequestDeleteCertificate(station, request)
- expect(response.status).toBe(DeleteCertificateStatusEnumType.Failed)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
- expect(typeof response.statusInfo?.reasonCode).toBe('string')
- expect(response.statusInfo?.reasonCode.length).toBeGreaterThan(0)
- expect(response.statusInfo?.reasonCode.length).toBeLessThanOrEqual(20)
+ assert.strictEqual(response.status, DeleteCertificateStatusEnumType.Failed)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
+ assert.ok(response.statusInfo.reasonCode.length > 0)
+ assert.ok(response.statusInfo.reasonCode.length <= 20)
})
})
})
+import { millisecondsToSeconds } from 'date-fns'
/**
* @file Tests for OCPP20IncomingRequestService GetBaseReport
* @description Unit tests for OCPP 2.0 GetBaseReport command handling (B07)
*/
-import { expect } from '@std/expect'
-import { millisecondsToSeconds } from 'date-fns'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.Accepted)
})
// FR: B08.FR.02
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.Accepted)
})
await it('should include registry variables with Actual attribute only for unsupported types', () => {
item.variable.name === (OCPP20OptionalVariableName.HeartbeatInterval as string) &&
item.component.name === (OCPP20ComponentName.OCPPCommCtrlr as string)
)
- expect(heartbeatEntry).toBeDefined()
+ assert.notStrictEqual(heartbeatEntry, undefined)
if (heartbeatEntry) {
const types =
heartbeatEntry.variableAttribute?.map((a: { type?: string; value?: string }) => a.type) ??
[]
- expect(types).toStrictEqual([AttributeEnumType.Actual])
+ assert.deepStrictEqual(types, [AttributeEnumType.Actual])
}
// Boolean variable (AuthorizeRemoteStart) should only include Actual
const authorizeRemoteStartEntry = reportData.find(
item.variable.name === (OCPP20RequiredVariableName.AuthorizeRemoteStart as string) &&
item.component.name === (OCPP20ComponentName.AuthCtrlr as string)
)
- expect(authorizeRemoteStartEntry).toBeDefined()
+ assert.notStrictEqual(authorizeRemoteStartEntry, undefined)
if (authorizeRemoteStartEntry) {
const types =
authorizeRemoteStartEntry.variableAttribute?.map(
(a: { type?: string; value?: string }) => a.type
) ?? []
- expect(types).toStrictEqual([AttributeEnumType.Actual])
+ assert.deepStrictEqual(types, [AttributeEnumType.Actual])
}
})
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.Accepted)
})
// FR: B08.FR.04
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.NotSupported)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.NotSupported)
})
// FR: B08.FR.05
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.Accepted)
})
// FR: B08.FR.06
// and checking if it returns Accepted status (which means data was built successfully)
const response = testableService.handleRequestGetBaseReport(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericDeviceModelStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericDeviceModelStatusEnumType.Accepted)
// We can also test the buildReportData method directly if needed
const reportData = testableService.buildReportData(
ReportBaseEnumType.ConfigurationInventory
)
- expect(Array.isArray(reportData)).toBe(true)
- expect(reportData.length).toBeGreaterThan(0)
+ assert.ok(Array.isArray(reportData))
+ assert.ok(reportData.length > 0)
// Check that each report data item has the expected structure
for (const item of reportData) {
- expect(item.component).toBeDefined()
- expect(item.component.name).toBeDefined()
- expect(item.variable).toBeDefined()
- expect(item.variable.name).toBeDefined()
- expect(item.variableAttribute).toBeDefined()
- expect(Array.isArray(item.variableAttribute)).toBe(true)
- expect(item.variableCharacteristics).toBeDefined()
+ assert.notStrictEqual(item.component, undefined)
+ assert.notStrictEqual(item.component.name, undefined)
+ assert.notStrictEqual(item.variable, undefined)
+ assert.notStrictEqual(item.variable.name, undefined)
+ assert.notStrictEqual(item.variableAttribute, undefined)
+ assert.ok(Array.isArray(item.variableAttribute))
+ assert.notStrictEqual(item.variableCharacteristics, undefined)
}
})
await it('should build correct report data for FullInventory with station info', () => {
const reportData = testableService.buildReportData(station, ReportBaseEnumType.FullInventory)
- expect(Array.isArray(reportData)).toBe(true)
- expect(reportData.length).toBeGreaterThan(0)
+ assert.ok(Array.isArray(reportData))
+ assert.ok(reportData.length > 0)
// Check for station info variables
const modelVariable = reportData.find(
item.variable.name === (OCPP20DeviceInfoVariableName.Model as string) &&
item.component.name === (OCPP20ComponentName.ChargingStation as string)
)
- expect(modelVariable).toBeDefined()
+ assert.notStrictEqual(modelVariable, undefined)
if (modelVariable) {
- expect(modelVariable.variableAttribute?.[0]?.value).toBe(TEST_CHARGE_POINT_MODEL)
+ assert.strictEqual(modelVariable.variableAttribute?.[0]?.value, TEST_CHARGE_POINT_MODEL)
}
const vendorVariable = reportData.find(
item.variable.name === (OCPP20DeviceInfoVariableName.VendorName as string) &&
item.component.name === (OCPP20ComponentName.ChargingStation as string)
)
- expect(vendorVariable).toBeDefined()
+ assert.notStrictEqual(vendorVariable, undefined)
if (vendorVariable) {
- expect(vendorVariable.variableAttribute?.[0]?.value).toBe(TEST_CHARGE_POINT_VENDOR)
+ assert.strictEqual(vendorVariable.variableAttribute?.[0]?.value, TEST_CHARGE_POINT_VENDOR)
}
})
await it('should build correct report data for SummaryInventory', () => {
const reportData = testableService.buildReportData(station, ReportBaseEnumType.SummaryInventory)
- expect(Array.isArray(reportData)).toBe(true)
- expect(reportData.length).toBeGreaterThan(0)
+ assert.ok(Array.isArray(reportData))
+ assert.ok(reportData.length > 0)
// Check for availability state variable
const availabilityVariable = reportData.find(
item.variable.name === (OCPP20DeviceInfoVariableName.AvailabilityState as string) &&
item.component.name === (OCPP20ComponentName.ChargingStation as string)
)
- expect(availabilityVariable).toBeDefined()
+ assert.notStrictEqual(availabilityVariable, undefined)
if (availabilityVariable) {
- expect(availabilityVariable.variableCharacteristics?.supportsMonitoring).toBe(true)
+ assert.strictEqual(availabilityVariable.variableCharacteristics?.supportsMonitoring, true)
}
})
variable: { name: OCPP20RequiredVariableName.TimeSource },
},
])
- expect(setResult[0].attributeStatus).toBe('Accepted')
+ assert.strictEqual(setResult[0].attributeStatus, 'Accepted')
// Build report; value should be truncated to length 10
const reportData = testableService.buildReportData(station, ReportBaseEnumType.FullInventory)
item.variable.name === (OCPP20RequiredVariableName.TimeSource as string) &&
item.component.name === (OCPP20ComponentName.ClockCtrlr as string)
)
- expect(timeSourceEntry).toBeDefined()
+ assert.notStrictEqual(timeSourceEntry, undefined)
if (timeSourceEntry) {
const reportedAttr = timeSourceEntry.variableAttribute?.find(
(a: { type?: string; value?: string }) => a.type === AttributeEnumType.Actual
)
- expect(reportedAttr).toBeDefined()
+ assert.notStrictEqual(reportedAttr, undefined)
if (reportedAttr && typeof reportedAttr.value === 'string') {
- expect(reportedAttr.value.length).toBe(10)
- expect(longValue.startsWith(reportedAttr.value)).toBe(true)
+ assert.strictEqual(reportedAttr.value.length, 10)
+ assert.ok(longValue.startsWith(reportedAttr.value))
}
}
})
ReportBaseEnumType.FullInventory
)
- expect(Array.isArray(reportData)).toBe(true)
- expect(reportData.length).toBeGreaterThan(0)
+ assert.ok(Array.isArray(reportData))
+ assert.ok(reportData.length > 0)
// Check if EVSE components are included when EVSEs exist
const evseComponents = reportData.filter(
(item: ReportDataType) => item.component.name === (OCPP20ComponentName.EVSE as string)
)
if (stationWithEvses.hasEvses) {
- expect(evseComponents.length).toBeGreaterThan(0)
+ assert.ok(evseComponents.length > 0)
}
})
'InvalidReportBase' as unknown as ReportBaseEnumType
)
- expect(Array.isArray(reportData)).toBe(true)
- expect(reportData.length).toBe(0)
+ assert.ok(Array.isArray(reportData))
+ assert.strictEqual(reportData.length, 0)
})
})
* @description Unit tests for OCPP 2.0 GetInstalledCertificateIds command handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.Accepted)
- expect(response.certificateHashDataChain).toBeDefined()
- expect(Array.isArray(response.certificateHashDataChain)).toBe(true)
- expect(response.certificateHashDataChain?.length).toBe(3)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.Accepted)
+ assert.notStrictEqual(response.certificateHashDataChain, undefined)
+ assert.ok(Array.isArray(response.certificateHashDataChain))
+ assert.strictEqual(response.certificateHashDataChain.length, 3)
})
})
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.Accepted)
- expect(response.certificateHashDataChain).toBeDefined()
- expect(response.certificateHashDataChain?.length).toBe(1)
- expect(response.certificateHashDataChain?.[0].certificateType).toBe(
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.Accepted)
+ assert.notStrictEqual(response.certificateHashDataChain, undefined)
+ if (response.certificateHashDataChain == null) {
+ assert.fail('Expected certificateHashDataChain to be defined')
+ }
+ assert.strictEqual(response.certificateHashDataChain.length, 1)
+ assert.strictEqual(
+ response.certificateHashDataChain[0].certificateType,
GetCertificateIdUseEnumType.V2GRootCertificate
)
})
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.Accepted)
- expect(response.certificateHashDataChain?.length).toBe(2)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.certificateHashDataChain?.length, 2)
})
})
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
// Per OCPP 2.0.1 spec: NotFound is returned when no certificates match the request
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.NotFound)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.NotFound)
// Per OCPP spec: certificateHashDataChain is omitted when empty, not an empty array
- expect(response.certificateHashDataChain).toBeUndefined()
+ assert.strictEqual(response.certificateHashDataChain, undefined)
})
await it('should return NotFound when filtered type has no certificates', async () => {
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.NotFound)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.NotFound)
})
})
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect([
- GetInstalledCertificateStatusEnumType.Accepted,
- GetInstalledCertificateStatusEnumType.NotFound,
- ]).toContain(response.status)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [
+ GetInstalledCertificateStatusEnumType.Accepted,
+ GetInstalledCertificateStatusEnumType.NotFound,
+ ].includes(response.status)
+ )
})
await it('should return valid CertificateHashDataChain structure', async () => {
const response: OCPP20GetInstalledCertificateIdsResponse =
await testableService.handleRequestGetInstalledCertificateIds(station, request)
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.Accepted)
- expect(response.certificateHashDataChain).toBeDefined()
- expect(response.certificateHashDataChain?.length).toBe(1)
-
- const chain = response.certificateHashDataChain?.[0]
- expect(chain?.certificateType).toBeDefined()
- expect(chain?.certificateHashData).toBeDefined()
- expect(chain?.certificateHashData.hashAlgorithm).toBeDefined()
- expect(chain?.certificateHashData.issuerNameHash).toBeDefined()
- expect(chain?.certificateHashData.issuerKeyHash).toBeDefined()
- expect(chain?.certificateHashData.serialNumber).toBeDefined()
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.Accepted)
+ assert.notStrictEqual(response.certificateHashDataChain, undefined)
+ if (response.certificateHashDataChain == null) {
+ assert.fail('Expected certificateHashDataChain to be defined')
+ }
+ assert.strictEqual(response.certificateHashDataChain.length, 1)
+
+ const chain = response.certificateHashDataChain[0]
+ assert.notStrictEqual(chain.certificateType, undefined)
+ assert.notStrictEqual(chain.certificateHashData, undefined)
+ assert.notStrictEqual(chain.certificateHashData.hashAlgorithm, undefined)
+ assert.notStrictEqual(chain.certificateHashData.issuerNameHash, undefined)
+ assert.notStrictEqual(chain.certificateHashData.issuerKeyHash, undefined)
+ assert.notStrictEqual(chain.certificateHashData.serialNumber, undefined)
})
})
request
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetInstalledCertificateStatusEnumType.NotFound)
- expect(response.statusInfo).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetInstalledCertificateStatusEnumType.NotFound)
+ assert.notStrictEqual(response.statusInfo, undefined)
})
})
})
+import { millisecondsToSeconds } from 'date-fns'
/**
* @file Tests for OCPP20IncomingRequestService GetVariables
* @description Unit tests for OCPP 2.0 GetVariables command handling (B06)
*/
-import { expect } from '@std/expect'
-import { millisecondsToSeconds } from 'date-fns'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response).toBeDefined()
- expect(response.getVariableResult).toBeDefined()
- expect(Array.isArray(response.getVariableResult)).toBe(true)
- expect(response.getVariableResult).toHaveLength(2)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.getVariableResult, undefined)
+ assert.ok(Array.isArray(response.getVariableResult))
+ assert.strictEqual(response.getVariableResult.length, 2)
// Check first variable (HeartbeatInterval)
const firstResult = response.getVariableResult[0]
- expect(firstResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(firstResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(firstResult.attributeValue).toBe(
+ assert.strictEqual(firstResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(firstResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ firstResult.attributeValue,
millisecondsToSeconds(Constants.DEFAULT_HEARTBEAT_INTERVAL).toString()
)
- expect(firstResult.component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(firstResult.variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(firstResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(firstResult.component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(firstResult.variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.strictEqual(firstResult.attributeStatusInfo, undefined)
// Check second variable (WebSocketPingInterval)
const secondResult = response.getVariableResult[1]
- expect(secondResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(secondResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(secondResult.attributeValue).toBe(Constants.DEFAULT_WEBSOCKET_PING_INTERVAL.toString())
- expect(secondResult.component.name).toBe(OCPP20ComponentName.ChargingStation)
- expect(secondResult.variable.name).toBe(OCPP20OptionalVariableName.WebSocketPingInterval)
- expect(secondResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(secondResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(secondResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ secondResult.attributeValue,
+ Constants.DEFAULT_WEBSOCKET_PING_INTERVAL.toString()
+ )
+ assert.strictEqual(secondResult.component.name, OCPP20ComponentName.ChargingStation)
+ assert.strictEqual(secondResult.variable.name, OCPP20OptionalVariableName.WebSocketPingInterval)
+ assert.strictEqual(secondResult.attributeStatusInfo, undefined)
})
// FR: B06.FR.02
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response).toBeDefined()
- expect(response.getVariableResult).toBeDefined()
- expect(Array.isArray(response.getVariableResult)).toBe(true)
- expect(response.getVariableResult).toHaveLength(2)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.getVariableResult, undefined)
+ assert.ok(Array.isArray(response.getVariableResult))
+ assert.strictEqual(response.getVariableResult.length, 2)
// Check first variable (should be UnknownVariable)
const firstResult = response.getVariableResult[0]
- expect(firstResult.attributeStatus).toBe(GetVariableStatusEnumType.UnknownVariable)
+ assert.strictEqual(firstResult.attributeStatus, GetVariableStatusEnumType.UnknownVariable)
// Defaulted attributeType now Actual, not undefined
- expect(firstResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(firstResult.attributeValue).toBeUndefined()
- expect(firstResult.component.name).toBe(OCPP20ComponentName.ChargingStation)
- expect(firstResult.variable.name).toBe('InvalidVariable')
- expect(firstResult.attributeStatusInfo).toBeDefined()
+ assert.strictEqual(firstResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(firstResult.attributeValue, undefined)
+ assert.strictEqual(firstResult.component.name, OCPP20ComponentName.ChargingStation)
+ assert.strictEqual(firstResult.variable.name, 'InvalidVariable')
+ assert.notStrictEqual(firstResult.attributeStatusInfo, undefined)
// Check second variable (should be UnknownComponent)
const secondResult = response.getVariableResult[1]
- expect(secondResult.attributeStatus).toBe(GetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(secondResult.attributeStatus, GetVariableStatusEnumType.UnknownComponent)
// Defaulted attributeType now Actual, not undefined
- expect(secondResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(secondResult.attributeValue).toBeUndefined()
- expect(secondResult.component.name).toBe('InvalidComponent')
- expect(secondResult.variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(secondResult.attributeStatusInfo).toBeDefined()
+ assert.strictEqual(secondResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(secondResult.attributeValue, undefined)
+ assert.strictEqual(secondResult.component.name, 'InvalidComponent')
+ assert.strictEqual(secondResult.variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.notStrictEqual(secondResult.attributeStatusInfo, undefined)
})
// FR: B06.FR.03
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response).toBeDefined()
- expect(response.getVariableResult).toBeDefined()
- expect(Array.isArray(response.getVariableResult)).toBe(true)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.getVariableResult, undefined)
+ assert.ok(Array.isArray(response.getVariableResult))
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
})
// FR: B06.FR.04
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.UnknownComponent)
})
// FR: B06.FR.05
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
})
await it('should truncate variable value based on ReportingValueSize', () => {
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.attributeValue?.length).toBe(2)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.attributeValue?.length, 2)
resetReportingValueSize(station)
})
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.attributeValue).toBeDefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.notStrictEqual(result.attributeValue, undefined)
})
await it('should enforce ItemsPerMessage limit', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult.length).toBe(2)
+ assert.strictEqual(response.getVariableResult.length, 2)
for (const r of response.getVariableResult) {
- expect(r.attributeStatus).toBe(GetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBeDefined()
+ assert.strictEqual(r.attributeStatus, GetVariableStatusEnumType.Rejected)
+ assert.notStrictEqual(r.attributeStatusInfo?.reasonCode, undefined)
}
resetLimits(station)
})
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult.length).toBe(2)
+ assert.strictEqual(response.getVariableResult.length, 2)
response.getVariableResult.forEach(r => {
- expect(r.attributeStatus).toBe(GetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(r.attributeStatus, GetVariableStatusEnumType.Rejected)
+ assert.strictEqual(r.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
})
resetLimits(station)
})
setStrictLimits(station, 100, limit)
const response = incomingRequestService.handleRequestGetVariables(station, request)
const actualSize = Buffer.byteLength(JSON.stringify(response.getVariableResult), 'utf8')
- expect(actualSize).toBeGreaterThan(limit)
- expect(response.getVariableResult).toHaveLength(request.getVariableData.length)
+ assert.ok(actualSize > limit)
+ assert.strictEqual(response.getVariableResult.length, request.getVariableData.length)
response.getVariableResult.forEach(r => {
- expect(r.attributeStatus).toBe(GetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(r.attributeStatus, GetVariableStatusEnumType.Rejected)
+ assert.strictEqual(r.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
})
resetLimits(station)
})
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.component.name).toBe(OCPP20ComponentName.ClockCtrlr)
- expect(result.variable.name).toBe(OCPP20RequiredVariableName.DateTime)
- expect(result.attributeValue).toBeDefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.component.name, OCPP20ComponentName.ClockCtrlr)
+ assert.strictEqual(result.variable.name, OCPP20RequiredVariableName.DateTime)
+ assert.notStrictEqual(result.attributeValue, undefined)
})
await it('should retrieve MessageTimeout from OCPPCommCtrlr', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(result.component.instance).toBe('Default')
- expect(result.variable.name).toBe(OCPP20RequiredVariableName.MessageTimeout)
- expect(result.attributeValue).toBeDefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(result.component.instance, 'Default')
+ assert.strictEqual(result.variable.name, OCPP20RequiredVariableName.MessageTimeout)
+ assert.notStrictEqual(result.attributeValue, undefined)
})
await it('should retrieve TxUpdatedInterval from SampledDataCtrlr and show default value', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.component.name).toBe(OCPP20ComponentName.SampledDataCtrlr)
- expect(result.variable.name).toBe(OCPP20RequiredVariableName.TxUpdatedInterval)
- expect(result.attributeValue).toBe('30')
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.component.name, OCPP20ComponentName.SampledDataCtrlr)
+ assert.strictEqual(result.variable.name, OCPP20RequiredVariableName.TxUpdatedInterval)
+ assert.strictEqual(result.attributeValue, '30')
})
await it('should retrieve list/sequence defaults for FileTransferProtocols, TimeSource, NetworkConfigurationPriority', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(3)
+ assert.strictEqual(response.getVariableResult.length, 3)
const fileTransfer = response.getVariableResult[0]
- expect(fileTransfer.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(fileTransfer.attributeValue).toBe('HTTPS,FTPS,SFTP')
+ assert.strictEqual(fileTransfer.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(fileTransfer.attributeValue, 'HTTPS,FTPS,SFTP')
const timeSource = response.getVariableResult[1]
- expect(timeSource.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(timeSource.attributeValue).toBe('NTP,GPS,RealTimeClock,Heartbeat')
+ assert.strictEqual(timeSource.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(timeSource.attributeValue, 'NTP,GPS,RealTimeClock,Heartbeat')
const netConfigPriority = response.getVariableResult[2]
- expect(netConfigPriority.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(netConfigPriority.attributeValue).toBe('1,2,3')
+ assert.strictEqual(netConfigPriority.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(netConfigPriority.attributeValue, '1,2,3')
})
await it('should retrieve list defaults for TxStartedMeasurands, TxEndedMeasurands, TxUpdatedMeasurands', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(3)
+ assert.strictEqual(response.getVariableResult.length, 3)
const txStarted = response.getVariableResult[0]
- expect(txStarted.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(txStarted.attributeValue).toBe(
+ assert.strictEqual(txStarted.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ txStarted.attributeValue,
`${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.POWER_ACTIVE_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`
)
const txEnded = response.getVariableResult[1]
- expect(txEnded.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(txEnded.attributeValue).toBe(
+ assert.strictEqual(txEnded.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ txEnded.attributeValue,
`${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_INTERVAL},${OCPP20MeasurandEnumType.VOLTAGE}`
)
const txUpdated = response.getVariableResult[2]
- expect(txUpdated.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(txUpdated.attributeValue).toBe(
+ assert.strictEqual(txUpdated.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ txUpdated.attributeValue,
`${OCPP20MeasurandEnumType.ENERGY_ACTIVE_IMPORT_REGISTER},${OCPP20MeasurandEnumType.CURRENT_IMPORT},${OCPP20MeasurandEnumType.VOLTAGE}`
)
})
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result.attributeType).toBe(AttributeEnumType.Target)
- expect(result.attributeValue).toBeUndefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeType, AttributeEnumType.Target)
+ assert.strictEqual(result.attributeValue, undefined)
})
// FR: B06.FR.15
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.UnknownVariable)
- expect(result.attributeValue).toBeUndefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.UnknownVariable)
+ assert.strictEqual(result.attributeValue, undefined)
})
// FR: B06.FR.09
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Rejected)
- expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.WriteOnly)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Rejected)
+ assert.strictEqual(result.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.WriteOnly)
})
await it('should reject MinSet and MaxSet for WebSocketPingInterval', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(2)
+ assert.strictEqual(response.getVariableResult.length, 2)
const minSet = response.getVariableResult[0]
const maxSet = response.getVariableResult[1]
- expect(minSet.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(minSet.attributeType).toBe(AttributeEnumType.MinSet)
- expect(minSet.attributeValue).toBeUndefined()
- expect(maxSet.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(maxSet.attributeType).toBe(AttributeEnumType.MaxSet)
- expect(maxSet.attributeValue).toBeUndefined()
+ assert.strictEqual(minSet.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(minSet.attributeType, AttributeEnumType.MinSet)
+ assert.strictEqual(minSet.attributeValue, undefined)
+ assert.strictEqual(maxSet.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(maxSet.attributeType, AttributeEnumType.MaxSet)
+ assert.strictEqual(maxSet.attributeValue, undefined)
})
await it('should reject MinSet for MemberList variable TxStartPoint', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
})
await it('should reject MaxSet for variable SecurityProfile (Actual only)', () => {
],
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
- expect(response.getVariableResult).toHaveLength(1)
+ assert.strictEqual(response.getVariableResult.length, 1)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
})
await it('should apply ValueSize then ReportingValueSize sequential truncation', () => {
}
const response = incomingRequestService.handleRequestGetVariables(station, request)
const result = response.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.attributeValue).toBeDefined()
- expect(result.attributeValue?.length).toBeLessThanOrEqual(3)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.notStrictEqual(result.attributeValue, undefined)
+ if (result.attributeValue == null) {
+ assert.fail('Expected attributeValue to be defined')
+ }
+ assert.ok(result.attributeValue.length <= 3)
resetReportingValueSize(station)
})
})
* @description Unit tests for OCPP 2.0 InstallCertificate command handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
- expect(response.status).toBe(InstallCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should accept valid MORootCertificate', async () => {
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should accept valid CSMSRootCertificate', async () => {
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should accept valid ManufacturerRootCertificate', async () => {
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
})
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
- expect(typeof response.statusInfo?.reasonCode).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
+ assert.strictEqual(typeof response.statusInfo?.reasonCode, 'string')
})
await it('should reject expired certificate when validation is enabled', async () => {
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
delete (mockStation.stationInfo as Record<string, unknown>).validateCertificateExpiry
})
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(response.status).toBe(InstallCertificateStatusEnumType.Failed)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Failed)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.notStrictEqual(response.statusInfo?.reasonCode, undefined)
})
})
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
- expect(response.status).toBeDefined()
- expect([
- InstallCertificateStatusEnumType.Accepted,
- InstallCertificateStatusEnumType.Rejected,
- InstallCertificateStatusEnumType.Failed,
- ]).toContain(response.status)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [
+ InstallCertificateStatusEnumType.Accepted,
+ InstallCertificateStatusEnumType.Failed,
+ InstallCertificateStatusEnumType.Rejected,
+ ].includes(response.status)
+ )
if (response.statusInfo != null) {
- expect(response.statusInfo.reasonCode).toBeDefined()
- expect(typeof response.statusInfo.reasonCode).toBe('string')
+ assert.notStrictEqual(response.statusInfo.reasonCode, undefined)
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
if (response.statusInfo.additionalInfo != null) {
- expect(typeof response.statusInfo.additionalInfo).toBe('string')
+ assert.strictEqual(typeof response.statusInfo.additionalInfo, 'string')
}
}
if (response.customData != null) {
- expect(response.customData.vendorId).toBeDefined()
- expect(typeof response.customData.vendorId).toBe('string')
+ assert.notStrictEqual(response.customData.vendorId, undefined)
+ assert.strictEqual(typeof response.customData.vendorId, 'string')
}
})
const response: OCPP20InstallCertificateResponse =
await testableService.handleRequestInstallCertificate(mockStation, request)
- expect(response.status).toBe(InstallCertificateStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBeDefined()
- expect(typeof response.statusInfo?.reasonCode).toBe('string')
- expect(response.statusInfo?.reasonCode.length).toBeGreaterThan(0)
- expect(response.statusInfo?.reasonCode.length).toBeLessThanOrEqual(20)
+ assert.strictEqual(response.status, InstallCertificateStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(typeof response.statusInfo.reasonCode, 'string')
+ assert.ok(response.statusInfo.reasonCode.length > 0)
+ assert.ok(response.statusInfo.reasonCode.length <= 20)
})
})
})
* @description Unit tests for OCPP 2.0 remote start pre-authorization (G03.FR.03)
*/
-import { expect } from '@std/expect'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
}
// Then: Request structure should be valid
- expect(request.idToken.idToken).toBe('VALID_TOKEN_001')
- expect(request.idToken.type).toBe(OCPP20IdTokenEnumType.ISO14443)
- expect(request.evseId).toBe(1)
- expect(request.remoteStartId).toBe(12345)
+ assert.strictEqual(request.idToken.idToken, 'VALID_TOKEN_001')
+ assert.strictEqual(request.idToken.type, OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(request.evseId, 1)
+ assert.strictEqual(request.remoteStartId, 12345)
})
await it('should include remoteStartId in request', () => {
}
// Then: remoteStartId should be present
- expect(request.remoteStartId).toBeDefined()
- expect(typeof request.remoteStartId).toBe('number')
- expect(request.remoteStartId).toBe(12346)
+ assert.notStrictEqual(request.remoteStartId, undefined)
+ assert.strictEqual(typeof request.remoteStartId, 'number')
+ assert.strictEqual(request.remoteStartId, 12346)
})
await it('should specify valid EVSE ID', () => {
}
// Then: EVSE ID should be specified
- expect(request.evseId).toBeDefined()
- expect(request.evseId).toBe(1)
+ assert.notStrictEqual(request.evseId, undefined)
+ assert.strictEqual(request.evseId, 1)
})
})
}
// Then: Request structure should be valid
- expect(request.idToken.idToken).toBe('BLOCKED_TOKEN_001')
- expect(request.idToken.type).toBe(OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(request.idToken.idToken, 'BLOCKED_TOKEN_001')
+ assert.strictEqual(request.idToken.type, OCPP20IdTokenEnumType.ISO14443)
})
await it('should not modify connector status before authorization', () => {
// Given: Connector in initial state
// Then: Connector status should remain unchanged before processing
const connectorStatus = mockStation.getConnectorStatus(1)
- expect(connectorStatus?.transactionStarted).toBe(false)
- expect(connectorStatus?.status).toBe(ConnectorStatusEnum.Available)
+ if (connectorStatus == null) {
+ assert.fail('Expected connectorStatus to be defined')
+ }
+ assert.strictEqual(connectorStatus.transactionStarted, false)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Available)
})
})
}
// Then: Both tokens should be present
- expect(request.idToken).toBeDefined()
- expect(request.groupIdToken).toBeDefined()
- expect(request.idToken.idToken).toBe('USER_TOKEN_001')
+ assert.notStrictEqual(request.idToken, undefined)
+ assert.notStrictEqual(request.groupIdToken, undefined)
+ assert.strictEqual(request.idToken.idToken, 'USER_TOKEN_001')
if (request.groupIdToken) {
- expect(request.groupIdToken.idToken).toBe('GROUP_TOKEN_001')
+ assert.strictEqual(request.groupIdToken.idToken, 'GROUP_TOKEN_001')
}
})
}
// Then: Different token types should be supported
- expect(request.groupIdToken?.type).toBe(OCPP20IdTokenEnumType.Central)
- expect(request.idToken.type).toBe(OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(request.groupIdToken?.type, OCPP20IdTokenEnumType.Central)
+ assert.strictEqual(request.idToken.type, OCPP20IdTokenEnumType.ISO14443)
})
})
}
// Then: evseId should be null (will be rejected by handler)
- expect(request.evseId).toBeNull()
+ assert.strictEqual(request.evseId, null)
})
await it('should handle request with undefined evseId', () => {
}
// Then: evseId should be undefined (will be rejected by handler)
- expect(request.evseId).toBeUndefined()
+ assert.strictEqual(request.evseId, undefined)
})
})
// Then: Connector should have active transaction
const connectorStatus = mockStation.getConnectorStatus(1)
- expect(connectorStatus?.transactionStarted).toBe(true)
- expect(connectorStatus?.status).toBe(ConnectorStatusEnum.Occupied)
- expect(connectorStatus?.transactionId).toBe('existing-tx-123')
- expect(RequestStartStopStatusEnumType.Rejected).toBeDefined()
+ if (connectorStatus == null) {
+ assert.fail('Expected connectorStatus to be defined')
+ }
+ assert.strictEqual(connectorStatus.transactionStarted, true)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Occupied)
+ assert.strictEqual(connectorStatus.transactionId, 'existing-tx-123')
+ assert.notStrictEqual(RequestStartStopStatusEnumType.Rejected, undefined)
})
await it('should preserve existing transaction details', () => {
// Then: Existing transaction should be preserved
const connectorStatus = mockStation.getConnectorStatus(1)
- expect(connectorStatus?.transactionId).toBe(existingTransactionId)
- expect(connectorStatus?.transactionIdTag).toBe(existingTokenTag)
+ if (connectorStatus == null) {
+ assert.fail('Expected connectorStatus to be defined')
+ }
+ assert.strictEqual(connectorStatus.transactionId, existingTransactionId)
+ assert.strictEqual(connectorStatus.transactionIdTag, existingTokenTag)
})
})
}
// Then: Charging profile should be present with correct structure
- expect(request.chargingProfile).toBeDefined()
- expect(request.chargingProfile?.id).toBe(1)
- expect(request.chargingProfile?.chargingProfileKind).toBe(
+ assert.notStrictEqual(request.chargingProfile, undefined)
+ if (request.chargingProfile == null) {
+ assert.fail('Expected chargingProfile to be defined')
+ }
+ assert.strictEqual(request.chargingProfile.id, 1)
+ assert.strictEqual(
+ request.chargingProfile.chargingProfileKind,
OCPP20ChargingProfileKindEnumType.Absolute
)
- expect(request.chargingProfile?.chargingProfilePurpose).toBe(
+ assert.strictEqual(
+ request.chargingProfile.chargingProfilePurpose,
OCPP20ChargingProfilePurposeEnumType.TxProfile
)
- expect(request.chargingProfile?.stackLevel).toBe(0)
+ assert.strictEqual(request.chargingProfile.stackLevel, 0)
})
await it('should support different charging profile kinds', () => {
}
// Then: Recurring profile should be supported
- expect(request.chargingProfile?.chargingProfileKind).toBe(
+ if (request.chargingProfile == null) {
+ assert.fail('Expected chargingProfile to be defined')
+ }
+ assert.strictEqual(
+ request.chargingProfile.chargingProfileKind,
OCPP20ChargingProfileKindEnumType.Recurring
)
- expect(request.chargingProfile?.stackLevel).toBe(1)
+ assert.strictEqual(request.chargingProfile.stackLevel, 1)
})
await it('should support optional charging profile', () => {
}
// Then: Charging profile should be optional
- expect(request.chargingProfile).toBeUndefined()
+ assert.strictEqual(request.chargingProfile, undefined)
})
})
await describe('G03.FR.03.007 - Request validation checks', async () => {
await it('should validate response status enum values', () => {
// Then: Response status enum should have required values
- expect(RequestStartStopStatusEnumType.Accepted).toBeDefined()
- expect(RequestStartStopStatusEnumType.Rejected).toBeDefined()
+ assert.notStrictEqual(RequestStartStopStatusEnumType.Accepted, undefined)
+ assert.notStrictEqual(RequestStartStopStatusEnumType.Rejected, undefined)
})
await it('should support OCPP 2.0.1 version', () => {
assert(mockStation != null)
// Given: Station with OCPP 2.0.1
- expect(mockStation.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_201)
+ assert.strictEqual(mockStation.stationInfo?.ocppVersion, OCPPVersion.VERSION_201)
})
await it('should support idToken with additional info', () => {
}
// Then: Should accept idToken with additionalInfo
- expect(request.idToken.additionalInfo).toBeDefined()
- expect(request.idToken.additionalInfo?.length).toBe(1)
- expect(request.idToken.additionalInfo?.[0].additionalIdToken).toBe('ADDITIONAL_001')
+ assert.notStrictEqual(request.idToken.additionalInfo, undefined)
+ if (request.idToken.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(request.idToken.additionalInfo.length, 1)
+ assert.strictEqual(request.idToken.additionalInfo[0].additionalIdToken, 'ADDITIONAL_001')
})
await it('should support various idToken types', () => {
// Then: All token types should be defined
tokenTypes.forEach(tokenType => {
- expect(tokenType).toBeDefined()
+ assert.notStrictEqual(tokenType, undefined)
})
})
})
await describe('G03.FR.03.008 - Service initialization', async () => {
await it('should initialize OCPP20IncomingRequestService', () => {
// Then: Service should be initialized
- expect(service).toBeDefined()
- expect(service).toBeInstanceOf(OCPP20IncomingRequestService)
+ assert.notStrictEqual(service, undefined)
+ assert.ok(service instanceof OCPP20IncomingRequestService)
})
await it('should have valid charging station configuration', () => {
assert(mockStation != null)
// Then: Charging station should have required configuration
- expect(mockStation).toBeDefined()
- expect(mockStation.evses).toBeDefined()
- expect(mockStation.evses.size).toBeGreaterThan(0)
- expect(mockStation.stationInfo?.ocppVersion).toBe(OCPPVersion.VERSION_201)
+ assert.notStrictEqual(mockStation, undefined)
+ assert.notStrictEqual(mockStation.evses, undefined)
+ assert.ok(mockStation.evses.size > 0)
+ assert.strictEqual(mockStation.stationInfo?.ocppVersion, OCPPVersion.VERSION_201)
})
})
})
* @file Tests for OCPP20IncomingRequestService RequestStartTransaction
* @description Unit tests for OCPP 2.0 RequestStartTransaction command handling (F01/F02)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response = await testableService.handleRequestStartTransaction(mockStation, validRequest)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
- expect(response.transactionId).toBeDefined()
- expect(typeof response.transactionId).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response.transactionId, undefined)
+ assert.strictEqual(typeof response.transactionId, 'string')
})
// FR: F01.FR.17, F02.FR.05 - Verify remoteStartId and idToken are stored for later TransactionEvent
requestWithRemoteStartId
)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
- expect(response.transactionId).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response.transactionId, undefined)
const connectorStatus = spyChargingStation.getConnectorStatus(1)
- expect(connectorStatus).toBeDefined()
- expect(connectorStatus?.remoteStartId).toBe(42)
- expect(connectorStatus?.transactionIdTag).toBe('REMOTE_TOKEN_456')
- expect(connectorStatus?.transactionStarted).toBe(true)
- expect(connectorStatus?.transactionId).toBe(response.transactionId)
+ assert.notStrictEqual(connectorStatus, undefined)
+ if (connectorStatus == null) {
+ assert.fail('Expected connectorStatus to be defined')
+ }
+ assert.strictEqual(connectorStatus.remoteStartId, 42)
+ assert.strictEqual(connectorStatus.transactionIdTag, 'REMOTE_TOKEN_456')
+ assert.strictEqual(connectorStatus.transactionStarted, true)
+ assert.strictEqual(connectorStatus.transactionId, response.transactionId)
OCPPAuthServiceFactory.clearAllInstances()
})
requestWithGroupToken
)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
- expect(response.transactionId).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response.transactionId, undefined)
})
// OCPP 2.0.1 §2.10 ChargingProfile validation tests
requestWithValidProfile
)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
- expect(response.transactionId).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response.transactionId, undefined)
})
// OCPP 2.0.1 §2.10: RequestStartTransaction requires chargingProfilePurpose=TxProfile
requestWithInvalidProfile
)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
})
// OCPP 2.0.1 §2.10: transactionId MUST NOT be present at RequestStartTransaction time
requestWithTransactionIdProfile
)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
})
// FR: F01.FR.07
}
// Should throw OCPPError for invalid evseId
- await expect(
- testableService.handleRequestStartTransaction(mockStation, invalidEvseRequest)
- ).rejects.toThrow('EVSE 999 does not exist on charging station')
+ await assert.rejects(
+ testableService.handleRequestStartTransaction(mockStation, invalidEvseRequest),
+ { message: /EVSE 999 does not exist on charging station/ }
+ )
})
// FR: F01.FR.09, F01.FR.10
const response = await testableService.handleRequestStartTransaction(mockStation, secondRequest)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
- expect(response.transactionId).toBeDefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response.transactionId, undefined)
})
// FR: F02.FR.01
const response = await testableService.handleRequestStartTransaction(mockStation, validRequest)
// Verify response structure
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response).toHaveProperty('status')
- expect(response).toHaveProperty('transactionId')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.notStrictEqual(response.transactionId, undefined)
// Verify status is valid enum value
- expect(Object.values(RequestStartStopStatusEnumType)).toContain(response.status)
+ assert.ok(Object.values(RequestStartStopStatusEnumType).includes(response.status))
// Verify transactionId is a string (UUID format in OCPP 2.0)
- expect(typeof response.transactionId).toBe('string')
- expect(response.transactionId).toBeDefined()
- expect(response.transactionId?.length).toBeGreaterThan(0)
+ assert.strictEqual(typeof response.transactionId, 'string')
+ assert.notStrictEqual(response.transactionId, undefined)
+ if (response.transactionId == null) {
+ assert.fail('Expected transactionId to be defined')
+ }
+ assert.ok(response.transactionId.length > 0)
})
})
* @description Unit tests for OCPP 2.0 RequestStopTransaction command handling (F03)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
startRequest
)
- expect(startResponse.status).toBe(RequestStartStopStatusEnumType.Accepted)
- expect(startResponse.transactionId).toBeDefined()
+ assert.strictEqual(startResponse.status, RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(startResponse.transactionId, undefined)
return startResponse.transactionId as string
}
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
// Verify response
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
// Verify TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
const transactionEvent = sentTransactionEvents[0]
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
- expect(transactionEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStop)
- expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(transactionEvent.transactionInfo.stoppedReason).toBe(OCPP20ReasonEnumType.Remote)
- expect(transactionEvent.evse?.id).toBe(1)
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
+ assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(transactionEvent.transactionInfo.stoppedReason, OCPP20ReasonEnumType.Remote)
+ assert.strictEqual(transactionEvent.evse?.id, 1)
})
// FR: F03.FR.02, F03.FR.03
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
// Verify response
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
// Verify correct TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
const transactionEvent = sentTransactionEvents[0]
- expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId2)
- expect(transactionEvent.evse?.id).toBe(2)
+ assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId2)
+ assert.strictEqual(transactionEvent.evse?.id, 2)
// Verify other transactions are still active (test implementation dependent)
- expect(mockStation.getConnectorIdByTransactionId(transactionId1)).toBe(1)
- expect(mockStation.getConnectorIdByTransactionId(transactionId3)).toBe(3)
+ assert.strictEqual(mockStation.getConnectorIdByTransactionId(transactionId1), 1)
+ assert.strictEqual(mockStation.getConnectorIdByTransactionId(transactionId3), 3)
})
// FR: F03.FR.08
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
// Verify rejection
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
// Verify no TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(0)
+ assert.strictEqual(sentTransactionEvents.length, 0)
})
// FR: F03.FR.08
const response = await testableService.handleRequestStopTransaction(mockStation, invalidRequest)
// Verify rejection
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
// Verify no TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(0)
+ assert.strictEqual(sentTransactionEvents.length, 0)
})
// FR: F03.FR.08
const response = await testableService.handleRequestStopTransaction(mockStation, invalidRequest)
// Verify rejection
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
// Verify no TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(0)
+ assert.strictEqual(sentTransactionEvents.length, 0)
})
// FR: F03.FR.02
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
// Verify acceptance (format is valid)
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
// Verify TransactionEvent was sent
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
})
await it('should handle TransactionEvent request failure gracefully', async () => {
)
// Should be rejected due to TransactionEvent failure
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected)
})
// FR: F04.FR.01
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
// Verify response structure
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response).toHaveProperty('status')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
// Verify status is valid enum value
- expect(Object.values(RequestStartStopStatusEnumType)).toContain(response.status)
+ assert.ok(Object.values(RequestStartStopStatusEnumType).includes(response.status))
// OCPP 2.0 RequestStopTransaction response should only contain status
- expect(Object.keys(response as object)).toStrictEqual(['status'])
+ assert.deepStrictEqual(Object.keys(response as object), ['status'])
})
await it('should handle custom data in request payload', async () => {
)
// Verify response
- expect(response).toBeDefined()
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
// Verify TransactionEvent was sent despite custom data
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
})
// FR: F03.FR.07, F03.FR.09
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
// Verify TransactionEvent structure and content
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
const transactionEvent = sentTransactionEvents[0]
// Validate required fields
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
- expect(transactionEvent.timestamp).toBeDefined()
- expect(transactionEvent.timestamp).toBeInstanceOf(Date)
- expect(transactionEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStop)
- expect(transactionEvent.seqNo).toBeDefined()
- expect(typeof transactionEvent.seqNo).toBe('number')
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.notStrictEqual(transactionEvent.timestamp, undefined)
+ assert.ok(transactionEvent.timestamp instanceof Date)
+ assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
+ assert.notStrictEqual(transactionEvent.seqNo, undefined)
+ assert.strictEqual(typeof transactionEvent.seqNo, 'number')
// Validate transaction info
- expect(transactionEvent.transactionInfo).toBeDefined()
- expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(transactionEvent.transactionInfo.stoppedReason).toBe(OCPP20ReasonEnumType.Remote)
+ assert.notStrictEqual(transactionEvent.transactionInfo, undefined)
+ assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(transactionEvent.transactionInfo.stoppedReason, OCPP20ReasonEnumType.Remote)
// Validate EVSE info
- expect(transactionEvent.evse).toBeDefined()
- expect(transactionEvent.evse?.id).toBe(2) // Should match the EVSE we used
+ assert.notStrictEqual(transactionEvent.evse, undefined)
+ assert.strictEqual(transactionEvent.evse?.id, 2) // Should match the EVSE we used
})
// FR: F03.FR.09
const transactionId = await startTransaction(3, 700)
const connectorStatus = mockStation.getConnectorStatus(3)
- expect(connectorStatus).toBeDefined()
+ assert.notStrictEqual(connectorStatus, undefined)
if (connectorStatus != null) {
connectorStatus.transactionEnergyActiveImportRegisterValue = 12345.67
}
const response = await testableService.handleRequestStopTransaction(mockStation, stopRequest)
- expect(response.status).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.strictEqual(response.status, RequestStartStopStatusEnumType.Accepted)
- expect(sentTransactionEvents).toHaveLength(1)
+ assert.strictEqual(sentTransactionEvents.length, 1)
const transactionEvent = sentTransactionEvents[0]
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Ended)
- expect(transactionEvent.meterValue).toBeDefined()
- expect(transactionEvent.meterValue).toHaveLength(1)
+ assert.notStrictEqual(transactionEvent.meterValue, undefined)
+ if (transactionEvent.meterValue == null) {
+ assert.fail('Expected meterValue to be defined')
+ }
+ assert.strictEqual(transactionEvent.meterValue.length, 1)
- const meterValue = transactionEvent.meterValue?.[0]
- expect(meterValue).toBeDefined()
- if (meterValue == null) return
- expect(meterValue.timestamp).toBeInstanceOf(Date)
- expect(meterValue.sampledValue).toBeDefined()
- expect(meterValue.sampledValue).toHaveLength(1)
+ const meterValue = transactionEvent.meterValue[0]
+ assert.notStrictEqual(meterValue, undefined)
+ assert.ok(meterValue.timestamp instanceof Date)
+ assert.notStrictEqual(meterValue.sampledValue, undefined)
+ assert.strictEqual(meterValue.sampledValue.length, 1)
const sampledValue = meterValue.sampledValue[0]
- expect(sampledValue.value).toBe(12345.67)
- expect(sampledValue.context).toBe('Transaction.End')
- expect(sampledValue.measurand).toBe('Energy.Active.Import.Register')
+ assert.strictEqual(sampledValue.value, 12345.67)
+ assert.strictEqual(sampledValue.context, 'Transaction.End')
+ assert.strictEqual(sampledValue.measurand, 'Energy.Active.Import.Register')
})
})
* @description Unit tests for OCPP 2.0 Reset command handling (B11/B12)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type {
resetRequest
)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
- expect([
- ResetStatusEnumType.Accepted,
- ResetStatusEnumType.Rejected,
- ResetStatusEnumType.Scheduled,
- ]).toContain(response.status)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
+ assert.ok(
+ [
+ ResetStatusEnumType.Accepted,
+ ResetStatusEnumType.Rejected,
+ ResetStatusEnumType.Scheduled,
+ ].includes(response.status)
+ )
})
await it('should handle Reset request with OnIdle type when no transactions', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBeDefined()
- expect([
- ResetStatusEnumType.Accepted,
- ResetStatusEnumType.Rejected,
- ResetStatusEnumType.Scheduled,
- ]).toContain(response.status)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [
+ ResetStatusEnumType.Accepted,
+ ResetStatusEnumType.Rejected,
+ ResetStatusEnumType.Scheduled,
+ ].includes(response.status)
+ )
})
// FR: B11.FR.03
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBeDefined()
- expect([
- ResetStatusEnumType.Accepted,
- ResetStatusEnumType.Rejected,
- ResetStatusEnumType.Scheduled,
- ]).toContain(response.status)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [
+ ResetStatusEnumType.Accepted,
+ ResetStatusEnumType.Rejected,
+ ResetStatusEnumType.Scheduled,
+ ].includes(response.status)
+ )
})
await it('should reject reset for non-existent EVSE when no transactions', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnknownEvse)
- expect(response.statusInfo?.additionalInfo).toContain('EVSE 999')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnknownEvse)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('EVSE 999'))
})
await it('should return proper response structure for immediate reset without transactions', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBeDefined()
- expect(typeof response.status).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.strictEqual(typeof response.status, 'string')
// B11.FR.02: Immediate reset without transactions returns Accepted
if (mockStation.getNumberOfRunningTransactions() === 0) {
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
}
})
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
await it('should reject EVSE-specific reset when EVSEs not supported (non-EVSE mode)', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
- expect(response.statusInfo?.additionalInfo).toContain(
- 'does not support resetting individual EVSE'
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(
+ response.statusInfo.additionalInfo.includes('does not support resetting individual EVSE')
)
// Restore EVSE support
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
})
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted) // Should accept immediate reset
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted) // Should accept immediate reset
+ assert.strictEqual(response.statusInfo, undefined)
})
// FR: B12.FR.01
await it('should handle OnIdle reset with active transactions', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Scheduled) // Should schedule OnIdle reset
- expect(response.statusInfo).toBeUndefined()
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Scheduled) // Should schedule OnIdle reset
+ assert.strictEqual(response.statusInfo, undefined)
})
// FR: B12.FR.03
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBeDefined()
- expect([ResetStatusEnumType.Accepted, ResetStatusEnumType.Scheduled]).toContain(
- response.status
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.status, undefined)
+ assert.ok(
+ [ResetStatusEnumType.Accepted, ResetStatusEnumType.Scheduled].includes(response.status)
)
})
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
- expect(response.statusInfo?.additionalInfo).toContain(
- 'does not support resetting individual EVSE'
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(
+ response.statusInfo.additionalInfo.includes('does not support resetting individual EVSE')
)
// Restore EVSE support
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.FwUpdateInProgress)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.FwUpdateInProgress)
})
await it('should return Rejected/FwUpdateInProgress when firmware is Downloaded', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.FwUpdateInProgress)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.FwUpdateInProgress)
})
await it('should return Rejected/FwUpdateInProgress when firmware is Installing', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.FwUpdateInProgress)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.FwUpdateInProgress)
})
await it('should return Accepted when firmware is Installed (complete)', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
await it('should return Accepted when firmware status is Idle', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
})
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Scheduled)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Scheduled)
})
await it('should return Accepted when reservation is expired', async () => {
resetRequest
)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
// Expired reservation does not block idle state
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
await it('should return Accepted when no reservations exist', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
})
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
await it('should return Scheduled when multiple blocking conditions exist', async () => {
resetRequest
)
- expect(response).toBeDefined()
- expect(response.status).toBe(ResetStatusEnumType.Scheduled)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, ResetStatusEnumType.Scheduled)
})
})
})
VARIABLE_REGISTRY[ALLOW_RESET_KEY].defaultValue = 'false'
const request: OCPP20ResetRequest = { type: ResetEnumType.Immediate }
const response = await testableService.handleRequestReset(station, request)
- expect(response.status).toBe(ResetStatusEnumType.Rejected)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotEnabled)
+ assert.strictEqual(response.status, ResetStatusEnumType.Rejected)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.NotEnabled)
})
await it('should proceed normally when AllowReset is true', async () => {
VARIABLE_REGISTRY[ALLOW_RESET_KEY].defaultValue = 'true'
const request: OCPP20ResetRequest = { type: ResetEnumType.Immediate }
const response = await testableService.handleRequestReset(station, request)
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
await it('should proceed normally when AllowReset defaultValue is undefined', async () => {
VARIABLE_REGISTRY[ALLOW_RESET_KEY].defaultValue = undefined
const request: OCPP20ResetRequest = { type: ResetEnumType.Immediate }
const response = await testableService.handleRequestReset(station, request)
- expect(response.status).toBe(ResetStatusEnumType.Accepted)
+ assert.strictEqual(response.status, ResetStatusEnumType.Accepted)
})
})
})
* @description Unit tests for OCPP 2.0 SetVariables command handling
*/
-import { expect } from '@std/expect'
import { millisecondsToSeconds } from 'date-fns'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response).toBeDefined()
- expect(response.setVariableResult).toBeDefined()
- expect(Array.isArray(response.setVariableResult)).toBe(true)
- expect(response.setVariableResult).toHaveLength(2)
+ assert.notStrictEqual(response, undefined)
+ assert.notStrictEqual(response.setVariableResult, undefined)
+ assert.ok(Array.isArray(response.setVariableResult))
+ assert.strictEqual(response.setVariableResult.length, 2)
const firstResult = response.setVariableResult[0]
- expect(firstResult.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(firstResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(firstResult.component.name).toBe(OCPP20ComponentName.ChargingStation)
- expect(firstResult.variable.name).toBe(OCPP20OptionalVariableName.WebSocketPingInterval)
- expect(firstResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(firstResult.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(firstResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(firstResult.component.name, OCPP20ComponentName.ChargingStation)
+ assert.strictEqual(firstResult.variable.name, OCPP20OptionalVariableName.WebSocketPingInterval)
+ assert.strictEqual(firstResult.attributeStatusInfo, undefined)
const secondResult = response.setVariableResult[1]
- expect(secondResult.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(secondResult.attributeType).toBe(AttributeEnumType.Actual)
- expect(secondResult.component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(secondResult.variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(secondResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(secondResult.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(secondResult.attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(secondResult.component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(secondResult.variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.strictEqual(secondResult.attributeStatusInfo, undefined)
})
// FR: B07.FR.02
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(2)
+ assert.strictEqual(response.setVariableResult.length, 2)
const firstResult = response.setVariableResult[0]
- expect(firstResult.attributeStatus).toBe(SetVariableStatusEnumType.UnknownVariable)
- expect(firstResult.attributeStatusInfo).toBeDefined()
+ assert.strictEqual(firstResult.attributeStatus, SetVariableStatusEnumType.UnknownVariable)
+ assert.notStrictEqual(firstResult.attributeStatusInfo, undefined)
const secondResult = response.setVariableResult[1]
- expect(secondResult.attributeStatus).toBe(SetVariableStatusEnumType.UnknownComponent)
- expect(secondResult.attributeStatusInfo).toBeDefined()
+ assert.strictEqual(secondResult.attributeStatus, SetVariableStatusEnumType.UnknownComponent)
+ assert.notStrictEqual(secondResult.attributeStatusInfo, undefined)
})
// FR: B07.FR.03
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const result = response.setVariableResult[0]
- expect(result.attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(result.attributeStatus, SetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedParam)
})
// FR: B07.FR.04
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const result = response.setVariableResult[0]
- expect(result.attributeStatus).toBe(SetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(result.attributeStatus, SetVariableStatusEnumType.UnknownComponent)
})
// FR: B07.FR.05
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const result = response.setVariableResult[0]
- expect(result.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(result.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(result.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
})
// FR: B07.FR.07
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(5)
+ assert.strictEqual(response.setVariableResult.length, 5)
const [accepted, unknownVariable, unsupportedAttrHeartbeat, unsupportedAttrWs, oversize] =
response.setVariableResult
- expect(accepted.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(accepted.attributeStatusInfo).toBeUndefined()
- expect(unknownVariable.attributeStatus).toBe(SetVariableStatusEnumType.UnknownVariable)
- expect(unsupportedAttrHeartbeat.attributeStatus).toBe(
+ assert.strictEqual(accepted.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(accepted.attributeStatusInfo, undefined)
+ assert.strictEqual(unknownVariable.attributeStatus, SetVariableStatusEnumType.UnknownVariable)
+ assert.strictEqual(
+ unsupportedAttrHeartbeat.attributeStatus,
SetVariableStatusEnumType.NotSupportedAttributeType
)
- expect(unsupportedAttrWs.attributeStatus).toBe(
+ assert.strictEqual(
+ unsupportedAttrWs.attributeStatus,
SetVariableStatusEnumType.NotSupportedAttributeType
)
- expect(oversize.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(oversize.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(oversize.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(oversize.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
})
// FR: B07.FR.08
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const result = response.setVariableResult[0]
- expect(result.attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(result.attributeStatus, SetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(result.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedParam)
})
// FR: B07.FR.09
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const result = response.setVariableResult[0]
- expect(result.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(result.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ReadOnly)
+ assert.strictEqual(result.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(result.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.ReadOnly)
})
// FR: B07.FR.10
],
})
- expect(getResponse.getVariableResult).toHaveLength(2)
+ assert.strictEqual(getResponse.getVariableResult.length, 2)
const hbResult = getResponse.getVariableResult[0]
const wsResult = getResponse.getVariableResult[1]
- expect(hbResult.attributeStatus).toBeDefined()
- expect(hbResult.attributeValue).toBe(hbNew)
- expect(wsResult.attributeValue).toBe(wsNew)
+ assert.notStrictEqual(hbResult.attributeStatus, undefined)
+ assert.strictEqual(hbResult.attributeValue, hbNew)
+ assert.strictEqual(wsResult.attributeValue, wsNew)
})
// FR: B07.FR.11
},
],
})
- expect(getBefore.getVariableResult[0].attributeValue).toBe(txValue)
+ assert.strictEqual(getBefore.getVariableResult[0].attributeValue, txValue)
const { OCPP20VariableManager } =
await import('../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js')
},
],
})
- expect(getAfter.getVariableResult[0].attributeValue).toBe('30') // default
+ assert.strictEqual(getAfter.getVariableResult[0].attributeValue, '30') // default
})
// FR: B07.FR.12
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(2)
+ assert.strictEqual(response.setVariableResult.length, 2)
response.setVariableResult.forEach(r => {
- expect(r.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooManyElements)
- expect(r.attributeStatusInfo?.additionalInfo).toMatch(/ItemsPerMessage limit 1 exceeded/)
+ assert.strictEqual(r.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (r.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ assert.strictEqual(r.attributeStatusInfo.reasonCode, ReasonCodeEnumType.TooManyElements)
+ if (r.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.match(r.attributeStatusInfo.additionalInfo, /ItemsPerMessage limit 1 exceeded/)
})
resetLimits(mockStation)
})
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
- expect(response.setVariableResult).toHaveLength(2)
+ assert.strictEqual(response.setVariableResult.length, 2)
response.setVariableResult.forEach(r => {
- expect(r.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
- expect(r.attributeStatusInfo?.additionalInfo).toMatch(/BytesPerMessage limit 10 exceeded/)
+ assert.strictEqual(r.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (r.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ assert.strictEqual(r.attributeStatusInfo.reasonCode, ReasonCodeEnumType.TooLargeElement)
+ if (r.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.match(r.attributeStatusInfo.additionalInfo, /BytesPerMessage limit 10 exceeded/)
})
resetLimits(mockStation)
})
postCalcLimit.toString(),
false
)
- expect(preEstimate).toBeLessThan(postCalcLimit)
+ assert.ok(preEstimate < postCalcLimit)
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, request)
const actualSize = Buffer.byteLength(JSON.stringify(response.setVariableResult), 'utf8')
- expect(actualSize).toBeGreaterThan(postCalcLimit)
- expect(response.setVariableResult).toHaveLength(request.setVariableData.length)
+ assert.ok(actualSize > postCalcLimit)
+ assert.strictEqual(response.setVariableResult.length, request.setVariableData.length)
response.setVariableResult.forEach(r => {
- expect(r.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(r.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
- expect(r.attributeStatusInfo?.additionalInfo).toMatch(
+ assert.strictEqual(r.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (r.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ assert.strictEqual(r.attributeStatusInfo.reasonCode, ReasonCodeEnumType.TooLargeElement)
+ if (r.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.match(
+ r.attributeStatusInfo.additionalInfo,
new RegExp(`BytesPerMessage limit ${postCalcLimit.toString()} exceeded`)
)
})
},
],
})
- expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ response.setVariableResult[0].attributeStatus,
+ SetVariableStatusEnumType.Accepted
+ )
response = testableService.handleRequestSetVariables(mockStation, {
setVariableData: [
{
],
})
const res = response.setVariableResult[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
resetValueSizeLimits(mockStation)
})
},
],
})
- expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ response.setVariableResult[0].attributeStatus,
+ SetVariableStatusEnumType.Accepted
+ )
response = testableService.handleRequestSetVariables(mockStation, {
setVariableData: [
{
],
})
const res = response.setVariableResult[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
resetValueSizeLimits(mockStation)
})
},
],
})
- expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ response.setVariableResult[0].attributeStatus,
+ SetVariableStatusEnumType.Accepted
+ )
response = testableService.handleRequestSetVariables(mockStation, {
setVariableData: [
{
],
})
const res = response.setVariableResult[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
resetValueSizeLimits(mockStation)
})
},
],
})
- expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(
+ response.setVariableResult[0].attributeStatus,
+ SetVariableStatusEnumType.Accepted
+ )
response = testableService.handleRequestSetVariables(mockStation, {
setVariableData: [
{
],
})
const res = response.setVariableResult[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.TooLargeElement)
resetValueSizeLimits(mockStation)
})
],
})
const res = response.setVariableResult[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
resetValueSizeLimits(mockStation)
})
},
],
})
- expect(getResponse.getVariableResult).toHaveLength(1)
+ assert.strictEqual(getResponse.getVariableResult.length, 1)
const result = getResponse.getVariableResult[0]
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result.attributeValue).toBe(url)
- expect(result.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.attributeValue, url)
+ assert.strictEqual(result.attributeStatusInfo, undefined)
resetLimits(mockStation)
})
}
const response: { setVariableResult: OCPP20SetVariableResultType[] } =
testableService.handleRequestSetVariables(mockStation, setRequest)
- expect(response.setVariableResult).toHaveLength(1)
+ assert.strictEqual(response.setVariableResult.length, 1)
const setResult = response.setVariableResult[0]
- expect(setResult.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(setResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(setResult.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setResult.attributeStatusInfo, undefined)
const getResponse: { getVariableResult: OCPP20GetVariableResultType[] } =
testableService.handleRequestGetVariables(mockStation, {
getVariableData: [
},
],
})
- expect(getResponse.getVariableResult).toHaveLength(1)
+ assert.strictEqual(getResponse.getVariableResult.length, 1)
const getResult = getResponse.getVariableResult[0]
- expect(getResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getResult.attributeValue).toBe(url)
- expect(getResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(getResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getResult.attributeValue, url)
+ assert.strictEqual(getResult.attributeStatusInfo, undefined)
resetLimits(mockStation)
})
})
* @description Unit tests for OCPP 2.0 TriggerMessage command handling (F06)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should return Accepted for Heartbeat trigger', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should return Accepted for StatusNotification trigger without EVSE', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should return Accepted for StatusNotification trigger with valid EVSE and connector', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should not validate EVSE when evse.id is 0', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
})
})
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.NotImplemented)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
- expect(response.statusInfo?.additionalInfo).toContain('MeterValues')
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.NotImplemented)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('MeterValues'))
})
await it('should return NotImplemented for TransactionEvent trigger', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.NotImplemented)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.NotImplemented)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
})
await it('should return NotImplemented for LogStatusNotification trigger', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.NotImplemented)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.NotImplemented)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
})
await it('should return NotImplemented for FirmwareStatusNotification trigger', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.NotImplemented)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.NotImplemented)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
})
})
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
- expect(response.statusInfo?.additionalInfo).toContain('does not support EVSEs')
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('does not support EVSEs'))
})
await it('should return Rejected with UnknownEvse for non-existent EVSE id', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnknownEvse)
- expect(response.statusInfo?.additionalInfo).toContain('999')
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnknownEvse)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('999'))
})
await it('should accept trigger when evse is undefined', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
})
})
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotEnabled)
- expect(response.statusInfo?.additionalInfo).toContain('F06.FR.17')
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Rejected)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.NotEnabled)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('F06.FR.17'))
})
await it('should return Accepted for BootNotification when boot was Rejected', () => {
request
)
- expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted)
+ assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted)
})
})
const response = testableService.handleRequestTriggerMessage(mockStation, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(typeof response.status).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.strictEqual(typeof response.status, 'string')
})
await it('should not return a Promise from synchronous handler', () => {
const result = testableService.handleRequestTriggerMessage(mockStation, request)
// A Promise would have a `then` property that is a function
- expect(typeof (result as unknown as Promise<unknown>).then).not.toBe('function')
+ assert.notStrictEqual(typeof (result as unknown as Promise<unknown>).then, 'function')
})
})
})
* @description Unit tests for OCPP 2.0 UnlockConnector command handling (F05)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type {
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.UnknownConnector)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedRequest)
- expect(response.statusInfo?.additionalInfo).toContain('does not support EVSEs')
+ assert.strictEqual(response.status, UnlockStatusEnumType.UnknownConnector)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnsupportedRequest)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('does not support EVSEs'))
})
})
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.UnknownConnector)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnknownEvse)
- expect(response.statusInfo?.additionalInfo).toContain('999')
+ assert.strictEqual(response.status, UnlockStatusEnumType.UnknownConnector)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnknownEvse)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('999'))
})
})
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.UnknownConnector)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnknownConnectorId)
- expect(response.statusInfo?.additionalInfo).toContain('99')
- expect(response.statusInfo?.additionalInfo).toContain('1')
+ assert.strictEqual(response.status, UnlockStatusEnumType.UnknownConnector)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.UnknownConnectorId)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('99'))
+ assert.ok(response.statusInfo.additionalInfo.includes('1'))
})
})
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.OngoingAuthorizedTransaction)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.TxInProgress)
- expect(response.statusInfo?.additionalInfo).toContain('1')
+ assert.strictEqual(response.status, UnlockStatusEnumType.OngoingAuthorizedTransaction)
+ if (response.statusInfo == null) {
+ assert.fail('Expected statusInfo to be defined')
+ }
+ assert.strictEqual(response.statusInfo.reasonCode, ReasonCodeEnumType.TxInProgress)
+ if (response.statusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.ok(response.statusInfo.additionalInfo.includes('1'))
})
await it('should return Unlocked when a different connector on the same EVSE has a transaction (F05.FR.02)', async () => {
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(multiConnectorStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.Unlocked)
+ assert.strictEqual(response.status, UnlockStatusEnumType.Unlocked)
})
})
const response: OCPP20UnlockConnectorResponse =
await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.Unlocked)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, UnlockStatusEnumType.Unlocked)
+ assert.strictEqual(response.statusInfo, undefined)
})
await it('should call requestHandler (StatusNotification) to set connector status Available after unlock', async () => {
await testableService.handleRequestUnlockConnector(mockStation, request)
// sendAndSetConnectorStatus calls requestHandler internally for StatusNotification
- expect(requestHandlerMock.mock.calls.length).toBeGreaterThan(0)
+ assert.ok(requestHandlerMock.mock.calls.length > 0)
})
await it('should return a Promise from async handler', async () => {
const result = testableService.handleRequestUnlockConnector(mockStation, request)
- expect(typeof (result as unknown as Promise<unknown>).then).toBe('function')
+ assert.strictEqual(typeof (result as unknown as Promise<unknown>).then, 'function')
await result
})
})
const request: OCPP20UnlockConnectorRequest = { connectorId: 1, evseId: 1 }
const response = await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
- expect(typeof response.status).toBe('string')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
+ assert.strictEqual(typeof response.status, 'string')
})
await it('should not include statusInfo on successful unlock', async () => {
const request: OCPP20UnlockConnectorRequest = { connectorId: 1, evseId: 1 }
const response = await testableService.handleRequestUnlockConnector(mockStation, request)
- expect(response.status).toBe(UnlockStatusEnumType.Unlocked)
- expect(response.statusInfo).toBeUndefined()
+ assert.strictEqual(response.status, UnlockStatusEnumType.Unlocked)
+ assert.strictEqual(response.statusInfo, undefined)
})
})
})
* @description Verifies certificate install, list, and delete operations end-to-end
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
installRequest
)
- expect(installResponse.status).toBeDefined()
- expect(Object.values(InstallCertificateStatusEnumType)).toContain(installResponse.status)
+ assert.notStrictEqual(installResponse.status, undefined)
+ assert.ok(Object.values(InstallCertificateStatusEnumType).includes(installResponse.status))
})
await it('should respond to GetInstalledCertificateIds without throwing', async () => {
getRequest
)
- expect(getResponse).toBeDefined()
- expect(getResponse.status).toBeDefined()
- expect(
+ assert.notStrictEqual(getResponse, undefined)
+ assert.notStrictEqual(getResponse.status, undefined)
+ assert.strictEqual(
getResponse.certificateHashDataChain === undefined ||
- Array.isArray(getResponse.certificateHashDataChain)
- ).toBe(true)
+ Array.isArray(getResponse.certificateHashDataChain),
+ true
+ )
})
await it('should handle DeleteCertificate request without throwing even for unknown cert hash', async () => {
deleteRequest
)
- expect(deleteResponse.status).toBeDefined()
+ assert.notStrictEqual(deleteResponse.status, undefined)
})
})
* @description Verifies that SetVariables and GetVariables produce consistent results
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
setRequest
)
- expect(setResponse.setVariableResult).toHaveLength(1)
+ assert.strictEqual(setResponse.setVariableResult.length, 1)
const setResult = setResponse.setVariableResult[0]
- expect(setResult.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setResult.attributeStatus, SetVariableStatusEnumType.Accepted)
const getResponse: OCPP20GetVariablesResponse = testableService.handleRequestGetVariables(
station,
getRequest
)
- expect(getResponse.getVariableResult).toHaveLength(1)
+ assert.strictEqual(getResponse.getVariableResult.length, 1)
const getResult = getResponse.getVariableResult[0]
- expect(getResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getResult.attributeValue).toBe('60')
+ assert.strictEqual(getResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getResult.attributeValue, '60')
})
await it('should return UnknownVariable for GetVariables on an unknown variable name', () => {
const getResponse = testableService.handleRequestGetVariables(station, getRequest)
- expect(getResponse.getVariableResult).toHaveLength(1)
+ assert.strictEqual(getResponse.getVariableResult.length, 1)
const result = getResponse.getVariableResult[0]
- expect(
+ assert.strictEqual(
result.attributeStatus === GetVariableStatusEnumType.UnknownVariable ||
- result.attributeStatus === GetVariableStatusEnumType.UnknownComponent
- ).toBe(true)
+ result.attributeStatus === GetVariableStatusEnumType.UnknownComponent,
+ true
+ )
})
await it('should handle multiple variables in a single SetVariables→GetVariables round trip', () => {
}
const setResponse = testableService.handleRequestSetVariables(station, setRequest)
- expect(setResponse.setVariableResult).toHaveLength(2)
+ assert.strictEqual(setResponse.setVariableResult.length, 2)
const getRequest: OCPP20GetVariablesRequest = {
getVariableData: [
}
const getResponse = testableService.handleRequestGetVariables(station, getRequest)
- expect(getResponse.getVariableResult).toHaveLength(2)
+ assert.strictEqual(getResponse.getVariableResult.length, 2)
for (const result of getResponse.getVariableResult) {
- expect(result.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result.attributeStatus, GetVariableStatusEnumType.Accepted)
}
})
}
const setResponse = testableService.handleRequestSetVariables(station, setRequest)
- expect(setResponse.setVariableResult).toHaveLength(1)
+ assert.strictEqual(setResponse.setVariableResult.length, 1)
const setResult = setResponse.setVariableResult[0]
- expect(
+ assert.strictEqual(
setResult.attributeStatus === SetVariableStatusEnumType.UnknownComponent ||
setResult.attributeStatus === SetVariableStatusEnumType.UnknownVariable ||
- setResult.attributeStatus === SetVariableStatusEnumType.Rejected
- ).toBe(true)
+ setResult.attributeStatus === SetVariableStatusEnumType.Rejected,
+ true
+ )
// Confirm GetVariables also rejects lookup on the same unknown component
const getRequest: OCPP20GetVariablesRequest = {
}
const getResponse = testableService.handleRequestGetVariables(station, getRequest)
- expect(getResponse.getVariableResult).toHaveLength(1)
+ assert.strictEqual(getResponse.getVariableResult.length, 1)
const getResult = getResponse.getVariableResult[0]
- expect(
+ assert.strictEqual(
getResult.attributeStatus === GetVariableStatusEnumType.UnknownComponent ||
- getResult.attributeStatus === GetVariableStatusEnumType.UnknownVariable
- ).toBe(true)
+ getResult.attributeStatus === GetVariableStatusEnumType.UnknownVariable,
+ true
+ )
})
})
* @file Tests for OCPP20RequestService BootNotification
* @description Unit tests for OCPP 2.0 BootNotification request building (B01)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
requestParams
) as OCPP20BootNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.chargingStation).toBeDefined()
- expect(payload.chargingStation.model).toBe(TEST_CHARGE_POINT_MODEL)
- expect(payload.chargingStation.vendorName).toBe(TEST_CHARGE_POINT_VENDOR)
- expect(payload.chargingStation.firmwareVersion).toBe(TEST_FIRMWARE_VERSION)
- expect(payload.chargingStation.serialNumber).toBe(TEST_CHARGE_POINT_SERIAL_NUMBER)
- expect(payload.reason).toBe(BootReasonEnumType.PowerUp)
+ assert.notStrictEqual(payload, undefined)
+ assert.notStrictEqual(payload.chargingStation, undefined)
+ assert.strictEqual(payload.chargingStation.model, TEST_CHARGE_POINT_MODEL)
+ assert.strictEqual(payload.chargingStation.vendorName, TEST_CHARGE_POINT_VENDOR)
+ assert.strictEqual(payload.chargingStation.firmwareVersion, TEST_FIRMWARE_VERSION)
+ assert.strictEqual(payload.chargingStation.serialNumber, TEST_CHARGE_POINT_SERIAL_NUMBER)
+ assert.strictEqual(payload.reason, BootReasonEnumType.PowerUp)
})
// FR: B01.FR.02
requestParams
) as OCPP20BootNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.chargingStation).toBeDefined()
- expect(payload.chargingStation.model).toBe('Advanced Model X1')
- expect(payload.chargingStation.vendorName).toBe('Advanced Vendor')
- expect(payload.chargingStation.firmwareVersion).toBe('2.1.3')
- expect(payload.chargingStation.serialNumber).toBe('ADV-SN-002')
- expect(payload.reason).toBe(BootReasonEnumType.ApplicationReset)
+ assert.notStrictEqual(payload, undefined)
+ assert.notStrictEqual(payload.chargingStation, undefined)
+ assert.strictEqual(payload.chargingStation.model, 'Advanced Model X1')
+ assert.strictEqual(payload.chargingStation.vendorName, 'Advanced Vendor')
+ assert.strictEqual(payload.chargingStation.firmwareVersion, '2.1.3')
+ assert.strictEqual(payload.chargingStation.serialNumber, 'ADV-SN-002')
+ assert.strictEqual(payload.reason, BootReasonEnumType.ApplicationReset)
})
// FR: B01.FR.03
requestParams
) as OCPP20BootNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.chargingStation).toBeDefined()
- expect(payload.chargingStation.model).toBe('Basic Model')
- expect(payload.chargingStation.vendorName).toBe('Basic Vendor')
- expect(payload.chargingStation.firmwareVersion).toBeUndefined()
- expect(payload.chargingStation.serialNumber).toBeUndefined()
- expect(payload.reason).toBe(BootReasonEnumType.FirmwareUpdate)
+ assert.notStrictEqual(payload, undefined)
+ assert.notStrictEqual(payload.chargingStation, undefined)
+ assert.strictEqual(payload.chargingStation.model, 'Basic Model')
+ assert.strictEqual(payload.chargingStation.vendorName, 'Basic Vendor')
+ assert.strictEqual(payload.chargingStation.firmwareVersion, undefined)
+ assert.strictEqual(payload.chargingStation.serialNumber, undefined)
+ assert.strictEqual(payload.reason, BootReasonEnumType.FirmwareUpdate)
})
// FR: B01.FR.04
requestParams
) as OCPP20BootNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.reason).toBe(reason)
- expect(payload.chargingStation).toBeDefined()
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.reason, reason)
+ assert.notStrictEqual(payload.chargingStation, undefined)
})
})
) as OCPP20BootNotificationRequest
// Validate that the payload has the exact structure of OCPP20BootNotificationRequest
- expect(typeof payload).toBe('object')
- expect(payload).toHaveProperty('chargingStation')
- expect(payload).toHaveProperty('reason')
- expect(Object.keys(payload as object)).toHaveLength(2)
+ assert.strictEqual(typeof payload, 'object')
+ assert.notStrictEqual(payload.chargingStation, undefined)
+ assert.notStrictEqual(payload.reason, undefined)
+ assert.strictEqual(Object.keys(payload as object).length, 2)
// Validate chargingStation structure
- expect(typeof payload.chargingStation).toBe('object')
- expect(payload.chargingStation).toHaveProperty('model')
- expect(payload.chargingStation).toHaveProperty('vendorName')
- expect(typeof payload.chargingStation.model).toBe('string')
- expect(typeof payload.chargingStation.vendorName).toBe('string')
+ assert.strictEqual(typeof payload.chargingStation, 'object')
+ assert.notStrictEqual(payload.chargingStation.model, undefined)
+ assert.notStrictEqual(payload.chargingStation.vendorName, undefined)
+ assert.strictEqual(typeof payload.chargingStation.model, 'string')
+ assert.strictEqual(typeof payload.chargingStation.vendorName, 'string')
// Validate optional fields
if (payload.chargingStation.firmwareVersion !== undefined) {
- expect(typeof payload.chargingStation.firmwareVersion).toBe('string')
+ assert.strictEqual(typeof payload.chargingStation.firmwareVersion, 'string')
}
if (payload.chargingStation.serialNumber !== undefined) {
- expect(typeof payload.chargingStation.serialNumber).toBe('string')
+ assert.strictEqual(typeof payload.chargingStation.serialNumber, 'string')
}
if (payload.chargingStation.customData !== undefined) {
- expect(typeof payload.chargingStation.customData).toBe('object')
+ assert.strictEqual(typeof payload.chargingStation.customData, 'object')
}
})
})
* @file Tests for OCPP20RequestService HeartBeat
* @description Unit tests for OCPP 2.0 Heartbeat request building (G02)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
requestParams
)
- expect(payload).toBeDefined()
- expect(typeof payload).toBe('object')
- expect(Object.keys(payload as object)).toHaveLength(0)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(typeof payload, 'object')
+ assert.strictEqual(Object.keys(payload as object).length, 0)
})
// FR: G02.FR.02
OCPP20RequestCommand.HEARTBEAT
)
- expect(payload).toBeDefined()
- expect(typeof payload).toBe('object')
- expect(Object.keys(payload as object)).toHaveLength(0)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(typeof payload, 'object')
+ assert.strictEqual(Object.keys(payload as object).length, 0)
})
// FR: G02.FR.03
)
// Validate that the payload is an empty object as required by OCPP 2.0 spec
- expect(typeof payload).toBe('object')
- expect(payload).not.toBeNull()
- expect(Array.isArray(payload)).toBe(false)
- expect(Object.keys(payload as object)).toHaveLength(0)
- expect(JSON.stringify(payload)).toBe('{}')
+ assert.strictEqual(typeof payload, 'object')
+ assert.notStrictEqual(payload, null)
+ assert.ok(!Array.isArray(payload))
+ assert.strictEqual(Object.keys(payload as object).length, 0)
+ assert.strictEqual(JSON.stringify(payload), '{}')
})
// FR: G02.FR.04
)
// All payloads should be identical empty objects
- expect(payload1).toStrictEqual(payload2)
- expect(payload2).toStrictEqual(payload3)
- expect(JSON.stringify(payload1)).toBe('{}')
- expect(JSON.stringify(payload2)).toBe('{}')
- expect(JSON.stringify(payload3)).toBe('{}')
+ assert.deepStrictEqual(payload1, payload2)
+ assert.deepStrictEqual(payload2, payload3)
+ assert.strictEqual(JSON.stringify(payload1), '{}')
+ assert.strictEqual(JSON.stringify(payload2), '{}')
+ assert.strictEqual(JSON.stringify(payload3), '{}')
})
// FR: G02.FR.05
)
// HeartBeat payload should remain empty regardless of charging station configuration
- expect(payload).toBeDefined()
- expect(typeof payload).toBe('object')
- expect(Object.keys(payload as object)).toHaveLength(0)
- expect(JSON.stringify(payload)).toBe('{}')
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(typeof payload, 'object')
+ assert.strictEqual(Object.keys(payload as object).length, 0)
+ assert.strictEqual(JSON.stringify(payload), '{}')
})
// FR: G02.FR.06
// According to OCPP 2.0 specification, HeartBeat request should be an empty object
// This validates compliance with the official OCPP 2.0 standard
- expect(payload).toBeDefined()
- expect(payload).toStrictEqual({})
- expect(has('constructor', payload)).toBe(false)
+ assert.notStrictEqual(payload, undefined)
+ assert.deepStrictEqual(payload, {})
+ assert.strictEqual(has('constructor', payload), false)
// Ensure it's a plain object and not an instance of another type
- expect(Object.getPrototypeOf(payload)).toBe(Object.prototype)
+ assert.strictEqual(Object.getPrototypeOf(payload), Object.prototype)
})
})
*/
/* cspell:ignore Bvbn NQIF CBCYX */
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import { createTestableRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
MOCK_EXI_REQUEST
)
- expect(sendMessageMock.mock.calls.length).toBe(1)
+ assert.strictEqual(sendMessageMock.mock.calls.length, 1)
const sentPayload = sendMessageMock.mock.calls[0]
.arguments[2] as OCPP20Get15118EVCertificateRequest
- expect(sentPayload.exiRequest).toBe(MOCK_EXI_REQUEST)
- expect(sentPayload.action).toBe(CertificateActionEnumType.Install)
+ assert.strictEqual(sentPayload.exiRequest, MOCK_EXI_REQUEST)
+ assert.strictEqual(sentPayload.action, CertificateActionEnumType.Install)
})
})
const sentPayload = sendMessageMock.mock.calls[0]
.arguments[2] as OCPP20Get15118EVCertificateRequest
- expect(sentPayload.exiRequest).toBe(MOCK_EXI_REQUEST)
- expect(sentPayload.action).toBe(CertificateActionEnumType.Update)
+ assert.strictEqual(sentPayload.exiRequest, MOCK_EXI_REQUEST)
+ assert.strictEqual(sentPayload.action, CertificateActionEnumType.Update)
})
})
MOCK_EXI_REQUEST
)
- expect(response).toBeDefined()
- expect(response.status).toBe(Iso15118EVCertificateStatusEnumType.Accepted)
- expect(response.exiResponse).toBe(MOCK_EXI_RESPONSE)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, Iso15118EVCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.exiResponse, MOCK_EXI_RESPONSE)
})
await it('should return Failed response from CSMS', async () => {
MOCK_EXI_REQUEST
)
- expect(response).toBeDefined()
- expect(response.status).toBe(Iso15118EVCertificateStatusEnumType.Failed)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidCertificate)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, Iso15118EVCertificateStatusEnumType.Failed)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.InvalidCertificate)
})
})
const sentPayload = sendMessageMock.mock.calls[0]
.arguments[2] as OCPP20Get15118EVCertificateRequest
- expect(sentPayload.iso15118SchemaVersion).toBe(MOCK_ISO15118_SCHEMA_VERSION)
+ assert.strictEqual(sentPayload.iso15118SchemaVersion, MOCK_ISO15118_SCHEMA_VERSION)
})
})
const sentPayload = sendMessageMock.mock.calls[0]
.arguments[2] as OCPP20Get15118EVCertificateRequest
// EXI should be passed through unchanged - no decoding/encoding
- expect(sentPayload.exiRequest).toBe(complexBase64EXI)
+ assert.strictEqual(sentPayload.exiRequest, complexBase64EXI)
})
})
})
await service.requestGetCertificateStatus(station, ocspRequestData)
- expect(sendMessageMock.mock.calls.length).toBe(1)
+ assert.strictEqual(sendMessageMock.mock.calls.length, 1)
const sentPayload = sendMessageMock.mock.calls[0]
.arguments[2] as OCPP20GetCertificateStatusRequest
- expect(sentPayload.ocspRequestData).toBeDefined()
- expect(sentPayload.ocspRequestData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA256)
- expect(sentPayload.ocspRequestData.issuerKeyHash).toBe(ocspRequestData.issuerKeyHash)
- expect(sentPayload.ocspRequestData.issuerNameHash).toBe(ocspRequestData.issuerNameHash)
- expect(sentPayload.ocspRequestData.serialNumber).toBe(ocspRequestData.serialNumber)
- expect(sentPayload.ocspRequestData.responderURL).toBe(ocspRequestData.responderURL)
+ assert.notStrictEqual(sentPayload.ocspRequestData, undefined)
+ assert.strictEqual(sentPayload.ocspRequestData.hashAlgorithm, HashAlgorithmEnumType.SHA256)
+ assert.strictEqual(sentPayload.ocspRequestData.issuerKeyHash, ocspRequestData.issuerKeyHash)
+ assert.strictEqual(
+ sentPayload.ocspRequestData.issuerNameHash,
+ ocspRequestData.issuerNameHash
+ )
+ assert.strictEqual(sentPayload.ocspRequestData.serialNumber, ocspRequestData.serialNumber)
+ assert.strictEqual(sentPayload.ocspRequestData.responderURL, ocspRequestData.responderURL)
})
})
createMockOCSPRequestData()
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetCertificateStatusEnumType.Accepted)
- expect(response.ocspResult).toBe(MOCK_OCSP_RESULT)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.ocspResult, MOCK_OCSP_RESULT)
})
await it('should return Failed response from CSMS', async () => {
createMockOCSPRequestData()
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetCertificateStatusEnumType.Failed)
- expect(response.statusInfo?.reasonCode).toBe(ReasonCodeEnumType.InternalError)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetCertificateStatusEnumType.Failed)
+ assert.strictEqual(response.statusInfo?.reasonCode, ReasonCodeEnumType.InternalError)
})
})
createMockOCSPRequestData()
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GetCertificateStatusEnumType.Accepted)
- expect(response.ocspResult).toBe(stubOcspResult)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GetCertificateStatusEnumType.Accepted)
+ assert.strictEqual(response.ocspResult, stubOcspResult)
// Verify sendMessage was called (no real network call)
- expect(sendMessageMock.mock.calls.length).toBe(1)
+ assert.strictEqual(sendMessageMock.mock.calls.length, 1)
})
})
})
)
const commandName = sendMessageMock.mock.calls[0].arguments[3]
- expect(commandName).toBe(OCPP20RequestCommand.GET_15118_EV_CERTIFICATE)
+ assert.strictEqual(commandName, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE)
})
await it('should send GET_CERTIFICATE_STATUS command name', async () => {
await service.requestGetCertificateStatus(station, createMockOCSPRequestData())
const commandName = sendMessageMock.mock.calls[0].arguments[3]
- expect(commandName).toBe(OCPP20RequestCommand.GET_CERTIFICATE_STATUS)
+ assert.strictEqual(commandName, OCPP20RequestCommand.GET_CERTIFICATE_STATUS)
})
})
})
* @description Unit tests for OCPP 2.0 NotifyReport request building (B07/B08)
*/
-import { expect } from '@std/expect'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(payload.requestId).toBe(123)
- expect(payload.seqNo).toBe(0)
- expect(payload.tbc).toBeUndefined()
- expect(payload.reportData).toBeUndefined()
+ assert.notStrictEqual(payload, undefined)
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(payload.requestId, 123)
+ assert.strictEqual(payload.seqNo, 0)
+ assert.strictEqual(payload.tbc, undefined)
+ assert.strictEqual(payload.reportData, undefined)
})
await it('should build NotifyReport request payload correctly with reportData', () => {
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(payload.requestId).toBe(456)
- expect(payload.seqNo).toBe(1)
- expect(payload.tbc).toBe(false)
- expect(payload.reportData).toStrictEqual(reportData)
- expect(Array.isArray(payload.reportData)).toBe(true)
- expect(payload.reportData).toHaveLength(1)
+ assert.notStrictEqual(payload, undefined)
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(payload.requestId, 456)
+ assert.strictEqual(payload.seqNo, 1)
+ assert.strictEqual(payload.tbc, false)
+ assert.deepStrictEqual(payload.reportData, reportData)
+ assert.ok(Array.isArray(payload.reportData))
+ assert.strictEqual(payload.reportData.length, 1)
})
await it('should build NotifyReport request payload correctly with multiple reportData items', () => {
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(payload.requestId).toBe(789)
- expect(payload.seqNo).toBe(2)
- expect(payload.tbc).toBe(true)
- expect(payload.reportData).toStrictEqual(reportData)
- expect(Array.isArray(payload.reportData)).toBe(true)
- expect(payload.reportData).toHaveLength(3)
+ assert.notStrictEqual(payload, undefined)
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(payload.requestId, 789)
+ assert.strictEqual(payload.seqNo, 2)
+ assert.strictEqual(payload.tbc, true)
+ assert.deepStrictEqual(payload.reportData, reportData)
+ assert.ok(Array.isArray(payload.reportData))
+ assert.strictEqual(payload.reportData.length, 3)
})
await it('should build NotifyReport request payload correctly with fragmented report (tbc=true)', () => {
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(payload.requestId).toBe(999)
- expect(payload.seqNo).toBe(0)
- expect(payload.tbc).toBe(true)
- expect(payload.reportData).toStrictEqual(reportData)
- expect(Array.isArray(payload.reportData)).toBe(true)
- expect(payload.reportData).toHaveLength(1)
+ assert.notStrictEqual(payload, undefined)
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(payload.requestId, 999)
+ assert.strictEqual(payload.seqNo, 0)
+ assert.strictEqual(payload.tbc, true)
+ assert.deepStrictEqual(payload.reportData, reportData)
+ assert.ok(Array.isArray(payload.reportData))
+ assert.strictEqual(payload.reportData.length, 1)
})
await it('should build NotifyReport request payload correctly with empty reportData array', () => {
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(payload.requestId).toBe(100)
- expect(payload.seqNo).toBe(0)
- expect(payload.tbc).toBe(false)
- expect(payload.reportData).toStrictEqual([])
- expect(Array.isArray(payload.reportData)).toBe(true)
- expect(payload.reportData).toHaveLength(0)
+ assert.notStrictEqual(payload, undefined)
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(payload.requestId, 100)
+ assert.strictEqual(payload.seqNo, 0)
+ assert.strictEqual(payload.tbc, false)
+ assert.deepStrictEqual(payload.reportData, [])
+ assert.ok(Array.isArray(payload.reportData))
+ assert.strictEqual(payload.reportData.length, 0)
})
await it('should handle different AttributeEnumType values correctly', () => {
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
+ assert.notStrictEqual(payload, undefined)
assert(payload.reportData != null)
const firstReport = payload.reportData[0]
assert(firstReport.variableAttribute != null)
- expect(firstReport.variableAttribute[0].type).toBe(attributeType)
- expect(firstReport.variableAttribute[0].value).toBe(`Test Value ${index.toString()}`)
+ assert.strictEqual(firstReport.variableAttribute[0].type, attributeType)
+ assert.strictEqual(firstReport.variableAttribute[0].value, `Test Value ${index.toString()}`)
})
})
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
+ assert.notStrictEqual(payload, undefined)
assert(payload.reportData != null)
const firstReport = payload.reportData[0]
assert(firstReport.variableCharacteristics != null)
assert(firstReport.variableAttribute != null)
- expect(firstReport.variableCharacteristics.dataType).toBe(testCase.dataType)
- expect(firstReport.variableAttribute[0].value).toBe(testCase.value)
+ assert.strictEqual(firstReport.variableCharacteristics.dataType, testCase.dataType)
+ assert.strictEqual(firstReport.variableAttribute[0].value, testCase.value)
})
})
) as OCPP20NotifyReportRequest
// Validate that the payload has the exact structure of OCPP20NotifyReportRequest
- expect(typeof payload).toBe('object')
- expect(payload).toHaveProperty('generatedAt')
- expect(payload).toHaveProperty('requestId')
- expect(payload).toHaveProperty('seqNo')
- expect(payload).toHaveProperty('reportData')
- expect(payload).toHaveProperty('tbc')
+ assert.strictEqual(typeof payload, 'object')
+ assert.notStrictEqual(payload.generatedAt, undefined)
+ assert.notStrictEqual(payload.requestId, undefined)
+ assert.notStrictEqual(payload.seqNo, undefined)
+ assert.notStrictEqual(payload.reportData, undefined)
+ assert.notStrictEqual(payload.tbc, undefined)
// Validate required fields
- expect(payload.generatedAt).toBeInstanceOf(Date)
- expect(typeof payload.requestId).toBe('number')
- expect(typeof payload.seqNo).toBe('number')
+ assert.ok(payload.generatedAt instanceof Date)
+ assert.strictEqual(typeof payload.requestId, 'number')
+ assert.strictEqual(typeof payload.seqNo, 'number')
// Validate optional fields
if (payload.reportData !== undefined) {
- expect(Array.isArray(payload.reportData)).toBe(true)
+ assert.ok(Array.isArray(payload.reportData))
if (payload.reportData.length > 0) {
- expect(typeof payload.reportData[0]).toBe('object')
- expect(payload.reportData[0]).toHaveProperty('component')
- expect(payload.reportData[0]).toHaveProperty('variable')
- expect(payload.reportData[0]).toHaveProperty('variableAttribute')
+ assert.strictEqual(typeof payload.reportData[0], 'object')
+ assert.notStrictEqual(payload.reportData[0].component, undefined)
+ assert.notStrictEqual(payload.reportData[0].variable, undefined)
+ assert.notStrictEqual(payload.reportData[0].variableAttribute, undefined)
}
}
if (payload.tbc !== undefined) {
- expect(typeof payload.tbc).toBe('boolean')
+ assert.strictEqual(typeof payload.tbc, 'boolean')
}
})
requestParams
) as OCPP20NotifyReportRequest
- expect(payload).toBeDefined()
+ assert.notStrictEqual(payload, undefined)
assert(payload.reportData != null)
const firstReport = payload.reportData[0]
assert(firstReport.variableAttribute != null)
- expect(firstReport.variableAttribute).toHaveLength(1)
- expect(firstReport.variableAttribute[0].type).toBe(AttributeEnumType.Actual)
+ assert.strictEqual(firstReport.variableAttribute.length, 1)
+ assert.strictEqual(firstReport.variableAttribute[0].type, AttributeEnumType.Actual)
})
await it('should preserve all payload properties correctly', () => {
) as OCPP20NotifyReportRequest
// Verify all input properties are preserved exactly
- expect(payload.generatedAt).toBe(testDate)
- expect(payload.requestId).toBe(3001)
- expect(payload.seqNo).toBe(15)
- expect(payload.tbc).toBe(true)
- expect(payload.reportData).toBe(reportData)
+ assert.strictEqual(payload.generatedAt, testDate)
+ assert.strictEqual(payload.requestId, 3001)
+ assert.strictEqual(payload.seqNo, 15)
+ assert.strictEqual(payload.tbc, true)
+ assert.strictEqual(payload.reportData, reportData)
// Verify no additional properties are added
const expectedKeys = ['generatedAt', 'reportData', 'requestId', 'seqNo', 'tbc']
const actualKeys = Object.keys(payload as object).sort()
expectedKeys.sort()
- expect(actualKeys).toStrictEqual(expectedKeys)
+ assert.deepStrictEqual(actualKeys, expectedKeys)
})
})
* @description Unit tests for OCPP 2.0 SignCertificate request building
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
CertificateSigningUseEnumType.ChargingStationCertificate
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
- expect(sendMessageMock.mock.calls.length).toBeGreaterThan(0)
+ assert.ok(sendMessageMock.mock.calls.length > 0)
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.csr).toBeDefined()
- expect(sentPayload.csr).toContain('-----BEGIN CERTIFICATE REQUEST-----')
- expect(sentPayload.csr).toContain('-----END CERTIFICATE REQUEST-----')
+ assert.notStrictEqual(sentPayload.csr, undefined)
+ assert.ok(sentPayload.csr.includes('-----BEGIN CERTIFICATE REQUEST-----'))
+ assert.ok(sentPayload.csr.includes('-----END CERTIFICATE REQUEST-----'))
})
await it('should include OrganizationName from SecurityCtrlr config in CSR', async () => {
)
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.csr).toBeDefined()
- expect(sentPayload.csr).toContain('-----BEGIN CERTIFICATE REQUEST-----')
+ assert.notStrictEqual(sentPayload.csr, undefined)
+ assert.ok(sentPayload.csr.includes('-----BEGIN CERTIFICATE REQUEST-----'))
const csrRegex =
/-----BEGIN CERTIFICATE REQUEST-----\n(.+?)\n-----END CERTIFICATE REQUEST-----/
const csrExecResult = csrRegex.exec(sentPayload.csr)
- expect(csrExecResult).toBeDefined()
+ assert.notStrictEqual(csrExecResult, undefined)
const csrData = csrExecResult?.[1]
const decodedCsr = Buffer.from(csrData ?? '', 'base64').toString('utf-8')
- expect(decodedCsr).toContain('O=Test Organization Inc.')
+ assert.ok(decodedCsr.includes('O=Test Organization Inc.'))
})
})
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.certificateType).toBe(
+ assert.strictEqual(
+ sentPayload.certificateType,
CertificateSigningUseEnumType.ChargingStationCertificate
)
})
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.certificateType).toBe(CertificateSigningUseEnumType.V2GCertificate)
+ assert.strictEqual(sentPayload.certificateType, CertificateSigningUseEnumType.V2GCertificate)
})
})
CertificateSigningUseEnumType.ChargingStationCertificate
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
})
await it('should return Rejected response from CSMS', async () => {
CertificateSigningUseEnumType.ChargingStationCertificate
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Rejected)
- expect(response.statusInfo).toBeDefined()
- expect(response.statusInfo?.reasonCode).toBe('InvalidCSR')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Rejected)
+ assert.notStrictEqual(response.statusInfo, undefined)
+ assert.strictEqual(response.statusInfo?.reasonCode, 'InvalidCSR')
})
})
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.csr).toBeDefined()
+ assert.notStrictEqual(sentPayload.csr, undefined)
// certificateType should be undefined when not specified
- expect(sentPayload.certificateType).toBeUndefined()
+ assert.strictEqual(sentPayload.certificateType, undefined)
})
})
CertificateSigningUseEnumType.ChargingStationCertificate
)
- expect(sendMessageMock.mock.calls.length).toBe(1)
+ assert.strictEqual(sendMessageMock.mock.calls.length, 1)
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
// Validate payload structure
- expect(typeof sentPayload).toBe('object')
- expect(sentPayload.csr).toBeDefined()
- expect(typeof sentPayload.csr).toBe('string')
- expect(sentPayload.csr.length).toBeGreaterThan(0)
- expect(sentPayload.csr.length).toBeLessThanOrEqual(5500) // Max length per schema
+ assert.strictEqual(typeof sentPayload, 'object')
+ assert.notStrictEqual(sentPayload.csr, undefined)
+ assert.strictEqual(typeof sentPayload.csr, 'string')
+ assert.ok(sentPayload.csr.length > 0)
+ assert.ok(sentPayload.csr.length <= 5500) // Max length per schema
})
await it('should send SIGN_CERTIFICATE command name', async () => {
const commandName = sendMessageMock.mock.calls[0].arguments[3]
- expect(commandName).toBe(OCPP20RequestCommand.SIGN_CERTIFICATE)
+ assert.strictEqual(commandName, OCPP20RequestCommand.SIGN_CERTIFICATE)
})
})
CertificateSigningUseEnumType.ChargingStationCertificate
)
- expect(response).toBeDefined()
- expect(response.status).toBe(GenericStatus.Accepted)
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(response.status, GenericStatus.Accepted)
const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
- expect(sentPayload.csr).toBeDefined()
- expect(sentPayload.csr).toContain('-----BEGIN CERTIFICATE REQUEST-----')
+ assert.notStrictEqual(sentPayload.csr, undefined)
+ assert.ok(sentPayload.csr.includes('-----BEGIN CERTIFICATE REQUEST-----'))
})
})
})
* @file Tests for OCPP20RequestService StatusNotification
* @description Unit tests for OCPP 2.0 StatusNotification request building (G01)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/index.js'
requestParams
) as OCPP20StatusNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.connectorId).toBe(1)
- expect(payload.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Available)
- expect(payload.evseId).toBe(1)
- expect(payload.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.connectorId, 1)
+ assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Available)
+ assert.strictEqual(payload.evseId, 1)
+ assert.strictEqual(payload.timestamp, testTimestamp)
})
// FR: G01.FR.02
requestParams
) as OCPP20StatusNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.connectorId).toBe(2)
- expect(payload.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Occupied)
- expect(payload.evseId).toBe(2)
- expect(payload.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.connectorId, 2)
+ assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Occupied)
+ assert.strictEqual(payload.evseId, 2)
+ assert.strictEqual(payload.timestamp, testTimestamp)
})
// FR: G01.FR.03
requestParams
) as OCPP20StatusNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.connectorId).toBe(1)
- expect(payload.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Faulted)
- expect(payload.evseId).toBe(1)
- expect(payload.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.connectorId, 1)
+ assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Faulted)
+ assert.strictEqual(payload.evseId, 1)
+ assert.strictEqual(payload.timestamp, testTimestamp)
})
// FR: G01.FR.04
requestParams
) as OCPP20StatusNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.connectorStatus).toBe(status)
- expect(payload.connectorId).toBe(index + 1)
- expect(payload.evseId).toBe(index + 1)
- expect(payload.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.connectorStatus, status)
+ assert.strictEqual(payload.connectorId, index + 1)
+ assert.strictEqual(payload.evseId, index + 1)
+ assert.strictEqual(payload.timestamp, testTimestamp)
})
})
) as OCPP20StatusNotificationRequest
// Validate that the payload has the exact structure of OCPP20StatusNotificationRequest
- expect(typeof payload).toBe('object')
- expect(payload).toHaveProperty('connectorId')
- expect(payload).toHaveProperty('connectorStatus')
- expect(payload).toHaveProperty('evseId')
- expect(payload).toHaveProperty('timestamp')
- expect(Object.keys(payload as object)).toHaveLength(4)
+ assert.strictEqual(typeof payload, 'object')
+ assert.notStrictEqual(payload.connectorId, undefined)
+ assert.notStrictEqual(payload.connectorStatus, undefined)
+ assert.notStrictEqual(payload.evseId, undefined)
+ assert.notStrictEqual(payload.timestamp, undefined)
+ assert.strictEqual(Object.keys(payload as object).length, 4)
// Validate field types
- expect(typeof payload.connectorId).toBe('number')
- expect(typeof payload.connectorStatus).toBe('string')
- expect(typeof payload.evseId).toBe('number')
- expect(payload.timestamp).toBeInstanceOf(Date)
+ assert.strictEqual(typeof payload.connectorId, 'number')
+ assert.strictEqual(typeof payload.connectorStatus, 'string')
+ assert.strictEqual(typeof payload.evseId, 'number')
+ assert.ok(payload.timestamp instanceof Date)
// Validate field values
- expect(payload.connectorId).toBe(3)
- expect(payload.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Reserved)
- expect(payload.evseId).toBe(2)
- expect(payload.timestamp).toBe(testTimestamp)
+ assert.strictEqual(payload.connectorId, 3)
+ assert.strictEqual(payload.connectorStatus, OCPP20ConnectorStatusEnumType.Reserved)
+ assert.strictEqual(payload.evseId, 2)
+ assert.strictEqual(payload.timestamp, testTimestamp)
})
// FR: G01.FR.06
requestParamsConnector0
) as OCPP20StatusNotificationRequest
- expect(payloadConnector0).toBeDefined()
- expect(payloadConnector0.connectorId).toBe(0)
- expect(payloadConnector0.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Available)
- expect(payloadConnector0.evseId).toBe(1)
- expect(payloadConnector0.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payloadConnector0, undefined)
+ assert.strictEqual(payloadConnector0.connectorId, 0)
+ assert.strictEqual(payloadConnector0.connectorStatus, OCPP20ConnectorStatusEnumType.Available)
+ assert.strictEqual(payloadConnector0.evseId, 1)
+ assert.strictEqual(payloadConnector0.timestamp, testTimestamp)
// Test with EVSE ID 0 (valid in OCPP 2.0 for the charging station itself)
const requestParamsEvse0: OCPP20StatusNotificationRequest = {
requestParamsEvse0
) as OCPP20StatusNotificationRequest
- expect(payloadEvse0).toBeDefined()
- expect(payloadEvse0.connectorId).toBe(1)
- expect(payloadEvse0.connectorStatus).toBe(OCPP20ConnectorStatusEnumType.Unavailable)
- expect(payloadEvse0.evseId).toBe(0)
- expect(payloadEvse0.timestamp).toBe(testTimestamp)
+ assert.notStrictEqual(payloadEvse0, undefined)
+ assert.strictEqual(payloadEvse0.connectorId, 1)
+ assert.strictEqual(payloadEvse0.connectorStatus, OCPP20ConnectorStatusEnumType.Unavailable)
+ assert.strictEqual(payloadEvse0.evseId, 0)
+ assert.strictEqual(payloadEvse0.timestamp, testTimestamp)
})
// FR: G01.FR.07
requestParams
) as OCPP20StatusNotificationRequest
- expect(payload).toBeDefined()
- expect(payload.timestamp).toBe(timestamp)
- expect(payload.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(payload, undefined)
+ assert.strictEqual(payload.timestamp, timestamp)
+ assert.ok(payload.timestamp instanceof Date)
})
})
})
* - Invalid registration status — deletes response and logs error
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { MockChargingStation } from '../../ChargingStationTestUtils.js'
status: RegistrationStatusEnumType.ACCEPTED,
}
await dispatch(payload)
- expect(mockStation.bootNotificationResponse).toBe(payload)
- expect(emitSpy.mock.calls.length).toBe(1)
- expect(emitSpy.mock.calls[0].arguments[0]).toBe(ChargingStationEvents.accepted)
+ assert.strictEqual(mockStation.bootNotificationResponse, payload)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.accepted)
})
await it('should store response and emit pending event for PENDING status', async () => {
status: RegistrationStatusEnumType.PENDING,
}
await dispatch(payload)
- expect(mockStation.bootNotificationResponse).toBe(payload)
- expect(emitSpy.mock.calls.length).toBe(1)
- expect(emitSpy.mock.calls[0].arguments[0]).toBe(ChargingStationEvents.pending)
+ assert.strictEqual(mockStation.bootNotificationResponse, payload)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.pending)
})
await it('should store response and emit rejected event for REJECTED status', async () => {
status: RegistrationStatusEnumType.REJECTED,
}
await dispatch(payload)
- expect(mockStation.bootNotificationResponse).toBe(payload)
- expect(emitSpy.mock.calls.length).toBe(1)
- expect(emitSpy.mock.calls[0].arguments[0]).toBe(ChargingStationEvents.rejected)
+ assert.strictEqual(mockStation.bootNotificationResponse, payload)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
+ assert.strictEqual(emitSpy.mock.calls[0].arguments[0], ChargingStationEvents.rejected)
})
await it('should set HeartbeatInterval configuration key when interval is provided', async () => {
}
await dispatch(payload)
const configKey = mockStation.ocppConfiguration?.configurationKey
- expect(configKey).toBeDefined()
- expect(configKey?.length).toBe(1)
- expect(configKey?.[0]?.key).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(configKey?.[0]?.value).toBe('300')
+ assert.notStrictEqual(configKey, undefined)
+ if (configKey == null) {
+ assert.fail('Expected configKey to be defined')
+ }
+ assert.strictEqual(configKey.length, 1)
+ assert.strictEqual(configKey[0].key, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.strictEqual(configKey[0].value, '300')
})
await it('should skip interval handling when interval is not provided', async () => {
} as unknown as OCPP20BootNotificationResponse
await dispatch(payload)
const configKey = mockStation.ocppConfiguration?.configurationKey
- expect(configKey).toBeDefined()
- expect(configKey?.length).toBe(0)
+ assert.notStrictEqual(configKey, undefined)
+ assert.strictEqual(configKey?.length, 0)
})
await it('should delete response and log error for invalid registration status', async () => {
status: 'INVALID_STATUS',
} as unknown as OCPP20BootNotificationResponse
await dispatch(payload)
- expect(mockStation.bootNotificationResponse).toBeUndefined()
+ assert.strictEqual(mockStation.bootNotificationResponse, undefined)
})
})
* @description Verifies Heartbeat, NotifyReport, and StatusNotification response handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { MockChargingStation } from '../../ChargingStationTestUtils.js'
await describe('G02 - HeartbeatResponse handler', async () => {
await it('should handle Heartbeat response without throwing', async () => {
const payload: OCPP20HeartbeatResponse = { currentTime: new Date() }
- await expect(
+ await assert.doesNotReject(
responseService.responseHandler(
mockStation,
OCPP20RequestCommand.HEARTBEAT,
payload as unknown as Parameters<typeof responseService.responseHandler>[2],
{} as Parameters<typeof responseService.responseHandler>[3]
)
- ).resolves.toBeUndefined()
+ )
})
})
await describe('B07 - NotifyReportResponse handler', async () => {
await it('should handle NotifyReport response without throwing', async () => {
const payload: OCPP20NotifyReportResponse = {}
- await expect(
+ await assert.doesNotReject(
responseService.responseHandler(
mockStation,
OCPP20RequestCommand.NOTIFY_REPORT,
payload as unknown as Parameters<typeof responseService.responseHandler>[2],
{} as Parameters<typeof responseService.responseHandler>[3]
)
- ).resolves.toBeUndefined()
+ )
})
})
await describe('G01 - StatusNotificationResponse handler', async () => {
await it('should handle StatusNotification response without throwing', async () => {
const payload: OCPP20StatusNotificationResponse = {}
- await expect(
+ await assert.doesNotReject(
responseService.responseHandler(
mockStation,
OCPP20RequestCommand.STATUS_NOTIFICATION,
payload as unknown as Parameters<typeof responseService.responseHandler>[2],
{} as Parameters<typeof responseService.responseHandler>[3]
)
- ).resolves.toBeUndefined()
+ )
})
})
})
* - All fields together
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { MockChargingStation } from '../../ChargingStationTestUtils.js'
await it('should handle empty TransactionEvent response without throwing', async () => {
const payload: OCPP20TransactionEventResponse = {}
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle totalCost field without throwing', async () => {
const payload: OCPP20TransactionEventResponse = { totalCost: 12.5 }
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle chargingPriority field without throwing', async () => {
const payload: OCPP20TransactionEventResponse = { chargingPriority: 1 }
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle idTokenInfo with Accepted status without throwing', async () => {
status: OCPP20AuthorizationStatusEnumType.Accepted,
},
}
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle idTokenInfo with Invalid status without throwing', async () => {
status: OCPP20AuthorizationStatusEnumType.Invalid,
},
}
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle updatedPersonalMessage field without throwing', async () => {
format: OCPP20MessageFormatEnumType.UTF8,
}
const payload: OCPP20TransactionEventResponse = { updatedPersonalMessage: message }
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
await it('should handle all optional fields present simultaneously without throwing', async () => {
totalCost: 9.99,
updatedPersonalMessage: message,
}
- await expect(dispatch(payload)).resolves.toBeUndefined()
+ await assert.doesNotReject(dispatch(payload))
})
})
* mode rejects at compile time).
*/
-import { expect } from '@std/expect'
import _Ajv, { type ValidateFunction } from 'ajv'
import _ajvFormats from 'ajv-formats'
+import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { afterEach, describe, it } from 'node:test'
await it('should compile ResetRequest schema without error (strict:false required)', () => {
// Verifies the AJV configuration works for schemas using additionalItems pattern
- expect(() => makeValidator('ResetRequest.json')).not.toThrow()
+ assert.doesNotThrow(() => {
+ makeValidator('ResetRequest.json')
+ })
})
await it('should compile GetVariablesRequest schema without error (uses additionalItems)', () => {
// GetVariablesRequest uses additionalItems:false — would fail in strict mode
- expect(() => makeValidator('GetVariablesRequest.json')).not.toThrow()
+ assert.doesNotThrow(() => {
+ makeValidator('GetVariablesRequest.json')
+ })
})
await it('should fail validation when Reset payload is missing required "type" field', () => {
const validate = makeValidator('ResetRequest.json')
- expect(validate({})).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
// AJV reports missingProperty for required field violations
const hasMissingType = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'type'
)
- expect(hasMissingType).toBe(true)
+ assert.strictEqual(hasMissingType, true)
})
await it('should fail validation when Reset payload has invalid "type" enum value', () => {
const validate = makeValidator('ResetRequest.json')
// Valid values are Immediate and OnIdle only; HardReset is OCPP 1.6
- expect(validate({ type: 'HardReset' })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ type: 'HardReset' }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasEnumError = validate.errors?.some(e => e.keyword === 'enum')
- expect(hasEnumError).toBe(true)
+ assert.strictEqual(hasEnumError, true)
})
await it('should fail validation when GetVariables has empty getVariableData array', () => {
const validate = makeValidator('GetVariablesRequest.json')
- expect(validate({ getVariableData: [] })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ getVariableData: [] }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMinItemsError = validate.errors?.some(e => e.keyword === 'minItems')
- expect(hasMinItemsError).toBe(true)
+ assert.strictEqual(hasMinItemsError, true)
})
await it('should fail validation when GetVariables is missing required getVariableData', () => {
const validate = makeValidator('GetVariablesRequest.json')
- expect(validate({})).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'getVariableData'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when SetVariables is missing required setVariableData', () => {
const validate = makeValidator('SetVariablesRequest.json')
- expect(validate({})).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'setVariableData'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when TriggerMessage has invalid requestedMessage enum value', () => {
const validate = makeValidator('TriggerMessageRequest.json')
- expect(validate({ requestedMessage: 'INVALID_MESSAGE_TYPE_XYZ' })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ requestedMessage: 'INVALID_MESSAGE_TYPE_XYZ' }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasEnumError = validate.errors?.some(e => e.keyword === 'enum')
- expect(hasEnumError).toBe(true)
+ assert.strictEqual(hasEnumError, true)
})
await it('should fail validation when TriggerMessage is missing required requestedMessage', () => {
const validate = makeValidator('TriggerMessageRequest.json')
- expect(validate({})).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'requestedMessage'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when UnlockConnector is missing required evseId', () => {
const validate = makeValidator('UnlockConnectorRequest.json')
- expect(validate({ connectorId: 1 })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ connectorId: 1 }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'evseId'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when UnlockConnector is missing required connectorId', () => {
const validate = makeValidator('UnlockConnectorRequest.json')
- expect(validate({ evseId: 1 })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ evseId: 1 }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'connectorId'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when RequestStartTransaction is missing required idToken', () => {
const validate = makeValidator('RequestStartTransactionRequest.json')
// remoteStartId is also required; provide it but omit idToken
- expect(validate({ remoteStartId: 1 })).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({ remoteStartId: 1 }), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'idToken'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should fail validation when CertificateSigned is missing required certificateChain', () => {
const validate = makeValidator('CertificateSignedRequest.json')
- expect(validate({})).toBe(false)
- expect(validate.errors).toBeDefined()
+ assert.strictEqual(validate({}), false)
+ assert.notStrictEqual(validate.errors, undefined)
const hasMissingProp = validate.errors?.some(
e =>
e.keyword === 'required' &&
(e.params as { missingProperty?: string }).missingProperty === 'certificateChain'
)
- expect(hasMissingProp).toBe(true)
+ assert.strictEqual(hasMissingProp, true)
})
await it('should pass validation for valid Reset payloads', () => {
const validate = makeValidator('ResetRequest.json')
- expect(validate({ type: 'Immediate' })).toBe(true)
- expect(validate({ type: 'OnIdle' })).toBe(true)
- expect(validate({ evseId: 1, type: 'OnIdle' })).toBe(true)
+ assert.strictEqual(validate({ type: 'Immediate' }), true)
+ assert.strictEqual(validate({ type: 'OnIdle' }), true)
+ assert.strictEqual(validate({ evseId: 1, type: 'OnIdle' }), true)
})
await it('should pass validation for valid TriggerMessage payloads', () => {
const validate = makeValidator('TriggerMessageRequest.json')
- expect(validate({ requestedMessage: 'Heartbeat' })).toBe(true)
- expect(validate({ requestedMessage: 'BootNotification' })).toBe(true)
+ assert.strictEqual(validate({ requestedMessage: 'Heartbeat' }), true)
+ assert.strictEqual(validate({ requestedMessage: 'BootNotification' }), true)
})
})
* - Periodic TransactionEvent at TxUpdatedInterval
*/
-import { expect } from '@std/expect'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
)
// Validate required fields
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(transactionEvent.triggerReason).toBe(triggerReason)
- expect(transactionEvent.seqNo).toBe(0) // First event should have seqNo 0
- expect(transactionEvent.timestamp).toBeInstanceOf(Date)
- expect(transactionEvent.evse).toBeDefined()
- expect(transactionEvent.evse?.id).toBe(1) // EVSE ID should match connector ID for this setup
- expect(transactionEvent.transactionInfo).toBeDefined()
- expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(transactionEvent.triggerReason, triggerReason)
+ assert.strictEqual(transactionEvent.seqNo, 0) // First event should have seqNo 0
+ assert.ok(transactionEvent.timestamp instanceof Date)
+ if (transactionEvent.evse == null) {
+ assert.fail('Expected evse to be defined')
+ }
+ assert.strictEqual(transactionEvent.evse.id, 1) // EVSE ID should match connector ID for this setup
+ assert.notStrictEqual(transactionEvent.transactionInfo, undefined)
+ assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
// Validate structure matches OCPP 2.0.1 schema requirements
- expect(typeof transactionEvent.eventType).toBe('string')
- expect(typeof transactionEvent.triggerReason).toBe('string')
- expect(typeof transactionEvent.seqNo).toBe('number')
- expect(transactionEvent.seqNo).toBeGreaterThanOrEqual(0)
+ assert.strictEqual(typeof transactionEvent.eventType, 'string')
+ assert.strictEqual(typeof transactionEvent.triggerReason, 'string')
+ assert.strictEqual(typeof transactionEvent.seqNo, 'number')
+ assert.strictEqual(transactionEvent.seqNo, 0)
})
await it('should increment sequence number for subsequent events', () => {
)
// Validate sequence number progression: 0 → 1 → 2
- expect(startEvent.seqNo).toBe(0)
- expect(updateEvent.seqNo).toBe(1)
- expect(endEvent.seqNo).toBe(2)
+ assert.strictEqual(startEvent.seqNo, 0)
+ assert.strictEqual(updateEvent.seqNo, 1)
+ assert.strictEqual(endEvent.seqNo, 2)
// Validate all events share same transaction ID
- expect(startEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(updateEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(endEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(startEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(updateEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(endEvent.transactionInfo.transactionId, transactionId)
})
await it('should handle optional parameters correctly', () => {
)
// Validate optional fields are included
- expect(transactionEvent.idToken).toBeDefined()
- expect(transactionEvent.idToken?.idToken).toBe('TEST_TOKEN_123')
- expect(transactionEvent.idToken?.type).toBe(OCPP20IdTokenEnumType.ISO14443)
- expect(transactionEvent.transactionInfo.chargingState).toBe(
+ if (transactionEvent.idToken == null) {
+ assert.fail('Expected idToken to be defined')
+ }
+ assert.strictEqual(transactionEvent.idToken.idToken, 'TEST_TOKEN_123')
+ assert.strictEqual(transactionEvent.idToken.type, OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(
+ transactionEvent.transactionInfo.chargingState,
OCPP20ChargingStateEnumType.Charging
)
- expect(transactionEvent.transactionInfo.remoteStartId).toBe(12345)
- expect(transactionEvent.cableMaxCurrent).toBe(32)
- expect(transactionEvent.numberOfPhasesUsed).toBe(3)
- expect(transactionEvent.offline).toBe(false)
- expect(transactionEvent.reservationId).toBe(67890)
+ assert.strictEqual(transactionEvent.transactionInfo.remoteStartId, 12345)
+ assert.strictEqual(transactionEvent.cableMaxCurrent, 32)
+ assert.strictEqual(transactionEvent.numberOfPhasesUsed, 3)
+ assert.strictEqual(transactionEvent.offline, false)
+ assert.strictEqual(transactionEvent.reservationId, 67890)
})
await it('should validate transaction ID format (identifier string ≤36 chars)', () => {
)
throw new Error('Should have thrown error for invalid identifier string')
} catch (error) {
- expect((error as Error).message).toContain('Invalid transaction ID format')
- expect((error as Error).message).toContain('≤36 characters')
+ assert.ok((error as Error).message.includes('Invalid transaction ID format'))
+ assert.ok((error as Error).message.includes('≤36 characters'))
}
})
transactionId
)
- expect(transactionEvent.triggerReason).toBe(triggerReason)
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(transactionEvent.triggerReason, triggerReason)
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Updated)
}
})
})
)
// Validate response structure (EmptyObject for OCPP 2.0.1 TransactionEventResponse)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
})
await it('should handle errors gracefully', async () => {
)
throw new Error('Should have thrown error')
} catch (error) {
- expect((error as Error).message).toContain('Network error')
+ assert.ok((error as Error).message.includes('Network error'))
}
})
})
// Verify sequence number is set
const connectorStatus = mockStation.getConnectorStatus(connectorId)
- expect(connectorStatus?.transactionSeqNo).toBeDefined()
+ assert.notStrictEqual(connectorStatus?.transactionSeqNo, undefined)
// Reset sequence number
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
// Verify sequence number is reset
- expect(connectorStatus?.transactionSeqNo).toBeUndefined()
+ assert.strictEqual(connectorStatus?.transactionSeqNo, undefined)
})
await it('should handle non-existent connector gracefully', () => {
const nonExistentConnectorId = 999
// Should not throw error for non-existent connector
- expect(() => {
+ assert.doesNotThrow(() => {
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, nonExistentConnectorId)
- }).not.toThrow()
+ })
})
})
'transactionInfo',
]
for (const field of requiredFields) {
- expect(transactionEvent).toHaveProperty(field)
- expect(transactionEvent[field as keyof typeof transactionEvent]).toBeDefined()
+ assert.ok(field in transactionEvent)
+ assert.notStrictEqual(transactionEvent[field as keyof typeof transactionEvent], undefined)
}
// Validate field types match schema requirements
- expect(typeof transactionEvent.eventType).toBe('string')
- expect(transactionEvent.timestamp).toBeInstanceOf(Date)
- expect(typeof transactionEvent.triggerReason).toBe('string')
- expect(typeof transactionEvent.seqNo).toBe('number')
- expect(typeof transactionEvent.evse).toBe('object')
- expect(typeof transactionEvent.transactionInfo).toBe('object')
+ assert.strictEqual(typeof transactionEvent.eventType, 'string')
+ assert.ok(transactionEvent.timestamp instanceof Date)
+ assert.strictEqual(typeof transactionEvent.triggerReason, 'string')
+ assert.strictEqual(typeof transactionEvent.seqNo, 'number')
+ assert.strictEqual(typeof transactionEvent.evse, 'object')
+ assert.strictEqual(typeof transactionEvent.transactionInfo, 'object')
// Validate EVSE structure
- expect(transactionEvent.evse).toBeDefined()
- expect(typeof transactionEvent.evse?.id).toBe('number')
- expect(transactionEvent.evse?.id).toBeGreaterThan(0)
+ if (transactionEvent.evse == null) {
+ assert.fail('Expected evse to be defined')
+ }
+ assert.strictEqual(typeof transactionEvent.evse.id, 'number')
+ assert.ok(transactionEvent.evse.id > 0)
// Validate transactionInfo structure
- expect(typeof transactionEvent.transactionInfo.transactionId).toBe('string')
+ assert.strictEqual(typeof transactionEvent.transactionInfo.transactionId, 'string')
// Validate enum values are strings (not numbers)
- expect(Object.values(OCPP20TransactionEventEnumType)).toContain(transactionEvent.eventType)
- expect(Object.values(OCPP20TriggerReasonEnumType)).toContain(transactionEvent.triggerReason)
+ assert.ok(
+ Object.values(OCPP20TransactionEventEnumType).includes(transactionEvent.eventType)
+ )
+ assert.ok(
+ Object.values(OCPP20TriggerReasonEnumType).includes(transactionEvent.triggerReason)
+ )
})
await it('should handle EVSE/connector mapping correctly', () => {
)
// For this test setup, EVSE ID should match connector ID
- expect(transactionEvent.evse).toBeDefined()
- expect(transactionEvent.evse?.id).toBe(connectorId)
+ if (transactionEvent.evse == null) {
+ assert.fail('Expected evse to be defined')
+ }
+ assert.strictEqual(transactionEvent.evse.id, connectorId)
// connectorId should only be included if different from EVSE ID
// In this case they should be the same, so connectorId should not be present
- expect(transactionEvent.evse?.connectorId).toBeUndefined()
+ assert.strictEqual(transactionEvent.evse.connectorId, undefined)
})
})
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
})
await it('should select RemoteStop for remote_command context with RequestStopTransaction', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStop)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStop)
})
await it('should select UnlockCommand for remote_command context with UnlockConnector', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.UnlockCommand)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.UnlockCommand)
})
await it('should select ResetCommand for remote_command context with Reset', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ResetCommand)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ResetCommand)
})
await it('should select Trigger for remote_command context with TriggerMessage', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
})
await it('should select Authorized for local_authorization context with idToken', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
})
await it('should select StopAuthorized for local_authorization context with stopAuthorized', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.StopAuthorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
})
await it('should select Deauthorized when isDeauthorized flag is true', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
})
await it('should select CablePluggedIn for cable_action context with plugged_in', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
})
await it('should select EVDetected for cable_action context with detected', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected)
})
await it('should select EVDeparted for cable_action context with unplugged', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted)
})
await it('should select ChargingStateChanged for charging_state context', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ChargingStateChanged)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ChargingStateChanged)
})
await it('should select MeterValuePeriodic for meter_value context with periodic flag', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValuePeriodic)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValuePeriodic)
})
await it('should select MeterValueClock for meter_value context without periodic flag', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValueClock)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValueClock)
})
await it('should select SignedDataReceived when isSignedDataReceived flag is true', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.SignedDataReceived)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.SignedDataReceived)
})
await it('should select appropriate system events for system_event context', () => {
context
)
- expect(triggerReason).toBe(testCase.expected)
+ assert.strictEqual(triggerReason, testCase.expected)
}
})
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EnergyLimitReached)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EnergyLimitReached)
})
await it('should select TimeLimitReached for time_limit context', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.TimeLimitReached)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.TimeLimitReached)
})
await it('should select AbnormalCondition for abnormal_condition context', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.AbnormalCondition)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.AbnormalCondition)
})
await it('should handle priority ordering with multiple applicable contexts', () => {
)
// Should select RemoteStart (priority 1) over Deauthorized (priority 2) or CablePluggedIn (priority 3)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart)
})
await it('should fallback to Trigger for unknown context source', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
})
await it('should fallback to Trigger for incomplete context', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger)
})
})
transactionId
)
- expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(transactionEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart)
- expect(transactionEvent.seqNo).toBe(0)
- expect(transactionEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(
+ transactionEvent.triggerReason,
+ OCPP20TriggerReasonEnumType.RemoteStart
+ )
+ assert.strictEqual(transactionEvent.seqNo, 0)
+ assert.strictEqual(transactionEvent.transactionInfo.transactionId, transactionId)
})
await it('should pass through optional parameters correctly', () => {
options
)
- expect(transactionEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(transactionEvent.idToken?.idToken).toBe('CONTEXT_TEST_TOKEN')
- expect(transactionEvent.transactionInfo.chargingState).toBe(
+ assert.strictEqual(transactionEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(transactionEvent.idToken?.idToken, 'CONTEXT_TEST_TOKEN')
+ assert.strictEqual(
+ transactionEvent.transactionInfo.chargingState,
OCPP20ChargingStateEnumType.Charging
)
})
)
// Validate response structure
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
})
await it('should handle context-aware error scenarios gracefully', async () => {
)
throw new Error('Should have thrown error')
} catch (error) {
- expect((error as Error).message).toContain('Context test error')
+ assert.ok((error as Error).message.includes('Context test error'))
}
})
})
transactionId
)
- expect(oldEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(oldEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(oldEvent.seqNo).toBe(0)
+ assert.strictEqual(oldEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(oldEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(oldEvent.seqNo, 0)
})
await it('should maintain compatibility with existing sendTransactionEvent calls', async () => {
transactionId
)
- expect(response).toBeDefined()
- expect(typeof response).toBe('object')
+ assert.notStrictEqual(response, undefined)
+ assert.strictEqual(typeof response, 'object')
})
})
})
OCPP20TransactionEventEnumType.Started,
startContext
)
- expect(triggerReason).toBe(expectedStartTrigger)
+ assert.strictEqual(triggerReason, expectedStartTrigger)
})
await it(`should build correct Started event for ${description}`, () => {
idToken != null ? { idToken } : undefined
)
- expect(startedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(startedEvent.triggerReason).toBe(expectedStartTrigger)
- expect(startedEvent.seqNo).toBe(0)
- expect(startedEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(startedEvent.triggerReason, expectedStartTrigger)
+ assert.strictEqual(startedEvent.seqNo, 0)
+ assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
if (includeIdToken) {
- expect(startedEvent.idToken).toBeDefined()
- expect(startedEvent.idToken?.idToken).toBe(`${id.toUpperCase()}_TOKEN_001`)
+ assert.notStrictEqual(startedEvent.idToken, undefined)
+ assert.strictEqual(startedEvent.idToken?.idToken, `${id.toUpperCase()}_TOKEN_001`)
}
})
)
// Validate event sequence
- expect(startedEvent.seqNo).toBe(0)
- expect(chargingEvent.seqNo).toBe(1)
- expect(endedEvent.seqNo).toBe(2)
+ assert.strictEqual(startedEvent.seqNo, 0)
+ assert.strictEqual(chargingEvent.seqNo, 1)
+ assert.strictEqual(endedEvent.seqNo, 2)
// All events share same transaction ID
- expect(startedEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(chargingEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(endedEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(startedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
})
await it(`should maintain independent sequence numbers on different connectors for ${description}`, () => {
)
// Verify independent sequence numbers
- expect(conn1Event1.seqNo).toBe(0)
- expect(conn1Event2.seqNo).toBe(1)
- expect(conn2Event1.seqNo).toBe(0)
- expect(conn2Event2.seqNo).toBe(1)
+ assert.strictEqual(conn1Event1.seqNo, 0)
+ assert.strictEqual(conn1Event2.seqNo, 1)
+ assert.strictEqual(conn2Event1.seqNo, 0)
+ assert.strictEqual(conn2Event2.seqNo, 1)
// Verify independent transaction IDs
- expect(conn1Event1.transactionInfo.transactionId).toBe(transaction1Id)
- expect(conn2Event1.transactionInfo.transactionId).toBe(transaction2Id)
+ assert.strictEqual(conn1Event1.transactionInfo.transactionId, transaction1Id)
+ assert.strictEqual(conn2Event1.transactionInfo.transactionId, transaction2Id)
})
})
}
)
// Assert sequence numbers follow correct order
- expect(cablePluggedEvent.seqNo).toBe(0)
- expect(evDetectedEvent.seqNo).toBe(1)
- expect(chargingStartedEvent.seqNo).toBe(2)
+ assert.strictEqual(cablePluggedEvent.seqNo, 0)
+ assert.strictEqual(evDetectedEvent.seqNo, 1)
+ assert.strictEqual(chargingStartedEvent.seqNo, 2)
// Assert all events share the same transaction ID
- expect(cablePluggedEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(evDetectedEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(chargingStartedEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(cablePluggedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(evDetectedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(chargingStartedEvent.transactionInfo.transactionId, transactionId)
// Assert event types match expected pattern
- expect(cablePluggedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(evDetectedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
- expect(chargingStartedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(cablePluggedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(evDetectedEvent.eventType, OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(chargingStartedEvent.eventType, OCPP20TransactionEventEnumType.Updated)
})
await it('should handle EVDeparted for cable removal ending transaction', () => {
)
// Assert proper sequencing for cable-initiated start and end
- expect(startEvent.seqNo).toBe(0)
- expect(startEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
- expect(endEvent.seqNo).toBe(1)
- expect(endEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted)
- expect(endEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(startEvent.seqNo, 0)
+ assert.strictEqual(startEvent.triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.strictEqual(endEvent.seqNo, 1)
+ assert.strictEqual(endEvent.triggerReason, OCPP20TriggerReasonEnumType.EVDeparted)
+ assert.strictEqual(endEvent.eventType, OCPP20TransactionEventEnumType.Ended)
})
})
]
// Assert EVDetected comes after CablePluggedIn and before authorization
- expect(events[0].triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
- expect(events[1].triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected)
- expect(events[2].triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(events[3].triggerReason).toBe(OCPP20TriggerReasonEnumType.ChargingStateChanged)
+ assert.strictEqual(events[0].triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.strictEqual(events[1].triggerReason, OCPP20TriggerReasonEnumType.EVDetected)
+ assert.strictEqual(events[2].triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(
+ events[3].triggerReason,
+ OCPP20TriggerReasonEnumType.ChargingStateChanged
+ )
// Assert continuous sequence numbers
for (let i = 0; i < events.length; i++) {
- expect(events[i].seqNo).toBe(i)
+ assert.strictEqual(events[i].seqNo, i)
}
})
})
// Get connector status object
const connectorStatus = mockStation.getConnectorStatus(connectorId)
- expect(connectorStatus).toBeDefined()
+ assert.notStrictEqual(connectorStatus, undefined)
if (connectorStatus == null) {
throw new Error('Connector status should be defined')
}
// Initial state: Available
connectorStatus.status = ConnectorStatusEnum.Available
- expect(connectorStatus.status).toBe(ConnectorStatusEnum.Available)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Available)
// After cable plug: Preparing (implied by transaction start)
connectorStatus.status = ConnectorStatusEnum.Preparing
connectorStatus.transactionStarted = true
- expect(connectorStatus.status).toBe(ConnectorStatusEnum.Preparing)
- expect(connectorStatus.transactionStarted).toBe(true)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Preparing)
+ assert.strictEqual(connectorStatus.transactionStarted, true)
// After EV detected and auth: Charging
connectorStatus.status = ConnectorStatusEnum.Charging
- expect(connectorStatus.status).toBe(ConnectorStatusEnum.Charging)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Charging)
// After EV departed: Available again
connectorStatus.status = ConnectorStatusEnum.Available
connectorStatus.transactionStarted = false
- expect(connectorStatus.status).toBe(ConnectorStatusEnum.Available)
- expect(connectorStatus.transactionStarted).toBe(false)
+ assert.strictEqual(connectorStatus.status, ConnectorStatusEnum.Available)
+ assert.strictEqual(connectorStatus.transactionStarted, false)
})
await it('should preserve transaction ID through cable-first flow states', () => {
const transactionId = generateUUID()
const connectorStatus = mockStation.getConnectorStatus(connectorId)
- expect(connectorStatus).toBeDefined()
+ assert.notStrictEqual(connectorStatus, undefined)
if (connectorStatus == null) {
throw new Error('Connector status should be defined')
}
connectorStatus.status = ConnectorStatusEnum.Charging
// Transaction ID should persist through state changes
- expect(connectorStatus.transactionId).toBe(transactionId)
- expect(connectorStatus.transactionStarted).toBe(true)
+ assert.strictEqual(connectorStatus.transactionId, transactionId)
+ assert.strictEqual(connectorStatus.transactionStarted, true)
// Transition to finished
connectorStatus.status = ConnectorStatusEnum.Finishing
// Still same transaction until fully ended
- expect(connectorStatus.transactionId).toBe(transactionId)
+ assert.strictEqual(connectorStatus.transactionId, transactionId)
})
})
// Verify sequence numbers are continuous through suspend/resume
for (let i = 0; i < events.length; i++) {
- expect(events[i].seqNo).toBe(i)
+ assert.strictEqual(events[i].seqNo, i)
}
// Verify all share same transaction ID
for (const event of events) {
- expect(event.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(event.transactionInfo.transactionId, transactionId)
}
})
})
TransactionContextFixtures.cablePluggedIn()
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
})
await it('should select EVDetected from cable_action context with detected state', () => {
TransactionContextFixtures.evDetected()
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected)
})
await it('should select EVDeparted from cable_action context with unplugged state', () => {
TransactionContextFixtures.evDeparted()
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted)
})
})
})
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized)
})
await it('should differentiate IdToken-first from Cable-first by trigger reason', () => {
cableFirstContext
)
- expect(idTokenTrigger).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(cableTrigger).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
- expect(idTokenTrigger).not.toBe(cableTrigger)
+ assert.strictEqual(idTokenTrigger, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(cableTrigger, OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.notStrictEqual(idTokenTrigger, cableTrigger)
})
})
{ idToken }
)
- expect(startedEvent.idToken).toBeDefined()
- expect(startedEvent.idToken?.idToken).toBe('VALID_TOKEN_E03_001')
- expect(startedEvent.idToken?.type).toBe(OCPP20IdTokenEnumType.ISO14443)
- expect(startedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(startedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
+ if (startedEvent.idToken == null) {
+ assert.fail('Expected idToken to be defined')
+ }
+ assert.strictEqual(startedEvent.idToken.idToken, 'VALID_TOKEN_E03_001')
+ assert.strictEqual(startedEvent.idToken.type, OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(startedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(startedEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
})
await it('should not include idToken in subsequent events (E03.FR.01 compliance)', () => {
{ chargingState: OCPP20ChargingStateEnumType.Charging, idToken }
)
- expect(startedEvent.idToken).toBeDefined()
- expect(updatedEvent.idToken).toBeUndefined()
+ assert.notStrictEqual(startedEvent.idToken, undefined)
+ assert.strictEqual(updatedEvent.idToken, undefined)
})
await it('should support various IdToken types for E03 flow', () => {
{ idToken: rfidToken }
)
- expect(rfidEvent.idToken?.type).toBe(OCPP20IdTokenEnumType.ISO14443)
+ assert.strictEqual(rfidEvent.idToken?.type, OCPP20IdTokenEnumType.ISO14443)
// Reset for eMAID test
OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId)
{ idToken: emaidToken }
)
- expect(emaidEvent.idToken?.type).toBe(OCPP20IdTokenEnumType.eMAID)
- expect(emaidEvent.idToken?.idToken).toBe('DE*ABC*E123456*1')
+ assert.strictEqual(emaidEvent.idToken?.type, OCPP20IdTokenEnumType.eMAID)
+ assert.strictEqual(emaidEvent.idToken.idToken, 'DE*ABC*E123456*1')
})
})
)
// Validate event sequence
- expect(authorizedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(authorizedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(authorizedEvent.idToken).toBeDefined()
- expect(authorizedEvent.seqNo).toBe(0)
+ assert.strictEqual(authorizedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(authorizedEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.notStrictEqual(authorizedEvent.idToken, undefined)
+ assert.strictEqual(authorizedEvent.seqNo, 0)
- expect(cableConnectedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
- expect(cableConnectedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
- expect(cableConnectedEvent.idToken).toBeUndefined() // E03.FR.01: idToken only in first event
- expect(cableConnectedEvent.seqNo).toBe(1)
+ assert.strictEqual(cableConnectedEvent.eventType, OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(
+ cableConnectedEvent.triggerReason,
+ OCPP20TriggerReasonEnumType.CablePluggedIn
+ )
+ assert.strictEqual(cableConnectedEvent.idToken, undefined) // E03.FR.01: idToken only in first event
+ assert.strictEqual(cableConnectedEvent.seqNo, 1)
- expect(chargingEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
- expect(chargingEvent.seqNo).toBe(2)
+ assert.strictEqual(chargingEvent.eventType, OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(chargingEvent.seqNo, 2)
- expect(endedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
- expect(endedEvent.seqNo).toBe(3)
+ assert.strictEqual(endedEvent.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(endedEvent.seqNo, 3)
// All events share same transaction ID
- expect(authorizedEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(cableConnectedEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(chargingEvent.transactionInfo.transactionId).toBe(transactionId)
- expect(endedEvent.transactionInfo.transactionId).toBe(transactionId)
+ assert.strictEqual(authorizedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(cableConnectedEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(chargingEvent.transactionInfo.transactionId, transactionId)
+ assert.strictEqual(endedEvent.transactionInfo.transactionId, transactionId)
})
await it('should differentiate E03 lifecycle from E02 Cable-First lifecycle', () => {
)
// Key difference: E03 starts with Authorized, E02 starts with CablePluggedIn
- expect(e03Start.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
- expect(e02Start.triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn)
+ assert.strictEqual(e03Start.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(e02Start.triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn)
// E03 includes idToken in first event, E02 may not
- expect(e03Start.idToken).toBeDefined()
- expect(e02Start.idToken).toBeUndefined()
+ assert.notStrictEqual(e03Start.idToken, undefined)
+ assert.strictEqual(e02Start.idToken, undefined)
})
})
transactionId
)
- expect(authorizedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(authorizedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized)
+ assert.strictEqual(authorizedEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(authorizedEvent.triggerReason, OCPP20TriggerReasonEnumType.Authorized)
- expect(timeoutEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
- expect(timeoutEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.EVConnectTimeout)
- expect(timeoutEvent.seqNo).toBe(1)
+ assert.strictEqual(timeoutEvent.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(
+ timeoutEvent.triggerReason,
+ OCPP20TriggerReasonEnumType.EVConnectTimeout
+ )
+ assert.strictEqual(timeoutEvent.seqNo, 1)
// Same transaction ID for both events
- expect(authorizedEvent.transactionInfo.transactionId).toBe(
+ assert.strictEqual(
+ authorizedEvent.transactionInfo.transactionId,
timeoutEvent.transactionInfo.transactionId
)
})
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
})
await it('should handle transaction end after token revocation', () => {
transactionId
)
- expect(startEvent.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(revokedEvent.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
- expect(revokedEvent.triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized)
+ assert.strictEqual(startEvent.eventType, OCPP20TransactionEventEnumType.Started)
+ assert.strictEqual(revokedEvent.eventType, OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(revokedEvent.triggerReason, OCPP20TriggerReasonEnumType.Deauthorized)
})
await it('should support StopAuthorized trigger for normal transaction end', () => {
context
)
- expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.StopAuthorized)
+ assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized)
})
})
// E03.FR.07: Sequence numbers must be continuous
events.forEach((event, index) => {
- expect(event.seqNo).toBe(index)
+ assert.strictEqual(event.seqNo, index)
})
})
const transaction2Id = generateUUID()
// E03.FR.08: transactionId MUST be unique
- expect(transaction1Id).not.toBe(transaction2Id)
+ assert.notStrictEqual(transaction1Id, transaction2Id)
const event1 = OCPP20ServiceUtils.buildTransactionEvent(
mockStation,
transaction2Id
)
- expect(event1.transactionInfo.transactionId).toBe(transaction1Id)
- expect(event2.transactionInfo.transactionId).toBe(transaction2Id)
- expect(event1.transactionInfo.transactionId).not.toBe(
+ assert.strictEqual(event1.transactionInfo.transactionId, transaction1Id)
+ assert.strictEqual(event2.transactionInfo.transactionId, transaction2Id)
+ assert.notStrictEqual(
+ event1.transactionInfo.transactionId,
event2.transactionInfo.transactionId
)
})
transactionId
)
- expect(sentRequests.length).toBe(0)
+ assert.strictEqual(sentRequests.length, 0)
- expect(response.idTokenInfo).toBeUndefined()
+ assert.strictEqual(response.idTokenInfo, undefined)
const connector = mockStation.getConnectorStatus(connectorId)
assert(connector != null)
assert(connector.transactionEventQueue != null)
- expect(connector.transactionEventQueue.length).toBe(1)
- expect(connector.transactionEventQueue[0].seqNo).toBe(0)
+ assert.strictEqual(connector.transactionEventQueue.length, 1)
+ assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
})
await it('should queue multiple TransactionEvents in order when offline', async () => {
)
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector?.transactionEventQueue?.length).toBe(3)
- assert(connector != null)
- assert(connector.transactionEventQueue != null)
+ assert.strictEqual(connector?.transactionEventQueue?.length, 3)
- expect(connector.transactionEventQueue[0].seqNo).toBe(0)
- expect(connector.transactionEventQueue[1].seqNo).toBe(1)
- expect(connector.transactionEventQueue[2].seqNo).toBe(2)
+ assert.strictEqual(connector.transactionEventQueue[0].seqNo, 0)
+ assert.strictEqual(connector.transactionEventQueue[1].seqNo, 1)
+ assert.strictEqual(connector.transactionEventQueue[2].seqNo, 2)
- expect(connector.transactionEventQueue[0].request.eventType).toBe(
+ assert.ok(
+ connector.transactionEventQueue[0].request.eventType,
OCPP20TransactionEventEnumType.Started
)
- expect(connector.transactionEventQueue[1].request.eventType).toBe(
+ assert.strictEqual(
+ connector.transactionEventQueue[1].request.eventType,
OCPP20TransactionEventEnumType.Updated
)
- expect(connector.transactionEventQueue[2].request.eventType).toBe(
+ assert.strictEqual(
+ connector.transactionEventQueue[2].request.eventType,
OCPP20TransactionEventEnumType.Ended
)
})
transactionId
)
- expect(sentRequests.length).toBe(1)
- expect(sentRequests[0].payload.seqNo).toBe(0)
+ assert.strictEqual(sentRequests.length, 1)
+ assert.strictEqual(sentRequests[0].payload.seqNo, 0)
setOnline(false)
)
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector?.transactionEventQueue?.length).toBe(2)
- assert(connector != null)
- assert(connector.transactionEventQueue != null)
- expect(connector.transactionEventQueue[0].seqNo).toBe(1)
- expect(connector.transactionEventQueue[1].seqNo).toBe(2)
+ assert.strictEqual(connector?.transactionEventQueue?.length, 2)
+ assert.strictEqual(connector.transactionEventQueue[0].seqNo, 1)
+ assert.strictEqual(connector.transactionEventQueue[1].seqNo, 2)
})
await it('should include timestamp in queued events', async () => {
const afterQueue = new Date()
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector?.transactionEventQueue?.[0]?.timestamp).toBeInstanceOf(Date)
- assert(connector != null)
- assert(connector.transactionEventQueue != null)
- expect(connector.transactionEventQueue[0].timestamp.getTime()).toBeGreaterThanOrEqual(
- beforeQueue.getTime()
- )
- expect(connector.transactionEventQueue[0].timestamp.getTime()).toBeLessThanOrEqual(
- afterQueue.getTime()
+ assert.ok(connector?.transactionEventQueue?.[0]?.timestamp instanceof Date)
+ assert.strictEqual(
+ connector.transactionEventQueue[0].timestamp.getTime() >= beforeQueue.getTime(),
+ true
)
+ assert.ok(connector.transactionEventQueue[0].timestamp.getTime() <= afterQueue.getTime())
})
})
transactionId
)
- expect(sentRequests.length).toBe(0)
+ assert.strictEqual(sentRequests.length, 0)
setOnline(true)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- expect(sentRequests.length).toBe(2)
- expect(sentRequests[0].payload.seqNo).toBe(0)
- expect(sentRequests[1].payload.seqNo).toBe(1)
+ assert.strictEqual(sentRequests.length, 2)
+ assert.strictEqual(sentRequests[0].payload.seqNo, 0)
+ assert.strictEqual(sentRequests[1].payload.seqNo, 1)
})
await it('should clear queue after sending', async () => {
)
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector?.transactionEventQueue?.length).toBe(1)
- assert(connector != null)
- assert(connector.transactionEventQueue != null)
+ assert.strictEqual(connector?.transactionEventQueue?.length, 1)
setOnline(true)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- expect(connector.transactionEventQueue.length).toBe(0)
+ assert.strictEqual(connector.transactionEventQueue.length, 0)
})
await it('should preserve FIFO order when draining queue', async () => {
setOnline(true)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- expect(sentRequests[0].payload.eventType).toBe(OCPP20TransactionEventEnumType.Started)
- expect(sentRequests[1].payload.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
- expect(sentRequests[2].payload.eventType).toBe(OCPP20TransactionEventEnumType.Ended)
+ assert.strictEqual(
+ sentRequests[0].payload.eventType,
+ OCPP20TransactionEventEnumType.Started
+ )
+ assert.strictEqual(
+ sentRequests[1].payload.eventType,
+ OCPP20TransactionEventEnumType.Updated
+ )
+ assert.strictEqual(sentRequests[2].payload.eventType, OCPP20TransactionEventEnumType.Ended)
- expect(sentRequests[0].payload.seqNo).toBe(0)
- expect(sentRequests[1].payload.seqNo).toBe(1)
- expect(sentRequests[2].payload.seqNo).toBe(2)
+ assert.strictEqual(sentRequests[0].payload.seqNo, 0)
+ assert.strictEqual(sentRequests[1].payload.seqNo, 1)
+ assert.strictEqual(sentRequests[2].payload.seqNo, 2)
})
await it('should handle empty queue gracefully', async () => {
const connectorId = 1
- await expect(
+ await assert.doesNotReject(
OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- ).resolves.toBeUndefined()
+ )
- expect(sentRequests.length).toBe(0)
+ assert.strictEqual(sentRequests.length, 0)
})
await it('should handle null queue gracefully', async () => {
assert(connector != null)
connector.transactionEventQueue = undefined
- await expect(
+ await assert.doesNotReject(
OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- ).resolves.toBeUndefined()
+ )
- expect(sentRequests.length).toBe(0)
+ assert.strictEqual(sentRequests.length, 0)
})
})
connectorId,
transactionId
)
- expect(sentRequests[0].payload.seqNo).toBe(0)
+ assert.strictEqual(sentRequests[0].payload.seqNo, 0)
setOnline(false)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, connectorId)
- expect(sentRequests[1].payload.seqNo).toBe(1)
- expect(sentRequests[2].payload.seqNo).toBe(2)
+ assert.strictEqual(sentRequests[1].payload.seqNo, 1)
+ assert.strictEqual(sentRequests[2].payload.seqNo, 2)
await OCPP20ServiceUtils.sendTransactionEvent(
mockStation,
transactionId
)
- expect(sentRequests[3].payload.seqNo).toBe(3)
+ assert.strictEqual(sentRequests[3].payload.seqNo, 3)
for (let i = 0; i < sentRequests.length; i++) {
- expect(sentRequests[i].payload.seqNo).toBe(i)
+ assert.strictEqual(sentRequests[i].payload.seqNo, i)
}
})
})
const connector1 = mockStation.getConnectorStatus(1)
const connector2 = mockStation.getConnectorStatus(2)
- expect(connector1?.transactionEventQueue?.length).toBe(2)
- expect(connector2?.transactionEventQueue?.length).toBe(1)
- assert(connector1 != null)
- assert(connector1.transactionEventQueue != null)
- assert(connector2 != null)
- assert(connector2.transactionEventQueue != null)
+ assert.strictEqual(connector1?.transactionEventQueue?.length, 2)
+ assert.strictEqual(connector2?.transactionEventQueue?.length, 1)
- expect(connector1.transactionEventQueue[0].request.transactionInfo.transactionId).toBe(
+ assert.strictEqual(
+ connector1.transactionEventQueue[0].request.transactionInfo.transactionId,
transactionId1
)
- expect(connector2.transactionEventQueue[0].request.transactionInfo.transactionId).toBe(
+ assert.strictEqual(
+ connector2.transactionEventQueue[0].request.transactionInfo.transactionId,
transactionId2
)
})
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, 1)
- expect(sentRequests.length).toBe(1)
- expect(
- (sentRequests[0].payload.transactionInfo as Record<string, unknown>).transactionId
- ).toBe(transactionId1)
+ assert.strictEqual(sentRequests.length, 1)
+ assert.strictEqual(
+ (sentRequests[0].payload.transactionInfo as Record<string, unknown>).transactionId,
+ transactionId1
+ )
const connector2 = mockStation.getConnectorStatus(2)
- expect(connector2?.transactionEventQueue?.length).toBe(1)
+ assert.strictEqual(connector2?.transactionEventQueue?.length, 1)
await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, 2)
- expect(sentRequests.length).toBe(2)
- expect(
- (sentRequests[1].payload.transactionInfo as Record<string, unknown>).transactionId
- ).toBe(transactionId2)
+ assert.strictEqual(sentRequests.length, 2)
+ assert.strictEqual(
+ (sentRequests[1].payload.transactionInfo as Record<string, unknown>).transactionId,
+ transactionId2
+ )
})
})
await OCPP20ServiceUtils.sendQueuedTransactionEvents(errorStation, connectorId)
- expect(callCount).toBe(3)
+ assert.strictEqual(callCount, 3)
})
})
})
// Verify no timer was started (method should return early)
const connector = ocpp16Station.getConnectorStatus(1)
- expect(connector?.transactionTxUpdatedSetInterval).toBeUndefined()
+ assert.strictEqual(connector?.transactionTxUpdatedSetInterval, undefined)
})
await it('should not start timer when interval is zero', () => {
// Simulate startTxUpdatedInterval with zero interval
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector).toBeDefined()
+ assert.notStrictEqual(connector, undefined)
assert(connector != null)
// Zero interval should not start timer
// This is verified by the implementation logging debug message
- expect(connector.transactionTxUpdatedSetInterval).toBeUndefined()
+ assert.strictEqual(connector.transactionTxUpdatedSetInterval, undefined)
})
await it('should not start timer when interval is negative', () => {
const connectorId = 1
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector).toBeDefined()
+ assert.notStrictEqual(connector, undefined)
assert(connector != null)
// Negative interval should not start timer
- expect(connector.transactionTxUpdatedSetInterval).toBeUndefined()
+ assert.strictEqual(connector.transactionTxUpdatedSetInterval, undefined)
})
await it('should handle non-existent connector gracefully', () => {
const nonExistentConnectorId = 999
// Should not throw for non-existent connector
- expect(() => {
+ assert.doesNotThrow(() => {
mockStation.getConnectorStatus(nonExistentConnectorId)
- }).not.toThrow()
+ })
// Should return undefined for non-existent connector
- expect(mockStation.getConnectorStatus(nonExistentConnectorId)).toBeUndefined()
+ assert.strictEqual(mockStation.getConnectorStatus(nonExistentConnectorId), undefined)
})
})
)
// Verify the request was sent with correct trigger reason
- expect(sentRequests.length).toBe(1)
- expect(sentRequests[0].command).toBe('TransactionEvent')
- expect(sentRequests[0].payload.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
- expect(sentRequests[0].payload.triggerReason).toBe(
+ assert.strictEqual(sentRequests.length, 1)
+ assert.strictEqual(sentRequests[0].command, 'TransactionEvent')
+ assert.strictEqual(
+ sentRequests[0].payload.eventType,
+ OCPP20TransactionEventEnumType.Updated
+ )
+ assert.strictEqual(
+ sentRequests[0].payload.triggerReason,
OCPP20TriggerReasonEnumType.MeterValuePeriodic
)
})
connectorId,
transactionId
)
- expect(startEvent.seqNo).toBe(0)
+ assert.strictEqual(startEvent.seqNo, 0)
// Send multiple periodic events (simulating timer ticks)
for (let i = 1; i <= 3; i++) {
connectorId,
transactionId
)
- expect(periodicEvent.seqNo).toBe(i)
+ assert.strictEqual(periodicEvent.seqNo, i)
}
// Verify sequence numbers are continuous: 0, 1, 2, 3
const connector = mockStation.getConnectorStatus(connectorId)
- expect(connector?.transactionSeqNo).toBe(3)
+ assert.strictEqual(connector?.transactionSeqNo, 3)
})
await it('should maintain correct eventType (Updated) for periodic events', async () => {
)
// Verify eventType is Updated (not Started or Ended)
- expect(sentRequests[0].payload.eventType).toBe(OCPP20TransactionEventEnumType.Updated)
+ assert.strictEqual(
+ sentRequests[0].payload.eventType,
+ OCPP20TransactionEventEnumType.Updated
+ )
})
await it('should include EVSE information in periodic events', async () => {
)
// Verify EVSE info is present
- expect(sentRequests[0].payload.evse).toBeDefined()
- expect((sentRequests[0].payload.evse as Record<string, unknown>).id).toBe(connectorId)
+ assert.notStrictEqual(sentRequests[0].payload.evse, undefined)
+ assert.strictEqual(
+ (sentRequests[0].payload.evse as Record<string, unknown>).id,
+ connectorId
+ )
})
await it('should include transactionInfo with correct transactionId', async () => {
)
// Verify transactionInfo contains the transaction ID
- expect(sentRequests[0].payload.transactionInfo).toBeDefined()
- expect(
- (sentRequests[0].payload.transactionInfo as Record<string, unknown>).transactionId
- ).toBe(transactionId)
+ assert.notStrictEqual(sentRequests[0].payload.transactionInfo, undefined)
+ assert.strictEqual(
+ (sentRequests[0].payload.transactionInfo as Record<string, unknown>).transactionId,
+ transactionId
+ )
})
})
connectorId,
transactionId
)
- expect(startEvent.seqNo).toBe(0)
+ assert.strictEqual(startEvent.seqNo, 0)
// 2. Multiple periodic updates (seqNo: 1, 2, 3)
for (let i = 1; i <= 3; i++) {
connectorId,
transactionId
)
- expect(updateEvent.seqNo).toBe(i)
+ assert.strictEqual(updateEvent.seqNo, i)
}
// 3. Ended event (seqNo: 4)
connectorId,
transactionId
)
- expect(endEvent.seqNo).toBe(4)
+ assert.strictEqual(endEvent.seqNo, 4)
})
await it('should handle multiple connectors with independent timers', () => {
)
// Verify independent sequence numbers
- expect(event1Start.seqNo).toBe(0)
- expect(event1Update.seqNo).toBe(1)
- expect(event2Start.seqNo).toBe(0)
- expect(event2Update.seqNo).toBe(1)
+ assert.strictEqual(event1Start.seqNo, 0)
+ assert.strictEqual(event1Update.seqNo, 1)
+ assert.strictEqual(event2Start.seqNo, 0)
+ assert.strictEqual(event2Update.seqNo, 1)
// Verify different transaction IDs
- expect(event1Start.transactionInfo.transactionId).toBe(transactionId1)
- expect(event2Start.transactionInfo.transactionId).toBe(transactionId2)
+ assert.strictEqual(event1Start.transactionInfo.transactionId, transactionId1)
+ assert.strictEqual(event2Start.transactionInfo.transactionId, transactionId2)
})
})
)
throw new Error('Should have thrown network error')
} catch (error) {
- expect((error as Error).message).toContain('Network timeout')
+ assert.ok((error as Error).message.includes('Network timeout'))
}
})
})
* @description Verifies message limit enforcement logic for OCPP 2.0 payloads
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
logger
)
- expect(result.rejected).toBe(false)
- expect(result.results).toStrictEqual([])
+ assert.strictEqual(result.rejected, false)
+ assert.deepStrictEqual(result.results, [])
})
await it('should return rejected:false for empty data array with both limits 0', () => {
logger
)
- expect(result.rejected).toBe(false)
- expect(result.results).toStrictEqual([])
+ assert.strictEqual(result.rejected, false)
+ assert.deepStrictEqual(result.results, [])
})
})
logger
)
- expect(result.rejected).toBe(false)
- expect(result.results).toStrictEqual([])
+ assert.strictEqual(result.rejected, false)
+ assert.deepStrictEqual(result.results, [])
})
await it('should return rejected:false when data length equals the items limit', () => {
logger
)
- expect(result.rejected).toBe(false)
- expect(result.results).toStrictEqual([])
+ assert.strictEqual(result.rejected, false)
+ assert.deepStrictEqual(result.results, [])
})
await it('should reject all items with TooManyElements when items limit is exceeded', () => {
logger
)
- expect(result.rejected).toBe(true)
- expect(result.results).toHaveLength(3)
+ assert.strictEqual(result.rejected, true)
+ assert.strictEqual(result.results.length, 3)
for (const r of result.results as RejectedResult[]) {
- expect(r.reasonCode).toBe('TooManyElements')
- expect(r.info).toContain('ItemsPerMessage limit 2')
+ assert.strictEqual(r.reasonCode, 'TooManyElements')
+ assert.ok(r.info.includes('ItemsPerMessage limit 2'))
}
})
logger
)
- expect(result.rejected).toBe(true)
- expect(result.results).toHaveLength(2)
+ assert.strictEqual(result.rejected, true)
+ assert.strictEqual(result.results.length, 2)
for (const r of result.results as RejectedResult[]) {
- expect(r.reasonCode).toBe('TooManyElements')
+ assert.strictEqual(r.reasonCode, 'TooManyElements')
}
})
logger
)
- expect(logger.debugCalls).toHaveLength(1)
- expect(String(logger.debugCalls[0][0])).toContain('ItemsPerMessage limit')
+ assert.strictEqual(logger.debugCalls.length, 1)
+ assert.ok(String(logger.debugCalls[0][0]).includes('ItemsPerMessage limit'))
})
})
logger
)
- expect(result.rejected).toBe(false)
- expect(result.results).toStrictEqual([])
+ assert.strictEqual(result.rejected, false)
+ assert.deepStrictEqual(result.results, [])
})
await it('should reject all items with TooLargeElement when bytes limit is exceeded', () => {
logger
)
- expect(result.rejected).toBe(true)
- expect(result.results).toHaveLength(1)
+ assert.strictEqual(result.rejected, true)
+ assert.strictEqual(result.results.length, 1)
const r = (result.results as RejectedResult[])[0]
- expect(r.reasonCode).toBe('TooLargeElement')
- expect(r.info).toContain('BytesPerMessage limit 1')
+ assert.strictEqual(r.reasonCode, 'TooLargeElement')
+ assert.ok(r.info.includes('BytesPerMessage limit 1'))
})
await it('should reject all items with TooLargeElement for multiple items over bytes limit', () => {
logger
)
- expect(result.rejected).toBe(true)
- expect(result.results).toHaveLength(2)
+ assert.strictEqual(result.rejected, true)
+ assert.strictEqual(result.results.length, 2)
for (const r of result.results as RejectedResult[]) {
- expect(r.reasonCode).toBe('TooLargeElement')
+ assert.strictEqual(r.reasonCode, 'TooLargeElement')
}
})
logger
)
- expect(logger.debugCalls).toHaveLength(1)
- expect(String(logger.debugCalls[0][0])).toContain('BytesPerMessage limit')
+ assert.strictEqual(logger.debugCalls.length, 1)
+ assert.ok(String(logger.debugCalls[0][0]).includes('BytesPerMessage limit'))
})
})
logger
)
- expect(result.rejected).toBe(true)
+ assert.strictEqual(result.rejected, true)
for (const r of result.results as RejectedResult[]) {
- expect(r.reasonCode).toBe('TooManyElements')
+ assert.strictEqual(r.reasonCode, 'TooManyElements')
}
})
})
logger
)
- expect(capturedItems).toHaveLength(1)
- expect(capturedItems[0]).toBe(item)
+ assert.strictEqual(capturedItems.length, 1)
+ assert.strictEqual(capturedItems[0], item)
})
await it('should pass reason with info and reasonCode to buildRejected callback', () => {
logger
)
- expect(capturedReasons).toHaveLength(1)
- expect(capturedReasons[0].reasonCode).toBe('TooLargeElement')
- expect(typeof capturedReasons[0].info).toBe('string')
- expect(capturedReasons[0].info.length).toBeGreaterThan(0)
+ assert.strictEqual(capturedReasons.length, 1)
+ assert.strictEqual(capturedReasons[0].reasonCode, 'TooLargeElement')
+ assert.strictEqual(typeof capturedReasons[0].info, 'string')
+ assert.ok(capturedReasons[0].info.length > 0)
})
})
})
* @description Unit tests for OCPP 2.0 variable management and device model
*/
-import { expect } from '@std/expect'
import { millisecondsToSeconds } from 'date-fns'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
const manager1 = OCPP20VariableManager.getInstance()
const manager2 = OCPP20VariableManager.getInstance()
- expect(manager1).toBeDefined()
- expect(manager1).toBe(manager2) // Same instance (singleton)
+ assert.notStrictEqual(manager1, undefined)
+ assert.strictEqual(manager1, manager2) // Same instance (singleton)
})
await describe('getVariables method tests', async () => {
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(2)
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 2)
// First variable: HeartbeatInterval
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeValue).toBe(
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ result[0].attributeValue,
millisecondsToSeconds(Constants.DEFAULT_HEARTBEAT_INTERVAL).toString()
)
- expect(result[0].component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(result[0].variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(result[0].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(result[0].variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.strictEqual(result[0].attributeStatusInfo, undefined)
// Second variable: EVConnectionTimeOut
- expect(result[1].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[1].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[1].attributeValue).toBe(Constants.DEFAULT_EV_CONNECTION_TIMEOUT.toString())
- expect(result[1].component.name).toBe(OCPP20ComponentName.TxCtrlr)
- expect(result[1].variable.name).toBe(OCPP20RequiredVariableName.EVConnectionTimeOut)
- expect(result[1].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[1].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[1].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ result[1].attributeValue,
+ Constants.DEFAULT_EV_CONNECTION_TIMEOUT.toString()
+ )
+ assert.strictEqual(result[1].component.name, OCPP20ComponentName.TxCtrlr)
+ assert.strictEqual(result[1].variable.name, OCPP20RequiredVariableName.EVConnectionTimeOut)
+ assert.strictEqual(result[1].attributeStatusInfo, undefined)
})
await it('should accept default true value for AuthorizeRemoteStart (AuthCtrlr)', () => {
},
]
const result = manager.getVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[0].attributeValue).toBe('true')
- expect(result[0].component.name).toBe(OCPP20ComponentName.AuthCtrlr)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[0].attributeValue, 'true')
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.AuthCtrlr)
})
await it('should accept setting and getting AuthorizeRemoteStart = true (AuthCtrlr)', () => {
variable: { name: OCPP20RequiredVariableName.AuthorizeRemoteStart },
},
])[0]
- expect(setRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setRes.attributeStatus, SetVariableStatusEnumType.Accepted)
const getRes = manager.getVariables(station, [
{
component: { name: OCPP20ComponentName.AuthCtrlr },
variable: { name: OCPP20RequiredVariableName.AuthorizeRemoteStart },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue).toBe('true')
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue, 'true')
})
await it('should reject invalid values for AuthorizeRemoteStart (AuthCtrlr)', () => {
variable: { name: OCPP20RequiredVariableName.AuthorizeRemoteStart },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.InvalidValue)
}
})
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(1)
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 1)
// Behavior: invalid component is rejected before variable support check
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.UnknownComponent)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeValue).toBeUndefined()
- expect(result[0].component.name).toBe('InvalidComponent')
- expect(result[0].variable.name).toBe('SomeVariable')
- expect(result[0].attributeStatusInfo).toBeDefined()
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
- expect(result[0].attributeStatusInfo?.additionalInfo).toContain('Component InvalidComponent')
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[0].attributeValue, undefined)
+ assert.strictEqual(result[0].component.name, 'InvalidComponent')
+ assert.strictEqual(result[0].variable.name, 'SomeVariable')
+ if (result[0].attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (result[0].attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(result[0].attributeStatusInfo.reasonCode, ReasonCodeEnumType.NotFound)
+ assert.ok(result[0].attributeStatusInfo.additionalInfo.includes('Component InvalidComponent'))
})
await it('should handle unsupported attribute type gracefully', () => {
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result[0].attributeType).toBe(AttributeEnumType.Target)
- expect(result[0].attributeValue).toBeUndefined()
- expect(result[0].component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(result[0].variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(result[0].attributeStatusInfo).toBeDefined()
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
- expect(result[0].attributeStatusInfo?.additionalInfo).toContain(
- 'Attribute type Target is not supported'
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(
+ result[0].attributeStatus,
+ GetVariableStatusEnumType.NotSupportedAttributeType
+ )
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Target)
+ assert.strictEqual(result[0].attributeValue, undefined)
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(result[0].variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ if (result[0].attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (result[0].attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ result[0].attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.UnsupportedParam
+ )
+ assert.ok(
+ result[0].attributeStatusInfo.additionalInfo.includes(
+ 'Attribute type Target is not supported'
+ )
)
})
},
]
const result = manager.getVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result[0].variable.name).toBe(OCPP20OptionalVariableName.WebSocketPingInterval)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(
+ result[0].attributeStatus,
+ GetVariableStatusEnumType.NotSupportedAttributeType
+ )
+ assert.strictEqual(result[0].variable.name, OCPP20OptionalVariableName.WebSocketPingInterval)
})
await it('should handle non-existent connector instance', () => {
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.UnknownComponent)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeValue).toBeUndefined()
- expect(result[0].component.name).toBe(OCPP20ComponentName.Connector)
- expect(result[0].component.instance).toBe('999')
- expect(result[0].variable.name).toBe(OCPP20RequiredVariableName.AuthorizeRemoteStart)
- expect(result[0].attributeStatusInfo).toBeDefined()
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
- expect(result[0].attributeStatusInfo?.additionalInfo).toContain(
- 'Component Connector is not supported'
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[0].attributeValue, undefined)
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.Connector)
+ assert.strictEqual(result[0].component.instance, '999')
+ assert.strictEqual(result[0].variable.name, OCPP20RequiredVariableName.AuthorizeRemoteStart)
+ if (result[0].attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (result[0].attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(result[0].attributeStatusInfo.reasonCode, ReasonCodeEnumType.NotFound)
+ assert.ok(
+ result[0].attributeStatusInfo.additionalInfo.includes(
+ 'Component Connector is not supported'
+ )
)
})
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(3)
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 3)
// First variable: HeartbeatInterval
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeValue).toBe(
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ result[0].attributeValue,
millisecondsToSeconds(Constants.DEFAULT_HEARTBEAT_INTERVAL).toString()
)
- expect(result[0].component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(result[0].variable.name).toBe(OCPP20OptionalVariableName.HeartbeatInterval)
- expect(result[0].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(result[0].variable.name, OCPP20OptionalVariableName.HeartbeatInterval)
+ assert.strictEqual(result[0].attributeStatusInfo, undefined)
// Second variable: WebSocketPingInterval
- expect(result[1].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[1].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[1].attributeValue).toBe(Constants.DEFAULT_WEBSOCKET_PING_INTERVAL.toString())
- expect(result[1].component.name).toBe(OCPP20ComponentName.ChargingStation)
- expect(result[1].variable.name).toBe(OCPP20OptionalVariableName.WebSocketPingInterval)
- expect(result[1].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[1].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[1].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(
+ result[1].attributeValue,
+ Constants.DEFAULT_WEBSOCKET_PING_INTERVAL.toString()
+ )
+ assert.strictEqual(result[1].component.name, OCPP20ComponentName.ChargingStation)
+ assert.strictEqual(result[1].variable.name, OCPP20OptionalVariableName.WebSocketPingInterval)
+ assert.strictEqual(result[1].attributeStatusInfo, undefined)
// Third variable: MessageTimeout
- expect(result[2].attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(result[2].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[2].attributeValue).toBe(Constants.DEFAULT_CONNECTION_TIMEOUT.toString())
- expect(result[2].component.name).toBe(OCPP20ComponentName.OCPPCommCtrlr)
- expect(result[2].component.instance).toBe('Default')
- expect(result[2].variable.name).toBe(OCPP20RequiredVariableName.MessageTimeout)
- expect(result[2].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[2].attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[2].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[2].attributeValue, Constants.DEFAULT_CONNECTION_TIMEOUT.toString())
+ assert.strictEqual(result[2].component.name, OCPP20ComponentName.OCPPCommCtrlr)
+ assert.strictEqual(result[2].component.instance, 'Default')
+ assert.strictEqual(result[2].variable.name, OCPP20RequiredVariableName.MessageTimeout)
+ assert.strictEqual(result[2].attributeStatusInfo, undefined)
})
await it('should reject unknown variable on EVSE component', () => {
const result = manager.getVariables(station, request)
- expect(Array.isArray(result)).toBe(true)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(GetVariableStatusEnumType.UnknownVariable)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeValue).toBeUndefined()
- expect(result[0].component.name).toBe(OCPP20ComponentName.EVSE)
- expect(result[0].component.instance).toBe('1')
- expect(result[0].variable.name).toBe(OCPP20RequiredVariableName.AuthorizeRemoteStart)
- expect(result[0].attributeStatusInfo).toBeDefined()
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
+ assert.ok(Array.isArray(result))
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, GetVariableStatusEnumType.UnknownVariable)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[0].attributeValue, undefined)
+ assert.strictEqual(result[0].component.name, OCPP20ComponentName.EVSE)
+ assert.strictEqual(result[0].component.instance, '1')
+ assert.strictEqual(result[0].variable.name, OCPP20RequiredVariableName.AuthorizeRemoteStart)
+ assert.notStrictEqual(result[0].attributeStatusInfo, undefined)
+ assert.strictEqual(result[0].attributeStatusInfo?.reasonCode, ReasonCodeEnumType.NotFound)
})
})
// Access private method through any casting for testing
const isValid = testable.isComponentValid(station, component)
- expect(isValid).toBe(true)
+ assert.strictEqual(isValid, true)
})
// Behavior: Connector component validation returns false (unsupported).
const component: ComponentType = { instance: '1', name: OCPP20ComponentName.Connector }
const isValid = testable.isComponentValid(station, component)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should reject invalid connector instance', () => {
const component: ComponentType = { instance: '999', name: OCPP20ComponentName.Connector }
const isValid = testable.isComponentValid(station, component)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
})
const variable: VariableType = { name: OCPP20OptionalVariableName.HeartbeatInterval }
const isSupported = testable.isVariableSupported(component, variable)
- expect(isSupported).toBe(true)
+ assert.strictEqual(isSupported, true)
})
await it('should support known OCPP variables', () => {
const variable: VariableType = { name: OCPP20OptionalVariableName.WebSocketPingInterval }
const isSupported = testable.isVariableSupported(component, variable)
- expect(isSupported).toBe(true)
+ assert.strictEqual(isSupported, true)
})
await it('should reject unknown variables', () => {
}
const isSupported = testable.isVariableSupported(component, variable)
- expect(isSupported).toBe(false)
+ assert.strictEqual(isSupported, false)
})
})
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(2)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(result[0].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[0].attributeStatusInfo).toBeUndefined()
- expect(result[1].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(result[1].attributeType).toBe(AttributeEnumType.Actual)
- expect(result[1].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result.length, 2)
+ assert.strictEqual(result[0].attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[0].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[0].attributeStatusInfo, undefined)
+ assert.strictEqual(result[1].attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[1].attributeType, AttributeEnumType.Actual)
+ assert.strictEqual(result[1].attributeStatusInfo, undefined)
})
await it('should reject setting variable on unknown component', () => {
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.UnknownComponent)
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, SetVariableStatusEnumType.UnknownComponent)
+ assert.strictEqual(result[0].attributeStatusInfo?.reasonCode, ReasonCodeEnumType.NotFound)
})
await it('should reject setting unknown variable', () => {
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.UnknownVariable)
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.NotFound)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, SetVariableStatusEnumType.UnknownVariable)
+ assert.strictEqual(result[0].attributeStatusInfo?.reasonCode, ReasonCodeEnumType.NotFound)
})
await it('should reject unsupported attribute type', () => {
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(
+ result[0].attributeStatus,
+ SetVariableStatusEnumType.NotSupportedAttributeType
+ )
+ assert.strictEqual(
+ result[0].attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.UnsupportedParam
+ )
})
await it('should reject value exceeding max length', () => {
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
- expect(result[0].attributeStatusInfo?.additionalInfo).toContain(
- 'exceeds effective size limit'
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (result[0].attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (result[0].attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ result[0].attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.TooLargeElement
+ )
+ assert.ok(
+ result[0].attributeStatusInfo.additionalInfo.includes('exceeds effective size limit')
)
})
},
]
const result = manager.setVariables(station, request)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(result[0].attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(result[0].attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(result[0].attributeStatusInfo, undefined)
})
await it('should reject TxUpdatedInterval zero and negative and non-integer', () => {
const zeroRes = manager.setVariables(station, zeroReq)[0]
const negRes = manager.setVariables(station, negReq)[0]
const nonIntRes = manager.setVariables(station, nonIntReq)[0]
- expect(zeroRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(zeroRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(zeroRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(negRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(negRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(negRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(nonIntRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(nonIntRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
+ assert.strictEqual(zeroRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (zeroRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (zeroRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ zeroRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(
+ zeroRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required')
+ )
+ assert.strictEqual(negRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (negRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (negRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ negRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(negRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required'))
+ assert.strictEqual(nonIntRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (nonIntRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (nonIntRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(nonIntRes.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(nonIntRes.attributeStatusInfo.additionalInfo.includes('Positive integer'))
})
await it('should accept setting ConnectionUrl with valid ws URL', () => {
},
]
const res = manager.setVariables(station, req)[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
})
await it('should accept ConnectionUrl with ftp scheme (no scheme restriction)', () => {
},
]
const res = manager.setVariables(station, req)[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
})
await it('should accept ConnectionUrl with custom mqtt scheme', () => {
},
]
const res = manager.setVariables(station, req)[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
})
await it('should allow ConnectionUrl retrieval after set', () => {
},
]
const getResult = manager.getVariables(station, getData)[0]
- expect(getResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getResult.attributeValue).toBe('wss://example.com/ocpp')
- expect(getResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(getResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getResult.attributeValue, 'wss://example.com/ocpp')
+ assert.strictEqual(getResult.attributeStatusInfo, undefined)
})
await it('should revert non-persistent TxUpdatedInterval after simulated restart', () => {
variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval },
},
])[0]
- expect(beforeReset.attributeValue).toBe('99')
+ assert.strictEqual(beforeReset.attributeValue, '99')
manager.resetRuntimeOverrides()
const afterReset = manager.getVariables(station, [
{
variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval },
},
])[0]
- expect(afterReset.attributeValue).toBe('30')
+ assert.strictEqual(afterReset.attributeValue, '30')
})
await it('should keep persistent ConnectionUrl after simulated restart', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(getResult.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getResult.attributeValue).toBe('https://central.example.com/ocpp')
- expect(getResult.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(getResult.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getResult.attributeValue, 'https://central.example.com/ocpp')
+ assert.strictEqual(getResult.attributeStatusInfo, undefined)
})
await it('should reject Target attribute for WebSocketPingInterval', () => {
},
]
const result = manager.setVariables(station, request)
- expect(result).toHaveLength(1)
- expect(result[0].attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
- expect(result[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(
+ result[0].attributeStatus,
+ SetVariableStatusEnumType.NotSupportedAttributeType
+ )
+ assert.strictEqual(
+ result[0].attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.UnsupportedParam
+ )
})
await it('should validate HeartbeatInterval positive integer >0', () => {
},
]
const res = manager.setVariables(station, req)[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
})
await it('should reject HeartbeatInterval zero, negative, non-integer', () => {
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(zeroRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(zeroRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(negRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(negRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(nonIntRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
+ if (zeroRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (zeroRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ zeroRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(
+ zeroRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required')
+ )
+ if (negRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (negRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ negRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(negRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required'))
+ if (nonIntRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (nonIntRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(nonIntRes.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(nonIntRes.attributeStatusInfo.additionalInfo.includes('Positive integer'))
})
await it('should accept WebSocketPingInterval zero (disable) and positive', () => {
variable: { name: OCPP20OptionalVariableName.WebSocketPingInterval },
},
])[0]
- expect(zeroRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(zeroRes.attributeStatusInfo).toBeUndefined()
- expect(posRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(posRes.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(zeroRes.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(zeroRes.attributeStatusInfo, undefined)
+ assert.strictEqual(posRes.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(posRes.attributeStatusInfo, undefined)
})
await it('should reject WebSocketPingInterval negative and non-integer', () => {
variable: { name: OCPP20OptionalVariableName.WebSocketPingInterval },
},
])[0]
- expect(negRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValueZeroNotAllowed)
- expect(negRes.attributeStatusInfo?.additionalInfo).toContain('Integer >= 0 required')
- expect(nonIntRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValueZeroNotAllowed)
- expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Integer >= 0 required')
+ if (negRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (negRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ negRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValueZeroNotAllowed
+ )
+ assert.ok(negRes.attributeStatusInfo.additionalInfo.includes('Integer >= 0 required'))
+ if (nonIntRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (nonIntRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ nonIntRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValueZeroNotAllowed
+ )
+ assert.ok(nonIntRes.attributeStatusInfo.additionalInfo.includes('Integer >= 0 required'))
})
await it('should validate EVConnectionTimeOut positive integer >0 and reject invalid', () => {
variable: { name: OCPP20RequiredVariableName.EVConnectionTimeOut },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(okRes.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatusInfo, undefined)
const zeroRes = manager.setVariables(station, [
{
attributeValue: '0',
variable: { name: OCPP20RequiredVariableName.EVConnectionTimeOut },
},
])[0]
- expect(zeroRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(zeroRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(negRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(negRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(nonIntRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
+ if (zeroRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (zeroRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ zeroRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(
+ zeroRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required')
+ )
+ if (negRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (negRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ negRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(negRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required'))
+ if (nonIntRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (nonIntRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(nonIntRes.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(nonIntRes.attributeStatusInfo.additionalInfo.includes('Positive integer'))
})
await it('should validate MessageTimeout positive integer >0 and reject invalid', () => {
variable: { name: OCPP20RequiredVariableName.MessageTimeout },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(okRes.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatusInfo, undefined)
const zeroRes = manager.setVariables(station, [
{
attributeValue: '0',
variable: { name: OCPP20RequiredVariableName.MessageTimeout },
},
])[0]
- expect(zeroRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(zeroRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(negRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
- expect(negRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer > 0 required')
- expect(nonIntRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(nonIntRes.attributeStatusInfo?.additionalInfo).toContain('Positive integer')
+ if (zeroRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (zeroRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ zeroRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(
+ zeroRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required')
+ )
+ if (negRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (negRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(
+ negRes.attributeStatusInfo.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
+ assert.ok(negRes.attributeStatusInfo.additionalInfo.includes('Positive integer > 0 required'))
+ if (nonIntRes.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (nonIntRes.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(nonIntRes.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(nonIntRes.attributeStatusInfo.additionalInfo.includes('Positive integer'))
})
await it('should avoid duplicate persistence operations when value unchanged', () => {
station,
OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name']
)
- expect(keyBefore).toBeDefined()
+ assert.notStrictEqual(keyBefore, undefined)
const originalValue = keyBefore?.value
const first = manager.setVariables(station, [
{
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(first.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(first.attributeStatus, SetVariableStatusEnumType.Accepted)
const changed = manager.setVariables(station, [
{
attributeValue: (parseInt(originalValue ?? '30', 10) + 5).toString(),
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(changed.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(changed.attributeStatus, SetVariableStatusEnumType.Accepted)
const keyAfterChange = getConfigurationKey(
station,
OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name']
)
- expect(keyAfterChange?.value).not.toBe(originalValue)
+ assert.notStrictEqual(keyAfterChange?.value, originalValue)
const reverted = manager.setVariables(station, [
{
attributeValue: originalValue ?? '30',
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(reverted.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(reverted.attributeStatus, SetVariableStatusEnumType.Accepted)
const keyAfterRevert = getConfigurationKey(
station,
OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name']
)
- expect(keyAfterRevert?.value).toBe(originalValue)
+ assert.strictEqual(keyAfterRevert?.value, originalValue)
})
await it('should add missing configuration key with default during self-check', () => {
station,
OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name']
)
- expect(before).toBeUndefined()
+ assert.strictEqual(before, undefined)
const res = manager.getVariables(station, [
{
component: { name: OCPP20ComponentName.TxCtrlr },
variable: { name: OCPP20RequiredVariableName.EVConnectionTimeOut },
},
])[0]
- expect(res.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
- expect(res.attributeValue).toBe(Constants.DEFAULT_EV_CONNECTION_TIMEOUT.toString())
+ assert.strictEqual(res.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
+ assert.strictEqual(res.attributeValue, Constants.DEFAULT_EV_CONNECTION_TIMEOUT.toString())
const after = getConfigurationKey(
station,
OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name']
)
- expect(after).toBeDefined()
+ assert.notStrictEqual(after, undefined)
})
await it('should clear runtime overrides via resetRuntimeOverrides()', () => {
variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval },
},
])[0]
- expect(beforeReset.attributeValue).toBe('123')
+ assert.strictEqual(beforeReset.attributeValue, '123')
manager.resetRuntimeOverrides()
const afterReset = manager.getVariables(station, [
{
variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval },
},
])[0]
- expect(afterReset.attributeValue).not.toBe('123')
- expect(afterReset.attributeValue).toBe('30')
+ assert.notStrictEqual(afterReset.attributeValue, '123')
+ assert.strictEqual(afterReset.attributeValue, '30')
})
await it('should reject HeartbeatInterval with leading whitespace', () => {
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- 'Non-empty digits only string required'
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes('Non-empty digits only string required')
)
})
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- 'Non-empty digits only string required'
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes('Non-empty digits only string required')
)
})
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- 'Non-empty digits only string required'
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes('Non-empty digits only string required')
)
})
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
- expect(res.attributeStatusInfo).toBeUndefined()
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatusInfo, undefined)
})
await it('should reject HeartbeatInterval blank string', () => {
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- 'Non-empty digits only string required'
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes('Non-empty digits only string required')
)
})
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- 'Non-empty digits only string required'
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes('Non-empty digits only string required')
)
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidURL)
- expect(res.attributeStatusInfo?.additionalInfo).toContain('Invalid URL format')
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidURL)
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('Invalid URL format'))
})
await it('should reject ConnectionUrl exceeding max length', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- `exceeds maximum length (${CONNECTION_URL_MAX_LENGTH.toString()})`
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes(
+ `exceeds maximum length (${CONNECTION_URL_MAX_LENGTH.toString()})`
+ )
)
})
variable: { name: OCPP20OptionalVariableName.HeartbeatInterval },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
const HEARTBEAT_INTERVAL_MAX_LENGTH =
VARIABLE_REGISTRY[
`${OCPP20ComponentName.OCPPCommCtrlr}::${OCPP20OptionalVariableName.HeartbeatInterval}`
].maxLength ?? 10
- expect(res.attributeStatusInfo?.additionalInfo).toContain(
- `exceeds maximum length (${HEARTBEAT_INTERVAL_MAX_LENGTH.toString()})`
+ assert.ok(
+ res.attributeStatusInfo.additionalInfo.includes(
+ `exceeds maximum length (${HEARTBEAT_INTERVAL_MAX_LENGTH.toString()})`
+ )
)
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
const tooLongRes = manager.setVariables(station, [
{
attributeValue: buildWsExampleUrl(51, 'x'),
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(tooLongRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(tooLongRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ tooLongRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.TooLargeElement
+ )
})
await it('should enforce ValueSize when ConfigurationValueSize unset', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
const tooLongRes = manager.setVariables(station, [
{
attributeValue: buildWsExampleUrl(41, 'y'),
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(tooLongRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(tooLongRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ tooLongRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.TooLargeElement
+ )
})
await it('should use smaller of ConfigurationValueSize and ValueSize (ValueSize smaller)', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
const tooLongRes = manager.setVariables(station, [
{
attributeValue: buildWsExampleUrl(56, 'z'),
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(tooLongRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(tooLongRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ tooLongRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.TooLargeElement
+ )
})
await it('should use smaller of ConfigurationValueSize and ValueSize (ConfigurationValueSize smaller)', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
const tooLongRes = manager.setVariables(station, [
{
attributeValue: buildWsExampleUrl(31, 'w'),
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(tooLongRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(tooLongRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.TooLargeElement)
+ assert.strictEqual(tooLongRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ tooLongRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.TooLargeElement
+ )
})
await it('should fallback to default limit when both invalid/non-positive', () => {
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted)
})
})
]
const results = manager.setVariables(station, updateAttempts)
// First (FileTransferProtocols) should be rejected (ReadOnly); others accepted
- expect(results[0].attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(results[0].attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ReadOnly)
+ assert.strictEqual(results[0].attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(results[0].attributeStatusInfo?.reasonCode, ReasonCodeEnumType.ReadOnly)
for (const r of results.slice(1)) {
- expect(r.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(r.attributeStatus, SetVariableStatusEnumType.Accepted)
}
})
variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue).toBe('HTTPS,FTPS,SFTP')
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue, 'HTTPS,FTPS,SFTP')
})
await it('should keep FileTransferProtocols value unchanged after rejected update attempt', () => {
variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
},
])[0]
- expect(initGet.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(initGet.attributeValue).toBe('HTTPS,FTPS,SFTP')
+ assert.strictEqual(initGet.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(initGet.attributeValue, 'HTTPS,FTPS,SFTP')
const beforeCfg = getConfigurationKey(
station,
OCPP20RequiredVariableName.FileTransferProtocols as unknown as VariableType['name']
)
- expect(beforeCfg?.value).toBe('HTTPS,FTPS,SFTP')
+ assert.strictEqual(beforeCfg?.value, 'HTTPS,FTPS,SFTP')
const rejected = manager.setVariables(station, [
{
attributeValue: 'HTTP,HTTPS',
variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
},
])[0]
- expect(rejected.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(rejected.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ReadOnly)
+ assert.strictEqual(rejected.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(rejected.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.ReadOnly)
const afterGet = manager.getVariables(station, [
{
component: { name: OCPP20ComponentName.OCPPCommCtrlr },
variable: { name: OCPP20RequiredVariableName.FileTransferProtocols },
},
])[0]
- expect(afterGet.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(afterGet.attributeValue).toBe('HTTPS,FTPS,SFTP')
+ assert.strictEqual(afterGet.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(afterGet.attributeValue, 'HTTPS,FTPS,SFTP')
const afterCfg = getConfigurationKey(
station,
OCPP20RequiredVariableName.FileTransferProtocols as unknown as VariableType['name']
)
- expect(afterCfg?.value).toBe(beforeCfg?.value)
+ assert.strictEqual(afterCfg?.value, beforeCfg.value)
})
await it('should reject removed TimeSource members RTC and Manual', () => {
variable: { name: OCPP20RequiredVariableName.TimeSource },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain('Member not in enumeration')
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('Member not in enumeration'))
})
await it('should accept extended TimeSource including RealTimeClock and MobileNetwork', () => {
variable: { name: OCPP20RequiredVariableName.TimeSource },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Accepted)
})
await it('should reject invalid list formats and members', () => {
variable: { name: lv.name },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
if (lv.name === OCPP20RequiredVariableName.FileTransferProtocols) {
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ReadOnly)
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.ReadOnly)
} else {
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
}
if (lv.name === OCPP20RequiredVariableName.FileTransferProtocols) {
// Read-only variable: additionalInfo reflects read-only status, skip format/member detail assertions
continue
}
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
if (pattern === '') {
- expect(res.attributeStatusInfo?.additionalInfo).toContain('List cannot be empty')
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('List cannot be empty'))
} else if (pattern.startsWith(',') || pattern.endsWith(',')) {
- expect(res.attributeStatusInfo?.additionalInfo).toContain('No leading/trailing comma')
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('No leading/trailing comma'))
} else if (pattern.includes(',,')) {
- expect(res.attributeStatusInfo?.additionalInfo).toContain('Empty list member')
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('Empty list member'))
} else if (pattern === 'HTTP,HTTP') {
- expect(res.attributeStatusInfo?.additionalInfo).toContain('Duplicate list member')
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('Duplicate list member'))
}
}
}
variable: { name: OCPP20RequiredVariableName.TxStopPoint },
},
])[0]
- expect(res.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.InvalidValue)
- expect(res.attributeStatusInfo?.additionalInfo).toContain('Member not in enumeration')
+ assert.strictEqual(res.attributeStatus, SetVariableStatusEnumType.Rejected)
+ if (res.attributeStatusInfo == null) {
+ assert.fail('Expected attributeStatusInfo to be defined')
+ }
+ if (res.attributeStatusInfo.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(res.attributeStatusInfo.reasonCode, ReasonCodeEnumType.InvalidValue)
+ assert.ok(res.attributeStatusInfo.additionalInfo.includes('Member not in enumeration'))
})
await describe('Unsupported MinSet/MaxSet attribute tests', async () => {
const res = manager.getVariables(station, [
{ attributeType: AttributeEnumType.MinSet, component, variable },
])[0]
- expect(res.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(res.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedParam)
})
await it('should return NotSupportedAttributeType for MaxSet WebSocketPingInterval', () => {
const res = manager.getVariables(station, [
{ attributeType: AttributeEnumType.MaxSet, component, variable },
])[0]
- expect(res.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
- expect(res.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.UnsupportedParam)
+ assert.strictEqual(res.attributeStatus, GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(res.attributeStatusInfo?.reasonCode, ReasonCodeEnumType.UnsupportedParam)
})
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(setRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setRes.attributeStatus, SetVariableStatusEnumType.Accepted)
// Now reduce ValueSize to 50 to force truncation at get-time
setValueSize(station, 50)
const getRes = manager.getVariables(station, [
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue?.length).toBe(50)
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue?.length, 50)
// First 50 chars should match original long value prefix
- expect(longUrl.startsWith(getRes.attributeValue ?? '')).toBe(true)
+ assert.ok(longUrl.startsWith(getRes.attributeValue ?? ''))
resetValueSizeLimits(station)
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(setRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setRes.attributeStatus, SetVariableStatusEnumType.Accepted)
// Reduce ValueSize below ReportingValueSize to 200 so first truncation occurs at 200, then second at 150
setValueSize(station, 200)
setReportingValueSize(station, 150)
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue?.length).toBe(150)
- expect(longUrl.startsWith(getRes.attributeValue ?? '')).toBe(true)
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue?.length, 150)
+ assert.ok(longUrl.startsWith(getRes.attributeValue ?? ''))
resetValueSizeLimits(station)
resetReportingValueSize(station)
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue?.length).toBe(1400)
- expect(overLongValue.startsWith(getRes.attributeValue ?? '')).toBe(true)
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue?.length, 1400)
+ assert.ok(overLongValue.startsWith(getRes.attributeValue ?? ''))
resetValueSizeLimits(station)
resetReportingValueSize(station)
})
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(setRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(setRes.attributeStatus, SetVariableStatusEnumType.Accepted)
// Set larger limits that would allow a bigger value if not for variable-level maxLength
setValueSize(station, 3000)
setReportingValueSize(station, 2800)
variable: { name: OCPP20VendorVariableName.ConnectionUrl },
},
])[0]
- expect(getRes.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(getRes.attributeValue?.length).toBe(connectionUrlMaxLength)
- expect(getRes.attributeValue).toBe(maxLenValue)
+ assert.strictEqual(getRes.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(getRes.attributeValue?.length, connectionUrlMaxLength)
+ assert.strictEqual(getRes.attributeValue, maxLenValue)
resetValueSizeLimits(station)
resetReportingValueSize(station)
})
station,
OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name']
)
- expect(before).toBeUndefined()
+ assert.strictEqual(before, undefined)
const res = manager.getVariables(station, [
{
component: { name: OCPP20ComponentName.SecurityCtrlr },
variable: { name: OCPP20RequiredVariableName.OrganizationName },
},
])[0]
- expect(res.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(res.attributeValue).toBe('Example Charging Services Ltd')
+ assert.strictEqual(res.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeValue, 'Example Charging Services Ltd')
const after = getConfigurationKey(
station,
OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name']
)
- expect(after).toBeDefined()
- expect(after?.value).toBe('Example Charging Services Ltd')
+ assert.notStrictEqual(after, undefined)
+ assert.strictEqual(after?.value, 'Example Charging Services Ltd')
})
await it('should accept setting OrganizationName and require reboot per OCPP 2.0.1 specification', () => {
},
])[0]
// OCPP 2.0.1 compliant behavior: OrganizationName changes require reboot
- expect(setRes.attributeStatus).toBe(SetVariableStatusEnumType.RebootRequired)
+ assert.strictEqual(setRes.attributeStatus, SetVariableStatusEnumType.RebootRequired)
const getRes = manager.getVariables(station, [
{
component: { name: OCPP20ComponentName.SecurityCtrlr },
variable: { name: OCPP20RequiredVariableName.OrganizationName },
},
])[0]
- expect(getRes.attributeValue).toBe('NewOrgName')
+ assert.strictEqual(getRes.attributeValue, 'NewOrgName')
})
await it('should preserve OrganizationName value after resetRuntimeOverrides()', () => {
variable: { name: OCPP20RequiredVariableName.OrganizationName },
},
])[0]
- expect(res.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(res.attributeStatus, GetVariableStatusEnumType.Accepted)
// Value should persist as 'PersistenceTestOrgName' after resetRuntimeOverrides (OCPP 2.0.1 compliant persistence)
- expect(res.attributeValue).toBe('PersistenceTestOrgName')
+ assert.strictEqual(res.attributeValue, 'PersistenceTestOrgName')
})
await it('should create configuration key for instance-scoped MessageAttemptInterval and persist Actual value (Actual-only, no MinSet/MaxSet)', () => {
station,
OCPP20RequiredVariableName.MessageAttemptInterval as unknown as VariableType['name']
)
- expect(cfgBefore).toBeUndefined()
+ assert.strictEqual(cfgBefore, undefined)
const initialGet = manager.getVariables(station, [
{
component: { instance: 'TransactionEvent', name: OCPP20ComponentName.OCPPCommCtrlr },
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(initialGet.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(initialGet.attributeValue).toBe('5')
+ assert.strictEqual(initialGet.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(initialGet.attributeValue, '5')
// Negative: MinSet not supported
const minSetRes = manager.setVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(minSetRes.attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(
+ minSetRes.attributeStatus,
+ SetVariableStatusEnumType.NotSupportedAttributeType
+ )
const getMin = manager.getVariables(station, [
{
attributeType: AttributeEnumType.MinSet,
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(getMin.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(
+ getMin.attributeStatus,
+ GetVariableStatusEnumType.NotSupportedAttributeType
+ )
// Negative: MaxSet not supported
const maxSetRes = manager.setVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(maxSetRes.attributeStatus).toBe(SetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(
+ maxSetRes.attributeStatus,
+ SetVariableStatusEnumType.NotSupportedAttributeType
+ )
const getMax = manager.getVariables(station, [
{
attributeType: AttributeEnumType.MaxSet,
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(getMax.attributeStatus).toBe(GetVariableStatusEnumType.NotSupportedAttributeType)
+ assert.strictEqual(
+ getMax.attributeStatus,
+ GetVariableStatusEnumType.NotSupportedAttributeType
+ )
// Attempt Actual value below registry min (min=1) -> reject
const belowMinRes = manager.setVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(belowMinRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(belowMinRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValuePositiveOnly)
+ assert.strictEqual(belowMinRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ belowMinRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.ValuePositiveOnly
+ )
// Attempt Actual value above registry max (max=3600) -> reject
const aboveMaxRes = manager.setVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(aboveMaxRes.attributeStatus).toBe(SetVariableStatusEnumType.Rejected)
- expect(aboveMaxRes.attributeStatusInfo?.reasonCode).toBe(ReasonCodeEnumType.ValueTooHigh)
+ assert.strictEqual(aboveMaxRes.attributeStatus, SetVariableStatusEnumType.Rejected)
+ assert.strictEqual(
+ aboveMaxRes.attributeStatusInfo?.reasonCode,
+ ReasonCodeEnumType.ValueTooHigh
+ )
// Accept Actual value within metadata bounds
const withinRes = manager.setVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(withinRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted)
+ assert.strictEqual(withinRes.attributeStatus, SetVariableStatusEnumType.Accepted)
// Retrieval now returns persisted value '7'
const afterSetGet = manager.getVariables(station, [
variable: { name: OCPP20RequiredVariableName.MessageAttemptInterval },
},
])[0]
- expect(afterSetGet.attributeStatus).toBe(GetVariableStatusEnumType.Accepted)
- expect(afterSetGet.attributeValue).toBe('7')
+ assert.strictEqual(afterSetGet.attributeStatus, GetVariableStatusEnumType.Accepted)
+ assert.strictEqual(afterSetGet.attributeValue, '7')
const cfgAfter = getConfigurationKey(
station,
OCPP20RequiredVariableName.MessageAttemptInterval as unknown as VariableType['name']
)
- expect(cfgAfter).toBeDefined()
- expect(cfgAfter?.value).toBe('7')
+ assert.notStrictEqual(cfgAfter, undefined)
+ assert.strictEqual(cfgAfter?.value, '7')
})
})
})
* wrapper/dispatch layer only — no overlap.
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { getIdTagsFile } from '../../../src/charging-station/Helpers.js'
stationInfo: { remoteAuthorization: false },
})
const result = await isIdTagAuthorized(station, 1, 'TAG-001')
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should authorize locally when tag is in local auth list', async () => {
setupLocalAuth(station, mocks, ['TAG-001', 'TAG-002'])
const result = await isIdTagAuthorized(station, 1, 'TAG-001')
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should set localAuthorizeIdTag and idTagLocalAuthorized on local auth success', async () => {
await isIdTagAuthorized(station, 1, 'TAG-001')
const connectorStatus = station.getConnectorStatus(1)
- expect(connectorStatus?.localAuthorizeIdTag).toBe('TAG-001')
- expect(connectorStatus?.idTagLocalAuthorized).toBe(true)
+ assert.strictEqual(connectorStatus?.localAuthorizeIdTag, 'TAG-001')
+ assert.strictEqual(connectorStatus.idTagLocalAuthorized, true)
})
await it('should authorize remotely when local auth is disabled and remote returns accepted', async () => {
})
const result = await isIdTagAuthorized(station, 1, 'TAG-001')
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false when remote authorization rejects the tag', async () => {
})
const result = await isIdTagAuthorized(station, 1, 'TAG-999')
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should return false for non-existent connector even with local auth enabled', async () => {
setupLocalAuth(station, mocks, ['TAG-001'])
const result = await isIdTagAuthorized(station, 99, 'TAG-001')
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
})
setupLocalAuth(station, mocks, ['TAG-001'])
const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false on auth error for OCPP 2.0 station', async () => {
})
const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should attempt unified auth service for OCPP 2.0.1 station', async () => {
})
const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001')
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
})
})
* - restoreConnectorStatus — restores Reserved or Available based on reservation state
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../src/charging-station/ChargingStation.js'
await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Occupied)
- expect(requestHandler.mock.calls.length).toBe(1)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Occupied)
+ assert.strictEqual(requestHandler.mock.calls.length, 1)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Occupied)
})
await it('should return early when connector does not exist', async () => {
await sendAndSetConnectorStatus(station, 99, ConnectorStatusEnum.Occupied)
- expect(requestHandler.mock.calls.length).toBe(0)
+ assert.strictEqual(requestHandler.mock.calls.length, 0)
})
await it('should skip sending when options.send is false', async () => {
send: false,
})
- expect(requestHandler.mock.calls.length).toBe(0)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Occupied)
+ assert.strictEqual(requestHandler.mock.calls.length, 0)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Occupied)
})
await it('should update connector status even when send is true', async () => {
const { station } = createStationWithRequestHandler()
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Available)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Available)
await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Unavailable)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Unavailable)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Unavailable)
})
await it('should call emitChargingStationEvent with connectorStatusChanged', async () => {
await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Occupied)
- expect(emitSpy.mock.calls.length).toBe(1)
+ assert.strictEqual(emitSpy.mock.calls.length, 1)
})
await it('should pass evseId to buildStatusNotificationRequest for OCPP 2.0', async () => {
await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Occupied, 1)
- expect(requestHandler.mock.calls.length).toBe(1)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Occupied)
+ assert.strictEqual(requestHandler.mock.calls.length, 1)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Occupied)
})
await it('should default options.send to true when options not provided', async () => {
await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Occupied)
- expect(requestHandler.mock.calls.length).toBe(1)
+ assert.strictEqual(requestHandler.mock.calls.length, 1)
})
})
await restoreConnectorStatus(station, 1, connector)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Reserved)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Reserved)
})
await it('should restore to Available when connector has no reservation and is not Available', async () => {
await restoreConnectorStatus(station, 1, connector)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Available)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Available)
})
await it('should not change status when connector is already Available with no reservation', async () => {
await restoreConnectorStatus(station, 1, connector)
- expect(requestHandler.mock.calls.length).toBe(0)
- expect(station.getConnectorStatus(1)?.status).toBe(ConnectorStatusEnum.Available)
+ assert.strictEqual(requestHandler.mock.calls.length, 0)
+ assert.strictEqual(station.getConnectorStatus(1)?.status, ConnectorStatusEnum.Available)
})
})
})
import type { ErrorObject } from 'ajv'
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../src/charging-station/ChargingStation.js'
await describe('getMessageTypeString', async () => {
await it('should return "request" for MessageType.CALL_MESSAGE', () => {
- expect(getMessageTypeString(MessageType.CALL_MESSAGE)).toBe('request')
+ assert.strictEqual(getMessageTypeString(MessageType.CALL_MESSAGE), 'request')
})
await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => {
- expect(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE)).toBe('response')
+ assert.strictEqual(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE), 'response')
})
await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => {
- expect(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE)).toBe('error')
+ assert.strictEqual(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE), 'error')
})
await it('should return "unknown" for undefined', () => {
- expect(getMessageTypeString(undefined)).toBe('unknown')
+ assert.strictEqual(getMessageTypeString(undefined), 'unknown')
})
})
await describe('ajvErrorsToErrorType', async () => {
await it('should return FormatViolation for null errors', () => {
- expect(ajvErrorsToErrorType(null)).toBe(ErrorType.FORMAT_VIOLATION)
+ assert.strictEqual(ajvErrorsToErrorType(null), ErrorType.FORMAT_VIOLATION)
})
await it('should return FormatViolation for undefined errors', () => {
- expect(ajvErrorsToErrorType(undefined)).toBe(ErrorType.FORMAT_VIOLATION)
+ assert.strictEqual(ajvErrorsToErrorType(undefined), ErrorType.FORMAT_VIOLATION)
})
await it('should return FormatViolation for empty errors array', () => {
- expect(ajvErrorsToErrorType([])).toBe(ErrorType.FORMAT_VIOLATION)
+ assert.strictEqual(ajvErrorsToErrorType([]), ErrorType.FORMAT_VIOLATION)
})
await it('should return TypeConstraintViolation for type keyword', () => {
- expect(ajvErrorsToErrorType([makeAjvError('type')])).toBe(ErrorType.TYPE_CONSTRAINT_VIOLATION)
+ assert.strictEqual(
+ ajvErrorsToErrorType([makeAjvError('type')]),
+ ErrorType.TYPE_CONSTRAINT_VIOLATION
+ )
})
await it('should return OccurrenceConstraintViolation for required keyword', () => {
- expect(ajvErrorsToErrorType([makeAjvError('required')])).toBe(
+ assert.strictEqual(
+ ajvErrorsToErrorType([makeAjvError('required')]),
ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION
)
})
await it('should return OccurrenceConstraintViolation for dependencies keyword', () => {
- expect(ajvErrorsToErrorType([makeAjvError('dependencies')])).toBe(
+ assert.strictEqual(
+ ajvErrorsToErrorType([makeAjvError('dependencies')]),
ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION
)
})
await it('should return PropertyConstraintViolation for format keyword', () => {
- expect(ajvErrorsToErrorType([makeAjvError('format')])).toBe(
+ assert.strictEqual(
+ ajvErrorsToErrorType([makeAjvError('format')]),
ErrorType.PROPERTY_CONSTRAINT_VIOLATION
)
})
await it('should return PropertyConstraintViolation for pattern keyword', () => {
- expect(ajvErrorsToErrorType([makeAjvError('pattern')])).toBe(
+ assert.strictEqual(
+ ajvErrorsToErrorType([makeAjvError('pattern')]),
ErrorType.PROPERTY_CONSTRAINT_VIOLATION
)
})
const date = new Date('2025-01-15T10:30:00.000Z')
const obj = { timestamp: date } as unknown as JsonType
convertDateToISOString(obj)
- expect((obj as Record<string, unknown>).timestamp).toBe('2025-01-15T10:30:00.000Z')
+ assert.strictEqual((obj as Record<string, unknown>).timestamp, '2025-01-15T10:30:00.000Z')
})
await it('should convert nested Date properties recursively', () => {
const date = new Date('2025-06-01T12:00:00.000Z')
const obj = { nested: { deep: { created: date } } } as unknown as JsonType
convertDateToISOString(obj)
- expect(
+ assert.deepStrictEqual(
((obj as Record<string, unknown>).nested as Record<string, unknown>).deep as Record<
string,
unknown
- >
- ).toStrictEqual({ created: '2025-06-01T12:00:00.000Z' })
+ >,
+ { created: '2025-06-01T12:00:00.000Z' }
+ )
})
await it('should convert Date values inside arrays', () => {
const date = new Date('2025-03-10T08:00:00.000Z')
const obj = { items: [date] } as unknown as JsonType
convertDateToISOString(obj)
- expect((obj as Record<string, unknown>).items).toStrictEqual(['2025-03-10T08:00:00.000Z'])
+ assert.deepStrictEqual((obj as Record<string, unknown>).items, ['2025-03-10T08:00:00.000Z'])
})
await it('should not modify non-Date properties', () => {
const obj = { active: true, count: 42, name: 'test' } as unknown as JsonType
convertDateToISOString(obj)
- expect(obj).toStrictEqual({ active: true, count: 42, name: 'test' })
+ assert.deepStrictEqual(obj, { active: true, count: 42, name: 'test' })
})
})
IncomingRequestCommand.REMOTE_START_TRANSACTION,
1
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return true for connector ID zero', () => {
IncomingRequestCommand.REMOTE_START_TRANSACTION,
0
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false for negative connector ID', () => {
IncomingRequestCommand.REMOTE_START_TRANSACTION,
-1
)
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
})
})
* - OCPPServiceUtils.isMessageTriggerSupported
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../src/charging-station/ChargingStation.js'
OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false when command is explicitly disabled', () => {
OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand
)
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should return true when commandsSupport is undefined', () => {
OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return true when incomingCommands is empty', () => {
OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
})
OCPP16RequestCommand.HEARTBEAT as RequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false when command is explicitly disabled', () => {
OCPP16RequestCommand.HEARTBEAT as RequestCommand
)
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should return true when commandsSupport is undefined', () => {
OCPP16RequestCommand.HEARTBEAT as RequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return true when outgoingCommands is empty', () => {
OCPP16RequestCommand.HEARTBEAT as RequestCommand
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
})
OCPP16MessageTrigger.Heartbeat as MessageTrigger
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false when trigger is explicitly disabled', () => {
OCPP16MessageTrigger.Heartbeat as MessageTrigger
)
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should return true when messageTriggerSupport is undefined', () => {
OCPP16MessageTrigger.Heartbeat as MessageTrigger
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return true when messageTriggerSupport is null', () => {
OCPP16MessageTrigger.Heartbeat as MessageTrigger
)
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
})
})
* @description Integration tests for OCPP authentication flows across service, adapters, cache, and strategies
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js'
const result = await authService16.authenticate(request)
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
- expect(typeof result.isOffline).toBe('boolean')
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
+ assert.strictEqual(typeof result.isOffline, 'boolean')
// Status should be one of the valid authorization statuses
- expect(Object.values(AuthorizationStatus)).toContain(result.status)
+ assert.ok(Object.values(AuthorizationStatus).includes(result.status))
})
await it('should handle multiple auth contexts', async () => {
})
const result = await authService16.authenticate(request)
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
}
})
})
const result = await authService16.authorize(request)
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
})
})
const result = await authService20.authenticate(request)
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
- expect(typeof result.isOffline).toBe('boolean')
- expect(Object.values(AuthorizationStatus)).toContain(result.status)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
+ assert.strictEqual(typeof result.isOffline, 'boolean')
+ assert.ok(Object.values(AuthorizationStatus).includes(result.status))
})
await it('should handle all auth contexts', async () => {
})
const result = await authService20.authenticate(request)
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
}
})
})
const result = await authServiceError.authenticate(request)
// Should return a result (not throw) with non-ACCEPTED status
- expect(result).toBeDefined()
- expect(result.status).not.toBe(AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(result, undefined)
+ assert.notStrictEqual(result.status, AuthorizationStatus.ACCEPTED)
})
})
const results = await Promise.all(promises)
// All requests should complete successfully
- expect(results.length).toBe(requestCount)
+ assert.strictEqual(results.length, requestCount)
for (const result of results) {
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
}
})
})
await service.initialize()
const localStrategy = service.getStrategy('local') as LocalAuthStrategy | undefined
- expect(localStrategy).toBeDefined()
+ assert.notStrictEqual(localStrategy, undefined)
const authCache = localStrategy?.getAuthCache()
- expect(authCache).toBeDefined()
+ assert.notStrictEqual(authCache, undefined)
})
// G04.INT.02 - All-status caching (T4)
cache.set('BLOCKED-ID', blockedResult)
const retrieved = cache.get('BLOCKED-ID')
- expect(retrieved).toBeDefined()
- expect(retrieved?.status).toBe(AuthorizationStatus.BLOCKED)
+ assert.notStrictEqual(retrieved, undefined)
+ assert.strictEqual(retrieved?.status, AuthorizationStatus.BLOCKED)
} finally {
cache.dispose()
}
)
const stats = cache.getStats()
- expect(stats.totalEntries).toBe(3)
+ assert.strictEqual(stats.totalEntries, 3)
// BLOCKED entry must still exist
const blocked = cache.get('BLOCKED-ENTRY')
- expect(blocked).toBeDefined()
- expect(blocked?.status).toBe(AuthorizationStatus.BLOCKED)
+ assert.notStrictEqual(blocked, undefined)
+ assert.strictEqual(blocked?.status, AuthorizationStatus.BLOCKED)
} finally {
cache.dispose()
}
// Wait 500ms, then access to reset TTL
await sleep(500)
const midResult = cache.get('SLIDING-ID')
- expect(midResult).toBeDefined()
- expect(midResult?.status).toBe(AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(midResult, undefined)
+ assert.strictEqual(midResult?.status, AuthorizationStatus.ACCEPTED)
// Wait another 700ms (total 1200ms from initial set, but only 700ms from last access)
await sleep(700)
const lateResult = cache.get('SLIDING-ID')
// Entry should still be valid because TTL was reset at the 500ms access
- expect(lateResult).toBeDefined()
- expect(lateResult?.status).toBe(AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(lateResult, undefined)
+ assert.strictEqual(lateResult?.status, AuthorizationStatus.ACCEPTED)
} finally {
cache.dispose()
}
await sleep(1100)
const result = cache.get('EXPIRE-ID')
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
} finally {
cache.dispose()
}
const result = await strategy.authenticate(request, config)
// Should be authorized from local list
- expect(result).toBeDefined()
- expect(result?.method).toBe(AuthenticationMethod.LOCAL_LIST)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.method, AuthenticationMethod.LOCAL_LIST)
// Verify cache does NOT contain the identifier (R17)
const cached = cache.get('LIST-ID')
- expect(cached).toBeUndefined()
+ assert.strictEqual(cached, undefined)
} finally {
cache.dispose()
}
cache.get('NONEXISTENT')
const statsBefore = cache.getStats()
- expect(statsBefore.hits).toBeGreaterThan(0)
- expect(statsBefore.misses).toBeGreaterThan(0)
+ assert.ok(statsBefore.hits > 0)
+ assert.ok(statsBefore.misses > 0)
// Clear entries — stats should be preserved
cache.clear()
const statsAfterClear = cache.getStats()
- expect(statsAfterClear.totalEntries).toBe(0)
- expect(statsAfterClear.hits).toBe(statsBefore.hits)
- expect(statsAfterClear.misses).toBe(statsBefore.misses)
+ assert.strictEqual(statsAfterClear.totalEntries, 0)
+ assert.strictEqual(statsAfterClear.hits, statsBefore.hits)
+ assert.strictEqual(statsAfterClear.misses, statsBefore.misses)
// Reset stats — counters should be zeroed
cache.resetStats()
const statsAfterReset = cache.getStats()
- expect(statsAfterReset.hits).toBe(0)
- expect(statsAfterReset.misses).toBe(0)
- expect(statsAfterReset.evictions).toBe(0)
+ assert.strictEqual(statsAfterReset.hits, 0)
+ assert.strictEqual(statsAfterReset.misses, 0)
+ assert.strictEqual(statsAfterReset.evictions, 0)
} finally {
cache.dispose()
}
* @file Tests for OCPP16AuthAdapter
* @description Unit tests for OCPP 1.6 authentication adapter
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
await describe('constructor', async () => {
await it('should initialize with correct OCPP version', () => {
- expect(adapter.ocppVersion).toBe(OCPPVersion.VERSION_16)
+ assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_16)
})
})
const result = adapter.convertToUnifiedIdentifier(idTag)
const expected = createMockIdentifier(OCPPVersion.VERSION_16, idTag)
- expect(result.value).toBe(expected.value)
- expect(result.type).toBe(expected.type)
- expect(result.ocppVersion).toBe(expected.ocppVersion)
+ assert.strictEqual(result.value, expected.value)
+ assert.strictEqual(result.type, expected.type)
+ assert.strictEqual(result.ocppVersion, expected.ocppVersion)
})
await it('should include additional data in unified identifier', () => {
const additionalData = { customField: 'customValue', parentId: 'PARENT_TAG' }
const result = adapter.convertToUnifiedIdentifier(idTag, additionalData)
- expect(result.value).toBe(idTag)
- expect(result.parentId).toBe('PARENT_TAG')
- expect(result.additionalInfo?.customField).toBe('customValue')
+ assert.strictEqual(result.value, idTag)
+ assert.strictEqual(result.parentId, 'PARENT_TAG')
+ assert.strictEqual(result.additionalInfo?.customField, 'customValue')
})
})
const identifier = createMockIdentifier(OCPPVersion.VERSION_16, 'TEST_ID_TAG')
const result = adapter.convertFromUnifiedIdentifier(identifier)
- expect(result).toBe('TEST_ID_TAG')
+ assert.strictEqual(result, 'TEST_ID_TAG')
})
})
await it('should validate correct OCPP 1.6 identifier', () => {
const identifier = createMockIdentifier(OCPPVersion.VERSION_16, 'VALID_TAG')
- expect(adapter.isValidIdentifier(identifier)).toBe(true)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), true)
})
await it('should reject identifier with empty value', () => {
const identifier = createMockIdentifier(OCPPVersion.VERSION_16, '')
- expect(adapter.isValidIdentifier(identifier)).toBe(false)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), false)
})
await it('should reject identifier exceeding max length (20 chars)', () => {
'THIS_TAG_IS_TOO_LONG_FOR_OCPP16'
)
- expect(adapter.isValidIdentifier(identifier)).toBe(false)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), false)
})
await it('should reject non-ID_TAG types', () => {
IdentifierType.CENTRAL
)
- expect(adapter.isValidIdentifier(identifier)).toBe(false)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), false)
})
})
await it('should create auth request for transaction start', () => {
const request = adapter.createAuthRequest('TEST_TAG', 1, 123, 'start')
- expect(request.identifier.value).toBe('TEST_TAG')
- expect(request.identifier.type).toBe(IdentifierType.ID_TAG)
- expect(request.connectorId).toBe(1)
- expect(request.transactionId).toBe('123')
- expect(request.context).toBe(AuthContext.TRANSACTION_START)
- expect(request.metadata?.ocppVersion).toBe(OCPPVersion.VERSION_16)
+ assert.strictEqual(request.identifier.value, 'TEST_TAG')
+ assert.strictEqual(request.identifier.type, IdentifierType.ID_TAG)
+ assert.strictEqual(request.connectorId, 1)
+ assert.strictEqual(request.transactionId, '123')
+ assert.strictEqual(request.context, AuthContext.TRANSACTION_START)
+ assert.strictEqual(request.metadata?.ocppVersion, OCPPVersion.VERSION_16)
})
await it('should map context strings to AuthContext enum', () => {
const remoteStartReq = adapter.createAuthRequest('TAG1', 1, undefined, 'remote_start')
- expect(remoteStartReq.context).toBe(AuthContext.REMOTE_START)
+ assert.strictEqual(remoteStartReq.context, AuthContext.REMOTE_START)
const remoteStopReq = adapter.createAuthRequest('TAG2', 2, undefined, 'remote_stop')
- expect(remoteStopReq.context).toBe(AuthContext.REMOTE_STOP)
+ assert.strictEqual(remoteStopReq.context, AuthContext.REMOTE_STOP)
const stopReq = adapter.createAuthRequest('TAG3', 3, undefined, 'stop')
- expect(stopReq.context).toBe(AuthContext.TRANSACTION_STOP)
+ assert.strictEqual(stopReq.context, AuthContext.TRANSACTION_STOP)
const defaultReq = adapter.createAuthRequest('TAG4', 4, undefined, 'unknown')
- expect(defaultReq.context).toBe(AuthContext.TRANSACTION_START)
+ assert.strictEqual(defaultReq.context, AuthContext.TRANSACTION_START)
})
})
const result = await adapter.authorizeRemote(identifier, 1, 123)
- expect(result.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result.method).toBeDefined()
- expect(result.isOffline).toBe(false)
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(result.method, undefined)
+ assert.strictEqual(result.isOffline, false)
+ assert.ok(result.timestamp instanceof Date)
})
await it('should handle authorization failure gracefully', async () => {
const result = await adapter.authorizeRemote(identifier, 1)
- expect(result.status).toBe(AuthorizationStatus.INVALID)
- expect(result.additionalInfo?.error).toBeDefined()
+ assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.notStrictEqual(result.additionalInfo?.error, undefined)
})
})
await describe('isRemoteAvailable', async () => {
await it('should return true when remote authorization is enabled and online', () => {
const isAvailable = adapter.isRemoteAvailable()
- expect(isAvailable).toBe(true)
+ assert.strictEqual(isAvailable, true)
})
await it('should return false when station is offline', () => {
mockStation.inAcceptedState = () => false
const isAvailable = adapter.isRemoteAvailable()
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
await it('should return false when remote authorization is disabled', () => {
}
const isAvailable = adapter.isRemoteAvailable()
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
})
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(true)
+ assert.strictEqual(isValid, true)
})
await it('should reject configuration with no auth methods', () => {
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should reject configuration with invalid timeout', () => {
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
})
await it('should return adapter status information', () => {
const status = adapter.getStatus()
- expect(status.ocppVersion).toBe(OCPPVersion.VERSION_16)
- expect(status.isOnline).toBe(true)
- expect(status.localAuthEnabled).toBe(true)
- expect(status.remoteAuthEnabled).toBe(true)
- expect(status.stationId).toBe('TEST-001')
+ assert.strictEqual(status.ocppVersion, OCPPVersion.VERSION_16)
+ assert.strictEqual(status.isOnline, true)
+ assert.strictEqual(status.localAuthEnabled, true)
+ assert.strictEqual(status.remoteAuthEnabled, true)
+ assert.strictEqual(status.stationId, 'TEST-001')
})
})
await it('should return OCPP 1.6 configuration schema', () => {
const schema = adapter.getConfigurationSchema()
- expect(schema.type).toBe('object')
- expect(schema.properties).toBeDefined()
+ assert.strictEqual(schema.type, 'object')
+ assert.notStrictEqual(schema.properties, undefined)
const properties = schema.properties as Record<string, unknown>
- expect(properties.localAuthListEnabled).toBeDefined()
- expect(properties.remoteAuthorization).toBeDefined()
+ assert.notStrictEqual(properties.localAuthListEnabled, undefined)
+ assert.notStrictEqual(properties.remoteAuthorization, undefined)
const required = schema.required as string[]
- expect(required).toContain('localAuthListEnabled')
- expect(required).toContain('remoteAuthorization')
+ assert.ok(required.includes('localAuthListEnabled'))
+ assert.ok(required.includes('remoteAuthorization'))
})
})
const response = adapter.convertToOCPP16Response(result)
- expect(response.idTagInfo.status).toBe(OCPP16AuthorizationStatus.ACCEPTED)
- expect(response.idTagInfo.parentIdTag).toBe('PARENT_TAG')
- expect(response.idTagInfo.expiryDate).toBe(expiryDate)
+ assert.strictEqual(response.idTagInfo.status, OCPP16AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(response.idTagInfo.parentIdTag, 'PARENT_TAG')
+ assert.strictEqual(response.idTagInfo.expiryDate, expiryDate)
})
})
})
* @file Tests for OCPP20AuthAdapter
* @description Unit tests for OCPP 2.0 authentication adapter
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it, mock } from 'node:test'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
await describe('constructor', async () => {
await it('should initialize with correct OCPP version', () => {
- expect(adapter.ocppVersion).toBe(OCPPVersion.VERSION_20)
+ assert.strictEqual(adapter.ocppVersion, OCPPVersion.VERSION_20)
})
})
const result = adapter.convertToUnifiedIdentifier(idToken)
const expected = createMockIdentifier(OCPPVersion.VERSION_20, 'TEST_TOKEN')
- expect(result.value).toBe(expected.value)
- expect(result.type).toBe(IdentifierType.ID_TAG)
- expect(result.ocppVersion).toBe(expected.ocppVersion)
- expect(result.additionalInfo?.ocpp20Type).toBe(OCPP20IdTokenEnumType.Central)
+ assert.strictEqual(result.value, expected.value)
+ assert.strictEqual(result.type, IdentifierType.ID_TAG)
+ assert.strictEqual(result.ocppVersion, expected.ocppVersion)
+ assert.strictEqual(result.additionalInfo?.ocpp20Type, OCPP20IdTokenEnumType.Central)
})
await it('should convert string to unified identifier', () => {
const result = adapter.convertToUnifiedIdentifier('STRING_TOKEN')
const expected = createMockIdentifier(OCPPVersion.VERSION_20, 'STRING_TOKEN')
- expect(result.value).toBe(expected.value)
- expect(result.type).toBe(expected.type)
- expect(result.ocppVersion).toBe(expected.ocppVersion)
+ assert.strictEqual(result.value, expected.value)
+ assert.strictEqual(result.type, expected.type)
+ assert.strictEqual(result.ocppVersion, expected.ocppVersion)
})
await it('should handle eMAID type correctly', () => {
const result = adapter.convertToUnifiedIdentifier(idToken)
- expect(result.value).toBe('EMAID123')
- expect(result.type).toBe(IdentifierType.E_MAID)
+ assert.strictEqual(result.value, 'EMAID123')
+ assert.strictEqual(result.type, IdentifierType.E_MAID)
})
await it('should include additional info from IdToken', () => {
const result = adapter.convertToUnifiedIdentifier(idToken)
- expect(result.additionalInfo).toBeDefined()
- expect(result.additionalInfo?.info_0).toBeDefined()
- expect(result.additionalInfo?.info_1).toBeDefined()
+ assert.notStrictEqual(result.additionalInfo, undefined)
+ assert.notStrictEqual(result.additionalInfo?.info_0, undefined)
+ assert.notStrictEqual(result.additionalInfo?.info_1, undefined)
})
})
const result = adapter.convertFromUnifiedIdentifier(identifier)
- expect(result.idToken).toBe('CENTRAL_TOKEN')
- expect(result.type).toBe(OCPP20IdTokenEnumType.Central)
+ assert.strictEqual(result.idToken, 'CENTRAL_TOKEN')
+ assert.strictEqual(result.type, OCPP20IdTokenEnumType.Central)
})
await it('should map E_MAID type correctly', () => {
const result = adapter.convertFromUnifiedIdentifier(identifier)
- expect(result.idToken).toBe('EMAID_TOKEN')
- expect(result.type).toBe(OCPP20IdTokenEnumType.eMAID)
+ assert.strictEqual(result.idToken, 'EMAID_TOKEN')
+ assert.strictEqual(result.type, OCPP20IdTokenEnumType.eMAID)
})
await it('should handle ID_TAG to Local mapping', () => {
const result = adapter.convertFromUnifiedIdentifier(identifier)
- expect(result.type).toBe(OCPP20IdTokenEnumType.Local)
+ assert.strictEqual(result.type, OCPP20IdTokenEnumType.Local)
})
})
IdentifierType.CENTRAL
)
- expect(adapter.isValidIdentifier(identifier)).toBe(true)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), true)
})
await it('should reject identifier with empty value', () => {
const identifier = createMockIdentifier(OCPPVersion.VERSION_20, '', IdentifierType.CENTRAL)
- expect(adapter.isValidIdentifier(identifier)).toBe(false)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), false)
})
await it('should reject identifier exceeding max length (36 chars)', () => {
IdentifierType.CENTRAL
)
- expect(adapter.isValidIdentifier(identifier)).toBe(false)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), false)
})
await it('should accept all OCPP 2.0 identifier types', () => {
for (const type of validTypes) {
const identifier = createMockIdentifier(OCPPVersion.VERSION_20, 'VALID_TOKEN', type)
- expect(adapter.isValidIdentifier(identifier)).toBe(true)
+ assert.strictEqual(adapter.isValidIdentifier(identifier), true)
}
})
})
'started'
)
- expect(request.identifier.value).toBe('TEST_TOKEN')
- expect(request.connectorId).toBe(1)
- expect(request.transactionId).toBe('trans_123')
- expect(request.context).toBe(AuthContext.TRANSACTION_START)
- expect(request.metadata?.ocppVersion).toBe(OCPPVersion.VERSION_20)
+ assert.strictEqual(request.identifier.value, 'TEST_TOKEN')
+ assert.strictEqual(request.connectorId, 1)
+ assert.strictEqual(request.transactionId, 'trans_123')
+ assert.strictEqual(request.context, AuthContext.TRANSACTION_START)
+ assert.strictEqual(request.metadata?.ocppVersion, OCPPVersion.VERSION_20)
})
await it('should map OCPP 2.0 contexts correctly', () => {
const startReq = adapter.createAuthRequest('TOKEN', 1, undefined, 'started')
- expect(startReq.context).toBe(AuthContext.TRANSACTION_START)
+ assert.strictEqual(startReq.context, AuthContext.TRANSACTION_START)
const stopReq = adapter.createAuthRequest('TOKEN', 2, undefined, 'ended')
- expect(stopReq.context).toBe(AuthContext.TRANSACTION_STOP)
+ assert.strictEqual(stopReq.context, AuthContext.TRANSACTION_STOP)
const remoteStartReq = adapter.createAuthRequest('TOKEN', 3, undefined, 'remote_start')
- expect(remoteStartReq.context).toBe(AuthContext.REMOTE_START)
+ assert.strictEqual(remoteStartReq.context, AuthContext.REMOTE_START)
const defaultReq = adapter.createAuthRequest('TOKEN', 4, undefined, 'unknown')
- expect(defaultReq.context).toBe(AuthContext.TRANSACTION_START)
+ assert.strictEqual(defaultReq.context, AuthContext.TRANSACTION_START)
})
})
const result = await adapter.authorizeRemote(identifier, 1, 'tx_123')
- expect(result.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
- expect(result.isOffline).toBe(false)
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.strictEqual(result.isOffline, false)
+ assert.ok(result.timestamp instanceof Date)
})
await it('should handle invalid token gracefully', async () => {
const result = await adapter.authorizeRemote(identifier, 1)
- expect(result.status).toBe(AuthorizationStatus.INVALID)
- expect(result.additionalInfo?.error).toBeDefined()
+ assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.notStrictEqual(result.additionalInfo?.error, undefined)
})
})
)
const isAvailable = adapter.isRemoteAvailable()
- expect(isAvailable).toBe(true)
+ assert.strictEqual(isAvailable, true)
})
await it('should return false when station is offline', t => {
)
const isAvailable = adapter.isRemoteAvailable()
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
})
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(true)
+ assert.strictEqual(isValid, true)
})
await it('should reject configuration with no auth methods', () => {
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
await it('should reject configuration with invalid timeout', () => {
}
const isValid = adapter.validateConfiguration(config)
- expect(isValid).toBe(false)
+ assert.strictEqual(isValid, false)
})
})
await it('should return adapter status information', () => {
const status = adapter.getStatus()
- expect(status.ocppVersion).toBe(OCPPVersion.VERSION_20)
- expect(status.isOnline).toBe(true)
- expect(status.stationId).toBe('TEST-002')
- expect(status.supportsIdTokenTypes).toBeDefined()
- expect(Array.isArray(status.supportsIdTokenTypes)).toBe(true)
+ assert.strictEqual(status.ocppVersion, OCPPVersion.VERSION_20)
+ assert.strictEqual(status.isOnline, true)
+ assert.strictEqual(status.stationId, 'TEST-002')
+ assert.notStrictEqual(status.supportsIdTokenTypes, undefined)
+ assert.ok(Array.isArray(status.supportsIdTokenTypes))
})
})
await it('should return OCPP 2.0 configuration schema', () => {
const schema = adapter.getConfigurationSchema()
- expect(schema.type).toBe('object')
- expect(schema.properties).toBeDefined()
+ assert.strictEqual(schema.type, 'object')
+ assert.notStrictEqual(schema.properties, undefined)
const properties = schema.properties as Record<string, unknown>
- expect(properties.authorizeRemoteStart).toBeDefined()
- expect(properties.localAuthorizeOffline).toBeDefined()
+ assert.notStrictEqual(properties.authorizeRemoteStart, undefined)
+ assert.notStrictEqual(properties.localAuthorizeOffline, undefined)
const required = schema.required as string[]
- expect(required).toContain('authorizeRemoteStart')
- expect(required).toContain('localAuthorizeOffline')
+ assert.ok(required.includes('authorizeRemoteStart'))
+ assert.ok(required.includes('localAuthorizeOffline'))
})
})
})
const response = adapter.convertToOCPP20Response(result)
- expect(response).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.strictEqual(response, RequestStartStopStatusEnumType.Accepted)
})
await it('should convert unified rejection statuses to OCPP 2.0 Rejected', () => {
status,
})
const response = adapter.convertToOCPP20Response(result)
- expect(response).toBe(RequestStartStopStatusEnumType.Rejected)
+ assert.strictEqual(response, RequestStartStopStatusEnumType.Rejected)
}
})
})
const isAvailable = offlineAdapter.isRemoteAvailable()
// Then: Remote should not be available
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
await it('should detect station is online when in accepted state', () => {
const isAvailable = offlineAdapter.isRemoteAvailable()
// Then: Remote should be available (assuming AuthorizeRemoteStart is enabled by default)
- expect(isAvailable).toBe(true)
+ assert.strictEqual(isAvailable, true)
})
await it('should have correct OCPP version for offline tests', () => {
// Verify we're testing the correct OCPP version
- expect(offlineAdapter.ocppVersion).toBe(OCPPVersion.VERSION_20)
+ assert.strictEqual(offlineAdapter.ocppVersion, OCPPVersion.VERSION_20)
})
})
const isAvailable = offlineAdapter.isRemoteAvailable()
// Then: Should not be available
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
await it('should handle errors gracefully when checking availability', () => {
const isAvailable = offlineAdapter.isRemoteAvailable()
// Then: Should safely return false
- expect(isAvailable).toBe(false)
+ assert.strictEqual(isAvailable, false)
})
})
await it('should initialize with default configuration for offline scenarios', () => {
// When: Adapter is created
// Then: Should have OCPP 2.0 version
- expect(offlineAdapter.ocppVersion).toBe(OCPPVersion.VERSION_20)
+ assert.strictEqual(offlineAdapter.ocppVersion, OCPPVersion.VERSION_20)
})
await it('should validate configuration schema for offline auth', () => {
const schema = offlineAdapter.getConfigurationSchema()
// Then: Should have required offline auth properties
- expect(schema).toBeDefined()
- expect(schema.properties).toBeDefined()
+ assert.notStrictEqual(schema, undefined)
+ assert.notStrictEqual(schema.properties, undefined)
// OCPP 2.0 uses variables, not configuration keys
// The actual offline behavior is controlled by AuthCtrlr variables
})
const status = offlineAdapter.getStatus()
// Then: Status should be defined and include online state
- expect(status).toBeDefined()
- expect(typeof status.isOnline).toBe('boolean')
- expect(status.ocppVersion).toBe(OCPPVersion.VERSION_20)
+ assert.notStrictEqual(status, undefined)
+ assert.strictEqual(typeof status.isOnline, 'boolean')
+ assert.strictEqual(status.ocppVersion, OCPPVersion.VERSION_20)
})
})
})
* @file Tests for InMemoryAuthCache
* @description Unit tests for in-memory authorization cache conformance (G03.FR.01)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { AuthorizationResult } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
// Retrieve from cache
const cachedResult = cache.get(identifier)
- expect(cachedResult).toBeDefined()
- expect(cachedResult?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(cachedResult?.timestamp).toStrictEqual(mockResult.timestamp)
+ assert.notStrictEqual(cachedResult, undefined)
+ assert.strictEqual(cachedResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.deepStrictEqual(cachedResult.timestamp, mockResult.timestamp)
})
await it('should track cache hits in statistics', () => {
cache.get(identifier)
const stats = cache.getStats()
- expect(stats.hits).toBe(2)
- expect(stats.misses).toBe(0)
- expect(stats.hitRate).toBe(100)
+ assert.strictEqual(stats.hits, 2)
+ assert.strictEqual(stats.misses, 0)
+ assert.strictEqual(stats.hitRate, 100)
})
await it('should update LRU order on cache hit', () => {
// Access token-3 to make it most recently used
const access3 = lruCache.get('token-3')
- expect(access3).toBeDefined() // Verify it's accessible before eviction test
+ assert.notStrictEqual(access3, undefined) // Verify it's accessible before eviction test
// Add new entry to trigger eviction
lruCache.set('token-4', mockResult)
const token3 = lruCache.get('token-3')
const token4 = lruCache.get('token-4')
- expect(token1).toBeUndefined()
- expect(token3).toBeDefined()
- expect(token4).toBeDefined()
+ assert.strictEqual(token1, undefined)
+ assert.notStrictEqual(token3, undefined)
+ assert.notStrictEqual(token4, undefined)
})
})
await it('should return undefined on cache miss', () => {
const result = cache.get('non-existent-token')
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should track cache misses in statistics', () => {
cache.get('miss-3')
const stats = cache.getStats()
- expect(stats.misses).toBe(3)
- expect(stats.hits).toBe(0)
- expect(stats.hitRate).toBe(0)
+ assert.strictEqual(stats.misses, 3)
+ assert.strictEqual(stats.hits, 0)
+ assert.strictEqual(stats.hitRate, 0)
})
await it('should calculate hit rate correctly with mixed hits/misses', () => {
cache.get('miss-3')
const stats = cache.getStats()
- expect(stats.hits).toBe(2)
- expect(stats.misses).toBe(3)
- expect(stats.hitRate).toBe(40) // 2/(2+3) * 100 = 40%
+ assert.strictEqual(stats.hits, 2)
+ assert.strictEqual(stats.misses, 3)
+ assert.strictEqual(stats.hitRate, 40) // 2/(2+3) * 100 = 40%
})
})
const result = cache.get(identifier)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
})
})
cache.get('token-2')
const stats = cache.getStats()
- expect(stats.expiredEntries).toBeGreaterThanOrEqual(2)
+ assert.ok(stats.expiredEntries >= 2)
})
})
t.mock.timers.tick(10)
const result = cacheWithShortTTL.get('token')
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
})
})
// Immediately retrieve
const result = cache.get(identifier)
- expect(result).toBeDefined()
- expect(result?.status).toBe(mockResult.status)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, mockResult.status)
})
})
// Verify it exists
let result = cache.get(identifier)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
// Remove it
cache.remove(identifier)
// Verify it's gone
result = cache.get(identifier)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should clear all entries', () => {
cache.set('token-3', mockResult)
const statsBefore = cache.getStats()
- expect(statsBefore.totalEntries).toBe(3)
+ assert.strictEqual(statsBefore.totalEntries, 3)
cache.clear()
const statsAfter = cache.getStats()
- expect(statsAfter.totalEntries).toBe(0)
+ assert.strictEqual(statsAfter.totalEntries, 0)
})
await it('should preserve statistics on clear', () => {
cache.get('miss')
const statsBefore = cache.getStats()
- expect(statsBefore.hits).toBeGreaterThan(0)
- expect(statsBefore.misses).toBeGreaterThan(0)
+ assert.ok(statsBefore.hits > 0)
+ assert.ok(statsBefore.misses > 0)
cache.clear()
const statsAfter = cache.getStats()
- expect(statsAfter.hits).toBe(statsBefore.hits)
- expect(statsAfter.misses).toBe(statsBefore.misses)
- expect(statsAfter.totalEntries).toBe(0)
+ assert.strictEqual(statsAfter.hits, statsBefore.hits)
+ assert.strictEqual(statsAfter.misses, statsBefore.misses)
+ assert.strictEqual(statsAfter.totalEntries, 0)
})
})
// 4th request should be rate limited
const result = cache.get(identifier)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
const stats = cache.getStats()
- expect(stats.rateLimit.blockedRequests).toBeGreaterThan(0)
+ assert.ok(stats.rateLimit.blockedRequests > 0)
})
await it('should track rate limit statistics', () => {
cache.set(identifier, mockResult) // Should be blocked
const stats = cache.getStats()
- expect(stats.rateLimit.totalChecks).toBeGreaterThan(0)
- expect(stats.rateLimit.blockedRequests).toBeGreaterThan(0)
+ assert.ok(stats.rateLimit.totalChecks > 0)
+ assert.ok(stats.rateLimit.blockedRequests > 0)
})
await it('should reset rate limit after window expires', async t => {
t.mock.timers.tick(1100)
const result = cache.get(identifier)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
})
// token-2 should still work
cache.set('token-2', mockResult)
const result = cache.get('token-2')
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should allow disabling rate limiting', () => {
}
const result = unratedCache.get('token')
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
const stats = unratedCache.getStats()
- expect(stats.rateLimit.blockedRequests).toBe(0)
+ assert.strictEqual(stats.rateLimit.blockedRequests, 0)
})
})
cache.set('token-6', mockResult)
const stats = cache.getStats()
- expect(stats.totalEntries).toBe(5)
+ assert.strictEqual(stats.totalEntries, 5)
// token-1 should be evicted
const token1 = cache.get('token-1')
- expect(token1).toBeUndefined()
+ assert.strictEqual(token1, undefined)
// token-6 should exist
const token6 = cache.get('token-6')
- expect(token6).toBeDefined()
+ assert.notStrictEqual(token6, undefined)
})
await it('should track eviction count in statistics', () => {
}
const stats = cache.getStats()
- expect(stats.totalEntries).toBe(5)
+ assert.strictEqual(stats.totalEntries, 5)
// Should have 5 evictions (10 sets - 5 capacity = 5 evictions)
- expect(stats.evictions).toBe(5)
+ assert.strictEqual(stats.evictions, 5)
})
})
const stats = cache.getStats()
- expect(stats.totalEntries).toBe(2)
- expect(stats.hits).toBe(1)
- expect(stats.misses).toBe(1)
- expect(stats.hitRate).toBe(50)
- expect(stats.memoryUsage).toBeGreaterThan(0)
+ assert.strictEqual(stats.totalEntries, 2)
+ assert.strictEqual(stats.hits, 1)
+ assert.strictEqual(stats.misses, 1)
+ assert.strictEqual(stats.hitRate, 50)
+ assert.ok(stats.memoryUsage > 0)
})
await it('should track memory usage estimate', () => {
const statsAfter = cache.getStats()
const memoryAfter = statsAfter.memoryUsage
- expect(memoryAfter).toBeGreaterThan(memoryBefore)
+ assert.ok(memoryAfter > memoryBefore)
})
await it('should provide rate limit statistics', () => {
const stats = cache.getStats()
- expect(stats.rateLimit).toBeDefined()
- expect(stats.rateLimit.totalChecks).toBeGreaterThan(0)
- expect(stats.rateLimit.blockedRequests).toBeGreaterThan(0)
- expect(stats.rateLimit.rateLimitedIdentifiers).toBeGreaterThan(0)
+ assert.notStrictEqual(stats.rateLimit, undefined)
+ assert.ok(stats.rateLimit.totalChecks > 0)
+ assert.ok(stats.rateLimit.blockedRequests > 0)
+ assert.ok(stats.rateLimit.rateLimitedIdentifiers > 0)
})
})
cache.set('', mockResult)
const result = cache.get('')
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should handle very long identifier strings', () => {
cache.set(longIdentifier, mockResult)
const result = cache.get(longIdentifier)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should handle concurrent operations', () => {
// Concurrent gets
const results = [cache.get('token-1'), cache.get('token-2'), cache.get('token-3')]
- expect(results[0]).toBeDefined()
- expect(results[1]).toBeDefined()
- expect(results[2]).toBeDefined()
+ assert.notStrictEqual(results[0], undefined)
+ assert.notStrictEqual(results[1], undefined)
+ assert.notStrictEqual(results[2], undefined)
})
await it('should handle zero TTL (immediate expiration)', () => {
cache.set('token', mockResult, 0)
const result = cache.get('token')
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
})
await it('should handle very large TTL values', () => {
cache.set('token', mockResult, 31536000)
const result = cache.get('token')
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
})
cache.set('valid-token', mockResult)
const result = cache.get('valid-token')
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await it('should handle BLOCKED authorization results', () => {
cache.set('blocked-token', mockResult)
const result = cache.get('blocked-token')
- expect(result?.status).toBe(AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
})
await it('should preserve authorization result metadata', () => {
cache.set('token', mockResult)
const result = cache.get('token')
- expect(result?.additionalInfo?.customField).toBe('test-value')
- expect(result?.additionalInfo?.reason).toBe('test-reason')
+ if (result == null) {
+ assert.fail('Expected result to be defined')
+ }
+ if (result.additionalInfo == null) {
+ assert.fail('Expected additionalInfo to be defined')
+ }
+ assert.strictEqual(result.additionalInfo.customField, 'test-value')
+ assert.strictEqual(result.additionalInfo.reason, 'test-reason')
})
await it('should handle offline authorization results', () => {
cache.set('offline-token', mockResult)
const result = cache.get('offline-token')
- expect(result?.isOffline).toBe(true)
- expect(result?.method).toBe(AuthenticationMethod.OFFLINE_FALLBACK)
+ if (result == null) {
+ assert.fail('Expected result to be defined')
+ }
+ assert.strictEqual(result.isOffline, true)
+ assert.strictEqual(result.method, AuthenticationMethod.OFFLINE_FALLBACK)
})
})
const blockedResult = lruCache.get('blocked-token')
const newResult = lruCache.get('new-token')
- expect(validResult).toBeDefined()
- expect(validResult?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(blockedResult).toBeUndefined()
- expect(newResult).toBeDefined()
+ assert.notStrictEqual(validResult, undefined)
+ assert.strictEqual(validResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(blockedResult, undefined)
+ assert.notStrictEqual(newResult, undefined)
})
await it('G03.FR.01.T5.02 - should fall back to LRU when all entries are ACCEPTED', () => {
const resultB = lruCache.get('token-b')
const resultC = lruCache.get('token-c')
- expect(resultA).toBeUndefined()
- expect(resultB).toBeDefined()
- expect(resultC).toBeDefined()
+ assert.strictEqual(resultA, undefined)
+ assert.notStrictEqual(resultB, undefined)
+ assert.notStrictEqual(resultC, undefined)
})
})
t.mock.timers.tick(50)
const midResult = shortCache.get('token')
- expect(midResult).toBeDefined()
- expect(midResult?.status).toBe(AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(midResult, undefined)
+ assert.strictEqual(midResult?.status, AuthorizationStatus.ACCEPTED)
t.mock.timers.tick(50)
const lateResult = shortCache.get('token')
- expect(lateResult).toBeDefined()
- expect(lateResult?.status).toBe(AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(lateResult, undefined)
+ assert.strictEqual(lateResult?.status, AuthorizationStatus.ACCEPTED)
})
})
t.mock.timers.tick(200)
const result = shortCache.get('token')
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
})
})
})
t.mock.timers.tick(10)
const result = shortCache.get('token')
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
})
})
// First access transitions to EXPIRED
const first = shortCache.get('token')
- expect(first?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.strictEqual(first?.status, AuthorizationStatus.EXPIRED)
// Second access should still return the entry (now with refreshed TTL as EXPIRED)
const second = shortCache.get('token')
- expect(second).toBeDefined()
- expect(second?.status).toBe(AuthorizationStatus.EXPIRED)
+ assert.notStrictEqual(second, undefined)
+ assert.strictEqual(second?.status, AuthorizationStatus.EXPIRED)
const stats = shortCache.getStats()
- expect(stats.totalEntries).toBe(1)
+ assert.strictEqual(stats.totalEntries, 1)
})
})
})
await describe('Helper - truncateId', async () => {
await it('should return identifier unchanged when short', () => {
const result = truncateId('ABCD')
- expect(result).toBe('ABCD')
+ assert.strictEqual(result, 'ABCD')
})
await it('should truncate long identifier with ellipsis', () => {
const result = truncateId('ABCDEFGHIJKLMNOP')
- expect(result).toBe('ABCDEFGH...')
+ assert.strictEqual(result, 'ABCDEFGH...')
})
})
maxEntries: 10,
rateLimit: { enabled: false },
})
- expect(() => {
+ assert.doesNotThrow(() => {
cleanupCache.dispose()
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
cleanupCache.dispose()
- }).not.toThrow()
+ })
})
await it('G03.FR.01.T10.02 - cleanup interval is not started when cleanupIntervalSeconds is 0', () => {
maxEntries: 10,
rateLimit: { enabled: false },
})
- expect(noCleanupCache.hasCleanupInterval()).toBe(false)
+ assert.strictEqual(noCleanupCache.hasCleanupInterval(), false)
noCleanupCache.dispose()
})
cleanupCache.set('id-2', createMockAuthorizationResult())
const statsBefore = cleanupCache.getStats()
- expect(statsBefore.totalEntries).toBe(2)
+ assert.strictEqual(statsBefore.totalEntries, 2)
t.mock.timers.tick(1100)
cleanupCache.runCleanup()
const statsAfterFirst = cleanupCache.getStats()
- expect(statsAfterFirst.totalEntries).toBe(2)
- expect(statsAfterFirst.expiredEntries).toBe(2)
+ assert.strictEqual(statsAfterFirst.totalEntries, 2)
+ assert.strictEqual(statsAfterFirst.expiredEntries, 2)
t.mock.timers.tick(1100)
cleanupCache.runCleanup()
const statsAfterSecond = cleanupCache.getStats()
- expect(statsAfterSecond.totalEntries).toBe(0)
- expect(statsAfterSecond.expiredEntries).toBe(2)
+ assert.strictEqual(statsAfterSecond.totalEntries, 0)
+ assert.strictEqual(statsAfterSecond.expiredEntries, 2)
cleanupCache.dispose()
})
})
}
const rateLimitsSize = boundedCache.getStats().rateLimit.rateLimitedIdentifiers
- expect(rateLimitsSize).toBeLessThanOrEqual(4)
+ assert.ok(rateLimitsSize <= 4)
boundedCache.dispose()
})
})
statsCache.get('id-miss') // miss
const before = statsCache.getStats()
- expect(before.evictions).toBeGreaterThan(0)
- expect(before.hits).toBeGreaterThan(0)
- expect(before.misses).toBeGreaterThan(0)
+ assert.ok(before.evictions > 0)
+ assert.ok(before.hits > 0)
+ assert.ok(before.misses > 0)
statsCache.clear()
const after = statsCache.getStats()
- expect(after.evictions).toBe(before.evictions)
- expect(after.hits).toBe(before.hits)
- expect(after.misses).toBe(before.misses)
- expect(after.totalEntries).toBe(0)
+ assert.strictEqual(after.evictions, before.evictions)
+ assert.strictEqual(after.hits, before.hits)
+ assert.strictEqual(after.misses, before.misses)
+ assert.strictEqual(after.totalEntries, 0)
statsCache.dispose()
})
statsCache.get('id-miss') // miss
const before = statsCache.getStats()
- expect(before.hits).toBeGreaterThan(0)
- expect(before.misses).toBeGreaterThan(0)
+ assert.ok(before.hits > 0)
+ assert.ok(before.misses > 0)
statsCache.resetStats()
const after = statsCache.getStats()
- expect(after.hits).toBe(0)
- expect(after.misses).toBe(0)
- expect(after.evictions).toBe(0)
+ assert.strictEqual(after.hits, 0)
+ assert.strictEqual(after.misses, 0)
+ assert.strictEqual(after.evictions, 0)
statsCache.dispose()
})
statsCache.clear() // clears entries but preserves stats
const afterClear = statsCache.getStats()
- expect(afterClear.hits).toBeGreaterThan(0) // stats preserved
- expect(afterClear.totalEntries).toBe(0) // entries gone
+ assert.ok(afterClear.hits > 0) // stats preserved
+ assert.strictEqual(afterClear.totalEntries, 0) // entries gone
statsCache.resetStats() // now zero out
const afterReset = statsCache.getStats()
- expect(afterReset.hits).toBe(0)
- expect(afterReset.misses).toBe(0)
+ assert.strictEqual(afterReset.hits, 0)
+ assert.strictEqual(afterReset.misses, 0)
statsCache.dispose()
})
})
* @description Unit tests for authentication component factory
*/
/* eslint-disable @typescript-eslint/no-confusing-void-expression */
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { AuthConfiguration } from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
})
const result = await AuthComponentFactory.createAdapters(chargingStation)
- expect(result.ocpp16Adapter).toBeDefined()
- expect(result.ocpp20Adapter).toBeUndefined()
+ assert.notStrictEqual(result.ocpp16Adapter, undefined)
+ assert.strictEqual(result.ocpp20Adapter, undefined)
})
await it('should create OCPP 2.0 adapter', async () => {
})
const result = await AuthComponentFactory.createAdapters(chargingStation)
- expect(result.ocpp16Adapter).toBeUndefined()
- expect(result.ocpp20Adapter).toBeDefined()
+ assert.strictEqual(result.ocpp16Adapter, undefined)
+ assert.notStrictEqual(result.ocpp20Adapter, undefined)
})
await it('should create OCPP 2.0.1 adapter', async () => {
})
const result = await AuthComponentFactory.createAdapters(chargingStation)
- expect(result.ocpp16Adapter).toBeUndefined()
- expect(result.ocpp20Adapter).toBeDefined()
+ assert.strictEqual(result.ocpp16Adapter, undefined)
+ assert.notStrictEqual(result.ocpp20Adapter, undefined)
})
await it('should throw error for unsupported version', async () => {
stationInfo: { ocppVersion: 'VERSION_15' as OCPPVersion },
})
- await expect(AuthComponentFactory.createAdapters(chargingStation)).rejects.toThrow(
- 'Unsupported OCPP version'
- )
+ await assert.rejects(AuthComponentFactory.createAdapters(chargingStation), {
+ message: /Unsupported OCPP version/,
+ })
})
await it('should throw error when no OCPP version', async () => {
const { station: chargingStation } = createMockChargingStation()
chargingStation.stationInfo = undefined
- await expect(AuthComponentFactory.createAdapters(chargingStation)).rejects.toThrow(
- 'OCPP version not found'
- )
+ await assert.rejects(AuthComponentFactory.createAdapters(chargingStation), {
+ message: /OCPP version not found/,
+ })
})
})
const result = AuthComponentFactory.createAuthCache(config)
- expect(result).toBeDefined()
- expect(result).toHaveProperty('get')
- expect(result).toHaveProperty('set')
- expect(result).toHaveProperty('clear')
- expect(result).toHaveProperty('getStats')
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(typeof result.get, 'function')
+ assert.strictEqual(typeof result.set, 'function')
+ assert.strictEqual(typeof result.clear, 'function')
+ assert.strictEqual(typeof result.getStats, 'function')
})
})
const result = AuthComponentFactory.createLocalAuthListManager(chargingStation, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
})
const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should create local strategy when enabled', async () => {
const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
if (result) {
- expect(result.priority).toBe(1)
+ assert.strictEqual(result.priority, 1)
}
})
})
const result = await AuthComponentFactory.createRemoteStrategy(adapters, undefined, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should create remote strategy when enabled', async () => {
const result = await AuthComponentFactory.createRemoteStrategy(adapters, undefined, config)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
if (result) {
- expect(result.priority).toBe(2)
+ assert.strictEqual(result.priority, 2)
}
})
})
config
)
- expect(result).toBeDefined()
- expect(result.priority).toBe(3)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result.priority, 3)
})
})
config
)
- expect(result).toHaveLength(1)
- expect(result[0].priority).toBe(3)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].priority, 3)
})
await it('should create and sort all strategies when enabled', async () => {
config
)
- expect(result).toHaveLength(3)
- expect(result[0].priority).toBe(1) // Local
- expect(result[1].priority).toBe(2) // Remote
- expect(result[2].priority).toBe(3) // Certificate
+ assert.strictEqual(result.length, 3)
+ assert.strictEqual(result[0].priority, 1) // Local
+ assert.strictEqual(result[1].priority, 2) // Remote
+ assert.strictEqual(result[2].priority, 3) // Certificate
})
})
remoteAuthorization: true,
}
- expect(() => {
+ assert.doesNotThrow(() => {
AuthComponentFactory.validateConfiguration(config)
- }).not.toThrow()
+ })
})
await it('should throw on invalid configuration', () => {
offlineAuthorizationEnabled: false,
}
- expect(() => {
+ assert.throws(() => {
AuthComponentFactory.validateConfiguration(config)
- }).toThrow()
+ })
})
})
})
* @file MockFactories
* @description Mock factory functions for authentication testing
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
import type {
result: AuthorizationResult,
expectedMethod?: AuthenticationMethod
): void => {
- expect(result.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.ok(result.timestamp instanceof Date)
if (expectedMethod !== undefined) {
- expect(result.method).toBe(expectedMethod)
+ assert.strictEqual(result.method, expectedMethod)
}
}
result: AuthorizationResult,
expectedStatus: AuthorizationStatus = AuthorizationStatus.INVALID
): void => {
- expect(result.status).toBe(expectedStatus)
- expect(result.status).not.toBe(AuthorizationStatus.ACCEPTED)
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.strictEqual(result.status, expectedStatus)
+ assert.notStrictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.ok(result.timestamp instanceof Date)
}
// ============================================================================
* @file Tests for OCPPAuthServiceFactory
* @description Unit tests for OCPP authentication service factory
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
await it('should create a new instance for a charging station', async () => {
const authService = await OCPPAuthServiceFactory.getInstance(mockStation16)
- expect(authService).toBeDefined()
- expect(typeof authService.authorize).toBe('function')
- expect(typeof authService.getConfiguration).toBe('function')
+ assert.notStrictEqual(authService, undefined)
+ assert.strictEqual(typeof authService.authorize, 'function')
+ assert.strictEqual(typeof authService.getConfiguration, 'function')
})
await it('should return cached instance for same charging station', async () => {
const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation20)
const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20)
- expect(authService1).toBe(authService2)
+ assert.strictEqual(authService1, authService2)
})
await it('should create different instances for different charging stations', async () => {
const authService1 = await OCPPAuthServiceFactory.getInstance(mockStation16)
const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation20)
- expect(authService1).not.toBe(authService2)
+ assert.notStrictEqual(authService1, authService2)
})
await it('should throw error for charging station without stationInfo', async () => {
try {
await OCPPAuthServiceFactory.getInstance(mockStation)
// If we get here, the test should fail
- expect(true).toBe(false) // Force failure
+ assert.strictEqual(true, false) // Force failure
} catch (error) {
- expect(error).toBeInstanceOf(Error)
- expect((error as Error).message).toContain('OCPP version not found in charging station')
+ assert.ok(error instanceof Error)
+ assert.ok(error.message.includes('OCPP version not found in charging station'))
}
})
})
const authService1 = await OCPPAuthServiceFactory.createInstance(mockStation16)
const authService2 = await OCPPAuthServiceFactory.createInstance(mockStation16)
- expect(authService1).toBeDefined()
- expect(authService2).toBeDefined()
- expect(authService1).not.toBe(authService2)
+ assert.notStrictEqual(authService1, undefined)
+ assert.notStrictEqual(authService2, undefined)
+ assert.notStrictEqual(authService1, authService2)
})
await it('should not cache created instances', async () => {
await OCPPAuthServiceFactory.createInstance(mockStation20)
const finalCount = OCPPAuthServiceFactory.getCachedInstanceCount()
- expect(finalCount).toBe(initialCount)
+ assert.strictEqual(finalCount, initialCount)
})
})
// Get instance again - should be a new instance
const authService2 = await OCPPAuthServiceFactory.getInstance(mockStation16)
- expect(authService1).not.toBe(authService2)
+ assert.notStrictEqual(authService1, authService2)
})
await it('should not throw when clearing non-existent instance', () => {
- expect(() => {
+ assert.doesNotThrow(() => {
OCPPAuthServiceFactory.clearInstance(mockStation20)
- }).not.toThrow()
+ })
})
})
// Verify all cleared
const count = OCPPAuthServiceFactory.getCachedInstanceCount()
- expect(count).toBe(0)
+ assert.strictEqual(count, 0)
})
})
})
await it('should return the number of cached instances', async () => {
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(0)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 0)
await OCPPAuthServiceFactory.getInstance(mockStation16)
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(1)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 1)
await OCPPAuthServiceFactory.getInstance(mockStation20)
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(2)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2)
// Getting same instance should not increase count
await OCPPAuthServiceFactory.getInstance(mockStation16)
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(2)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 2)
})
})
const stats = OCPPAuthServiceFactory.getStatistics()
- expect(stats).toBeDefined()
- expect(stats.cachedInstances).toBe(2)
- expect(stats.stationIds).toHaveLength(2)
- expect(stats.stationIds).toContain('TEST-CS-stats-16')
- expect(stats.stationIds).toContain('TEST-CS-stats-20')
+ assert.notStrictEqual(stats, undefined)
+ assert.strictEqual(stats.cachedInstances, 2)
+ assert.strictEqual(stats.stationIds.length, 2)
+ assert.ok(stats.stationIds.includes('TEST-CS-stats-16'))
+ assert.ok(stats.stationIds.includes('TEST-CS-stats-20'))
})
await it('should return empty statistics when no instances cached', () => {
const stats = OCPPAuthServiceFactory.getStatistics()
- expect(stats.cachedInstances).toBe(0)
- expect(stats.stationIds).toHaveLength(0)
+ assert.strictEqual(stats.cachedInstances, 0)
+ assert.strictEqual(stats.stationIds.length, 0)
})
})
await it('should create service for OCPP 1.6 station', async () => {
const authService = await OCPPAuthServiceFactory.getInstance(mockStation16)
- expect(authService).toBeDefined()
- expect(typeof authService.authorize).toBe('function')
- expect(typeof authService.getConfiguration).toBe('function')
+ assert.notStrictEqual(authService, undefined)
+ assert.strictEqual(typeof authService.authorize, 'function')
+ assert.strictEqual(typeof authService.getConfiguration, 'function')
})
await it('should create service for OCPP 2.0 station', async () => {
const authService = await OCPPAuthServiceFactory.getInstance(mockStation20)
- expect(authService).toBeDefined()
- expect(typeof authService.authorize).toBe('function')
- expect(typeof authService.testConnectivity).toBe('function')
+ assert.notStrictEqual(authService, undefined)
+ assert.strictEqual(typeof authService.authorize, 'function')
+ assert.strictEqual(typeof authService.testConnectivity, 'function')
})
})
await OCPPAuthServiceFactory.getInstance(station)
}
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(5)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 5)
// Clear one instance
OCPPAuthServiceFactory.clearInstance(mockStations[0])
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(4)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 4)
// Clear all
OCPPAuthServiceFactory.clearAllInstances()
- expect(OCPPAuthServiceFactory.getCachedInstanceCount()).toBe(0)
+ assert.strictEqual(OCPPAuthServiceFactory.getCachedInstanceCount(), 0)
})
})
})
* @file Tests for OCPPAuthServiceImpl
* @description Unit tests for OCPP authentication service implementation
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
await it('should initialize with OCPP 1.6 charging station', () => {
const authService: OCPPAuthService = new OCPPAuthServiceImpl(mockStation16)
- expect(authService).toBeDefined()
- expect(typeof authService.authorize).toBe('function')
- expect(typeof authService.getConfiguration).toBe('function')
+ assert.notStrictEqual(authService, undefined)
+ assert.strictEqual(typeof authService.authorize, 'function')
+ assert.strictEqual(typeof authService.getConfiguration, 'function')
})
await it('should initialize with OCPP 2.0 charging station', () => {
const authService = new OCPPAuthServiceImpl(mockStation20)
- expect(authService).toBeDefined()
+ assert.notStrictEqual(authService, undefined)
})
})
const authService = new OCPPAuthServiceImpl(mockStation)
const config = authService.getConfiguration()
- expect(config).toBeDefined()
- expect(config.localAuthListEnabled).toBe(true)
- expect(config.authorizationCacheEnabled).toBe(true)
- expect(config.offlineAuthorizationEnabled).toBe(true)
+ assert.notStrictEqual(config, undefined)
+ assert.strictEqual(config.localAuthListEnabled, true)
+ assert.strictEqual(config.authorizationCacheEnabled, true)
+ assert.strictEqual(config.offlineAuthorizationEnabled, true)
})
})
})
const config = authService.getConfiguration()
- expect(config.authorizationTimeout).toBe(60)
- expect(config.localAuthListEnabled).toBe(false)
+ assert.strictEqual(config.authorizationTimeout, 60)
+ assert.strictEqual(config.localAuthListEnabled, false)
})
})
value: 'VALID_ID_TAG',
}
- expect(authService.isSupported(idTagIdentifier)).toBe(true)
+ assert.strictEqual(authService.isSupported(idTagIdentifier), true)
})
await it('should check if identifier type is supported for OCPP 2.0', async () => {
value: 'CENTRAL_ID',
}
- expect(authService.isSupported(centralIdentifier)).toBe(true)
+ assert.strictEqual(authService.isSupported(centralIdentifier), true)
})
})
const authService = new OCPPAuthServiceImpl(mockStation)
const isConnected = authService.testConnectivity()
- expect(typeof isConnected).toBe('boolean')
+ assert.strictEqual(typeof isConnected, 'boolean')
})
})
await it('should clear authorization cache', () => {
const authService = new OCPPAuthServiceImpl(mockStation)
- expect(() => {
+ assert.doesNotThrow(() => {
authService.clearCache()
- }).not.toThrow()
+ })
})
})
value: 'TAG_TO_INVALIDATE',
}
- expect(() => {
+ assert.doesNotThrow(() => {
authService.invalidateCache(identifier)
- }).not.toThrow()
+ })
})
})
const authService = new OCPPAuthServiceImpl(mockStation)
const stats = await authService.getStats()
- expect(stats).toBeDefined()
- expect(stats.totalRequests).toBeDefined()
- expect(stats.successfulAuth).toBeDefined()
- expect(stats.failedAuth).toBeDefined()
- expect(stats.cacheHitRate).toBeDefined()
+ assert.notStrictEqual(stats, undefined)
+ assert.notStrictEqual(stats.totalRequests, undefined)
+ assert.notStrictEqual(stats.successfulAuth, undefined)
+ assert.notStrictEqual(stats.failedAuth, undefined)
+ assert.notStrictEqual(stats.cacheHitRate, undefined)
})
})
timestamp: new Date(),
})
- expect(result).toBeDefined()
- expect(result.status).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.notStrictEqual(result.status, undefined)
+ assert.ok(result.timestamp instanceof Date)
})
await it('should return INVALID status when all strategies fail', async () => {
timestamp: new Date(),
})
- expect(result.status).toBe(AuthorizationStatus.INVALID)
- expect(result.method).toBe(AuthenticationMethod.NONE)
+ assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result.method, AuthenticationMethod.NONE)
})
})
const result = await authService.isLocallyAuthorized(identifier, 1)
// Result can be undefined or AuthorizationResult
- expect(result === undefined || typeof result === 'object').toBe(true)
+ assert.ok(result === undefined || typeof result === 'object')
})
})
timestamp: new Date(),
})
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should handle OCPP 2.0 specific identifiers', async () => {
timestamp: new Date(),
})
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
})
timestamp: new Date(),
})
- expect(result.status).toBe(AuthorizationStatus.INVALID)
+ assert.strictEqual(result.status, AuthorizationStatus.INVALID)
})
})
timestamp: new Date(),
})
- expect(result).toBeDefined()
- expect(result.timestamp).toBeInstanceOf(Date)
+ assert.notStrictEqual(result, undefined)
+ assert.ok(result.timestamp instanceof Date)
})
await it('should handle TRANSACTION_STOP context', async () => {
transactionId: 'TXN-123',
})
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
await it('should handle REMOTE_START context', async () => {
timestamp: new Date(),
})
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
})
})
await authService.initialize()
const localStrategy = authService.getStrategy('local')
- expect(localStrategy).toBeDefined()
- expect(localStrategy).toBeInstanceOf(LocalAuthStrategy)
+ assert.notStrictEqual(localStrategy, undefined)
+ assert.ok(localStrategy instanceof LocalAuthStrategy)
- const local = localStrategy as LocalAuthStrategy
- expect(local.getAuthCache()).toBeDefined()
+ const local = localStrategy
+ assert.notStrictEqual(local.getAuthCache(), undefined)
})
})
})
* @file Tests for CertificateAuthStrategy
* @description Unit tests for certificate-based authentication strategy
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
await describe('constructor', async () => {
await it('should initialize with correct name and priority', () => {
- expect(strategy.name).toBe('CertificateAuthStrategy')
- expect(strategy.priority).toBe(3)
+ assert.strictEqual(strategy.name, 'CertificateAuthStrategy')
+ assert.strictEqual(strategy.priority, 3)
})
})
await describe('initialize', async () => {
await it('should initialize successfully when certificate auth is enabled', () => {
const config = createTestAuthConfig({ certificateAuthEnabled: true })
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.initialize(config)
- }).not.toThrow()
+ })
})
await it('should handle disabled certificate auth gracefully', () => {
const config = createTestAuthConfig({ certificateAuthEnabled: false })
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.initialize(config)
- }).not.toThrow()
+ })
})
})
},
})
- expect(strategy.canHandle(request, config)).toBe(true)
+ assert.strictEqual(strategy.canHandle(request, config), true)
})
await it('should return false for non-certificate identifiers', () => {
},
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
await it('should return false for OCPP 1.6', () => {
},
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
await it('should return false when certificate auth is disabled', () => {
},
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
await it('should return false when missing certificate data', () => {
},
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.CERTIFICATE_BASED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.CERTIFICATE_BASED)
})
await it('should reject invalid certificate serial numbers', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.BLOCKED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
})
await it('should reject revoked certificates', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.BLOCKED)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
})
await it('should handle missing certificate data', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.INVALID)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
})
await it('should handle invalid hash algorithm', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.INVALID)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
})
await it('should handle invalid hash format', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.INVALID)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
})
})
await it('should return strategy statistics', () => {
const stats = strategy.getStats()
- expect(stats.isInitialized).toBe(false)
- expect(stats.totalRequests).toBe(0)
- expect(stats.successfulAuths).toBe(0)
- expect(stats.failedAuths).toBe(0)
+ assert.strictEqual(stats.isInitialized, false)
+ assert.strictEqual(stats.totalRequests, 0)
+ assert.strictEqual(stats.successfulAuths, 0)
+ assert.strictEqual(stats.failedAuths, 0)
})
await it('should update stats after authentication', async () => {
await strategy.authenticate(request, config)
const stats = strategy.getStats()
- expect(stats.totalRequests).toBe(1)
- expect(stats.successfulAuths).toBe(1)
+ assert.strictEqual(stats.totalRequests, 1)
+ assert.strictEqual(stats.successfulAuths, 1)
})
})
strategy.cleanup()
const stats = strategy.getStats()
- expect(stats.isInitialized).toBe(false)
+ assert.strictEqual(stats.isInitialized, false)
})
})
})
* @file Tests for LocalAuthStrategy
* @description Unit tests for local authorization strategy (cache and local list)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type {
await describe('constructor', async () => {
await it('should initialize with correct name and priority', () => {
- expect(strategy.name).toBe('LocalAuthStrategy')
- expect(strategy.priority).toBe(1)
+ assert.strictEqual(strategy.name, 'LocalAuthStrategy')
+ assert.strictEqual(strategy.priority, 1)
})
await it('should initialize without dependencies', () => {
const strategyNoDeps = new LocalAuthStrategy()
- expect(strategyNoDeps.name).toBe('LocalAuthStrategy')
+ assert.strictEqual(strategyNoDeps.name, 'LocalAuthStrategy')
})
})
authorizationCacheEnabled: true,
localAuthListEnabled: true,
})
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.initialize(config)
- }).not.toThrow()
+ })
})
})
const request = createMockAuthRequest({
identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST_TAG', IdentifierType.ID_TAG),
})
- expect(strategy.canHandle(request, config)).toBe(true)
+ assert.strictEqual(strategy.canHandle(request, config), true)
})
await it('should return true when cache is enabled', () => {
const request = createMockAuthRequest({
identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST_TAG', IdentifierType.ID_TAG),
})
- expect(strategy.canHandle(request, config)).toBe(true)
+ assert.strictEqual(strategy.canHandle(request, config), true)
})
await it('should return false when nothing is enabled', () => {
const request = createMockAuthRequest({
identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST_TAG', IdentifierType.ID_TAG),
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.LOCAL_LIST)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.LOCAL_LIST)
})
await it('should authenticate using cache', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.CACHE)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.CACHE)
})
await it('should use offline fallback for transaction stop', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.OFFLINE_FALLBACK)
- expect(result?.isOffline).toBe(true)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.OFFLINE_FALLBACK)
+ assert.strictEqual(result.isOffline, true)
})
await it('should return undefined when no local auth available', async () => {
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
})
})
strategy.cacheResult('TEST_TAG', result, 300)
- expect(cachedValue).toBeDefined()
+ assert.notStrictEqual(cachedValue, undefined)
})
await it('should handle cache errors gracefully', () => {
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
})
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.cacheResult('TEST_TAG', result)
- }).not.toThrow()
+ })
})
})
}
strategy.invalidateCache('TEST_TAG')
- expect(removedKey).toBe('TEST_TAG')
+ assert.strictEqual(removedKey, 'TEST_TAG')
})
})
})
})
- await expect(strategy.isInLocalList('LOCAL_TAG')).resolves.toBe(true)
+ assert.strictEqual(await strategy.isInLocalList('LOCAL_TAG'), true)
})
await it('should return false when identifier is not in local list', async () => {
resolve(undefined)
})
- await expect(strategy.isInLocalList('UNKNOWN_TAG')).resolves.toBe(false)
+ assert.strictEqual(await strategy.isInLocalList('UNKNOWN_TAG'), false)
})
})
await it('should return strategy statistics', () => {
const stats = strategy.getStats()
- expect(stats.totalRequests).toBe(0)
- expect(stats.cacheHits).toBe(0)
- expect(stats.localListHits).toBe(0)
- expect(stats.isInitialized).toBe(false)
- expect(stats.hasAuthCache).toBe(true)
- expect(stats.hasLocalAuthListManager).toBe(true)
+ assert.strictEqual(stats.totalRequests, 0)
+ assert.strictEqual(stats.cacheHits, 0)
+ assert.strictEqual(stats.localListHits, 0)
+ assert.strictEqual(stats.isInitialized, false)
+ assert.strictEqual(stats.hasAuthCache, true)
+ assert.strictEqual(stats.hasLocalAuthListManager, true)
})
})
await it('should reset strategy state', () => {
strategy.cleanup()
const stats = strategy.getStats()
- expect(stats.isInitialized).toBe(false)
+ assert.strictEqual(stats.isInitialized, false)
})
})
})
* @file Tests for RemoteAuthStrategy
* @description Unit tests for remote (CSMS) authorization strategy
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import type {
await describe('constructor', async () => {
await it('should initialize with correct name and priority', () => {
- expect(strategy.name).toBe('RemoteAuthStrategy')
- expect(strategy.priority).toBe(2)
+ assert.strictEqual(strategy.name, 'RemoteAuthStrategy')
+ assert.strictEqual(strategy.priority, 2)
})
await it('should initialize without dependencies', () => {
const strategyNoDeps = new RemoteAuthStrategy()
- expect(strategyNoDeps.name).toBe('RemoteAuthStrategy')
- expect(strategyNoDeps.priority).toBe(2)
+ assert.strictEqual(strategyNoDeps.name, 'RemoteAuthStrategy')
+ assert.strictEqual(strategyNoDeps.priority, 2)
})
})
await describe('initialize', async () => {
await it('should initialize successfully with adapters', () => {
const config = createTestAuthConfig({ authorizationCacheEnabled: true })
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.initialize(config)
- }).not.toThrow()
+ })
})
await it('should validate adapter configurations', () => {
mockOCPP16Adapter.validateConfiguration = () => true
mockOCPP20Adapter.validateConfiguration = () => true
const config = createTestAuthConfig()
- expect(() => {
+ assert.doesNotThrow(() => {
strategy.initialize(config)
- }).not.toThrow()
+ })
})
})
IdentifierType.ID_TAG
),
})
- expect(strategy.canHandle(request, config)).toBe(true)
+ assert.strictEqual(strategy.canHandle(request, config), true)
})
await it('should return false when remote authorization is explicitly disabled', () => {
IdentifierType.ID_TAG
),
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
await it('should return false when no adapter available', () => {
IdentifierType.ID_TAG
),
})
- expect(strategyNoAdapters.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategyNoAdapters.canHandle(request, config), false)
})
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await it('should authenticate using OCPP 2.0 adapter', async () => {
const result = await strategy.authenticate(request, config)
- expect(result).toBeDefined()
- expect(result?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(result?.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await it('should cache successful authorization results', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('CACHE_TAG')
+ assert.strictEqual(cachedKey, 'CACHE_TAG')
})
await it('G03.FR.01.T4.01 - should cache BLOCKED authorization status', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('BLOCKED_TAG')
+ assert.strictEqual(cachedKey, 'BLOCKED_TAG')
})
await it('G03.FR.01.T4.02 - should cache EXPIRED authorization status', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('EXPIRED_TAG')
+ assert.strictEqual(cachedKey, 'EXPIRED_TAG')
})
await it('G03.FR.01.T4.03 - should cache INVALID authorization status', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('INVALID_TAG')
+ assert.strictEqual(cachedKey, 'INVALID_TAG')
})
await it('G03.FR.01.T4.04 - should still cache ACCEPTED authorization status (regression)', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('ACCEPTED_TAG')
+ assert.strictEqual(cachedKey, 'ACCEPTED_TAG')
})
await it('should return undefined when remote is unavailable', async () => {
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should return undefined when no adapter available', async () => {
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should handle remote authorization errors gracefully', async () => {
})
const result = await strategy.authenticate(request, config)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('G03.FR.01.T8.01 - should not cache identifier that is in local auth list', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBeUndefined()
+ assert.strictEqual(cachedKey, undefined)
})
await it('G03.FR.01.T8.02 - should cache identifier that is not in local auth list', async () => {
})
await strategy.authenticate(request, config)
- expect(cachedKey).toBe('REMOTE_AUTH_TAG')
+ assert.strictEqual(cachedKey, 'REMOTE_AUTH_TAG')
})
})
identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST', IdentifierType.ID_TAG),
})
- expect(newStrategy.canHandle(request, config)).toBe(true)
+ assert.strictEqual(newStrategy.canHandle(request, config), true)
})
await it('should remove adapter', () => {
identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST', IdentifierType.ID_TAG),
})
- expect(strategy.canHandle(request, config)).toBe(false)
+ assert.strictEqual(strategy.canHandle(request, config), false)
})
})
await it('should test connectivity successfully', async () => {
strategy.initialize(createTestAuthConfig())
const result = await strategy.testConnectivity()
- expect(result).toBe(true)
+ assert.strictEqual(result, true)
})
await it('should return false when not initialized', async () => {
const newStrategy = new RemoteAuthStrategy()
const result = await newStrategy.testConnectivity()
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
await it('should return false when all adapters unavailable', async () => {
strategy.initialize(createTestAuthConfig())
const result = await strategy.testConnectivity()
- expect(result).toBe(false)
+ assert.strictEqual(result, false)
})
})
await describe('getStats', async () => {
- await it('should return strategy statistics', () => {
- void expect(strategy.getStats()).resolves.toMatchObject({
- adapterCount: 2,
- failedRemoteAuth: 0,
- hasAuthCache: true,
- isInitialized: false,
- successfulRemoteAuth: 0,
- totalRequests: 0,
- })
+ await it('should return strategy statistics', async () => {
+ const stats = await strategy.getStats()
+ assert.strictEqual(stats.adapterCount, 2)
+ assert.strictEqual(stats.failedRemoteAuth, 0)
+ assert.strictEqual(stats.hasAuthCache, true)
+ assert.strictEqual(stats.isInitialized, false)
+ assert.strictEqual(stats.successfulRemoteAuth, 0)
+ assert.strictEqual(stats.totalRequests, 0)
})
await it('should include adapter statistics', async () => {
strategy.initialize(createTestAuthConfig())
const stats = await strategy.getStats()
- expect(stats.adapterStats).toBeDefined()
+ assert.notStrictEqual(stats.adapterStats, undefined)
})
})
await it('should reset strategy state', async () => {
strategy.cleanup()
const stats = await strategy.getStats()
- expect(stats.isInitialized).toBe(false)
- expect(stats.totalRequests).toBe(0)
+ assert.strictEqual(stats.isInitialized, false)
+ assert.strictEqual(stats.totalRequests, 0)
})
})
})
* @file Tests for AuthTypes
* @description Unit tests for authentication type definitions and mappings
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
})
await describe('IdentifierTypeGuards', async () => {
await it('should correctly identify OCPP 1.6 types', () => {
- expect(isOCPP16Type(IdentifierType.ID_TAG)).toBe(true)
- expect(isOCPP16Type(IdentifierType.CENTRAL)).toBe(false)
- expect(isOCPP16Type(IdentifierType.LOCAL)).toBe(false)
+ assert.strictEqual(isOCPP16Type(IdentifierType.ID_TAG), true)
+ assert.strictEqual(isOCPP16Type(IdentifierType.CENTRAL), false)
+ assert.strictEqual(isOCPP16Type(IdentifierType.LOCAL), false)
})
await it('should correctly identify OCPP 2.0 types', () => {
- expect(isOCPP20Type(IdentifierType.CENTRAL)).toBe(true)
- expect(isOCPP20Type(IdentifierType.LOCAL)).toBe(true)
- expect(isOCPP20Type(IdentifierType.E_MAID)).toBe(true)
- expect(isOCPP20Type(IdentifierType.ID_TAG)).toBe(false)
+ assert.strictEqual(isOCPP20Type(IdentifierType.CENTRAL), true)
+ assert.strictEqual(isOCPP20Type(IdentifierType.LOCAL), true)
+ assert.strictEqual(isOCPP20Type(IdentifierType.E_MAID), true)
+ assert.strictEqual(isOCPP20Type(IdentifierType.ID_TAG), false)
})
await it('should correctly identify certificate-based types', () => {
- expect(isCertificateBased(IdentifierType.CERTIFICATE)).toBe(true)
- expect(isCertificateBased(IdentifierType.ID_TAG)).toBe(false)
- expect(isCertificateBased(IdentifierType.LOCAL)).toBe(false)
+ assert.strictEqual(isCertificateBased(IdentifierType.CERTIFICATE), true)
+ assert.strictEqual(isCertificateBased(IdentifierType.ID_TAG), false)
+ assert.strictEqual(isCertificateBased(IdentifierType.LOCAL), false)
})
await it('should identify types requiring additional info', () => {
- expect(requiresAdditionalInfo(IdentifierType.E_MAID)).toBe(true)
- expect(requiresAdditionalInfo(IdentifierType.ISO14443)).toBe(true)
- expect(requiresAdditionalInfo(IdentifierType.ISO15693)).toBe(true)
- expect(requiresAdditionalInfo(IdentifierType.MAC_ADDRESS)).toBe(true)
- expect(requiresAdditionalInfo(IdentifierType.ID_TAG)).toBe(false)
- expect(requiresAdditionalInfo(IdentifierType.LOCAL)).toBe(false)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.E_MAID), true)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.ISO14443), true)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.ISO15693), true)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.MAC_ADDRESS), true)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.ID_TAG), false)
+ assert.strictEqual(requiresAdditionalInfo(IdentifierType.LOCAL), false)
})
})
await describe('OCPP 1.6 Status Mapping', async () => {
await it('should map OCPP 1.6 ACCEPTED to unified ACCEPTED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
- expect(result).toBe(AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result, AuthorizationStatus.ACCEPTED)
})
await it('should map OCPP 1.6 BLOCKED to unified BLOCKED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.BLOCKED)
- expect(result).toBe(AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result, AuthorizationStatus.BLOCKED)
})
await it('should map OCPP 1.6 EXPIRED to unified EXPIRED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.EXPIRED)
- expect(result).toBe(AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result, AuthorizationStatus.EXPIRED)
})
await it('should map OCPP 1.6 INVALID to unified INVALID', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.INVALID)
- expect(result).toBe(AuthorizationStatus.INVALID)
+ assert.strictEqual(result, AuthorizationStatus.INVALID)
})
await it('should map OCPP 1.6 CONCURRENT_TX to unified CONCURRENT_TX', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.CONCURRENT_TX)
- expect(result).toBe(AuthorizationStatus.CONCURRENT_TX)
+ assert.strictEqual(result, AuthorizationStatus.CONCURRENT_TX)
})
})
await describe('OCPP 2.0 Token Type Mapping', async () => {
await it('should map OCPP 2.0 Central to unified CENTRAL', () => {
const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Central)
- expect(result).toBe(IdentifierType.CENTRAL)
+ assert.strictEqual(result, IdentifierType.CENTRAL)
})
await it('should map OCPP 2.0 Local to unified LOCAL', () => {
const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.Local)
- expect(result).toBe(IdentifierType.LOCAL)
+ assert.strictEqual(result, IdentifierType.LOCAL)
})
await it('should map OCPP 2.0 eMAID to unified E_MAID', () => {
const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.eMAID)
- expect(result).toBe(IdentifierType.E_MAID)
+ assert.strictEqual(result, IdentifierType.E_MAID)
})
await it('should map OCPP 2.0 ISO14443 to unified ISO14443', () => {
const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.ISO14443)
- expect(result).toBe(IdentifierType.ISO14443)
+ assert.strictEqual(result, IdentifierType.ISO14443)
})
await it('should map OCPP 2.0 KeyCode to unified KEY_CODE', () => {
const result = mapOCPP20TokenType(OCPP20IdTokenEnumType.KeyCode)
- expect(result).toBe(IdentifierType.KEY_CODE)
+ assert.strictEqual(result, IdentifierType.KEY_CODE)
})
})
await describe('Unified to OCPP 1.6 Status Mapping', async () => {
await it('should map unified ACCEPTED to OCPP 1.6 ACCEPTED', () => {
const result = mapToOCPP16Status(AuthorizationStatus.ACCEPTED)
- expect(result).toBe(OCPP16AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result, OCPP16AuthorizationStatus.ACCEPTED)
})
await it('should map unified BLOCKED to OCPP 1.6 BLOCKED', () => {
const result = mapToOCPP16Status(AuthorizationStatus.BLOCKED)
- expect(result).toBe(OCPP16AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result, OCPP16AuthorizationStatus.BLOCKED)
})
await it('should map unified EXPIRED to OCPP 1.6 EXPIRED', () => {
const result = mapToOCPP16Status(AuthorizationStatus.EXPIRED)
- expect(result).toBe(OCPP16AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result, OCPP16AuthorizationStatus.EXPIRED)
})
await it('should map unsupported statuses to OCPP 1.6 INVALID', () => {
- expect(mapToOCPP16Status(AuthorizationStatus.PENDING)).toBe(
+ assert.strictEqual(
+ mapToOCPP16Status(AuthorizationStatus.PENDING),
OCPP16AuthorizationStatus.INVALID
)
- expect(mapToOCPP16Status(AuthorizationStatus.UNKNOWN)).toBe(
+ assert.strictEqual(
+ mapToOCPP16Status(AuthorizationStatus.UNKNOWN),
OCPP16AuthorizationStatus.INVALID
)
- expect(mapToOCPP16Status(AuthorizationStatus.NOT_AT_THIS_LOCATION)).toBe(
+ assert.strictEqual(
+ mapToOCPP16Status(AuthorizationStatus.NOT_AT_THIS_LOCATION),
OCPP16AuthorizationStatus.INVALID
)
})
await describe('Unified to OCPP 2.0 Status Mapping', async () => {
await it('should map unified ACCEPTED to OCPP 2.0 Accepted', () => {
const result = mapToOCPP20Status(AuthorizationStatus.ACCEPTED)
- expect(result).toBe(RequestStartStopStatusEnumType.Accepted)
+ assert.strictEqual(result, RequestStartStopStatusEnumType.Accepted)
})
await it('should map rejection statuses to OCPP 2.0 Rejected', () => {
- expect(mapToOCPP20Status(AuthorizationStatus.BLOCKED)).toBe(
+ assert.strictEqual(
+ mapToOCPP20Status(AuthorizationStatus.BLOCKED),
RequestStartStopStatusEnumType.Rejected
)
- expect(mapToOCPP20Status(AuthorizationStatus.INVALID)).toBe(
+ assert.strictEqual(
+ mapToOCPP20Status(AuthorizationStatus.INVALID),
RequestStartStopStatusEnumType.Rejected
)
- expect(mapToOCPP20Status(AuthorizationStatus.EXPIRED)).toBe(
+ assert.strictEqual(
+ mapToOCPP20Status(AuthorizationStatus.EXPIRED),
RequestStartStopStatusEnumType.Rejected
)
})
await describe('Unified to OCPP 2.0 Token Type Mapping', async () => {
await it('should map unified CENTRAL to OCPP 2.0 Central', () => {
const result = mapToOCPP20TokenType(IdentifierType.CENTRAL)
- expect(result).toBe(OCPP20IdTokenEnumType.Central)
+ assert.strictEqual(result, OCPP20IdTokenEnumType.Central)
})
await it('should map unified E_MAID to OCPP 2.0 eMAID', () => {
const result = mapToOCPP20TokenType(IdentifierType.E_MAID)
- expect(result).toBe(OCPP20IdTokenEnumType.eMAID)
+ assert.strictEqual(result, OCPP20IdTokenEnumType.eMAID)
})
await it('should map unified ID_TAG to OCPP 2.0 Local', () => {
const result = mapToOCPP20TokenType(IdentifierType.ID_TAG)
- expect(result).toBe(OCPP20IdTokenEnumType.Local)
+ assert.strictEqual(result, OCPP20IdTokenEnumType.Local)
})
await it('should map unified LOCAL to OCPP 2.0 Local', () => {
const result = mapToOCPP20TokenType(IdentifierType.LOCAL)
- expect(result).toBe(OCPP20IdTokenEnumType.Local)
+ assert.strictEqual(result, OCPP20IdTokenEnumType.Local)
})
})
})
await it('should create error with required properties', () => {
const error = new AuthenticationError('Test error', AuthErrorCode.INVALID_IDENTIFIER)
- expect(error).toBeInstanceOf(Error)
- expect(error).toBeInstanceOf(AuthenticationError)
- expect(error.name).toBe('AuthenticationError')
- expect(error.message).toBe('Test error')
- expect(error.code).toBe(AuthErrorCode.INVALID_IDENTIFIER)
+ assert.ok(error instanceof Error)
+ assert.ok(error instanceof AuthenticationError)
+ assert.strictEqual(error.name, 'AuthenticationError')
+ assert.strictEqual(error.message, 'Test error')
+ assert.strictEqual(error.code, AuthErrorCode.INVALID_IDENTIFIER)
})
await it('should create error with optional context', () => {
ocppVersion: OCPPVersion.VERSION_16,
})
- expect(error.context).toBe(AuthContext.TRANSACTION_START)
- expect(error.identifier).toBe('TEST_ID')
- expect(error.ocppVersion).toBe(OCPPVersion.VERSION_16)
+ assert.strictEqual(error.context, AuthContext.TRANSACTION_START)
+ assert.strictEqual(error.identifier, 'TEST_ID')
+ assert.strictEqual(error.ocppVersion, OCPPVersion.VERSION_16)
})
await it('should create error with cause', () => {
cause,
})
- expect(error.cause).toBe(cause)
+ assert.strictEqual(error.cause, cause)
})
await it('should support all error codes', () => {
for (const code of errorCodes) {
const error = new AuthenticationError('Test', code)
- expect(error.code).toBe(code)
+ assert.strictEqual(error.code, code)
}
})
})
value: 'VALID_ID_TAG',
}
- expect(identifier.value).toBe('VALID_ID_TAG')
- expect(identifier.type).toBe(IdentifierType.ID_TAG)
- expect(identifier.ocppVersion).toBe(OCPPVersion.VERSION_16)
+ assert.strictEqual(identifier.value, 'VALID_ID_TAG')
+ assert.strictEqual(identifier.type, IdentifierType.ID_TAG)
+ assert.strictEqual(identifier.ocppVersion, OCPPVersion.VERSION_16)
})
await it('should create valid OCPP 2.0 identifier with additional info', () => {
value: 'EMAID123456',
}
- expect(identifier.value).toBe('EMAID123456')
- expect(identifier.type).toBe(IdentifierType.E_MAID)
- expect(identifier.ocppVersion).toBe(OCPPVersion.VERSION_20)
- expect(identifier.additionalInfo).toBeDefined()
- expect(identifier.additionalInfo?.issuer).toBe('EMSProvider')
+ assert.strictEqual(identifier.value, 'EMAID123456')
+ assert.strictEqual(identifier.type, IdentifierType.E_MAID)
+ assert.strictEqual(identifier.ocppVersion, OCPPVersion.VERSION_20)
+ assert.notStrictEqual(identifier.additionalInfo, undefined)
+ assert.strictEqual(identifier.additionalInfo?.issuer, 'EMSProvider')
})
await it('should support certificate-based identifier', () => {
value: 'CERT_IDENTIFIER',
}
- expect(identifier.certificateHashData).toBeDefined()
- expect(identifier.certificateHashData?.hashAlgorithm).toBe('SHA256')
+ assert.notStrictEqual(identifier.certificateHashData, undefined)
+ assert.strictEqual(identifier.certificateHashData?.hashAlgorithm, 'SHA256')
})
})
await describe('Enums', async () => {
await it('should have correct AuthContext values', () => {
- expect(AuthContext.TRANSACTION_START).toBe('TransactionStart')
- expect(AuthContext.TRANSACTION_STOP).toBe('TransactionStop')
- expect(AuthContext.REMOTE_START).toBe('RemoteStart')
- expect(AuthContext.REMOTE_STOP).toBe('RemoteStop')
- expect(AuthContext.RESERVATION).toBe('Reservation')
- expect(AuthContext.UNLOCK_CONNECTOR).toBe('UnlockConnector')
+ assert.strictEqual(AuthContext.TRANSACTION_START, 'TransactionStart')
+ assert.strictEqual(AuthContext.TRANSACTION_STOP, 'TransactionStop')
+ assert.strictEqual(AuthContext.REMOTE_START, 'RemoteStart')
+ assert.strictEqual(AuthContext.REMOTE_STOP, 'RemoteStop')
+ assert.strictEqual(AuthContext.RESERVATION, 'Reservation')
+ assert.strictEqual(AuthContext.UNLOCK_CONNECTOR, 'UnlockConnector')
})
await it('should have correct AuthenticationMethod values', () => {
- expect(AuthenticationMethod.LOCAL_LIST).toBe('LocalList')
- expect(AuthenticationMethod.REMOTE_AUTHORIZATION).toBe('RemoteAuthorization')
- expect(AuthenticationMethod.CACHE).toBe('Cache')
- expect(AuthenticationMethod.CERTIFICATE_BASED).toBe('CertificateBased')
- expect(AuthenticationMethod.OFFLINE_FALLBACK).toBe('OfflineFallback')
+ assert.strictEqual(AuthenticationMethod.LOCAL_LIST, 'LocalList')
+ assert.strictEqual(AuthenticationMethod.REMOTE_AUTHORIZATION, 'RemoteAuthorization')
+ assert.strictEqual(AuthenticationMethod.CACHE, 'Cache')
+ assert.strictEqual(AuthenticationMethod.CERTIFICATE_BASED, 'CertificateBased')
+ assert.strictEqual(AuthenticationMethod.OFFLINE_FALLBACK, 'OfflineFallback')
})
await it('should have correct AuthorizationStatus values', () => {
- expect(AuthorizationStatus.ACCEPTED).toBe('Accepted')
- expect(AuthorizationStatus.BLOCKED).toBe('Blocked')
- expect(AuthorizationStatus.EXPIRED).toBe('Expired')
- expect(AuthorizationStatus.INVALID).toBe('Invalid')
- expect(AuthorizationStatus.CONCURRENT_TX).toBe('ConcurrentTx')
+ assert.strictEqual(AuthorizationStatus.ACCEPTED, 'Accepted')
+ assert.strictEqual(AuthorizationStatus.BLOCKED, 'Blocked')
+ assert.strictEqual(AuthorizationStatus.EXPIRED, 'Expired')
+ assert.strictEqual(AuthorizationStatus.INVALID, 'Invalid')
+ assert.strictEqual(AuthorizationStatus.CONCURRENT_TX, 'ConcurrentTx')
})
await it('should have correct IdentifierType values', () => {
- expect(IdentifierType.ID_TAG).toBe('IdTag')
- expect(IdentifierType.CENTRAL).toBe('Central')
- expect(IdentifierType.LOCAL).toBe('Local')
- expect(IdentifierType.E_MAID).toBe('eMAID')
- expect(IdentifierType.KEY_CODE).toBe('KeyCode')
+ assert.strictEqual(IdentifierType.ID_TAG, 'IdTag')
+ assert.strictEqual(IdentifierType.CENTRAL, 'Central')
+ assert.strictEqual(IdentifierType.LOCAL, 'Local')
+ assert.strictEqual(IdentifierType.E_MAID, 'eMAID')
+ assert.strictEqual(IdentifierType.KEY_CODE, 'KeyCode')
})
})
})
* @file Tests for AuthHelpers
* @description Unit tests for authentication helper utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
await describe('calculateTTL', async () => {
await it('should return undefined for undefined expiry date', () => {
const result = AuthHelpers.calculateTTL(undefined)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should return undefined for expired date', () => {
const expiredDate = new Date(Date.now() - 1000)
const result = AuthHelpers.calculateTTL(expiredDate)
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should calculate correct TTL in seconds for future date', () => {
const futureDate = new Date(Date.now() + 5000)
const result = AuthHelpers.calculateTTL(futureDate)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
if (result !== undefined) {
- expect(result).toBeGreaterThanOrEqual(4)
- expect(result).toBeLessThanOrEqual(5)
+ assert.ok(result >= 4)
+ assert.ok(result <= 5)
}
})
await it('should round down TTL to nearest second', () => {
const futureDate = new Date(Date.now() + 5500)
const result = AuthHelpers.calculateTTL(futureDate)
- expect(result).toBe(5)
+ assert.strictEqual(result, 5)
})
})
const request = AuthHelpers.createAuthRequest(identifier, context)
- expect(request.identifier).toBe(identifier)
- expect(request.context).toBe(context)
- expect(request.allowOffline).toBe(true)
- expect(request.timestamp).toBeInstanceOf(Date)
- expect(request.connectorId).toBeUndefined()
- expect(request.metadata).toBeUndefined()
+ assert.strictEqual(request.identifier, identifier)
+ assert.strictEqual(request.context, context)
+ assert.strictEqual(request.allowOffline, true)
+ assert.ok(request.timestamp instanceof Date)
+ assert.strictEqual(request.connectorId, undefined)
+ assert.strictEqual(request.metadata, undefined)
})
await it('should create auth request with connector ID', () => {
const request = AuthHelpers.createAuthRequest(identifier, context, connectorId)
- expect(request.connectorId).toBe(1)
+ assert.strictEqual(request.connectorId, 1)
})
await it('should create auth request with metadata', () => {
const request = AuthHelpers.createAuthRequest(identifier, context, undefined, metadata)
- expect(request.metadata).toStrictEqual({ source: 'test' })
+ assert.deepStrictEqual(request.metadata, { source: 'test' })
})
})
AuthenticationMethod.LOCAL_LIST
)
- expect(result.status).toBe(AuthorizationStatus.BLOCKED)
- expect(result.method).toBe(AuthenticationMethod.LOCAL_LIST)
- expect(result.isOffline).toBe(false)
- expect(result.timestamp).toBeInstanceOf(Date)
- expect(result.additionalInfo).toBeUndefined()
+ assert.strictEqual(result.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result.method, AuthenticationMethod.LOCAL_LIST)
+ assert.strictEqual(result.isOffline, false)
+ assert.ok(result.timestamp instanceof Date)
+ assert.strictEqual(result.additionalInfo, undefined)
})
await it('should create rejected result with reason', () => {
'Token expired on 2024-01-01'
)
- expect(result.status).toBe(AuthorizationStatus.EXPIRED)
- expect(result.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
- expect(result.additionalInfo).toStrictEqual({ reason: 'Token expired on 2024-01-01' })
+ assert.strictEqual(result.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.deepStrictEqual(result.additionalInfo, { reason: 'Token expired on 2024-01-01' })
})
})
const message = AuthHelpers.formatAuthError(error, identifier)
- expect(message).toContain('VERY_LON...')
- expect(message).toContain('IdTag')
- expect(message).toContain('Connection timeout')
+ assert.ok(message.includes('VERY_LON...'))
+ assert.ok(message.includes('IdTag'))
+ assert.ok(message.includes('Connection timeout'))
})
await it('should handle short identifiers correctly', () => {
const message = AuthHelpers.formatAuthError(error, identifier)
- expect(message).toContain('SHORT...')
- expect(message).toContain('Local')
- expect(message).toContain('Invalid format')
+ assert.ok(message.includes('SHORT...'))
+ assert.ok(message.includes('Local'))
+ assert.ok(message.includes('Invalid format'))
})
})
await describe('getStatusMessage', async () => {
await it('should return message for ACCEPTED status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.ACCEPTED)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.ACCEPTED),
'Authorization accepted'
)
})
await it('should return message for BLOCKED status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.BLOCKED)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.BLOCKED),
'Identifier is blocked'
)
})
await it('should return message for EXPIRED status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.EXPIRED)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.EXPIRED),
'Authorization has expired'
)
})
await it('should return message for INVALID status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.INVALID)).toBe('Invalid identifier')
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.INVALID),
+ 'Invalid identifier'
+ )
})
await it('should return message for CONCURRENT_TX status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.CONCURRENT_TX)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.CONCURRENT_TX),
'Concurrent transaction in progress'
)
})
await it('should return message for NOT_AT_THIS_LOCATION status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_LOCATION)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_LOCATION),
'Not authorized at this location'
)
})
await it('should return message for NOT_AT_THIS_TIME status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_TIME)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_TIME),
'Not authorized at this time'
)
})
await it('should return message for PENDING status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.PENDING)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.PENDING),
'Authorization pending'
)
})
await it('should return message for UNKNOWN status', () => {
- expect(AuthHelpers.getStatusMessage(AuthorizationStatus.UNKNOWN)).toBe(
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.UNKNOWN),
'Unknown authorization status'
)
})
await it('should return generic message for unknown status', () => {
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- testing invalid status value
- expect(AuthHelpers.getStatusMessage('INVALID_STATUS' as any)).toBe('Authorization failed')
+ assert.strictEqual(
+ AuthHelpers.getStatusMessage(AuthorizationStatus.NO_CREDIT),
+ 'Authorization failed'
+ )
})
})
timestamp: new Date(),
}
- expect(AuthHelpers.isPermanentFailure(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isPermanentFailure(result), true)
})
await it('should return true for EXPIRED status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isPermanentFailure(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isPermanentFailure(result), true)
})
await it('should return true for INVALID status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isPermanentFailure(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isPermanentFailure(result), true)
})
await it('should return false for ACCEPTED status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isPermanentFailure(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isPermanentFailure(result), false)
})
await it('should return false for PENDING status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isPermanentFailure(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isPermanentFailure(result), false)
})
})
timestamp: new Date(),
}
- expect(AuthHelpers.isResultValid(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isResultValid(result), false)
})
await it('should return true for ACCEPTED without expiry date', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isResultValid(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isResultValid(result), true)
})
await it('should return false for expired ACCEPTED result', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isResultValid(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isResultValid(result), false)
})
await it('should return true for non-expired ACCEPTED result', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isResultValid(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isResultValid(result), true)
})
})
timestamp: new Date(),
}
- expect(AuthHelpers.isTemporaryFailure(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isTemporaryFailure(result), true)
})
await it('should return true for UNKNOWN status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isTemporaryFailure(result)).toBe(true)
+ assert.strictEqual(AuthHelpers.isTemporaryFailure(result), true)
})
await it('should return false for BLOCKED status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isTemporaryFailure(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isTemporaryFailure(result), false)
})
await it('should return false for ACCEPTED status', () => {
timestamp: new Date(),
}
- expect(AuthHelpers.isTemporaryFailure(result)).toBe(false)
+ assert.strictEqual(AuthHelpers.isTemporaryFailure(result), false)
})
})
await describe('mergeAuthResults', async () => {
await it('should return undefined for empty array', () => {
const result = AuthHelpers.mergeAuthResults([])
- expect(result).toBeUndefined()
+ assert.strictEqual(result, undefined)
})
await it('should return first ACCEPTED result', () => {
]
const merged = AuthHelpers.mergeAuthResults(results)
- expect(merged?.status).toBe(AuthorizationStatus.ACCEPTED)
- expect(merged?.method).toBe(AuthenticationMethod.REMOTE_AUTHORIZATION)
+ assert.strictEqual(merged?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(merged.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await it('should merge information when all results are rejections', () => {
]
const merged = AuthHelpers.mergeAuthResults(results)
- expect(merged?.status).toBe(AuthorizationStatus.BLOCKED)
- expect(merged?.method).toBe(AuthenticationMethod.LOCAL_LIST)
- expect(merged?.isOffline).toBe(true)
- expect(merged?.additionalInfo).toStrictEqual({
+ assert.strictEqual(merged?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(merged.method, AuthenticationMethod.LOCAL_LIST)
+ assert.strictEqual(merged.isOffline, true)
+ assert.deepStrictEqual(merged.additionalInfo, {
attemptedMethods: 'LocalList, RemoteAuthorization',
totalAttempts: 2,
})
const sanitized = AuthHelpers.sanitizeForLogging(result)
- expect(sanitized).toStrictEqual({
+ assert.deepStrictEqual(sanitized, {
hasExpiryDate: true,
hasGroupId: true,
hasPersonalMessage: true,
const sanitized = AuthHelpers.sanitizeForLogging(result)
- expect(sanitized).toStrictEqual({
+ assert.deepStrictEqual(sanitized, {
hasExpiryDate: false,
hasGroupId: false,
hasPersonalMessage: false,
* @file Tests for AuthValidators
* @description Unit tests for authentication validation utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
})
await describe('isValidCacheTTL', async () => {
await it('should return true for undefined TTL', () => {
- expect(AuthValidators.isValidCacheTTL(undefined)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(undefined), true)
})
await it('should return true for zero TTL', () => {
- expect(AuthValidators.isValidCacheTTL(0)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(0), true)
})
await it('should return true for positive TTL', () => {
- expect(AuthValidators.isValidCacheTTL(3600)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(3600), true)
})
await it('should return false for negative TTL', () => {
- expect(AuthValidators.isValidCacheTTL(-1)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(-1), false)
})
await it('should return false for infinite TTL', () => {
- expect(AuthValidators.isValidCacheTTL(Infinity)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(Infinity), false)
})
await it('should return false for NaN TTL', () => {
- expect(AuthValidators.isValidCacheTTL(NaN)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidCacheTTL(NaN), false)
})
})
await describe('isValidConnectorId', async () => {
await it('should return true for undefined connector ID', () => {
- expect(AuthValidators.isValidConnectorId(undefined)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidConnectorId(undefined), true)
})
await it('should return true for zero connector ID', () => {
- expect(AuthValidators.isValidConnectorId(0)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidConnectorId(0), true)
})
await it('should return true for positive connector ID', () => {
- expect(AuthValidators.isValidConnectorId(1)).toBe(true)
- expect(AuthValidators.isValidConnectorId(100)).toBe(true)
+ assert.strictEqual(AuthValidators.isValidConnectorId(1), true)
+ assert.strictEqual(AuthValidators.isValidConnectorId(100), true)
})
await it('should return false for negative connector ID', () => {
- expect(AuthValidators.isValidConnectorId(-1)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidConnectorId(-1), false)
})
await it('should return false for non-integer connector ID', () => {
- expect(AuthValidators.isValidConnectorId(1.5)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidConnectorId(1.5), false)
})
})
await describe('isValidIdentifierValue', async () => {
await it('should return false for empty string', () => {
- expect(AuthValidators.isValidIdentifierValue('')).toBe(false)
+ assert.strictEqual(AuthValidators.isValidIdentifierValue(''), false)
})
await it('should return false for whitespace-only string', () => {
- expect(AuthValidators.isValidIdentifierValue(' ')).toBe(false)
+ assert.strictEqual(AuthValidators.isValidIdentifierValue(' '), false)
})
await it('should return true for valid identifier', () => {
- expect(AuthValidators.isValidIdentifierValue('TEST123')).toBe(true)
+ assert.strictEqual(AuthValidators.isValidIdentifierValue('TEST123'), true)
})
await it('should return true for identifier with spaces', () => {
- expect(AuthValidators.isValidIdentifierValue(' TEST123 ')).toBe(true)
+ assert.strictEqual(AuthValidators.isValidIdentifierValue(' TEST123 '), true)
})
await it('should return false for non-string input', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- testing invalid type input
- expect(AuthValidators.isValidIdentifierValue(123 as any)).toBe(false)
+ assert.strictEqual(AuthValidators.isValidIdentifierValue(123 as any), false)
})
})
await describe('sanitizeIdTag', async () => {
await it('should trim whitespace', () => {
- expect(AuthValidators.sanitizeIdTag(' TEST123 ')).toBe('TEST123')
+ assert.strictEqual(AuthValidators.sanitizeIdTag(' TEST123 '), 'TEST123')
})
await it('should truncate to 20 characters', () => {
const longIdTag = 'VERY_LONG_IDENTIFIER_VALUE_123456789'
- expect(AuthValidators.sanitizeIdTag(longIdTag)).toBe('VERY_LONG_IDENTIFIER')
- expect(AuthValidators.sanitizeIdTag(longIdTag).length).toBe(20)
+ assert.strictEqual(AuthValidators.sanitizeIdTag(longIdTag), 'VERY_LONG_IDENTIFIER')
+ assert.strictEqual(AuthValidators.sanitizeIdTag(longIdTag).length, 20)
})
await it('should not truncate short identifiers', () => {
- expect(AuthValidators.sanitizeIdTag('SHORT')).toBe('SHORT')
+ assert.strictEqual(AuthValidators.sanitizeIdTag('SHORT'), 'SHORT')
})
await it('should return empty string for non-string input', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing invalid type input
- expect(AuthValidators.sanitizeIdTag(123 as any)).toBe('')
+ assert.strictEqual(AuthValidators.sanitizeIdTag(123 as any), '')
})
await it('should handle empty string', () => {
- expect(AuthValidators.sanitizeIdTag('')).toBe('')
+ assert.strictEqual(AuthValidators.sanitizeIdTag(''), '')
})
})
await describe('sanitizeIdToken', async () => {
await it('should trim whitespace', () => {
- expect(AuthValidators.sanitizeIdToken(' TOKEN123 ')).toBe('TOKEN123')
+ assert.strictEqual(AuthValidators.sanitizeIdToken(' TOKEN123 '), 'TOKEN123')
})
await it('should truncate to 36 characters', () => {
const longIdToken = 'VERY_LONG_IDENTIFIER_VALUE_1234567890123456789'
- expect(AuthValidators.sanitizeIdToken(longIdToken)).toBe(
+ assert.strictEqual(
+ AuthValidators.sanitizeIdToken(longIdToken),
'VERY_LONG_IDENTIFIER_VALUE_123456789'
)
- expect(AuthValidators.sanitizeIdToken(longIdToken).length).toBe(36)
+ assert.strictEqual(AuthValidators.sanitizeIdToken(longIdToken).length, 36)
})
await it('should not truncate short identifiers', () => {
- expect(AuthValidators.sanitizeIdToken('SHORT_TOKEN')).toBe('SHORT_TOKEN')
+ assert.strictEqual(AuthValidators.sanitizeIdToken('SHORT_TOKEN'), 'SHORT_TOKEN')
})
await it('should return empty string for non-string input', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing invalid type input
- expect(AuthValidators.sanitizeIdToken(123 as any)).toBe('')
+ assert.strictEqual(AuthValidators.sanitizeIdToken(123 as any), '')
})
await it('should handle empty string', () => {
- expect(AuthValidators.sanitizeIdToken('')).toBe('')
+ assert.strictEqual(AuthValidators.sanitizeIdToken(''), '')
})
})
offlineAuthorizationEnabled: false,
}
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(true)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), true)
})
await it('should return false for null configuration', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing null input
- expect(AuthValidators.validateAuthConfiguration(null as any)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(null as any), false)
})
await it('should return false for undefined configuration', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing undefined input
- expect(AuthValidators.validateAuthConfiguration(undefined as any)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(undefined as any), false)
})
await it('should return false for missing required boolean fields', () => {
// certificateAuthEnabled missing
} as Partial<AuthConfiguration>
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false)
})
await it('should return false for non-positive authorization timeout', () => {
offlineAuthorizationEnabled: false,
}
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false)
})
await it('should return false for negative cache lifetime', () => {
offlineAuthorizationEnabled: false,
}
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false)
})
await it('should return false for non-integer max cache entries', () => {
offlineAuthorizationEnabled: false,
}
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(false)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false)
})
await it('should return true for valid configuration with optional fields', () => {
offlineAuthorizationEnabled: false,
}
- expect(AuthValidators.validateAuthConfiguration(config)).toBe(true)
+ assert.strictEqual(AuthValidators.validateAuthConfiguration(config), true)
})
})
await describe('validateIdentifier', async () => {
await it('should return false for undefined identifier', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing undefined input
- expect(AuthValidators.validateIdentifier(undefined as any)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(undefined as any), false)
})
await it('should return false for null identifier', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- testing null input
- expect(AuthValidators.validateIdentifier(null as any)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(null as any), false)
})
await it('should return false for empty value', () => {
value: '',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), false)
})
await it('should return false for ID_TAG exceeding 20 characters', () => {
value: 'VERY_LONG_IDENTIFIER_VALUE_123456789',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), false)
})
await it('should return true for valid ID_TAG within 20 characters', () => {
value: 'VALID_ID_TAG',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for OCPP 2.0 LOCAL type within 36 characters', () => {
value: 'LOCAL_TOKEN_123',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return false for OCPP 2.0 type exceeding 36 characters', () => {
value: 'VERY_LONG_CENTRAL_IDENTIFIER_VALUE_1234567890123456789',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), false)
})
await it('should return true for CENTRAL type within 36 characters', () => {
value: 'CENTRAL_TOKEN',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for E_MAID type', () => {
value: 'DE-ABC-123456',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for ISO14443 type', () => {
value: '04A2B3C4D5E6F7',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for KEY_CODE type', () => {
value: '1234',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for MAC_ADDRESS type', () => {
value: '00:11:22:33:44:55',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return true for NO_AUTHORIZATION type', () => {
value: 'NO_AUTH',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(true)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), true)
})
await it('should return false for unsupported type', () => {
value: 'VALUE',
}
- expect(AuthValidators.validateIdentifier(identifier)).toBe(false)
+ assert.strictEqual(AuthValidators.validateIdentifier(identifier), false)
})
})
})
* @description Unit tests for authentication configuration validation
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.doesNotThrow(() => {
AuthConfigValidator.validate(config)
- }).not.toThrow()
+ })
})
await it('should reject negative authorizationCacheLifetime', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject zero authorizationCacheLifetime', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject non-integer authorizationCacheLifetime', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject negative maxCacheEntries', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject zero maxCacheEntries', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject non-integer maxCacheEntries', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject negative authorizationTimeout', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject zero authorizationTimeout', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should reject non-integer authorizationTimeout', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.throws(() => {
AuthConfigValidator.validate(config)
- }).toThrow(AuthenticationError)
+ }, AuthenticationError)
})
await it('should accept configuration with cache disabled', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.doesNotThrow(() => {
AuthConfigValidator.validate(config)
- }).not.toThrow()
+ })
})
await it('should accept minimal valid values', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.doesNotThrow(() => {
AuthConfigValidator.validate(config)
- }).not.toThrow()
+ })
})
await it('should accept large valid values', () => {
unknownIdAuthorization: AuthorizationStatus.INVALID,
}
- expect(() => {
+ assert.doesNotThrow(() => {
AuthConfigValidator.validate(config)
- }).not.toThrow()
+ })
})
})
})
* @description Unit tests for HTTP-based UI server and response handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import { gunzipSync } from 'node:zlib'
const res = new MockServerResponse()
server.addResponseHandler(TEST_UUID, res)
- expect(server.hasResponseHandler(TEST_UUID)).toBe(true)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), true)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
- expect(res.ended).toBe(true)
- expect(res.statusCode).toBe(200)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
+ assert.strictEqual(res.ended, true)
+ assert.strictEqual(res.statusCode, 200)
})
await it('should log error when response handler not found', () => {
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should set status code 400 for failure responses', () => {
server.addResponseHandler(TEST_UUID, res)
server.sendResponse([TEST_UUID, { status: ResponseStatus.FAILURE }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
- expect(res.ended).toBe(true)
- expect(res.statusCode).toBe(400)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
+ assert.strictEqual(res.ended, true)
+ assert.strictEqual(res.statusCode, 400)
})
await it('should handle send errors gracefully without throwing', () => {
server.addResponseHandler(TEST_UUID, res)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should set application/json Content-Type header', () => {
server.addResponseHandler(TEST_UUID, res)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(res.headers['Content-Type']).toBe('application/json')
+ assert.strictEqual(res.headers['Content-Type'], 'application/json')
})
await it('should clean up response handlers after each response', () => {
server.addResponseHandler('uuid-1' as UUIDv4, res1)
server.addResponseHandler('uuid-2' as UUIDv4, res2)
- expect(server.getResponseHandlersSize()).toBe(2)
+ assert.strictEqual(server.getResponseHandlersSize(), 2)
server.sendResponse(['uuid-1' as UUIDv4, { status: ResponseStatus.SUCCESS }])
- expect(server.getResponseHandlersSize()).toBe(1)
+ assert.strictEqual(server.getResponseHandlersSize(), 1)
server.sendResponse(['uuid-2' as UUIDv4, { status: ResponseStatus.SUCCESS }])
- expect(server.getResponseHandlersSize()).toBe(0)
+ assert.strictEqual(server.getResponseHandlersSize(), 0)
})
await it('should clear all handlers on server stop', () => {
const res = new MockServerResponse()
server.addResponseHandler(TEST_UUID, res)
- expect(server.getResponseHandlersSize()).toBe(1)
+ assert.strictEqual(server.getResponseHandlersSize(), 1)
server.stop()
- expect(server.getResponseHandlersSize()).toBe(0)
+ assert.strictEqual(server.getResponseHandlersSize(), 0)
})
await it('should serialize response payload to JSON correctly', () => {
server.addResponseHandler(TEST_UUID, res)
server.sendResponse([TEST_UUID, payload])
- expect(res.body).toBeDefined()
+ assert.notStrictEqual(res.body, undefined)
const parsedBody = JSON.parse(res.body ?? '{}') as Record<string, unknown>
- expect(parsedBody.status).toBe('success')
- expect(parsedBody.hashIdsSucceeded).toStrictEqual(['station-1', 'station-2'])
+ assert.strictEqual(parsedBody.status, 'success')
+ assert.deepStrictEqual(parsedBody.hashIdsSucceeded, ['station-1', 'station-2'])
})
await it('should include error details in failure response', () => {
server.addResponseHandler(TEST_UUID, res)
server.sendResponse([TEST_UUID, payload])
- expect(res.body).toBeDefined()
+ assert.notStrictEqual(res.body, undefined)
const parsedBody = JSON.parse(res.body ?? '{}') as Record<string, unknown>
- expect(parsedBody.status).toBe('failure')
- expect(parsedBody.errorMessage).toBe('Test error')
- expect(parsedBody.hashIdsFailed).toStrictEqual(['station-1'])
+ assert.strictEqual(parsedBody.status, 'failure')
+ assert.strictEqual(parsedBody.errorMessage, 'Test error')
+ assert.deepStrictEqual(parsedBody.hashIdsFailed, ['station-1'])
})
await it('should create server with valid HTTP configuration', () => {
- expect(server).toBeDefined()
+ assert.notStrictEqual(server, undefined)
})
await it('should create server with custom host and port', () => {
})
)
- expect(serverCustom).toBeDefined()
+ assert.notStrictEqual(serverCustom, undefined)
})
await describe('Gzip compression', async () => {
gzipServer.setAcceptsGzip(TEST_UUID, false)
gzipServer.sendResponse([TEST_UUID, createLargePayload()])
- expect(res.headers['Content-Encoding']).toBeUndefined()
- expect(res.headers['Content-Type']).toBe('application/json')
+ assert.strictEqual(res.headers['Content-Encoding'], undefined)
+ assert.strictEqual(res.headers['Content-Type'], 'application/json')
})
await it('should skip compression for small response payloads', () => {
gzipServer.setAcceptsGzip(TEST_UUID, true)
gzipServer.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(res.headers['Content-Encoding']).toBeUndefined()
- expect(res.headers['Content-Type']).toBe('application/json')
+ assert.strictEqual(res.headers['Content-Encoding'], undefined)
+ assert.strictEqual(res.headers['Content-Type'], 'application/json')
})
await it('should skip compression when payload below threshold', () => {
gzipServer.setAcceptsGzip(TEST_UUID, true)
gzipServer.sendResponse([TEST_UUID, smallPayload])
- expect(res.headers['Content-Encoding']).toBeUndefined()
+ assert.strictEqual(res.headers['Content-Encoding'], undefined)
})
await it('should set gzip Content-Encoding header for large responses', async () => {
await waitForStreamFlush(GZIP_STREAM_FLUSH_DELAY_MS)
- expect(res.headers['Content-Encoding']).toBe('gzip')
- expect(res.headers['Content-Type']).toBe('application/json')
- expect(res.headers.Vary).toBe('Accept-Encoding')
+ assert.strictEqual(res.headers['Content-Encoding'], 'gzip')
+ assert.strictEqual(res.headers['Content-Type'], 'application/json')
+ assert.strictEqual(res.headers.Vary, 'Accept-Encoding')
})
await it('should decompress gzip response to original payload', async () => {
await waitForStreamFlush(GZIP_STREAM_FLUSH_DELAY_MS)
- expect(res.bodyBuffer).toBeDefined()
+ assert.notStrictEqual(res.bodyBuffer, undefined)
if (res.bodyBuffer == null) {
throw new Error('Expected bodyBuffer to be defined')
}
const decompressed = gunzipSync(res.bodyBuffer).toString('utf8')
const parsedBody = JSON.parse(decompressed) as Record<string, unknown>
- expect(parsedBody.status).toBe('success')
- expect(parsedBody.data).toBe(payload.data)
+ assert.strictEqual(parsedBody.status, 'success')
+ assert.strictEqual(parsedBody.data, payload.data)
})
await it('should skip compression when acceptsGzip context missing', () => {
gzipServer.addResponseHandler(TEST_UUID, res)
gzipServer.sendResponse([TEST_UUID, createLargePayload()])
- expect(res.headers['Content-Encoding']).toBeUndefined()
- expect(res.headers['Content-Type']).toBe('application/json')
+ assert.strictEqual(res.headers['Content-Encoding'], undefined)
+ assert.strictEqual(res.headers['Content-Type'], 'application/json')
})
await it('should cleanup acceptsGzip context after response sent', async () => {
gzipServer.addResponseHandler(TEST_UUID, res)
gzipServer.setAcceptsGzip(TEST_UUID, true)
- expect(gzipServer.getAcceptsGzip().has(TEST_UUID)).toBe(true)
+ assert.strictEqual(gzipServer.getAcceptsGzip().has(TEST_UUID), true)
gzipServer.sendResponse([TEST_UUID, createLargePayload()])
await waitForStreamFlush(GZIP_STREAM_FLUSH_DELAY_MS)
- expect(gzipServer.getAcceptsGzip().has(TEST_UUID)).toBe(false)
+ assert.strictEqual(gzipServer.getAcceptsGzip().has(TEST_UUID), false)
})
})
})
* @description Unit tests for UI server security utilities (rate limiting, validation)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
})
await describe('IsValidCredential', async () => {
await it('should return true for matching credentials', () => {
- expect(isValidCredential('myPassword123', 'myPassword123')).toBe(true)
+ assert.strictEqual(isValidCredential('myPassword123', 'myPassword123'), true)
})
await it('should return false for non-matching credentials', () => {
- expect(isValidCredential('password1', 'password2')).toBe(false)
+ assert.strictEqual(isValidCredential('password1', 'password2'), false)
})
await it('should return true for empty string credentials', () => {
- expect(isValidCredential('', '')).toBe(true)
+ assert.strictEqual(isValidCredential('', ''), true)
})
await it('should return false for different length credentials', () => {
// cspell:disable-next-line
- expect(isValidCredential('short', 'verylongpassword')).toBe(false)
+ assert.strictEqual(isValidCredential('short', 'verylongpassword'), false)
})
})
await it('should return true when bytes under limit', () => {
limiter = createBodySizeLimiter(1000)
- expect(limiter(500)).toBe(true)
+ assert.strictEqual(limiter(500), true)
})
await it('should return false when accumulated bytes exceed limit', () => {
limiter = createBodySizeLimiter(1000)
limiter(600)
- expect(limiter(500)).toBe(false)
+ assert.strictEqual(limiter(500), false)
})
await it('should return true at exact limit boundary', () => {
limiter = createBodySizeLimiter(1000)
- expect(limiter(1000)).toBe(true)
+ assert.strictEqual(limiter(1000), true)
})
})
limiter = createRateLimiter(5, 1000)
for (let i = 0; i < 5; i++) {
- expect(limiter('192.168.1.1')).toBe(true)
+ assert.strictEqual(limiter('192.168.1.1'), true)
}
})
limiter('192.168.1.1')
limiter('192.168.1.1')
limiter('192.168.1.1')
- expect(limiter('192.168.1.1')).toBe(false)
+ assert.strictEqual(limiter('192.168.1.1'), false)
})
await it('should reset window after time expires', async t => {
limiter = createRateLimiter(2, 100)
limiter('10.0.0.1')
limiter('10.0.0.1')
- expect(limiter('10.0.0.1')).toBe(false)
+ assert.strictEqual(limiter('10.0.0.1'), false)
t.mock.timers.tick(101)
- expect(limiter('10.0.0.1')).toBe(true)
+ assert.strictEqual(limiter('10.0.0.1'), true)
})
})
await it('should reject new IPs when at max tracked capacity', () => {
limiter = createRateLimiter(10, 60000, 3)
- expect(limiter('192.168.1.1')).toBe(true)
- expect(limiter('192.168.1.2')).toBe(true)
- expect(limiter('192.168.1.3')).toBe(true)
- expect(limiter('192.168.1.4')).toBe(false)
+ assert.strictEqual(limiter('192.168.1.1'), true)
+ assert.strictEqual(limiter('192.168.1.2'), true)
+ assert.strictEqual(limiter('192.168.1.3'), true)
+ assert.strictEqual(limiter('192.168.1.4'), false)
})
await it('should allow existing IPs when at max capacity', () => {
limiter = createRateLimiter(10, 60000, 2)
- expect(limiter('192.168.1.1')).toBe(true)
- expect(limiter('192.168.1.2')).toBe(true)
- expect(limiter('192.168.1.1')).toBe(true)
- expect(limiter('192.168.1.2')).toBe(true)
+ assert.strictEqual(limiter('192.168.1.1'), true)
+ assert.strictEqual(limiter('192.168.1.2'), true)
+ assert.strictEqual(limiter('192.168.1.1'), true)
+ assert.strictEqual(limiter('192.168.1.2'), true)
})
await it('should cleanup expired entries when at capacity', async t => {
await withMockTimers(t, ['Date', 'setTimeout'], () => {
limiter = createRateLimiter(10, 50, 2)
- expect(limiter('192.168.1.1')).toBe(true)
- expect(limiter('192.168.1.2')).toBe(true)
+ assert.strictEqual(limiter('192.168.1.1'), true)
+ assert.strictEqual(limiter('192.168.1.2'), true)
t.mock.timers.tick(51)
- expect(limiter('192.168.1.3')).toBe(true)
+ assert.strictEqual(limiter('192.168.1.3'), true)
})
})
})
await describe('IsValidNumberOfStations', async () => {
await it('should return true for valid number within limit', () => {
- expect(isValidNumberOfStations(50, DEFAULT_MAX_STATIONS)).toBe(true)
+ assert.strictEqual(isValidNumberOfStations(50, DEFAULT_MAX_STATIONS), true)
})
await it('should return false when exceeding max stations', () => {
- expect(isValidNumberOfStations(150, DEFAULT_MAX_STATIONS)).toBe(false)
+ assert.strictEqual(isValidNumberOfStations(150, DEFAULT_MAX_STATIONS), false)
})
await it('should return false for zero stations', () => {
- expect(isValidNumberOfStations(0, DEFAULT_MAX_STATIONS)).toBe(false)
+ assert.strictEqual(isValidNumberOfStations(0, DEFAULT_MAX_STATIONS), false)
})
await it('should return false for negative stations', () => {
- expect(isValidNumberOfStations(-5, DEFAULT_MAX_STATIONS)).toBe(false)
+ assert.strictEqual(isValidNumberOfStations(-5, DEFAULT_MAX_STATIONS), false)
})
await it('should return true at exact max stations boundary', () => {
- expect(isValidNumberOfStations(DEFAULT_MAX_STATIONS, DEFAULT_MAX_STATIONS)).toBe(true)
+ assert.strictEqual(isValidNumberOfStations(DEFAULT_MAX_STATIONS, DEFAULT_MAX_STATIONS), true)
})
})
})
* @description Unit tests for WebSocket-based UI server and response handling
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { UUIDv4 } from '../../../src/types/index.js'
const ws = createMockUIWebSocket()
server.addResponseHandler(TEST_UUID, ws)
- expect(server.hasResponseHandler(TEST_UUID)).toBe(true)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), true)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
- expect(ws.sentMessages.length).toBe(1)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
+ assert.strictEqual(ws.sentMessages.length, 1)
})
await it('should log error when response handler not found', () => {
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should delete handler when WebSocket not in OPEN state', () => {
server.addResponseHandler(TEST_UUID, ws)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
- expect(ws.sentMessages.length).toBe(0)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
+ assert.strictEqual(ws.sentMessages.length, 0)
})
await it('should handle send errors gracefully without throwing', () => {
server.addResponseHandler(TEST_UUID, ws)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should preserve broadcast handler until explicit deletion (issue #1642)', async () => {
return undefined
})
- expect(server.hasResponseHandler(TEST_UUID)).toBe(true)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), true)
server.sendResponse([TEST_UUID, { status: ResponseStatus.SUCCESS }])
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should delete non-broadcast handler immediately after response', async () => {
server.sendResponse(response)
}
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should preserve handler when service throws error', async () => {
// Expected error
}
- expect(server.getResponseHandlersSize()).toBe(1)
+ assert.strictEqual(server.getResponseHandlersSize(), 1)
})
await it('should clean up response handlers after each response', () => {
server.addResponseHandler('uuid-1' as UUIDv4, ws1)
server.addResponseHandler('uuid-2' as UUIDv4, ws2)
- expect(server.getResponseHandlersSize()).toBe(2)
+ assert.strictEqual(server.getResponseHandlersSize(), 2)
server.sendResponse(['uuid-1' as UUIDv4, { status: ResponseStatus.SUCCESS }])
- expect(server.getResponseHandlersSize()).toBe(1)
+ assert.strictEqual(server.getResponseHandlersSize(), 1)
server.sendResponse(['uuid-2' as UUIDv4, { status: ResponseStatus.SUCCESS }])
- expect(server.getResponseHandlersSize()).toBe(0)
+ assert.strictEqual(server.getResponseHandlersSize(), 0)
})
await it('should clear all handlers on server stop', () => {
const ws = createMockUIWebSocket()
server.addResponseHandler(TEST_UUID, ws)
- expect(server.getResponseHandlersSize()).toBe(1)
+ assert.strictEqual(server.getResponseHandlersSize(), 1)
server.stop()
- expect(server.getResponseHandlersSize()).toBe(0)
+ assert.strictEqual(server.getResponseHandlersSize(), 0)
})
await it('should create server with valid WebSocket configuration', () => {
const config = createMockUIServerConfiguration()
const server = new TestableUIWebSocketServer(config)
- expect(server).toBeDefined()
+ assert.notStrictEqual(server, undefined)
})
await it('should create server with custom host and port', () => {
})
const server = new TestableUIWebSocketServer(config)
- expect(server).toBeDefined()
+ assert.notStrictEqual(server, undefined)
})
})
* @description Unit tests for abstract UI service base class functionality
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { ProcedureName, ProtocolVersion, ResponseStatus } from '../../../../src/types/index.js'
server.testRegisterProtocolVersionUIService(ProtocolVersion['0.0.1'])
const service = server.getUIService(ProtocolVersion['0.0.1'])
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
service.sendResponse(TEST_UUID, { status: ResponseStatus.SUCCESS })
service.stop()
}
- expect(server.hasResponseHandler(TEST_UUID)).toBe(false)
+ assert.strictEqual(server.hasResponseHandler(TEST_UUID), false)
})
await it('should return charging stations list for LIST_CHARGING_STATIONS', async () => {
const request = createProtocolRequest(TEST_UUID, ProcedureName.LIST_CHARGING_STATIONS, {})
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
const response = await service.requestHandler(request)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
if (response != null) {
- expect(response[0]).toBe(TEST_UUID)
- expect(response[1].status).toBe(ResponseStatus.SUCCESS)
- expect(response[1].chargingStations).toBeDefined()
- expect(Array.isArray(response[1].chargingStations)).toBe(true)
+ assert.strictEqual(response[0], TEST_UUID)
+ assert.strictEqual(response[1].status, ResponseStatus.SUCCESS)
+ assert.notStrictEqual(response[1].chargingStations, undefined)
+ assert.ok(Array.isArray(response[1].chargingStations))
}
service.stop()
}
const request = createProtocolRequest(TEST_UUID, ProcedureName.LIST_TEMPLATES, {})
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
const response = await service.requestHandler(request)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
if (response != null) {
- expect(response[0]).toBe(TEST_UUID)
- expect(response[1].status).toBe(ResponseStatus.SUCCESS)
- expect(response[1].templates).toBeDefined()
- expect(Array.isArray(response[1].templates)).toBe(true)
- expect(response[1].templates).toContain('template1.json')
- expect(response[1].templates).toContain('template2.json')
+ assert.strictEqual(response[0], TEST_UUID)
+ assert.strictEqual(response[1].status, ResponseStatus.SUCCESS)
+ const templates = response[1].templates
+ if (!Array.isArray(templates)) {
+ assert.fail('Expected templates to be an array')
+ }
+ assert.ok(templates.includes('template1.json'))
+ assert.ok(templates.includes('template2.json'))
}
service.stop()
}
const request = createProtocolRequest(TEST_UUID, 'UnknownProcedure' as ProcedureName, {})
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
const response = await service.requestHandler(request)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
if (response != null) {
- expect(response[0]).toBe(TEST_UUID)
- expect(response[1].status).toBe(ResponseStatus.FAILURE)
- expect(response[1].errorMessage).toBeDefined()
+ assert.strictEqual(response[0], TEST_UUID)
+ assert.strictEqual(response[1].status, ResponseStatus.FAILURE)
+ assert.notStrictEqual(response[1].errorMessage, undefined)
}
service.stop()
}
const service = server.getUIService(ProtocolVersion['0.0.1'])
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
- expect(service.getBroadcastChannelExpectedResponses(TEST_UUID)).toBe(0)
+ assert.strictEqual(service.getBroadcastChannelExpectedResponses(TEST_UUID), 0)
service.stop()
}
})
const service = server.getUIService(ProtocolVersion['0.0.1'])
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
service.stop()
- expect(service.getBroadcastChannelExpectedResponses(TEST_UUID)).toBe(0)
+ assert.strictEqual(service.getBroadcastChannelExpectedResponses(TEST_UUID), 0)
}
})
const request = createProtocolRequest(TEST_UUID, ProcedureName.ADD_CHARGING_STATIONS, {})
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
const response = await service.requestHandler(request)
- expect(response).toBeDefined()
+ assert.notStrictEqual(response, undefined)
if (response != null) {
- expect(response[0]).toBe(TEST_UUID)
- expect(response[1].status).toBe(ResponseStatus.FAILURE)
+ assert.strictEqual(response[0], TEST_UUID)
+ assert.strictEqual(response[1].status, ResponseStatus.FAILURE)
}
service.stop()
}
const service = server.getUIService(ProtocolVersion['0.0.1'])
- expect(service).toBeDefined()
+ assert.notStrictEqual(service, undefined)
if (service != null) {
service.stop()
}
const uiServicesMap = Reflect.get(server, 'uiServices') as Map<unknown, unknown>
- expect(uiServicesMap.size).toBe(1)
+ assert.strictEqual(uiServicesMap.size, 1)
const service = server.getUIService(ProtocolVersion['0.0.1'])
if (service != null) {
* @file Tests for BaseError
* @description Unit tests for base error class functionality
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { BaseError } from '../../src/exception/BaseError.js'
})
await it('should create instance with default values', () => {
const baseError = new BaseError()
- expect(baseError).toBeInstanceOf(BaseError)
- expect(baseError.name).toBe('BaseError')
- expect(baseError.message).toBe('')
- expect(typeof baseError.stack === 'string').toBe(true)
- expect(baseError.stack).not.toBe('')
- expect(baseError.cause).toBeUndefined()
- expect(baseError.date).toBeInstanceOf(Date)
+ assert.ok(baseError instanceof BaseError)
+ assert.strictEqual(baseError.name, 'BaseError')
+ assert.strictEqual(baseError.message, '')
+ assert.ok(typeof baseError.stack === 'string')
+ assert.notStrictEqual(baseError.stack, '')
+ assert.strictEqual(baseError.cause, undefined)
+ assert.ok(baseError.date instanceof Date)
})
await it('should create instance with custom message', () => {
const baseError = new BaseError('Test message')
- expect(baseError).toBeInstanceOf(BaseError)
- expect(baseError.message).toBe('Test message')
+ assert.ok(baseError instanceof BaseError)
+ assert.strictEqual(baseError.message, 'Test message')
})
await it('should be an instance of Error', () => {
const baseError = new BaseError()
- expect(baseError instanceof Error).toBe(true)
+ assert.ok(baseError instanceof Error)
})
await it('should contain stack trace with class name', () => {
const baseError = new BaseError()
- expect(baseError.stack?.includes('BaseError')).toBe(true)
+ assert.ok(baseError.stack?.includes('BaseError'))
})
await it('should set date close to current time', () => {
const beforeNow = Date.now()
const baseError = new BaseError()
const afterNow = Date.now()
- expect(baseError.date.getTime() >= beforeNow - 1000).toBe(true)
- expect(baseError.date.getTime() <= afterNow + 1000).toBe(true)
+ assert.ok(baseError.date.getTime() >= beforeNow - 1000)
+ assert.ok(baseError.date.getTime() <= afterNow + 1000)
})
await it('should set name to subclass name when extended', () => {
class TestSubError extends BaseError {}
const testSubError = new TestSubError()
- expect(testSubError.name).toBe('TestSubError')
- expect(testSubError).toBeInstanceOf(BaseError)
- expect(testSubError).toBeInstanceOf(Error)
+ assert.strictEqual(testSubError.name, 'TestSubError')
+ assert.ok(testSubError instanceof BaseError)
+ assert.ok(testSubError instanceof Error)
})
})
* @file Tests for OCPPError
* @description Unit tests for OCPP-specific error class
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { BaseError } from '../../src/exception/BaseError.js'
await it('should create instance with error code and default values', () => {
const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, '')
- expect(ocppError).toBeInstanceOf(OCPPError)
- expect(ocppError.name).toBe('OCPPError')
- expect(ocppError.message).toBe('')
- expect(ocppError.code).toBe(ErrorType.GENERIC_ERROR)
- expect(ocppError.command).toBe(Constants.UNKNOWN_OCPP_COMMAND)
- expect(ocppError.details).toBeUndefined()
- expect(typeof ocppError.stack === 'string').toBe(true)
- expect(ocppError.stack).not.toBe('')
- expect(ocppError.cause).toBeUndefined()
- expect(ocppError.date).toBeInstanceOf(Date)
+ assert.ok(ocppError instanceof OCPPError)
+ assert.strictEqual(ocppError.name, 'OCPPError')
+ assert.strictEqual(ocppError.message, '')
+ assert.strictEqual(ocppError.code, ErrorType.GENERIC_ERROR)
+ assert.strictEqual(ocppError.command, Constants.UNKNOWN_OCPP_COMMAND)
+ assert.strictEqual(ocppError.details, undefined)
+ assert.ok(typeof ocppError.stack === 'string')
+ assert.notStrictEqual(ocppError.stack, '')
+ assert.strictEqual(ocppError.cause, undefined)
+ assert.ok(ocppError.date instanceof Date)
})
await it('should be an instance of BaseError and Error', () => {
const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, 'test')
- expect(ocppError).toBeInstanceOf(BaseError)
- expect(ocppError).toBeInstanceOf(Error)
+ assert.ok(ocppError instanceof BaseError)
+ assert.ok(ocppError instanceof Error)
})
await it('should create instance with custom command', () => {
const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, 'test', RequestCommand.HEARTBEAT)
- expect(ocppError.command).toBe(RequestCommand.HEARTBEAT)
+ assert.strictEqual(ocppError.command, RequestCommand.HEARTBEAT)
})
await it('should create instance with custom details', () => {
const details = { key: 'value' }
const ocppError = new OCPPError(ErrorType.GENERIC_ERROR, 'test', undefined, details)
- expect(ocppError.details).toStrictEqual({ key: 'value' })
+ assert.deepStrictEqual(ocppError.details, { key: 'value' })
})
await it('should handle different error types', () => {
const ocppError = new OCPPError(ErrorType.NOT_IMPLEMENTED, 'test')
- expect(ocppError.code).toBe(ErrorType.NOT_IMPLEMENTED)
+ assert.strictEqual(ocppError.code, ErrorType.NOT_IMPLEMENTED)
})
})
* @description Unit and integration tests for MikroORM-based performance storage
*/
import { MikroORM } from '@mikro-orm/better-sqlite'
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { existsSync, rmSync } from 'node:fs'
import { afterEach, beforeEach, describe, it } from 'node:test'
const { mockOrm } = buildMockOrm()
storage.setOrm(mockOrm)
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
- expect([...storage.getPerformanceStatistics()].length).toBe(1)
+ assert.strictEqual([...storage.getPerformanceStatistics()].length, 1)
// Act
await storage.close()
// Assert
- expect([...storage.getPerformanceStatistics()].length).toBe(0)
+ assert.strictEqual([...storage.getPerformanceStatistics()].length, 0)
})
await it('should call orm.close when ORM is initialized', async t => {
await storage.close()
// Assert
- expect(closeMock.mock.calls.length).toBe(1)
+ assert.strictEqual(closeMock.mock.calls.length, 1)
})
await it('should delete orm reference after closing', async () => {
await storage.close()
// Assert
- expect(storage.getOrm()).toBeUndefined()
+ assert.strictEqual(storage.getOrm(), undefined)
})
await it('should not fail when closing without prior open', async () => {
await storage.close()
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
await storage.storePerformanceStatistics(stats)
// Assert
- expect(upsertCalls.length).toBe(1)
+ assert.strictEqual(upsertCalls.length, 1)
const call = upsertCalls[0] as { data: Record<string, unknown>; entity: unknown }
- expect(call.entity).toBe(PerformanceRecord)
+ assert.strictEqual(call.entity, PerformanceRecord)
const statsArray = call.data.statisticsData as Record<string, unknown>[]
- expect(Array.isArray(statsArray)).toBe(true)
- expect(statsArray.length).toBe(1)
- expect(statsArray[0].name).toBe('Heartbeat')
- expect(statsArray[0].requestCount).toBe(100)
- expect(statsArray[0].avgTimeMeasurement).toBe(10.5)
+ assert.ok(Array.isArray(statsArray))
+ assert.strictEqual(statsArray.length, 1)
+ assert.strictEqual(statsArray[0].name, 'Heartbeat')
+ assert.strictEqual(statsArray[0].requestCount, 100)
+ assert.strictEqual(statsArray[0].avgTimeMeasurement, 10.5)
})
await it('should spread measurementTimeSeries into plain array', async () => {
const call = upsertCalls[0] as { data: Record<string, unknown> }
const statsArray = call.data.statisticsData as Record<string, unknown>[]
const timeSeries = statsArray[0].measurementTimeSeries as unknown[]
- expect(Array.isArray(timeSeries)).toBe(true)
- expect(timeSeries.length).toBe(2)
+ assert.ok(Array.isArray(timeSeries))
+ assert.strictEqual(timeSeries.length, 2)
})
await it('should call upsert with PerformanceRecord entity class', async () => {
// Assert
const call = upsertCalls[0] as { entity: unknown }
- expect(call.entity).toBe(PerformanceRecord)
+ assert.strictEqual(call.entity, PerformanceRecord)
})
await it('should cache statistics in memory after store', async () => {
// Assert
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(1)
- expect(cached[0].id).toBe('station-1')
+ assert.strictEqual(cached.length, 1)
+ assert.strictEqual(cached[0].id, 'station-1')
})
await it('should handle multiple distinct records', async () => {
await storage.storePerformanceStatistics(buildTestStatistics('station-3'))
// Assert
- expect(upsertCalls.length).toBe(3)
+ assert.strictEqual(upsertCalls.length, 3)
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(3)
+ assert.strictEqual(cached.length, 3)
})
await it('should handle statisticsData entry without measurementTimeSeries', async () => {
// Assert
const call = upsertCalls[0] as { data: Record<string, unknown> }
const statsArray = call.data.statisticsData as Record<string, unknown>[]
- expect(statsArray.length).toBe(2)
+ assert.strictEqual(statsArray.length, 2)
const statusEntry = statsArray.find(e => e.name === 'StatusNotification')
- expect(statusEntry).toBeDefined()
- expect(statusEntry?.measurementTimeSeries).toBeUndefined()
+ assert.notStrictEqual(statusEntry, undefined)
+ assert.strictEqual(statusEntry?.measurementTimeSeries, undefined)
})
})
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should still cache statistics even when store fails', async () => {
// Assert
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(1)
- expect(cached[0].id).toBe('station-1')
+ assert.strictEqual(cached.length, 1)
+ assert.strictEqual(cached[0].id, 'station-1')
})
await it('should log error when upsert throws', async t => {
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
await it('should open database and create schema', { skip: SKIP_SQLITE }, async () => {
await storage.open()
- expect(existsSync(TEST_DB_PATH)).toBe(true)
+ assert.ok(existsSync(TEST_DB_PATH))
})
await it('should persist record to SQLite database', { skip: SKIP_SQLITE }, async () => {
})
try {
const record = await verifyOrm.em.fork().findOne(PerformanceRecord, { id: 'station-1' })
- expect(record).toBeDefined()
- expect(record?.name).toBe('cs-station-1')
- expect(record?.uri).toBe('ws://localhost:8080')
+ assert.notStrictEqual(record, undefined)
+ assert.strictEqual(record?.name, 'cs-station-1')
+ assert.strictEqual(record.uri, 'ws://localhost:8080')
} finally {
await verifyOrm.close()
}
})
try {
const records = await verifyOrm.em.fork().findAll(PerformanceRecord)
- expect(records.length).toBe(1)
- expect(records[0].name).toBe('updated')
+ assert.strictEqual(records.length, 1)
+ assert.strictEqual(records[0].name, 'updated')
} finally {
await verifyOrm.close()
}
})
try {
const record = await verifyOrm.em.fork().findOne(PerformanceRecord, { id: 'station-1' })
- expect(record).toBeDefined()
- expect(Array.isArray(record?.statisticsData)).toBe(true)
- expect(record?.statisticsData.length).toBe(1)
- const entry = record?.statisticsData[0]
- expect(entry).toBeDefined()
- expect(entry?.name).toBe('Heartbeat')
- expect(entry?.requestCount).toBe(100)
+ if (record == null) {
+ assert.fail('Expected record to be defined')
+ }
+ assert.ok(Array.isArray(record.statisticsData))
+ assert.strictEqual(record.statisticsData.length, 1)
+ const entry = record.statisticsData[0]
+ assert.notStrictEqual(entry, undefined)
+ assert.strictEqual(entry.name, 'Heartbeat')
+ assert.strictEqual(entry.requestCount, 100)
} finally {
await verifyOrm.close()
}
})
try {
const record = await verifyOrm.em.fork().findOne(PerformanceRecord, { id: 'station-1' })
- expect(record).toBeDefined()
- expect(record?.name).toBe('cs-station-1')
+ assert.notStrictEqual(record, undefined)
+ assert.strictEqual(record?.name, 'cs-station-1')
} finally {
await verifyOrm.close()
await freshStorage.close()
})
try {
const records = await verifyOrm.em.fork().findAll(PerformanceRecord)
- expect(records.length).toBe(3)
+ assert.strictEqual(records.length, 3)
const ids = records.map(r => r.id).sort()
- expect(ids).toStrictEqual(['station-1', 'station-2', 'station-3'])
+ assert.deepStrictEqual(ids, ['station-1', 'station-2', 'station-3'])
} finally {
await verifyOrm.close()
}
* @file Tests for MongoDBStorage
* @description Unit tests for MongoDB-based performance storage
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, beforeEach, describe, it } from 'node:test'
import { MongoDBStorage } from '../../../src/performance/storage/MongoDBStorage.js'
await describe('constructor', async () => {
await it('should extract database name from URI path', () => {
const dbName = Reflect.get(storage, 'dbName') as string
- expect(dbName).toBe('e-mobility-test')
+ assert.strictEqual(dbName, 'e-mobility-test')
})
await it('should initialize with opened set to false', () => {
- expect(storage.getOpened()).toBe(false)
+ assert.strictEqual(storage.getOpened(), false)
})
})
storage.setClient(mockClient)
storage.setOpened(true)
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
- expect([...storage.getPerformanceStatistics()].length).toBe(1)
+ assert.strictEqual([...storage.getPerformanceStatistics()].length, 1)
// Act
await storage.close()
// Assert
- expect([...storage.getPerformanceStatistics()].length).toBe(0)
+ assert.strictEqual([...storage.getPerformanceStatistics()].length, 0)
})
await it('should call client.close when opened', async t => {
await storage.close()
// Assert
- expect(closeMock.mock.calls.length).toBe(1)
+ assert.strictEqual(closeMock.mock.calls.length, 1)
})
await it('should set opened to false after closing', async () => {
await storage.close()
// Assert
- expect(storage.getOpened()).toBe(false)
+ assert.strictEqual(storage.getOpened(), false)
})
await it('should not call client.close when not opened', async t => {
await storage.close()
// Assert
- expect(closeMock.mock.calls.length).toBe(0)
+ assert.strictEqual(closeMock.mock.calls.length, 0)
})
await it('should log error when client.close throws', async t => {
await storage.close()
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
await storage.open()
// Assert
- expect(connectMock.mock.calls.length).toBe(1)
- expect(storage.getOpened()).toBe(true)
+ assert.strictEqual(connectMock.mock.calls.length, 1)
+ assert.strictEqual(storage.getOpened(), true)
})
await it('should not connect when already opened', async t => {
await storage.open()
// Assert
- expect(connectMock.mock.calls.length).toBe(0)
+ assert.strictEqual(connectMock.mock.calls.length, 0)
})
await it('should log error when connect throws', async t => {
await storage.open()
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
await storage.storePerformanceStatistics(stats)
// Assert
- expect(replaceOneCalls.length).toBe(1)
+ assert.strictEqual(replaceOneCalls.length, 1)
const replacement = replaceOneCalls[0].replacement as Record<string, unknown>
const statsArray = replacement.statisticsData as Record<string, unknown>[]
- expect(Array.isArray(statsArray)).toBe(true)
- expect(statsArray.length).toBe(1)
- expect(statsArray[0].name).toBe('Heartbeat')
- expect(statsArray[0].requestCount).toBe(100)
- expect(statsArray[0].avgTimeMeasurement).toBe(10.5)
+ assert.ok(Array.isArray(statsArray))
+ assert.strictEqual(statsArray.length, 1)
+ assert.strictEqual(statsArray[0].name, 'Heartbeat')
+ assert.strictEqual(statsArray[0].requestCount, 100)
+ assert.strictEqual(statsArray[0].avgTimeMeasurement, 10.5)
})
await it('should spread measurementTimeSeries into plain array', async () => {
const replacement = replaceOneCalls[0].replacement as Record<string, unknown>
const statsArray = replacement.statisticsData as Record<string, unknown>[]
const timeSeries = statsArray[0].measurementTimeSeries as unknown[]
- expect(Array.isArray(timeSeries)).toBe(true)
- expect(timeSeries.length).toBe(2)
+ assert.ok(Array.isArray(timeSeries))
+ assert.strictEqual(timeSeries.length, 2)
})
await it('should call replaceOne with upsert and correct filter', async () => {
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(replaceOneCalls.length).toBe(1)
- expect(replaceOneCalls[0].filter).toStrictEqual({ id: 'station-1' })
- expect(replaceOneCalls[0].options).toStrictEqual({ upsert: true })
+ assert.strictEqual(replaceOneCalls.length, 1)
+ assert.deepStrictEqual(replaceOneCalls[0].filter, { id: 'station-1' })
+ assert.deepStrictEqual(replaceOneCalls[0].options, { upsert: true })
})
await it('should use correct collection name', async () => {
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(collectionCalls.length).toBe(1)
- expect(collectionCalls[0].collectionName).toBe(Constants.PERFORMANCE_RECORDS_TABLE)
+ assert.strictEqual(collectionCalls.length, 1)
+ assert.strictEqual(collectionCalls[0].collectionName, Constants.PERFORMANCE_RECORDS_TABLE)
})
await it('should cache statistics in memory after store', async () => {
// Assert
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(1)
- expect(cached[0].id).toBe('station-1')
+ assert.strictEqual(cached.length, 1)
+ assert.strictEqual(cached[0].id, 'station-1')
})
await it('should handle multiple distinct records', async () => {
await storage.storePerformanceStatistics(buildTestStatistics('station-3'))
// Assert
- expect(replaceOneCalls.length).toBe(3)
+ assert.strictEqual(replaceOneCalls.length, 3)
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(3)
+ assert.strictEqual(cached.length, 3)
})
await it('should handle statisticsData entry without measurementTimeSeries', async () => {
// Assert
const replacement = replaceOneCalls[0].replacement as Record<string, unknown>
const statsArray = replacement.statisticsData as Record<string, unknown>[]
- expect(statsArray.length).toBe(2)
+ assert.strictEqual(statsArray.length, 2)
const statusEntry = statsArray.find(e => e.name === 'StatusNotification')
- expect(statusEntry).toBeDefined()
- expect(statusEntry?.measurementTimeSeries).toBeUndefined()
+ assert.notStrictEqual(statusEntry, undefined)
+ assert.strictEqual(statusEntry?.measurementTimeSeries, undefined)
})
})
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should still cache statistics even when store fails', async () => {
// Assert
const cached = [...storage.getPerformanceStatistics()]
- expect(cached.length).toBe(1)
- expect(cached[0].id).toBe('station-1')
+ assert.strictEqual(cached.length, 1)
+ assert.strictEqual(cached[0].id, 'station-1')
})
await it('should log error when replaceOne throws', async t => {
await storage.storePerformanceStatistics(buildTestStatistics('station-1'))
// Assert
- expect(errorMock.mock.calls.length).toBe(1)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
})
* @file Tests for ConfigurationData
* @description Unit tests for configuration data types and enumerations
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import {
})
await it('should define ConfigurationSection enumeration values', () => {
- expect(ConfigurationSection.log).toBe('log')
- expect(ConfigurationSection.performanceStorage).toBe('performanceStorage')
- expect(ConfigurationSection.uiServer).toBe('uiServer')
- expect(ConfigurationSection.worker).toBe('worker')
+ assert.strictEqual(ConfigurationSection.log, 'log')
+ assert.strictEqual(ConfigurationSection.performanceStorage, 'performanceStorage')
+ assert.strictEqual(ConfigurationSection.uiServer, 'uiServer')
+ assert.strictEqual(ConfigurationSection.worker, 'worker')
})
await it('should define SupervisionUrlDistribution enumeration values', () => {
- expect(SupervisionUrlDistribution.CHARGING_STATION_AFFINITY).toBe('charging-station-affinity')
- expect(SupervisionUrlDistribution.RANDOM).toBe('random')
- expect(SupervisionUrlDistribution.ROUND_ROBIN).toBe('round-robin')
+ assert.strictEqual(
+ SupervisionUrlDistribution.CHARGING_STATION_AFFINITY,
+ 'charging-station-affinity'
+ )
+ assert.strictEqual(SupervisionUrlDistribution.RANDOM, 'random')
+ assert.strictEqual(SupervisionUrlDistribution.ROUND_ROBIN, 'round-robin')
})
await it('should define ApplicationProtocolVersion enumeration values', () => {
- expect(ApplicationProtocolVersion.VERSION_11).toBe('1.1')
- expect(ApplicationProtocolVersion.VERSION_20).toBe('2.0')
+ assert.strictEqual(ApplicationProtocolVersion.VERSION_11, '1.1')
+ assert.strictEqual(ApplicationProtocolVersion.VERSION_20, '2.0')
})
})
* @file Tests for AsyncLock
* @description Unit tests for asynchronous lock utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { AsyncLock, AsyncLockType } from '../../src/utils/AsyncLock.js'
promises.push(AsyncLock.runExclusive(AsyncLockType.configuration, fn))
}
await Promise.all(promises)
- expect(executed).toStrictEqual(new Array(runs).fill(0).map((_, i) => ++i))
+ assert.deepStrictEqual(
+ executed,
+ new Array(runs).fill(0).map((_, i) => ++i)
+ )
})
await it('should run asynchronous functions exclusively in sequence', async () => {
promises.push(AsyncLock.runExclusive(AsyncLockType.configuration, asyncFn))
}
await Promise.all(promises)
- expect(executed).toStrictEqual(new Array(runs).fill(0).map((_, i) => ++i))
+ assert.deepStrictEqual(
+ executed,
+ new Array(runs).fill(0).map((_, i) => ++i)
+ )
})
await it('should propagate error thrown in exclusive function', async () => {
- await expect(
+ await assert.rejects(
AsyncLock.runExclusive(AsyncLockType.configuration, () => {
throw new Error('test error')
- })
- ).rejects.toThrow('test error')
+ }),
+ { message: /test error/ }
+ )
})
await it('should release lock after error and allow subsequent runs', async () => {
- await expect(
+ await assert.rejects(
AsyncLock.runExclusive(AsyncLockType.configuration, () => {
throw new Error('first fails')
- })
- ).rejects.toThrow('first fails')
+ }),
+ { message: /first fails/ }
+ )
let recovered = false
await AsyncLock.runExclusive(AsyncLockType.configuration, () => {
recovered = true
})
- expect(recovered).toBe(true)
+ assert.strictEqual(recovered, true)
})
await it('should isolate locks across different lock types', async () => {
await perfPromise
resolveConfig()
await configPromise
- expect(order[0]).toBe('performance')
- expect(order[1]).toBe('configuration')
+ assert.strictEqual(order[0], 'performance')
+ assert.strictEqual(order[1], 'configuration')
})
await it('should return value from exclusive function', async () => {
const result = await AsyncLock.runExclusive(AsyncLockType.configuration, () => 42)
- expect(result).toBe(42)
+ assert.strictEqual(result, 42)
})
})
* buildConnectorsStatus, buildEvsesStatus, buildChargingStationAutomaticTransactionGeneratorConfiguration,
* and the OutputFormat enum.
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
await describe('OutputFormat', async () => {
await it('should have correct enum values', () => {
- expect(OutputFormat.configuration).toBe('configuration')
- expect(OutputFormat.worker).toBe('worker')
+ assert.strictEqual(OutputFormat.configuration, 'configuration')
+ assert.strictEqual(OutputFormat.worker, 'worker')
})
})
const station = createMockStationForConfigUtils({ connectors })
const result = buildConnectorsStatus(station)
- expect(result.length).toBe(2)
+ assert.strictEqual(result.length, 2)
for (const connector of result) {
- expect('transactionSetInterval' in connector).toBe(false)
- expect('transactionEventQueue' in connector).toBe(false)
- expect('transactionTxUpdatedSetInterval' in connector).toBe(false)
+ assert.ok(!('transactionSetInterval' in connector))
+ assert.ok(!('transactionEventQueue' in connector))
+ assert.ok(!('transactionTxUpdatedSetInterval' in connector))
}
- expect(result[1].availability).toBe(AvailabilityType.Operative)
+ assert.strictEqual(result[1].availability, AvailabilityType.Operative)
} finally {
clearInterval(interval1)
clearInterval(interval2)
await it('should handle empty connectors map', () => {
const station = createMockStationForConfigUtils({ connectors: new Map() })
const result = buildConnectorsStatus(station)
- expect(result.length).toBe(0)
+ assert.strictEqual(result.length, 0)
})
await it('should preserve non-internal fields', () => {
const station = createMockStationForConfigUtils({ connectors })
const result = buildConnectorsStatus(station)
- expect(result.length).toBe(1)
- expect(result[0].availability).toBe(AvailabilityType.Operative)
- expect(result[0].transactionId).toBe(42)
- expect(result[0].transactionStarted).toBe(true)
+ assert.strictEqual(result.length, 1)
+ assert.strictEqual(result[0].availability, AvailabilityType.Operative)
+ assert.strictEqual(result[0].transactionId, 42)
+ assert.strictEqual(result[0].transactionStarted, true)
})
})
const station = createMockStationForConfigUtils({ evses })
const result = buildEvsesStatus(station, OutputFormat.configuration)
- expect(result.length).toBe(2)
+ assert.strictEqual(result.length, 2)
const evse1 = result[1] as Record<string, unknown>
- expect('connectorsStatus' in evse1).toBe(true)
- expect('connectors' in evse1).toBe(false)
+ assert.ok('connectorsStatus' in evse1)
+ assert.ok(!('connectors' in evse1))
})
await it('should strip internal fields from evse connectors in configuration format', () => {
const evse1 = result[0] as Record<string, unknown>
const connectorsStatus = evse1.connectorsStatus as ConnectorStatus[]
- expect(connectorsStatus.length).toBe(1)
- expect('transactionSetInterval' in connectorsStatus[0]).toBe(false)
- expect('transactionEventQueue' in connectorsStatus[0]).toBe(false)
- expect('transactionTxUpdatedSetInterval' in connectorsStatus[0]).toBe(false)
+ assert.strictEqual(connectorsStatus.length, 1)
+ assert.ok(!('transactionSetInterval' in connectorsStatus[0]))
+ assert.ok(!('transactionEventQueue' in connectorsStatus[0]))
+ assert.ok(!('transactionTxUpdatedSetInterval' in connectorsStatus[0]))
})
await it('should return worker format with connectors array', () => {
const station = createMockStationForConfigUtils({ evses })
const result = buildEvsesStatus(station, OutputFormat.worker)
- expect(result.length).toBe(2)
+ assert.strictEqual(result.length, 2)
const evse1 = result[1] as Record<string, unknown>
- expect('connectors' in evse1).toBe(true)
- expect(Array.isArray(evse1.connectors)).toBe(true)
+ assert.ok('connectors' in evse1)
+ assert.ok(Array.isArray(evse1.connectors))
})
await it('should default to configuration format when no format specified', () => {
const station = createMockStationForConfigUtils({ evses })
const result = buildEvsesStatus(station)
- expect(result.length).toBe(1)
+ assert.strictEqual(result.length, 1)
const evse = result[0] as Record<string, unknown>
- expect('connectorsStatus' in evse).toBe(true)
- expect('connectors' in evse).toBe(false)
+ assert.ok('connectorsStatus' in evse)
+ assert.ok(!('connectors' in evse))
})
await it('should handle empty evses map', () => {
const station = createMockStationForConfigUtils({ evses: new Map() })
const result = buildEvsesStatus(station, OutputFormat.configuration)
- expect(result.length).toBe(0)
+ assert.strictEqual(result.length, 0)
})
await it('should throw RangeError for unknown output format', () => {
})
const station = createMockStationForConfigUtils({ evses })
- expect(() => {
+ assert.throws(() => {
buildEvsesStatus(station, 'unknown' as OutputFormat)
- }).toThrow(RangeError)
+ }, RangeError)
})
})
})
const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
- expect(result.automaticTransactionGenerator).toStrictEqual(atgConfig)
- expect(result.automaticTransactionGeneratorStatuses).toBeDefined()
- expect(Array.isArray(result.automaticTransactionGeneratorStatuses)).toBe(true)
- expect(result.automaticTransactionGeneratorStatuses?.length).toBe(1)
+ assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+ assert.notStrictEqual(result.automaticTransactionGeneratorStatuses, undefined)
+ assert.ok(Array.isArray(result.automaticTransactionGeneratorStatuses))
+ assert.strictEqual(result.automaticTransactionGeneratorStatuses.length, 1)
})
await it('should return ATG configuration without statuses when no ATG instance', () => {
})
const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
- expect(result.automaticTransactionGenerator).toStrictEqual(atgConfig)
- expect(result.automaticTransactionGeneratorStatuses).toBeUndefined()
+ assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+ assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined)
})
await it('should return undefined ATG config when not configured', () => {
})
const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
- expect(result.automaticTransactionGenerator).toBeUndefined()
- expect(result.automaticTransactionGeneratorStatuses).toBeUndefined()
+ assert.strictEqual(result.automaticTransactionGenerator, undefined)
+ assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined)
})
await it('should return ATG configuration without statuses when connectorsStatus is null', () => {
})
const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station)
- expect(result.automaticTransactionGenerator).toStrictEqual(atgConfig)
- expect(result.automaticTransactionGeneratorStatuses).toBeUndefined()
+ assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig)
+ assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined)
})
})
})
* - getSupervisionUrlDistribution — returns distribution strategy
* - workerPoolInUse / workerDynamicPoolInUse — pool type checks
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type {
await it('should return configuration data', () => {
const data = Configuration.getConfigurationData()
- expect(data).toBeDefined()
- expect(Array.isArray(data?.stationTemplateUrls)).toBe(true)
+ assert.notStrictEqual(data, undefined)
+ assert.ok(Array.isArray(data?.stationTemplateUrls))
})
await it('should return the same data on subsequent calls', () => {
const first = Configuration.getConfigurationData()
const second = Configuration.getConfigurationData()
- expect(first).toBe(second)
+ assert.strictEqual(first, second)
})
await it('should return log configuration with defaults', () => {
const log = Configuration.getConfigurationSection<LogConfiguration>(ConfigurationSection.log)
- expect(log).toBeDefined()
- expect(typeof log.enabled).toBe('boolean')
- expect(typeof log.file).toBe('string')
- expect(typeof log.level).toBe('string')
- expect(typeof log.format).toBe('string')
+ assert.notStrictEqual(log, undefined)
+ assert.strictEqual(typeof log.enabled, 'boolean')
+ assert.strictEqual(typeof log.file, 'string')
+ assert.strictEqual(typeof log.level, 'string')
+ assert.strictEqual(typeof log.format, 'string')
})
await it('should include default log values', () => {
const log = Configuration.getConfigurationSection<LogConfiguration>(ConfigurationSection.log)
- expect(log.level).toBe('info')
- expect(log.format).toBe('simple')
- expect(log.rotate).toBe(true)
- expect(log.enabled).toBe(true)
+ assert.strictEqual(log.level, 'info')
+ assert.strictEqual(log.format, 'simple')
+ assert.strictEqual(log.rotate, true)
+ assert.strictEqual(log.enabled, true)
})
await it('should return worker configuration with defaults', () => {
const worker = Configuration.getConfigurationSection<WorkerConfiguration>(
ConfigurationSection.worker
)
- expect(worker).toBeDefined()
- expect(worker.processType).toBe(WorkerProcessType.workerSet)
- expect(worker.startDelay).toBe(500)
- expect(typeof worker.poolMinSize).toBe('number')
- expect(typeof worker.poolMaxSize).toBe('number')
- expect(worker.elementsPerWorker).toBe('auto')
+ assert.notStrictEqual(worker, undefined)
+ assert.strictEqual(worker.processType, WorkerProcessType.workerSet)
+ assert.strictEqual(worker.startDelay, 500)
+ assert.strictEqual(typeof worker.poolMinSize, 'number')
+ assert.strictEqual(typeof worker.poolMaxSize, 'number')
+ assert.strictEqual(worker.elementsPerWorker, 'auto')
})
await it('should include default worker process type', () => {
const worker = Configuration.getConfigurationSection<WorkerConfiguration>(
ConfigurationSection.worker
)
- expect(worker.processType).toBe(WorkerProcessType.workerSet)
+ assert.strictEqual(worker.processType, WorkerProcessType.workerSet)
})
await it('should return UI server configuration with defaults', () => {
const uiServer = Configuration.getConfigurationSection<UIServerConfiguration>(
ConfigurationSection.uiServer
)
- expect(uiServer).toBeDefined()
- expect(uiServer.enabled).toBe(false)
- expect(uiServer.type).toBe(ApplicationProtocol.WS)
- expect(uiServer.version).toBe(ApplicationProtocolVersion.VERSION_11)
- expect(uiServer.options).toBeDefined()
- expect(typeof uiServer.options?.host).toBe('string')
- expect(typeof uiServer.options?.port).toBe('number')
+ assert.notStrictEqual(uiServer, undefined)
+ assert.strictEqual(uiServer.enabled, false)
+ assert.strictEqual(uiServer.type, ApplicationProtocol.WS)
+ assert.strictEqual(uiServer.version, ApplicationProtocolVersion.VERSION_11)
+ assert.notStrictEqual(uiServer.options, undefined)
+ assert.strictEqual(typeof uiServer.options?.host, 'string')
+ assert.strictEqual(typeof uiServer.options?.port, 'number')
})
await it('should return performance storage configuration', () => {
const storage = Configuration.getConfigurationSection<StorageConfiguration>(
ConfigurationSection.performanceStorage
)
- expect(storage).toBeDefined()
- expect(storage.enabled).toBe(true)
- expect(storage.type).toBe(StorageType.NONE)
+ assert.notStrictEqual(storage, undefined)
+ assert.strictEqual(storage.enabled, true)
+ assert.strictEqual(storage.type, StorageType.NONE)
})
await it('should return station template URLs', () => {
const urls = Configuration.getStationTemplateUrls()
- expect(urls).toBeDefined()
- expect(Array.isArray(urls)).toBe(true)
+ assert.notStrictEqual(urls, undefined)
+ assert.ok(Array.isArray(urls))
})
await it('should return supervision URL distribution', () => {
const distribution = Configuration.getSupervisionUrlDistribution()
- expect(distribution).toBeDefined()
- expect(
- distribution != null && Object.values(SupervisionUrlDistribution).includes(distribution)
- ).toBe(true)
+ assert.notStrictEqual(distribution, undefined)
+ assert.strictEqual(
+ distribution != null && Object.values(SupervisionUrlDistribution).includes(distribution),
+ true
+ )
})
await it('should default to ROUND_ROBIN when not configured', () => {
try {
const distribution = Configuration.getSupervisionUrlDistribution()
- expect(distribution).toBe(SupervisionUrlDistribution.ROUND_ROBIN)
+ assert.strictEqual(distribution, SupervisionUrlDistribution.ROUND_ROBIN)
} finally {
internals.configurationData = originalData
resetSectionCache()
})
await it('should return false for workerPoolInUse with default workerSet config', () => {
- expect(Configuration.workerPoolInUse()).toBe(false)
+ assert.strictEqual(Configuration.workerPoolInUse(), false)
})
await it('should return false for workerDynamicPoolInUse with default workerSet config', () => {
- expect(Configuration.workerDynamicPoolInUse()).toBe(false)
+ assert.strictEqual(Configuration.workerDynamicPoolInUse(), false)
})
await it('should return supervision URLs from configuration', () => {
const urls = Configuration.getSupervisionUrls()
- expect(urls == null || typeof urls === 'string' || Array.isArray(urls)).toBe(true)
+ assert.ok(urls == null || typeof urls === 'string' || Array.isArray(urls))
})
await it('should throw for unknown configuration section', () => {
- expect(() => {
+ assert.throws(() => {
Configuration.getConfigurationSection('unknown' as ConfigurationSection)
- }).toThrow(Error)
+ }, Error)
})
})
* @file Tests for ConfigurationUtils
* @description Unit tests for configuration utility functions
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { StorageType } from '../../src/types/index.js'
})
await it('should return log prefix with simulator configuration', () => {
- expect(logPrefix()).toContain(' Simulator configuration |')
+ assert.ok(logPrefix().includes(' Simulator configuration |'))
})
await it('should build file URI path for performance storage', () => {
const result = buildPerformanceUriFilePath('test.json')
- expect(result).toContain('test.json')
- expect(result).toMatch(/^file:\/\/.*test\.json$/)
+ assert.ok(result.includes('test.json'))
+ assert.match(result, /^file:\/\/.*test\.json$/)
})
await it('should return appropriate URI for storage types', () => {
// Test JSON_FILE storage type
const jsonUri = getDefaultPerformanceStorageUri(StorageType.JSON_FILE)
- expect(jsonUri).toMatch(/^file:\/\/.*\.json$/)
- expect(jsonUri).toContain('performanceRecords.json')
+ assert.match(jsonUri, /^file:\/\/.*\.json$/)
+ assert.ok(jsonUri.includes('performanceRecords.json'))
// Test SQLITE storage type
const sqliteUri = getDefaultPerformanceStorageUri(StorageType.SQLITE)
- expect(sqliteUri).toMatch(/^file:\/\/.*\.db$/)
- expect(sqliteUri).toContain('charging-stations-simulator.db')
+ assert.match(sqliteUri, /^file:\/\/.*\.db$/)
+ assert.ok(sqliteUri.includes('charging-stations-simulator.db'))
// Test unsupported storage type
- expect(() => {
+ assert.throws(() => {
getDefaultPerformanceStorageUri('unsupported' as StorageType)
- }).toThrow(Error)
+ }, Error)
})
await it('should validate worker elements per worker configuration', () => {
// These calls should not throw exceptions
- expect(() => {
+ assert.doesNotThrow(() => {
checkWorkerElementsPerWorker(undefined)
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
checkWorkerElementsPerWorker('auto')
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
checkWorkerElementsPerWorker('all')
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
checkWorkerElementsPerWorker(4)
- }).not.toThrow()
+ })
// These calls should throw exceptions
- expect(() => {
+ assert.throws(() => {
checkWorkerElementsPerWorker(0)
- }).toThrow(RangeError)
- expect(() => {
+ }, RangeError)
+ assert.throws(() => {
checkWorkerElementsPerWorker(-1)
- }).toThrow(RangeError)
- expect(() => {
+ }, RangeError)
+ assert.throws(() => {
checkWorkerElementsPerWorker(1.5)
- }).toThrow(SyntaxError)
+ }, SyntaxError)
})
})
* @file Tests for ElectricUtils
* @description Unit tests for electrical calculations (AC/DC power, amperage)
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { ACElectricUtils, DCElectricUtils } from '../../src/utils/ElectricUtils.js'
})
await it('should calculate DC power from voltage and current', () => {
- expect(DCElectricUtils.power(230, 1)).toBe(230)
+ assert.strictEqual(DCElectricUtils.power(230, 1), 230)
})
await it('should calculate DC amperage from power and voltage', () => {
- expect(DCElectricUtils.amperage(1, 230)).toBe(0)
+ assert.strictEqual(DCElectricUtils.amperage(1, 230), 0)
})
await it('should calculate total AC power for all phases', () => {
- expect(ACElectricUtils.powerTotal(3, 230, 1)).toBe(690)
+ assert.strictEqual(ACElectricUtils.powerTotal(3, 230, 1), 690)
})
await it('should calculate AC power per phase', () => {
- expect(ACElectricUtils.powerPerPhase(230, 1)).toBe(230)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 1), 230)
})
await it('should calculate total AC amperage for all phases', () => {
- expect(ACElectricUtils.amperageTotal(3, 1)).toBe(3)
+ assert.strictEqual(ACElectricUtils.amperageTotal(3, 1), 3)
})
await it('should calculate total AC amperage from power and voltage', () => {
- expect(ACElectricUtils.amperageTotalFromPower(690, 230)).toBe(3)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(690, 230), 3)
})
await it('should calculate AC amperage per phase from power', () => {
- expect(ACElectricUtils.amperagePerPhaseFromPower(3, 690, 230)).toBe(1)
+ assert.strictEqual(ACElectricUtils.amperagePerPhaseFromPower(3, 690, 230), 1)
})
await it('should return 0 for DC amperage when voltage is zero', () => {
- expect(DCElectricUtils.amperage(1000, 0)).toBe(0)
+ assert.strictEqual(DCElectricUtils.amperage(1000, 0), 0)
})
await it('should return 0 for AC amperage when voltage is zero', () => {
- expect(ACElectricUtils.amperageTotalFromPower(1000, 0)).toBe(0)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(1000, 0), 0)
})
await it('should return 0 for AC amperage when cosPhi is zero', () => {
- expect(ACElectricUtils.amperageTotalFromPower(1000, 230, 0)).toBe(0)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(1000, 230, 0), 0)
})
await it('should return 0 for AC amperage per phase when phases is zero or negative', () => {
- expect(ACElectricUtils.amperagePerPhaseFromPower(0, 690, 230)).toBe(0)
- expect(ACElectricUtils.amperagePerPhaseFromPower(-1, 690, 230)).toBe(0)
+ assert.strictEqual(ACElectricUtils.amperagePerPhaseFromPower(0, 690, 230), 0)
+ assert.strictEqual(ACElectricUtils.amperagePerPhaseFromPower(-1, 690, 230), 0)
})
await it('should round AC power per phase with non-unity cosPhi', () => {
- expect(ACElectricUtils.powerPerPhase(230, 10, COS_PHI_RESIDENTIAL)).toBe(1955)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 10, COS_PHI_RESIDENTIAL), 1955)
})
await it('should round DC amperage when power is not evenly divisible by voltage', () => {
- expect(DCElectricUtils.amperage(100, 3)).toBe(33)
+ assert.strictEqual(DCElectricUtils.amperage(100, 3), 33)
})
await it('should calculate DC power as voltage times current', () => {
- expect(DCElectricUtils.power(0, 10)).toBe(0)
- expect(DCElectricUtils.power(400, 0)).toBe(0)
+ assert.strictEqual(DCElectricUtils.power(0, 10), 0)
+ assert.strictEqual(DCElectricUtils.power(400, 0), 0)
})
await it('should calculate 7.4 kW single-phase AC home charger values', () => {
// 230V × 32A × 1 phase × cosPhi=1 = 7360W
- expect(ACElectricUtils.powerPerPhase(230, 32)).toBe(7360)
- expect(ACElectricUtils.powerTotal(1, 230, 32)).toBe(7360)
- expect(ACElectricUtils.amperageTotalFromPower(7360, 230)).toBe(32)
- expect(ACElectricUtils.amperagePerPhaseFromPower(1, 7360, 230)).toBe(32)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 32), 7360)
+ assert.strictEqual(ACElectricUtils.powerTotal(1, 230, 32), 7360)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(7360, 230), 32)
+ assert.strictEqual(ACElectricUtils.amperagePerPhaseFromPower(1, 7360, 230), 32)
})
await it('should calculate 22 kW three-phase AC wall box values', () => {
// 230V × 32A × 3 phases × cosPhi=1 = 22080W
- expect(ACElectricUtils.powerPerPhase(230, 32)).toBe(7360)
- expect(ACElectricUtils.powerTotal(3, 230, 32)).toBe(22080)
- expect(ACElectricUtils.amperageTotalFromPower(22080, 230)).toBe(96)
- expect(ACElectricUtils.amperagePerPhaseFromPower(3, 22080, 230)).toBe(32)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 32), 7360)
+ assert.strictEqual(ACElectricUtils.powerTotal(3, 230, 32), 22080)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(22080, 230), 96)
+ assert.strictEqual(ACElectricUtils.amperagePerPhaseFromPower(3, 22080, 230), 32)
})
await it('should calculate 50 kW DC fast charger values', () => {
- expect(DCElectricUtils.power(400, 125)).toBe(50000)
- expect(DCElectricUtils.amperage(50000, 400)).toBe(125)
+ assert.strictEqual(DCElectricUtils.power(400, 125), 50000)
+ assert.strictEqual(DCElectricUtils.amperage(50000, 400), 125)
})
await it('should calculate 150 kW DC high-power charger values', () => {
- expect(DCElectricUtils.power(500, 300)).toBe(150000)
- expect(DCElectricUtils.amperage(150000, 500)).toBe(300)
+ assert.strictEqual(DCElectricUtils.power(500, 300), 150000)
+ assert.strictEqual(DCElectricUtils.amperage(150000, 500), 300)
})
await it('should handle industrial cosPhi values for AC calculations', () => {
- expect(ACElectricUtils.powerPerPhase(230, 32, COS_PHI_INDUSTRIAL)).toBe(6992)
- expect(ACElectricUtils.powerTotal(3, 230, 32, COS_PHI_INDUSTRIAL)).toBe(20976)
- expect(ACElectricUtils.amperageTotalFromPower(6992, 230, COS_PHI_INDUSTRIAL)).toBe(32)
- expect(ACElectricUtils.powerPerPhase(230, 32, COS_PHI_POOR)).toBe(6624)
- expect(ACElectricUtils.amperageTotalFromPower(6624, 230, COS_PHI_POOR)).toBe(32)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 32, COS_PHI_INDUSTRIAL), 6992)
+ assert.strictEqual(ACElectricUtils.powerTotal(3, 230, 32, COS_PHI_INDUSTRIAL), 20976)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(6992, 230, COS_PHI_INDUSTRIAL), 32)
+ assert.strictEqual(ACElectricUtils.powerPerPhase(230, 32, COS_PHI_POOR), 6624)
+ assert.strictEqual(ACElectricUtils.amperageTotalFromPower(6624, 230, COS_PHI_POOR), 32)
})
})
* @file Tests for ErrorUtils
* @description Unit tests for error handling utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import process from 'node:process'
import { afterEach, beforeEach, describe, it } from 'node:test'
const { errorMock } = createLoggerMocks(t, logger)
const error = new Error() as NodeJS.ErrnoException
error.code = 'ENOENT'
- expect(() => {
+ assert.throws(() => {
handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {})
- }).toThrow(error)
- expect(errorMock.mock.calls.length).toBe(1)
+ }, error)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should log warning with logger when throwError is false', t => {
const { warnMock } = createLoggerMocks(t, logger)
const error = new Error() as NodeJS.ErrnoException
error.code = 'ENOENT'
- expect(() => {
+ assert.doesNotThrow(() => {
handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
throwError: false,
})
- }).not.toThrow()
- expect(warnMock.mock.calls.length).toBe(1)
+ })
+ assert.strictEqual(warnMock.mock.calls.length, 1)
})
await it('should throw error with console output when consoleOut is true', t => {
const { errorMock } = createConsoleMocks(t, { error: true })
const error = new Error() as NodeJS.ErrnoException
error.code = 'ENOENT'
- expect(() => {
+ assert.throws(() => {
handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
consoleOut: true,
})
- }).toThrow(error)
- expect(errorMock?.mock.calls.length).toBe(1)
+ }, error)
+ assert.strictEqual(errorMock?.mock.calls.length, 1)
})
await it('should log console warning when consoleOut and throwError are false', t => {
const { warnMock } = createConsoleMocks(t, { error: true, warn: true })
const error = new Error() as NodeJS.ErrnoException
error.code = 'ENOENT'
- expect(() => {
+ assert.doesNotThrow(() => {
handleFileException('path/to/module.js', FileType.Authorization, error, 'log prefix |', {
consoleOut: true,
throwError: false,
})
- }).not.toThrow()
- expect(warnMock?.mock.calls.length).toBe(1)
+ })
+ assert.strictEqual(warnMock?.mock.calls.length, 1)
})
await it('should produce correct log message for each error code', t => {
throwError: false,
})
}
- expect(warnMock.mock.calls.length).toBe(errorCodes.length)
+ assert.strictEqual(warnMock.mock.calls.length, errorCodes.length)
for (let i = 0; i < errorCodes.length; i++) {
const logMessage = String(warnMock.mock.calls[i].arguments[0]).toLowerCase()
- expect(logMessage.includes(errorCodes[i].expectedSubstring)).toBe(true)
+ assert.ok(logMessage.includes(errorCodes[i].expectedSubstring))
}
})
await it('should register uncaught exception handler on process', t => {
const onMock = t.mock.method(process, 'on')
handleUncaughtException()
- expect(onMock.mock.calls.length).toBe(1)
- expect(onMock.mock.calls[0].arguments[0]).toBe('uncaughtException')
+ assert.strictEqual(onMock.mock.calls.length, 1)
+ assert.strictEqual(onMock.mock.calls[0].arguments[0], 'uncaughtException')
})
await it('should register unhandled rejection handler on process', t => {
const onMock = t.mock.method(process, 'on')
handleUnhandledRejection()
- expect(onMock.mock.calls.length).toBe(1)
- expect(onMock.mock.calls[0].arguments[0]).toBe('unhandledRejection')
+ assert.strictEqual(onMock.mock.calls.length, 1)
+ assert.strictEqual(onMock.mock.calls[0].arguments[0], 'unhandledRejection')
})
await it('should log error and not throw for send message errors by default', t => {
const { errorMock } = createLoggerMocks(t, logger)
t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
- expect(() => {
+ assert.doesNotThrow(() => {
handleSendMessageError(
chargingStation,
RequestCommand.BOOT_NOTIFICATION,
MessageType.CALL_MESSAGE,
error
)
- }).not.toThrow()
- expect(errorMock.mock.calls.length).toBe(1)
+ })
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should throw for send message errors when throwError is true', t => {
const { errorMock } = createLoggerMocks(t, logger)
t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
- expect(() => {
+ assert.throws(() => {
handleSendMessageError(
chargingStation,
RequestCommand.BOOT_NOTIFICATION,
error,
{ throwError: true }
)
- }).toThrow(error)
- expect(errorMock.mock.calls.length).toBe(1)
+ }, error)
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should log error and not throw for incoming request errors by default', t => {
const { errorMock } = createLoggerMocks(t, logger)
t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
- expect(() => {
+ assert.doesNotThrow(() => {
handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error)
- }).not.toThrow(error)
- expect(errorMock.mock.calls.length).toBe(1)
+ })
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
await it('should throw for incoming request errors when throwError is true', t => {
createLoggerMocks(t, logger)
t.mock.method(chargingStation, 'logPrefix')
const error = new Error()
- expect(() => {
+ assert.throws(() => {
handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error, {
throwError: true,
})
- }).toThrow()
+ })
})
await it('should return error response for incoming request errors', t => {
const errorResponse = {
status: GenericStatus.Rejected,
}
- expect(
+ assert.deepStrictEqual(
handleIncomingRequestError(chargingStation, IncomingRequestCommand.CLEAR_CACHE, error, {
errorResponse,
- })
- ).toStrictEqual(errorResponse)
- expect(errorMock.mock.calls.length).toBe(1)
+ }),
+ errorResponse
+ )
+ assert.strictEqual(errorMock.mock.calls.length, 1)
})
})
* @file Tests for FileUtils
* @description Unit tests for file watching utility functions
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { mkdtempSync, rmSync, type WatchListener, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
const result = watchJsonFile('', FileType.Authorization, 'test prefix |', noop)
- expect(result).toBeUndefined()
- expect(infoMock.mock.calls.length).toBe(1)
+ assert.strictEqual(result, undefined)
+ assert.strictEqual(infoMock.mock.calls.length, 1)
})
await it('should include file type and log prefix in info log message for empty path', t => {
watchJsonFile('', FileType.ChargingStationConfiguration, 'CS-001 |', noop)
- expect(infoMock.mock.calls.length).toBe(1)
+ assert.strictEqual(infoMock.mock.calls.length, 1)
const logMessage = infoMock.mock.calls[0].arguments[0] as unknown as string
- expect(logMessage.includes(FileType.ChargingStationConfiguration)).toBe(true)
- expect(logMessage.includes('CS-001 |')).toBe(true)
+ assert.ok(logMessage.includes(FileType.ChargingStationConfiguration))
+ assert.ok(logMessage.includes('CS-001 |'))
})
await it('should handle watch error and return undefined for nonexistent file', t => {
noop
)
- expect(result).toBeUndefined()
- expect(warnMock.mock.calls.length).toBe(1)
+ assert.strictEqual(result, undefined)
+ assert.strictEqual(warnMock.mock.calls.length, 1)
})
await it('should return FSWatcher for valid file path', () => {
try {
const result = watchJsonFile(tmpFile, FileType.Authorization, 'test |', noop)
- expect(result).toBeDefined()
+ assert.notStrictEqual(result, undefined)
result?.close()
} finally {
rmSync(tmpDir, { recursive: true })
const result = watchJsonFile(tmpFile, FileType.Authorization, 'test |', listener)
- expect(result).toBeDefined()
- expect(typeof result?.close).toBe('function')
+ assert.notStrictEqual(result, undefined)
+ assert.strictEqual(typeof result?.close, 'function')
result?.close()
- expect(receivedEvent).toBe(false)
+ assert.strictEqual(receivedEvent, false)
} finally {
rmSync(tmpDir, { recursive: true })
}
+import { CircularBuffer } from 'mnemonist'
/**
* @file Tests for MessageChannelUtils
* @description Unit tests for charging station worker message builders and performance statistics conversion
*/
-import { expect } from '@std/expect'
-import { CircularBuffer } from 'mnemonist'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
const station = createMockStationForMessages()
const message = buildAddedMessage(station)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.added)
- expect(message.data).toBeDefined()
- expect(message.data.started).toBe(true)
- expect(message.data.stationInfo.chargingStationId).toBe('CS-TEST-00001')
- expect(message.data.supervisionUrl).toBe('ws://localhost:8080/CS-TEST-00001')
- expect(typeof message.data.timestamp).toBe('number')
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.added)
+ assert.notStrictEqual(message.data, undefined)
+ assert.strictEqual(message.data.started, true)
+ assert.strictEqual(message.data.stationInfo.chargingStationId, 'CS-TEST-00001')
+ assert.strictEqual(message.data.supervisionUrl, 'ws://localhost:8080/CS-TEST-00001')
+ assert.strictEqual(typeof message.data.timestamp, 'number')
})
await it('should build deleted message with correct event', () => {
const station = createMockStationForMessages()
const message = buildDeletedMessage(station)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.deleted)
- expect(message.data).toBeDefined()
- expect(message.data.stationInfo.chargingStationId).toBe('CS-TEST-00001')
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.deleted)
+ assert.notStrictEqual(message.data, undefined)
+ assert.strictEqual(message.data.stationInfo.chargingStationId, 'CS-TEST-00001')
})
await it('should build started message with correct event', () => {
const station = createMockStationForMessages()
const message = buildStartedMessage(station)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.started)
- expect(message.data.started).toBe(true)
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.started)
+ assert.strictEqual(message.data.started, true)
})
await it('should build stopped message with correct event', () => {
const station = createMockStationForMessages()
const message = buildStoppedMessage(station)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.stopped)
- expect(message.data).toBeDefined()
- expect(message.data.supervisionUrl).toBe('ws://localhost:8080/CS-TEST-00001')
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.stopped)
+ assert.notStrictEqual(message.data, undefined)
+ assert.strictEqual(message.data.supervisionUrl, 'ws://localhost:8080/CS-TEST-00001')
})
await it('should build updated message with correct event', () => {
const station = createMockStationForMessages()
const message = buildUpdatedMessage(station)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.updated)
- expect(message.data).toBeDefined()
- expect(message.data.stationInfo.chargingStationId).toBe('CS-TEST-00001')
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.updated)
+ assert.notStrictEqual(message.data, undefined)
+ assert.strictEqual(message.data.stationInfo.chargingStationId, 'CS-TEST-00001')
})
await it('should include ws state in station messages', () => {
const station = createMockStationForMessages()
const message = buildAddedMessage(station)
- expect(message.data.wsState).toBe(1)
+ assert.strictEqual(message.data.wsState, 1)
})
await it('should include connectors status in station messages', () => {
const station = createMockStationForMessages()
const message = buildAddedMessage(station)
- expect(Array.isArray(message.data.connectors)).toBe(true)
- expect(message.data.connectors.length).toBe(2)
+ assert.ok(Array.isArray(message.data.connectors))
+ assert.strictEqual(message.data.connectors.length, 2)
})
await it('should convert CircularBuffer to array in statistics data', () => {
const message = buildPerformanceStatisticsMessage(statistics)
- expect(message.event).toBe(ChargingStationWorkerMessageEvents.performanceStatistics)
- expect(message.data.id).toBe('test-station-id')
- expect(message.data.name).toBe('test-station')
+ assert.strictEqual(message.event, ChargingStationWorkerMessageEvents.performanceStatistics)
+ assert.strictEqual(message.data.id, 'test-station-id')
+ assert.strictEqual(message.data.name, 'test-station')
const heartbeatStats = message.data.statisticsData.get('Heartbeat')
- expect(heartbeatStats).toBeDefined()
- expect(Array.isArray(heartbeatStats?.measurementTimeSeries)).toBe(true)
- const timeSeries = heartbeatStats?.measurementTimeSeries as TimestampedData[]
- expect(timeSeries.length).toBe(2)
- expect(timeSeries[0].value).toBe(42)
- expect(timeSeries[1].value).toBe(84)
+ assert.notStrictEqual(heartbeatStats, undefined)
+ assert.ok(Array.isArray(heartbeatStats?.measurementTimeSeries))
+ const timeSeries = heartbeatStats.measurementTimeSeries
+ assert.strictEqual(timeSeries.length, 2)
+ assert.strictEqual(timeSeries[0].value, 42)
+ assert.strictEqual(timeSeries[1].value, 84)
})
await it('should preserve non-CircularBuffer measurement time series', () => {
const message = buildPerformanceStatisticsMessage(statistics)
const heartbeat = message.data.statisticsData.get('Heartbeat')
- expect(Array.isArray(heartbeat?.measurementTimeSeries)).toBe(true)
+ assert.ok(Array.isArray(heartbeat?.measurementTimeSeries))
})
await it('should preserve statistics metadata in performance message', () => {
const message = buildPerformanceStatisticsMessage(statistics)
- expect(message.data.createdAt).toBe(createdAt)
- expect(message.data.updatedAt).toBe(updatedAt)
- expect(message.data.uri).toBe('ws://localhost:8080')
+ assert.strictEqual(message.data.createdAt, createdAt)
+ assert.strictEqual(message.data.updatedAt, updatedAt)
+ assert.strictEqual(message.data.uri, 'ws://localhost:8080')
})
})
* @file Tests for StatisticUtils
* @description Unit tests for statistical calculation utilities
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { average, max, median, min, percentile, std } from '../../src/utils/StatisticUtils.js'
standardCleanup()
})
await it('should calculate arithmetic mean of array values', () => {
- 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)
+ assert.strictEqual(average([]), 0)
+ assert.strictEqual(average([0.08]), 0.08)
+ assert.strictEqual(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]), 3.1642857142857146)
+ assert.strictEqual(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02]), 2.8533333333333335)
})
await it('should calculate median value of array', () => {
- 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)
+ assert.strictEqual(median([]), 0)
+ assert.strictEqual(median([0.08]), 0.08)
+ assert.strictEqual(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]), 3.05)
+ assert.strictEqual(median([0.25, 4.75, 3.05, 6.04, 1.01, 2.02]), 2.535)
})
await it('should return minimum value from arguments', () => {
- expect(min()).toBe(Number.POSITIVE_INFINITY)
- expect(min(0, 1)).toBe(0)
- expect(min(1, 0)).toBe(0)
- expect(min(0, -1)).toBe(-1)
- expect(min(-1, 0)).toBe(-1)
+ assert.strictEqual(min(), Number.POSITIVE_INFINITY)
+ assert.strictEqual(min(0, 1), 0)
+ assert.strictEqual(min(1, 0), 0)
+ assert.strictEqual(min(0, -1), -1)
+ assert.strictEqual(min(-1, 0), -1)
})
await it('should return maximum value from arguments', () => {
- expect(max()).toBe(Number.NEGATIVE_INFINITY)
- expect(max(0, 1)).toBe(1)
- expect(max(1, 0)).toBe(1)
- expect(max(0, -1)).toBe(0)
- expect(max(-1, 0)).toBe(0)
+ assert.strictEqual(max(), Number.NEGATIVE_INFINITY)
+ assert.strictEqual(max(0, 1), 1)
+ assert.strictEqual(max(1, 0), 1)
+ assert.strictEqual(max(0, -1), 0)
+ assert.strictEqual(max(-1, 0), 0)
})
await it('should calculate nth percentile of array', () => {
- expect(percentile([], 25)).toBe(0)
- expect(percentile([0.08], 50)).toBe(0.08)
+ assert.strictEqual(percentile([], 25), 0)
+ assert.strictEqual(percentile([0.08], 50), 0.08)
const array0 = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]
- expect(percentile(array0, 0)).toBe(0.25)
- expect(percentile(array0, 50)).toBe(3.05)
- expect(percentile(array0, 80)).toBe(4.974)
- expect(percentile(array0, 85)).toBe(5.131)
- expect(percentile(array0, 90)).toBe(5.434)
- expect(percentile(array0, 95)).toBe(5.736999999999999)
- expect(percentile(array0, 100)).toBe(6.04)
+ assert.strictEqual(percentile(array0, 0), 0.25)
+ assert.strictEqual(percentile(array0, 50), 3.05)
+ assert.strictEqual(percentile(array0, 80), 4.974)
+ assert.strictEqual(percentile(array0, 85), 5.131)
+ assert.strictEqual(percentile(array0, 90), 5.434)
+ assert.strictEqual(percentile(array0, 95), 5.736999999999999)
+ assert.strictEqual(percentile(array0, 100), 6.04)
})
await it('should calculate standard deviation of array', () => {
- expect(std([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(2.1879050645374383)
+ assert.strictEqual(std([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]), 2.1879050645374383)
})
await it('should return 0 for standard deviation of empty or single-element array', () => {
- expect(std([])).toBe(0)
- expect(std([42])).toBe(0)
+ assert.strictEqual(std([]), 0)
+ assert.strictEqual(std([42]), 0)
})
await it('should throw TypeError for non-array input to std', () => {
- expect(() => std(null as unknown as number[])).toThrow(TypeError)
- expect(() => std(undefined as unknown as number[])).toThrow(TypeError)
+ assert.throws(() => std(null as unknown as number[]), TypeError)
+ assert.throws(() => std(undefined as unknown as number[]), TypeError)
})
await it('should throw TypeError for non-array input to percentile', () => {
- expect(() => percentile(null as unknown as number[], 50)).toThrow(TypeError)
+ assert.throws(() => percentile(null as unknown as number[], 50), TypeError)
})
await it('should throw RangeError for out-of-range percentile', () => {
- expect(() => percentile([1, 2, 3], -1)).toThrow(RangeError)
- expect(() => percentile([1, 2, 3], 101)).toThrow(RangeError)
+ assert.throws(() => percentile([1, 2, 3], -1), RangeError)
+ assert.throws(() => percentile([1, 2, 3], 101), RangeError)
})
await it('should accept pre-computed average parameter', () => {
const data = [0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03]
const avg = average(data)
- expect(std(data, avg)).toBe(std(data))
+ assert.strictEqual(std(data, avg), std(data))
})
})
+import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
+import { CircularBuffer } from 'mnemonist'
/**
* @file Tests for Utils
* @description Unit tests for general utility functions
*/
-import { expect } from '@std/expect'
-import { hoursToMilliseconds, hoursToSeconds } from 'date-fns'
-import { CircularBuffer } from 'mnemonist'
+import assert from 'node:assert/strict'
import { randomInt } from 'node:crypto'
import process from 'node:process'
import { version } from 'node:process'
})
await it('should generate valid UUIDs and validate them correctly', () => {
const uuid = generateUUID()
- expect(uuid).toBeDefined()
- expect(uuid.length).toBe(36)
- expect(validateUUID(uuid)).toBe(true)
- expect(validateUUID('abcdef00-0000-4000-9000-000000000000')).toBe(true)
- expect(validateUUID('abcdef00-0000-4000-a000-000000000000')).toBe(true)
- expect(validateUUID('abcdef00-0000-4000-0000-000000000000')).toBe(false)
- expect(validateUUID('')).toBe(false)
+ assert.notStrictEqual(uuid, undefined)
+ assert.strictEqual(uuid.length, 36)
+ assert.strictEqual(validateUUID(uuid), true)
+ assert.strictEqual(validateUUID('abcdef00-0000-4000-9000-000000000000'), true)
+ assert.strictEqual(validateUUID('abcdef00-0000-4000-a000-000000000000'), true)
+ assert.strictEqual(validateUUID('abcdef00-0000-4000-0000-000000000000'), false)
+ assert.strictEqual(validateUUID(''), false)
// Shall invalidate Nil UUID
- expect(validateUUID('00000000-0000-0000-0000-000000000000')).toBe(false)
- expect(validateUUID('987FBC9-4BED-3078-CF07A-9141BA07C9F3')).toBe(false)
+ assert.strictEqual(validateUUID('00000000-0000-0000-0000-000000000000'), false)
+ assert.strictEqual(validateUUID('987FBC9-4BED-3078-CF07A-9141BA07C9F3'), false)
// Shall invalidate non-string inputs
- expect(validateUUID(123)).toBe(false)
- expect(validateUUID(null)).toBe(false)
- expect(validateUUID(undefined)).toBe(false)
- expect(validateUUID({})).toBe(false)
- expect(validateUUID([])).toBe(false)
- expect(validateUUID(true)).toBe(false)
+ assert.strictEqual(validateUUID(123), false)
+ assert.strictEqual(validateUUID(null), false)
+ assert.strictEqual(validateUUID(undefined), false)
+ assert.strictEqual(validateUUID({}), false)
+ assert.strictEqual(validateUUID([]), false)
+ assert.strictEqual(validateUUID(true), false)
})
await it('should validate identifier strings within length constraints', () => {
- 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('abc123', 36)).toBe(true)
- expect(validateIdentifierString('valid-identifier', 36)).toBe(true)
- expect(validateIdentifierString('a'.repeat(36), 36)).toBe(true)
- expect(validateIdentifierString('', 36)).toBe(false)
- expect(validateIdentifierString('a'.repeat(37), 36)).toBe(false)
- expect(validateIdentifierString('a'.repeat(100), 36)).toBe(false)
- expect(validateIdentifierString(' ', 36)).toBe(false)
- expect(validateIdentifierString('\t\n', 36)).toBe(false)
- expect(validateIdentifierString('valid', 4)).toBe(false)
+ assert.strictEqual(validateIdentifierString('550e8400-e29b-41d4-a716-446655440000', 36), true)
+ assert.strictEqual(validateIdentifierString('CSMS-TXN-12345', 36), true)
+ assert.strictEqual(validateIdentifierString('a', 36), true)
+ assert.strictEqual(validateIdentifierString('abc123', 36), true)
+ assert.strictEqual(validateIdentifierString('valid-identifier', 36), true)
+ assert.strictEqual(validateIdentifierString('a'.repeat(36), 36), true)
+ assert.strictEqual(validateIdentifierString('', 36), false)
+ assert.strictEqual(validateIdentifierString('a'.repeat(37), 36), false)
+ assert.strictEqual(validateIdentifierString('a'.repeat(100), 36), false)
+ assert.strictEqual(validateIdentifierString(' ', 36), false)
+ assert.strictEqual(validateIdentifierString('\t\n', 36), false)
+ assert.strictEqual(validateIdentifierString('valid', 4), false)
})
await it('should sleep for specified milliseconds using timer mock', async t => {
const sleepPromise = sleep(delay)
t.mock.timers.tick(delay)
const timeout = await sleepPromise
- expect(timeout).toBeDefined()
- expect(typeof timeout).toBe('object')
+ assert.notStrictEqual(timeout, undefined)
+ assert.strictEqual(typeof timeout, 'object')
clearTimeout(timeout)
})
})
await it('should format milliseconds duration into human readable string', () => {
- 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')
+ assert.strictEqual(formatDurationMilliSeconds(0), '0 seconds')
+ assert.strictEqual(formatDurationMilliSeconds(900), '0 seconds')
+ assert.strictEqual(formatDurationMilliSeconds(1000), '1 second')
+ assert.strictEqual(formatDurationMilliSeconds(hoursToMilliseconds(4380)), '182 days 12 hours')
})
await it('should format seconds duration into human readable string', () => {
- 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')
+ assert.strictEqual(formatDurationSeconds(0), '0 seconds')
+ assert.strictEqual(formatDurationSeconds(0.9), '0 seconds')
+ assert.strictEqual(formatDurationSeconds(1), '1 second')
+ assert.strictEqual(formatDurationSeconds(hoursToSeconds(4380)), '182 days 12 hours')
})
await it('should validate date objects and timestamps correctly', () => {
- expect(isValidDate(undefined)).toBe(false)
- expect(isValidDate(-1)).toBe(true)
- expect(isValidDate(0)).toBe(true)
- expect(isValidDate(1)).toBe(true)
- expect(isValidDate(-0.5)).toBe(true)
- expect(isValidDate(0.5)).toBe(true)
- expect(isValidDate(new Date())).toBe(true)
+ assert.strictEqual(isValidDate(undefined), false)
+ assert.strictEqual(isValidDate(-1), true)
+ assert.strictEqual(isValidDate(0), true)
+ assert.strictEqual(isValidDate(1), true)
+ assert.strictEqual(isValidDate(-0.5), true)
+ assert.strictEqual(isValidDate(0.5), true)
+ assert.strictEqual(isValidDate(new Date()), true)
})
await it('should convert various input types to Date objects', () => {
- expect(convertToDate(undefined)).toBe(undefined)
- expect(convertToDate(null)).toBe(undefined)
- expect(() => convertToDate('')).toThrow(new Error("Cannot convert to date: ''"))
- expect(() => convertToDate('00:70:61')).toThrow(new Error("Cannot convert to date: '00:70:61'"))
- expect(convertToDate(0)).toStrictEqual(new Date('1970-01-01T00:00:00.000Z'))
- expect(convertToDate(-1)).toStrictEqual(new Date('1969-12-31T23:59:59.999Z'))
+ assert.strictEqual(convertToDate(undefined), undefined)
+ assert.strictEqual(convertToDate(null), undefined)
+ assert.throws(
+ () => {
+ convertToDate('')
+ },
+ { message: /Cannot convert to date: ''/ }
+ )
+ assert.throws(
+ () => {
+ convertToDate('00:70:61')
+ },
+ { message: /Cannot convert to date: '00:70:61'/ }
+ )
+ assert.deepStrictEqual(convertToDate(0), new Date('1970-01-01T00:00:00.000Z'))
+ assert.deepStrictEqual(convertToDate(-1), new Date('1969-12-31T23:59:59.999Z'))
const dateStr = '2020-01-01T00:00:00.000Z'
let date = convertToDate(dateStr)
- expect(date).toBeInstanceOf(Date)
- expect(date).toStrictEqual(new Date(dateStr))
+ assert.ok(date instanceof Date)
+ assert.deepStrictEqual(date, new Date(dateStr))
date = convertToDate(new Date(dateStr))
- expect(date).toBeInstanceOf(Date)
- expect(date).toStrictEqual(new Date(dateStr))
+ assert.ok(date instanceof Date)
+ assert.deepStrictEqual(date, new Date(dateStr))
})
await it('should convert various input types to integers', () => {
- expect(convertToInt(undefined)).toBe(0)
- expect(convertToInt(null)).toBe(0)
- expect(convertToInt(0)).toBe(0)
+ assert.strictEqual(convertToInt(undefined), 0)
+ assert.strictEqual(convertToInt(null), 0)
+ assert.strictEqual(convertToInt(0), 0)
const randomInteger = randomInt(Constants.MAX_RANDOM_INTEGER)
- expect(convertToInt(randomInteger)).toBe(randomInteger)
- expect(convertToInt('-1')).toBe(-1)
- expect(convertToInt('1')).toBe(1)
- expect(convertToInt('1.1')).toBe(1)
- expect(convertToInt('1.9')).toBe(1)
- expect(convertToInt('1.999')).toBe(1)
- expect(convertToInt(-1)).toBe(-1)
- expect(convertToInt(1)).toBe(1)
- expect(convertToInt(1.1)).toBe(1)
- expect(convertToInt(1.9)).toBe(1)
- expect(convertToInt(1.999)).toBe(1)
- expect(() => {
- convertToInt('NaN')
- }).toThrow("Cannot convert to integer: 'NaN'")
+ assert.strictEqual(convertToInt(randomInteger), randomInteger)
+ assert.strictEqual(convertToInt('-1'), -1)
+ assert.strictEqual(convertToInt('1'), 1)
+ assert.strictEqual(convertToInt('1.1'), 1)
+ assert.strictEqual(convertToInt('1.9'), 1)
+ assert.strictEqual(convertToInt('1.999'), 1)
+ assert.strictEqual(convertToInt(-1), -1)
+ assert.strictEqual(convertToInt(1), 1)
+ assert.strictEqual(convertToInt(1.1), 1)
+ assert.strictEqual(convertToInt(1.9), 1)
+ assert.strictEqual(convertToInt(1.999), 1)
+ assert.throws(
+ () => {
+ convertToInt('NaN')
+ },
+ { message: /Cannot convert to integer: 'NaN'/ }
+ )
})
await it('should convert various input types to floats', () => {
- expect(convertToFloat(undefined)).toBe(0)
- expect(convertToFloat(null)).toBe(0)
- expect(convertToFloat(0)).toBe(0)
+ assert.strictEqual(convertToFloat(undefined), 0)
+ assert.strictEqual(convertToFloat(null), 0)
+ assert.strictEqual(convertToFloat(0), 0)
const randomFloat = getRandomFloat()
- expect(convertToFloat(randomFloat)).toBe(randomFloat)
- expect(convertToFloat('-1')).toBe(-1)
- expect(convertToFloat('1')).toBe(1)
- expect(convertToFloat('1.1')).toBe(1.1)
- expect(convertToFloat('1.9')).toBe(1.9)
- expect(convertToFloat('1.999')).toBe(1.999)
- expect(convertToFloat(-1)).toBe(-1)
- expect(convertToFloat(1)).toBe(1)
- expect(convertToFloat(1.1)).toBe(1.1)
- expect(convertToFloat(1.9)).toBe(1.9)
- expect(convertToFloat(1.999)).toBe(1.999)
- expect(() => {
- convertToFloat('NaN')
- }).toThrow("Cannot convert to float: 'NaN'")
+ assert.strictEqual(convertToFloat(randomFloat), randomFloat)
+ assert.strictEqual(convertToFloat('-1'), -1)
+ assert.strictEqual(convertToFloat('1'), 1)
+ assert.strictEqual(convertToFloat('1.1'), 1.1)
+ assert.strictEqual(convertToFloat('1.9'), 1.9)
+ assert.strictEqual(convertToFloat('1.999'), 1.999)
+ assert.strictEqual(convertToFloat(-1), -1)
+ assert.strictEqual(convertToFloat(1), 1)
+ assert.strictEqual(convertToFloat(1.1), 1.1)
+ assert.strictEqual(convertToFloat(1.9), 1.9)
+ assert.strictEqual(convertToFloat(1.999), 1.999)
+ assert.throws(
+ () => {
+ convertToFloat('NaN')
+ },
+ { message: /Cannot convert to float: 'NaN'/ }
+ )
})
await it('should convert various input types to booleans', () => {
- expect(convertToBoolean(undefined)).toBe(false)
- expect(convertToBoolean(null)).toBe(false)
- expect(convertToBoolean('true')).toBe(true)
- expect(convertToBoolean('false')).toBe(false)
- expect(convertToBoolean('TRUE')).toBe(true)
- expect(convertToBoolean('FALSE')).toBe(false)
- expect(convertToBoolean('1')).toBe(true)
- expect(convertToBoolean('0')).toBe(false)
- expect(convertToBoolean(1)).toBe(true)
- expect(convertToBoolean(0)).toBe(false)
- expect(convertToBoolean(true)).toBe(true)
- expect(convertToBoolean(false)).toBe(false)
- expect(convertToBoolean('')).toBe(false)
- expect(convertToBoolean('NoNBoolean')).toBe(false)
+ assert.strictEqual(convertToBoolean(undefined), false)
+ assert.strictEqual(convertToBoolean(null), false)
+ assert.strictEqual(convertToBoolean('true'), true)
+ assert.strictEqual(convertToBoolean('false'), false)
+ assert.strictEqual(convertToBoolean('TRUE'), true)
+ assert.strictEqual(convertToBoolean('FALSE'), false)
+ assert.strictEqual(convertToBoolean('1'), true)
+ assert.strictEqual(convertToBoolean('0'), false)
+ assert.strictEqual(convertToBoolean(1), true)
+ assert.strictEqual(convertToBoolean(0), false)
+ assert.strictEqual(convertToBoolean(true), true)
+ assert.strictEqual(convertToBoolean(false), false)
+ assert.strictEqual(convertToBoolean(''), false)
+ assert.strictEqual(convertToBoolean('NoNBoolean'), false)
})
await it('should generate cryptographically secure random numbers between 0 and 1', () => {
const random = secureRandom()
- expect(typeof random === 'number').toBe(true)
- expect(random).toBeGreaterThanOrEqual(0)
- expect(random).toBeLessThan(1)
+ assert.ok(typeof random === 'number')
+ assert.ok(random >= 0)
+ assert.ok(random < 1)
})
await it('should round numbers to specified decimal places correctly', () => {
- expect(roundTo(0, 2)).toBe(0)
- expect(roundTo(0.5, 0)).toBe(1)
- expect(roundTo(0.5, 2)).toBe(0.5)
- expect(roundTo(-0.5, 0)).toBe(-1)
- expect(roundTo(-0.5, 2)).toBe(-0.5)
- expect(roundTo(1.005, 0)).toBe(1)
- expect(roundTo(1.005, 2)).toBe(1.01)
- expect(roundTo(2.175, 2)).toBe(2.18)
- expect(roundTo(5.015, 2)).toBe(5.02)
- expect(roundTo(-1.005, 2)).toBe(-1.01)
- expect(roundTo(-2.175, 2)).toBe(-2.18)
- expect(roundTo(-5.015, 2)).toBe(-5.02)
+ assert.strictEqual(roundTo(0, 2), 0)
+ assert.strictEqual(roundTo(0.5, 0), 1)
+ assert.strictEqual(roundTo(0.5, 2), 0.5)
+ assert.strictEqual(roundTo(-0.5, 0), -1)
+ assert.strictEqual(roundTo(-0.5, 2), -0.5)
+ assert.strictEqual(roundTo(1.005, 0), 1)
+ assert.strictEqual(roundTo(1.005, 2), 1.01)
+ assert.strictEqual(roundTo(2.175, 2), 2.18)
+ assert.strictEqual(roundTo(5.015, 2), 5.02)
+ assert.strictEqual(roundTo(-1.005, 2), -1.01)
+ assert.strictEqual(roundTo(-2.175, 2), -2.18)
+ assert.strictEqual(roundTo(-5.015, 2), -5.02)
})
await it('should generate random floats within specified range', () => {
let randomFloat = getRandomFloat()
- expect(typeof randomFloat === 'number').toBe(true)
- expect(randomFloat).toBeGreaterThanOrEqual(0)
- expect(randomFloat).toBeLessThanOrEqual(Number.MAX_VALUE)
- expect(randomFloat).not.toStrictEqual(getRandomFloat())
- expect(() => getRandomFloat(0, 1)).toThrow(new RangeError('Invalid interval'))
- expect(() => getRandomFloat(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)).toThrow(
- new RangeError('Invalid interval')
+ assert.ok(typeof randomFloat === 'number')
+ assert.ok(randomFloat >= 0)
+ assert.ok(randomFloat <= Number.MAX_VALUE)
+ assert.notDeepStrictEqual(randomFloat, getRandomFloat())
+ assert.throws(
+ () => {
+ getRandomFloat(0, 1)
+ },
+ { message: /Invalid interval/ }
+ )
+ assert.throws(
+ () => {
+ getRandomFloat(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)
+ },
+ { message: /Invalid interval/ }
)
randomFloat = getRandomFloat(0, -Number.MAX_VALUE)
- expect(randomFloat).toBeGreaterThanOrEqual(-Number.MAX_VALUE)
- expect(randomFloat).toBeLessThanOrEqual(0)
+ assert.ok(randomFloat >= -Number.MAX_VALUE)
+ assert.ok(randomFloat <= 0)
})
await it('should extract numeric values from timestamped circular buffer', () => {
- expect(
+ assert.deepStrictEqual(
extractTimeSeriesValues(
new CircularBuffer<TimestampedData>(Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY)
- )
- ).toStrictEqual([])
+ ),
+ []
+ )
const circularBuffer = new CircularBuffer<TimestampedData>(
Array,
Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY
circularBuffer.push({ timestamp: Date.now(), value: 1.1 })
circularBuffer.push({ timestamp: Date.now(), value: 2.2 })
circularBuffer.push({ timestamp: Date.now(), value: 3.3 })
- expect(extractTimeSeriesValues(circularBuffer)).toStrictEqual([1.1, 2.2, 3.3])
+ assert.deepStrictEqual(extractTimeSeriesValues(circularBuffer), [1.1, 2.2, 3.3])
})
await it('should correctly identify async functions from other types', () => {
function named () {},
]
for (const value of nonAsyncValues) {
- expect(isAsyncFunction(value)).toBe(false)
+ assert.strictEqual(isAsyncFunction(value), false)
}
const asyncValues: unknown[] = [async () => {}, async function () {}, async function named () {}]
for (const value of asyncValues) {
- expect(isAsyncFunction(value)).toBe(true)
+ assert.strictEqual(isAsyncFunction(value), true)
}
/* eslint-enable @typescript-eslint/no-empty-function */
}
const testClass = new TestClass()
/* eslint-disable @typescript-eslint/unbound-method -- Testing unbound method detection for async/sync determination */
- expect(isAsyncFunction(testClass.testSync)).toBe(false)
- expect(isAsyncFunction(testClass.testAsync)).toBe(true)
- expect(isAsyncFunction(testClass.testArrowSync)).toBe(false)
- expect(isAsyncFunction(testClass.testArrowAsync)).toBe(true)
- expect(isAsyncFunction(TestClass.testStaticSync)).toBe(false)
- expect(isAsyncFunction(TestClass.testStaticAsync)).toBe(true)
+ assert.strictEqual(isAsyncFunction(testClass.testSync), false)
+ assert.strictEqual(isAsyncFunction(testClass.testAsync), true)
+ assert.strictEqual(isAsyncFunction(testClass.testArrowSync), false)
+ assert.strictEqual(isAsyncFunction(testClass.testArrowAsync), true)
+ assert.strictEqual(isAsyncFunction(TestClass.testStaticSync), false)
+ assert.strictEqual(isAsyncFunction(TestClass.testStaticAsync), true)
/* eslint-enable @typescript-eslint/unbound-method */
})
await it('should deep clone objects, arrays, dates, maps and sets', () => {
const obj = { 1: 1 }
- expect(clone(obj)).toStrictEqual(obj)
- expect(clone(obj) === obj).toBe(false)
+ assert.deepStrictEqual(clone(obj), obj)
+ assert.ok(!(clone(obj) === obj))
const nestedObj = { 1: obj, 2: obj }
- expect(clone(nestedObj)).toStrictEqual(nestedObj)
- expect(clone(nestedObj) === nestedObj).toBe(false)
+ assert.deepStrictEqual(clone(nestedObj), nestedObj)
+ assert.ok(!(clone(nestedObj) === nestedObj))
const array = [1, 2]
- expect(clone(array)).toStrictEqual(array)
- expect(clone(array) === array).toBe(false)
+ assert.deepStrictEqual(clone(array), array)
+ assert.ok(!(clone(array) === array))
const objArray = [obj, obj]
- expect(clone(objArray)).toStrictEqual(objArray)
- expect(clone(objArray) === objArray).toBe(false)
+ assert.deepStrictEqual(clone(objArray), objArray)
+ assert.ok(!(clone(objArray) === objArray))
const date = new Date()
- expect(clone(date)).toStrictEqual(date)
- expect(clone(date) === date).toBe(false)
+ assert.deepStrictEqual(clone(date), date)
+ assert.ok(!(clone(date) === date))
if (runtime === JSRuntime.node && satisfies(version, '>=22.0.0')) {
const url = new URL('https://domain.tld')
- expect(() => clone(url)).toThrow(new Error('Cannot clone object of unsupported type.'))
+ assert.throws(
+ () => {
+ clone(url)
+ },
+ { message: /Cannot clone object of unsupported type./ }
+ )
}
const map = new Map([['1', '2']])
- expect(clone(map)).toStrictEqual(map)
- expect(clone(map) === map).toBe(false)
+ assert.deepStrictEqual(clone(map), map)
+ assert.ok(!(clone(map) === map))
const set = new Set(['1'])
- expect(clone(set)).toStrictEqual(set)
- expect(clone(set) === set).toBe(false)
+ assert.deepStrictEqual(clone(set), set)
+ assert.ok(!(clone(set) === set))
const weakMap = new WeakMap([[{ 1: 1 }, { 2: 2 }]])
- expect(() => clone(weakMap)).toThrow(new Error('#<WeakMap> could not be cloned.'))
+ assert.throws(
+ () => {
+ clone(weakMap)
+ },
+ { message: /#<WeakMap> could not be cloned./ }
+ )
const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }])
- expect(() => clone(weakSet)).toThrow(new Error('#<WeakSet> could not be cloned.'))
+ assert.throws(
+ () => {
+ clone(weakSet)
+ },
+ { message: /#<WeakSet> could not be cloned./ }
+ )
})
await it('should execute function only once regardless of call count', () => {
const fn = (): number => ++called
const onceFn = once(fn)
const result1 = onceFn()
- expect(called).toBe(1)
- expect(result1).toBe(1)
+ assert.strictEqual(called, 1)
+ assert.strictEqual(result1, 1)
const result2 = onceFn()
- expect(called).toBe(1)
- expect(result2).toBe(1)
+ assert.strictEqual(called, 1)
+ assert.strictEqual(result2, 1)
const result3 = onceFn()
- expect(called).toBe(1)
- expect(result3).toBe(1)
+ assert.strictEqual(called, 1)
+ assert.strictEqual(result3, 1)
})
await it('should check if property exists in object using has()', () => {
- expect(has('', 'test')).toBe(false)
- expect(has('test', '')).toBe(false)
- expect(has('test', 'test')).toBe(false)
- expect(has('', undefined)).toBe(false)
- expect(has('', null)).toBe(false)
- expect(has('', [])).toBe(false)
- expect(has('', {})).toBe(false)
- expect(has(1, { 1: 1 })).toBe(true)
- expect(has('1', { 1: 1 })).toBe(true)
- expect(has(2, { 1: 1 })).toBe(false)
- expect(has('2', { 1: 1 })).toBe(false)
- expect(has('1', { 1: '1' })).toBe(true)
- expect(has(1, { 1: '1' })).toBe(true)
- expect(has('2', { 1: '1' })).toBe(false)
- expect(has(2, { 1: '1' })).toBe(false)
+ assert.strictEqual(has('', 'test'), false)
+ assert.strictEqual(has('test', ''), false)
+ assert.strictEqual(has('test', 'test'), false)
+ assert.strictEqual(has('', undefined), false)
+ assert.strictEqual(has('', null), false)
+ assert.strictEqual(has('', []), false)
+ assert.strictEqual(has('', {}), false)
+ assert.strictEqual(has(1, { 1: 1 }), true)
+ assert.strictEqual(has('1', { 1: 1 }), true)
+ assert.strictEqual(has(2, { 1: 1 }), false)
+ assert.strictEqual(has('2', { 1: 1 }), false)
+ assert.strictEqual(has('1', { 1: '1' }), true)
+ assert.strictEqual(has(1, { 1: '1' }), true)
+ assert.strictEqual(has('2', { 1: '1' }), false)
+ assert.strictEqual(has(2, { 1: '1' }), false)
})
await it('should detect empty strings, objects, arrays, maps and sets', () => {
- expect(isEmpty('')).toBe(true)
- expect(isEmpty(' ')).toBe(true)
- expect(isEmpty(' ')).toBe(true)
- expect(isEmpty('test')).toBe(false)
- expect(isEmpty(' test')).toBe(false)
- expect(isEmpty('test ')).toBe(false)
- expect(isEmpty(undefined)).toBe(false)
- expect(isEmpty(null)).toBe(false)
- expect(isEmpty(0)).toBe(false)
- expect(isEmpty({})).toBe(true)
- expect(isEmpty([])).toBe(true)
- expect(isEmpty(new Map())).toBe(true)
- expect(isEmpty(new Set())).toBe(true)
- expect(isEmpty(new WeakMap())).toBe(false)
- expect(isEmpty(new WeakSet())).toBe(false)
+ assert.strictEqual(isEmpty(''), true)
+ assert.strictEqual(isEmpty(' '), true)
+ assert.strictEqual(isEmpty(' '), true)
+ assert.strictEqual(isEmpty('test'), false)
+ assert.strictEqual(isEmpty(' test'), false)
+ assert.strictEqual(isEmpty('test '), false)
+ assert.strictEqual(isEmpty(undefined), false)
+ assert.strictEqual(isEmpty(null), false)
+ assert.strictEqual(isEmpty(0), false)
+ assert.strictEqual(isEmpty({}), true)
+ assert.strictEqual(isEmpty([]), true)
+ assert.strictEqual(isEmpty(new Map()), true)
+ assert.strictEqual(isEmpty(new Set()), true)
+ assert.strictEqual(isEmpty(new WeakMap()), false)
+ assert.strictEqual(isEmpty(new WeakSet()), false)
})
await it('should detect non-empty strings correctly', () => {
- expect(isNotEmptyString('')).toBe(false)
- expect(isNotEmptyString(' ')).toBe(false)
- expect(isNotEmptyString(' ')).toBe(false)
- expect(isNotEmptyString('test')).toBe(true)
- expect(isNotEmptyString(' test')).toBe(true)
- expect(isNotEmptyString('test ')).toBe(true)
- expect(isNotEmptyString(undefined)).toBe(false)
- expect(isNotEmptyString(null)).toBe(false)
- expect(isNotEmptyString(0)).toBe(false)
- expect(isNotEmptyString({})).toBe(false)
- expect(isNotEmptyString([])).toBe(false)
- expect(isNotEmptyString(new Map())).toBe(false)
- expect(isNotEmptyString(new Set())).toBe(false)
- expect(isNotEmptyString(new WeakMap())).toBe(false)
- expect(isNotEmptyString(new WeakSet())).toBe(false)
+ assert.strictEqual(isNotEmptyString(''), false)
+ assert.strictEqual(isNotEmptyString(' '), false)
+ assert.strictEqual(isNotEmptyString(' '), false)
+ assert.strictEqual(isNotEmptyString('test'), true)
+ assert.strictEqual(isNotEmptyString(' test'), true)
+ assert.strictEqual(isNotEmptyString('test '), true)
+ assert.strictEqual(isNotEmptyString(undefined), false)
+ assert.strictEqual(isNotEmptyString(null), false)
+ assert.strictEqual(isNotEmptyString(0), false)
+ assert.strictEqual(isNotEmptyString({}), false)
+ assert.strictEqual(isNotEmptyString([]), false)
+ assert.strictEqual(isNotEmptyString(new Map()), false)
+ assert.strictEqual(isNotEmptyString(new Set()), false)
+ assert.strictEqual(isNotEmptyString(new WeakMap()), false)
+ assert.strictEqual(isNotEmptyString(new WeakSet()), false)
})
await it('should detect non-empty arrays correctly', () => {
- expect(isNotEmptyArray([])).toBe(false)
- expect(isNotEmptyArray([1, 2])).toBe(true)
- expect(isNotEmptyArray(['1', '2'])).toBe(true)
- expect(isNotEmptyArray(undefined)).toBe(false)
- expect(isNotEmptyArray(null)).toBe(false)
- expect(isNotEmptyArray('')).toBe(false)
- expect(isNotEmptyArray('test')).toBe(false)
- expect(isNotEmptyArray(0)).toBe(false)
- expect(isNotEmptyArray({})).toBe(false)
- expect(isNotEmptyArray(new Map())).toBe(false)
- expect(isNotEmptyArray(new Set())).toBe(false)
- expect(isNotEmptyArray(new WeakMap())).toBe(false)
- expect(isNotEmptyArray(new WeakSet())).toBe(false)
+ assert.strictEqual(isNotEmptyArray([]), false)
+ assert.strictEqual(isNotEmptyArray([1, 2]), true)
+ assert.strictEqual(isNotEmptyArray(['1', '2']), true)
+ assert.strictEqual(isNotEmptyArray(undefined), false)
+ assert.strictEqual(isNotEmptyArray(null), false)
+ assert.strictEqual(isNotEmptyArray(''), false)
+ assert.strictEqual(isNotEmptyArray('test'), false)
+ assert.strictEqual(isNotEmptyArray(0), false)
+ assert.strictEqual(isNotEmptyArray({}), false)
+ assert.strictEqual(isNotEmptyArray(new Map()), false)
+ assert.strictEqual(isNotEmptyArray(new Set()), false)
+ assert.strictEqual(isNotEmptyArray(new WeakMap()), false)
+ assert.strictEqual(isNotEmptyArray(new WeakSet()), false)
})
await it('should insert substring at specified index position', () => {
- expect(insertAt('test', 'ing', 'test'.length)).toBe('testing')
+ assert.strictEqual(insertAt('test', 'ing', 'test'.length), 'testing')
/* eslint-disable @cspell/spellchecker -- Testing string insertion with intentional misspelling 'ing' at position 2 */
- expect(insertAt('test', 'ing', 2)).toBe('teingst')
+ assert.strictEqual(insertAt('test', 'ing', 2), 'teingst')
/* eslint-enable @cspell/spellchecker */
})
await it('should convert to integer or return NaN for invalid input', () => {
- expect(convertToIntOrNaN(undefined)).toBe(0)
- expect(convertToIntOrNaN(null)).toBe(0)
- expect(convertToIntOrNaN('0')).toBe(0)
- expect(convertToIntOrNaN('42')).toBe(42)
- expect(convertToIntOrNaN('-7')).toBe(-7)
- expect(convertToIntOrNaN('10.9')).toBe(10)
- expect(Number.isNaN(convertToIntOrNaN('NaN'))).toBe(true)
- expect(Number.isNaN(convertToIntOrNaN('abc'))).toBe(true)
+ assert.strictEqual(convertToIntOrNaN(undefined), 0)
+ assert.strictEqual(convertToIntOrNaN(null), 0)
+ assert.strictEqual(convertToIntOrNaN('0'), 0)
+ assert.strictEqual(convertToIntOrNaN('42'), 42)
+ assert.strictEqual(convertToIntOrNaN('-7'), -7)
+ assert.strictEqual(convertToIntOrNaN('10.9'), 10)
+ assert.ok(Number.isNaN(convertToIntOrNaN('NaN')))
+ assert.ok(Number.isNaN(convertToIntOrNaN('abc')))
})
await it('should check if array is sorted according to comparator', () => {
- 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>([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false)
- expect(isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false)
+ assert.strictEqual(
+ isArraySorted<number>([], (a, b) => a - b),
+ true
+ )
+ assert.strictEqual(
+ isArraySorted<number>([1], (a, b) => a - b),
+ true
+ )
+ assert.strictEqual(
+ isArraySorted<number>([1, 2, 3, 4, 5], (a, b) => a - b),
+ true
+ )
+ assert.strictEqual(
+ isArraySorted<number>([1, 2, 3, 5, 4], (a, b) => a - b),
+ false
+ )
+ assert.strictEqual(
+ isArraySorted<number>([2, 1, 3, 4, 5], (a, b) => a - b),
+ false
+ )
})
await it('should clamp values to safe timer range (0 to MAX_SETINTERVAL_DELAY)', () => {
- expect(clampToSafeTimerValue(0)).toBe(0)
- expect(clampToSafeTimerValue(1000)).toBe(1000)
- expect(clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY)).toBe(
+ assert.strictEqual(clampToSafeTimerValue(0), 0)
+ assert.strictEqual(clampToSafeTimerValue(1000), 1000)
+ assert.strictEqual(
+ clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY),
Constants.MAX_SETINTERVAL_DELAY
)
- expect(clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY + 1)).toBe(
+ assert.strictEqual(
+ clampToSafeTimerValue(Constants.MAX_SETINTERVAL_DELAY + 1),
Constants.MAX_SETINTERVAL_DELAY
)
- expect(clampToSafeTimerValue(Number.MAX_SAFE_INTEGER)).toBe(Constants.MAX_SETINTERVAL_DELAY)
- expect(clampToSafeTimerValue(-1)).toBe(0)
- expect(clampToSafeTimerValue(-1000)).toBe(0)
+ assert.strictEqual(
+ clampToSafeTimerValue(Number.MAX_SAFE_INTEGER),
+ Constants.MAX_SETINTERVAL_DELAY
+ )
+ assert.strictEqual(clampToSafeTimerValue(-1), 0)
+ assert.strictEqual(clampToSafeTimerValue(-1000), 0)
})
// -------------------------------------------------------------------------
// retryNumber = 0: 2^0 * 100 = 100ms base
const delay0 = exponentialDelay(0)
- expect(delay0).toBeGreaterThanOrEqual(100)
- expect(delay0).toBeLessThanOrEqual(120) // 100 + 20% max jitter
+ assert.ok(delay0 >= 100)
+ assert.ok(delay0 <= 120) // 100 + 20% max jitter
// retryNumber = 1: 2^1 * 100 = 200ms base
const delay1 = exponentialDelay(1)
- expect(delay1).toBeGreaterThanOrEqual(200)
- expect(delay1).toBeLessThanOrEqual(240) // 200 + 20% max jitter
+ assert.ok(delay1 >= 200)
+ assert.ok(delay1 <= 240) // 200 + 20% max jitter
// retryNumber = 2: 2^2 * 100 = 400ms base
const delay2 = exponentialDelay(2)
- expect(delay2).toBeGreaterThanOrEqual(400)
- expect(delay2).toBeLessThanOrEqual(480) // 400 + 20% max jitter
+ assert.ok(delay2 >= 400)
+ assert.ok(delay2 <= 480) // 400 + 20% max jitter
// retryNumber = 3: 2^3 * 100 = 800ms base
const delay3 = exponentialDelay(3)
- expect(delay3).toBeGreaterThanOrEqual(800)
- expect(delay3).toBeLessThanOrEqual(960) // 800 + 20% max jitter
+ assert.ok(delay3 >= 800)
+ assert.ok(delay3 <= 960) // 800 + 20% max jitter
})
await it('should calculate exponential delay with custom delay factor', () => {
// Custom delayFactor = 50ms
const delay0 = exponentialDelay(0, 50)
- expect(delay0).toBeGreaterThanOrEqual(50)
- expect(delay0).toBeLessThanOrEqual(60) // 50 + 20% max jitter
+ assert.ok(delay0 >= 50)
+ assert.ok(delay0 <= 60) // 50 + 20% max jitter
const delay1 = exponentialDelay(1, 50)
- expect(delay1).toBeGreaterThanOrEqual(100)
- expect(delay1).toBeLessThanOrEqual(120)
+ assert.ok(delay1 >= 100)
+ assert.ok(delay1 <= 120)
// Custom delayFactor = 200ms
const delay2 = exponentialDelay(2, 200)
- expect(delay2).toBeGreaterThanOrEqual(800) // 2^2 * 200 = 800
- expect(delay2).toBeLessThanOrEqual(960)
+ assert.ok(delay2 >= 800) // 2^2 * 200 = 800
+ assert.ok(delay2 <= 960)
})
await it('should follow 2^n exponential growth pattern', () => {
for (let i = 1; i < delays.length; i++) {
const ratio = delays[i] / delays[i - 1]
// Allow for jitter variance - ratio should be roughly 2x
- expect(ratio).toBeGreaterThan(1.5)
- expect(ratio).toBeLessThan(2.5)
+ assert.ok(ratio > 1.5)
+ assert.ok(ratio < 2.5)
}
})
// With jitter, we expect at least some variation
// (unlikely to get 10 identical values with secure random)
- expect(delays.size).toBeGreaterThan(1)
+ assert.ok(delays.size > 1)
})
await it('should keep jitter within 0-20% range of base delay', () => {
const jitter = delay - baseDelay
// Jitter should be non-negative and at most 20% of base delay
- expect(jitter).toBeGreaterThanOrEqual(0)
- expect(jitter).toBeLessThanOrEqual(baseDelay * 0.2)
+ assert.ok(jitter >= 0)
+ assert.ok(jitter <= baseDelay * 0.2)
}
})
await it('should handle edge cases (default retry, large retry, small factor)', () => {
// Default retryNumber (0)
const defaultRetry = exponentialDelay()
- expect(defaultRetry).toBeGreaterThanOrEqual(100) // 2^0 * 100
- expect(defaultRetry).toBeLessThanOrEqual(120)
+ assert.ok(defaultRetry >= 100) // 2^0 * 100
+ assert.ok(defaultRetry <= 120)
// Large retry number (verify no overflow issues)
const largeRetry = exponentialDelay(10, 100)
// 2^10 * 100 = 102400ms base
- expect(largeRetry).toBeGreaterThanOrEqual(102400)
- expect(largeRetry).toBeLessThanOrEqual(122880) // 102400 + 20%
+ assert.ok(largeRetry >= 102400)
+ assert.ok(largeRetry <= 122880) // 102400 + 20%
// Very small delay factor
const smallFactor = exponentialDelay(2, 1)
- expect(smallFactor).toBeGreaterThanOrEqual(4) // 2^2 * 1
- expect(smallFactor).toBeLessThan(5) // 4 + 20%
+ assert.ok(smallFactor >= 4) // 2^2 * 1
+ assert.ok(smallFactor < 5) // 4 + 20%
})
await it('should calculate appropriate delays for WebSocket reconnection scenarios', () => {
// First reconnect attempt (retry 1)
const firstDelay = exponentialDelay(1, delayFactor)
- expect(firstDelay).toBeGreaterThanOrEqual(200) // 2^1 * 100
- expect(firstDelay).toBeLessThanOrEqual(240)
+ assert.ok(firstDelay >= 200) // 2^1 * 100
+ assert.ok(firstDelay <= 240)
// After several failures (retry 5)
const fifthDelay = exponentialDelay(5, delayFactor)
- expect(fifthDelay).toBeGreaterThanOrEqual(3200) // 2^5 * 100
- expect(fifthDelay).toBeLessThanOrEqual(3840)
+ assert.ok(fifthDelay >= 3200) // 2^5 * 100
+ assert.ok(fifthDelay <= 3840)
// Maximum practical retry (retry 10 = ~102 seconds)
const maxDelay = exponentialDelay(10, delayFactor)
- expect(maxDelay).toBeGreaterThanOrEqual(102400) // ~102 seconds
- expect(maxDelay).toBeLessThanOrEqual(122880)
+ assert.ok(maxDelay >= 102400) // ~102 seconds
+ assert.ok(maxDelay <= 122880)
})
await it('should return timestamped log prefix with optional string', () => {
const result = logPrefix()
- expect(typeof result).toBe('string')
- expect(result.length).toBeGreaterThan(0)
+ assert.strictEqual(typeof result, 'string')
+ assert.ok(result.length > 0)
const withPrefix = logPrefix(' Test |')
- expect(withPrefix).toContain(' Test |')
+ assert.ok(withPrefix.includes(' Test |'))
})
await it('should deep merge objects with source overriding target', () => {
// Simple merge
- expect(mergeDeepRight({ a: 1 }, { b: 2 })).toStrictEqual({ a: 1, b: 2 })
+ assert.deepStrictEqual(mergeDeepRight({ a: 1 }, { b: 2 }), { a: 1, b: 2 })
// Source overrides target
- expect(mergeDeepRight({ a: 1 }, { a: 2 })).toStrictEqual({ a: 2 })
+ assert.deepStrictEqual(mergeDeepRight({ a: 1 }, { a: 2 }), { a: 2 })
// Nested merge
- expect(mergeDeepRight({ a: { b: 1, c: 2 } }, { a: { c: 3, d: 4 } })).toStrictEqual({
+ assert.deepStrictEqual(mergeDeepRight({ a: { b: 1, c: 2 } }, { a: { c: 3, d: 4 } }), {
a: { b: 1, c: 3, d: 4 },
})
// Deeply nested
- expect(mergeDeepRight({ a: { b: { c: 1 } } }, { a: { b: { d: 2 } } })).toStrictEqual({
+ assert.deepStrictEqual(mergeDeepRight({ a: { b: { c: 1 } } }, { a: { b: { d: 2 } } }), {
a: { b: { c: 1, d: 2 } },
})
// Non-object source value replaces target object
- expect(mergeDeepRight({ a: { b: 1 } }, { a: 'string' })).toStrictEqual({ a: 'string' })
+ assert.deepStrictEqual(mergeDeepRight({ a: { b: 1 } }, { a: 'string' }), { a: 'string' })
// Empty objects
- expect(mergeDeepRight({}, { a: 1 })).toStrictEqual({ a: 1 })
- expect(mergeDeepRight({ a: 1 }, {})).toStrictEqual({ a: 1 })
+ assert.deepStrictEqual(mergeDeepRight({}, { a: 1 }), { a: 1 })
+ assert.deepStrictEqual(mergeDeepRight({ a: 1 }, {}), { a: 1 })
})
await it('should stringify objects with Map and Set support', () => {
// Basic object
- expect(JSONStringify({ a: 1 })).toBe('{"a":1}')
+ assert.strictEqual(JSONStringify({ a: 1 }), '{"a":1}')
// Map as array (default)
const map = new Map([['key', { value: 1 }]])
- expect(JSONStringify(map)).toBe('[["key",{"value":1}]]')
+ assert.strictEqual(JSONStringify(map), '[["key",{"value":1}]]')
// Map as object
- expect(JSONStringify(map, undefined, MapStringifyFormat.object)).toBe('{"key":{"value":1}}')
+ assert.strictEqual(
+ JSONStringify(map, undefined, MapStringifyFormat.object),
+ '{"key":{"value":1}}'
+ )
// Set
const set = new Set([{ a: 1 }])
- expect(JSONStringify(set)).toBe('[{"a":1}]')
+ assert.strictEqual(JSONStringify(set), '[{"a":1}]')
// With space formatting
- expect(JSONStringify({ a: 1 }, 2)).toBe('{\n "a": 1\n}')
+ assert.strictEqual(JSONStringify({ a: 1 }, 2), '{\n "a": 1\n}')
})
await it('should return human readable string for websocket close codes', () => {
// Known codes
- expect(getWebSocketCloseEventStatusString(1000)).toBe('Normal Closure')
- expect(getWebSocketCloseEventStatusString(1001)).toBe('Going Away')
- expect(getWebSocketCloseEventStatusString(1006)).toBe('Abnormal Closure')
- expect(getWebSocketCloseEventStatusString(1011)).toBe('Server Internal Error')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1000), 'Normal Closure')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1001), 'Going Away')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1006), 'Abnormal Closure')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1011), 'Server Internal Error')
// Ranges
- expect(getWebSocketCloseEventStatusString(0)).toBe('(Unused)')
- expect(getWebSocketCloseEventStatusString(999)).toBe('(Unused)')
- expect(getWebSocketCloseEventStatusString(1016)).toBe('(For WebSocket standard)')
- expect(getWebSocketCloseEventStatusString(1999)).toBe('(For WebSocket standard)')
- expect(getWebSocketCloseEventStatusString(2000)).toBe('(For WebSocket extensions)')
- expect(getWebSocketCloseEventStatusString(2999)).toBe('(For WebSocket extensions)')
- expect(getWebSocketCloseEventStatusString(3000)).toBe('(For libraries and frameworks)')
- expect(getWebSocketCloseEventStatusString(3999)).toBe('(For libraries and frameworks)')
- expect(getWebSocketCloseEventStatusString(4000)).toBe('(For applications)')
- expect(getWebSocketCloseEventStatusString(4999)).toBe('(For applications)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(0), '(Unused)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(999), '(Unused)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1016), '(For WebSocket standard)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(1999), '(For WebSocket standard)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(2000), '(For WebSocket extensions)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(2999), '(For WebSocket extensions)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(3000), '(For libraries and frameworks)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(3999), '(For libraries and frameworks)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(4000), '(For applications)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(4999), '(For applications)')
// Unknown
- expect(getWebSocketCloseEventStatusString(5000)).toBe('(Unknown)')
+ assert.strictEqual(getWebSocketCloseEventStatusString(5000), '(Unknown)')
})
await it('should generate random float rounded to specified scale', () => {
const result = getRandomFloatRounded(10, 0, 2)
- expect(result).toBeGreaterThanOrEqual(0)
- expect(result).toBeLessThanOrEqual(10)
+ assert.ok(result >= 0)
+ assert.ok(result <= 10)
// Check rounding to 2 decimal places
const decimalStr = result.toString()
if (decimalStr.includes('.')) {
- expect(decimalStr.split('.')[1].length).toBeLessThanOrEqual(2)
+ assert.ok(decimalStr.split('.')[1].length <= 2)
}
// Default scale
const defaultScale = getRandomFloatRounded(10, 0)
- expect(defaultScale).toBeGreaterThanOrEqual(0)
- expect(defaultScale).toBeLessThanOrEqual(10)
+ assert.ok(defaultScale >= 0)
+ assert.ok(defaultScale <= 10)
})
await it('should generate fluctuated random float within percentage range', () => {
// 0% fluctuation returns static value rounded
- expect(getRandomFloatFluctuatedRounded(100, 0)).toBe(100)
+ assert.strictEqual(getRandomFloatFluctuatedRounded(100, 0), 100)
// 10% fluctuation: 100 ± 10
const result = getRandomFloatFluctuatedRounded(100, 10)
- expect(result).toBeGreaterThanOrEqual(90)
- expect(result).toBeLessThanOrEqual(110)
+ assert.ok(result >= 90)
+ assert.ok(result <= 110)
// Invalid fluctuation percent
- expect(() => getRandomFloatFluctuatedRounded(100, -1)).toThrow(RangeError)
- expect(() => getRandomFloatFluctuatedRounded(100, 101)).toThrow(RangeError)
+ assert.throws(() => {
+ getRandomFloatFluctuatedRounded(100, -1)
+ }, RangeError)
+ assert.throws(() => {
+ getRandomFloatFluctuatedRounded(100, 101)
+ }, RangeError)
// Negative static value with fluctuation
const negResult = getRandomFloatFluctuatedRounded(-100, 10)
- expect(negResult).toBeGreaterThanOrEqual(-110)
- expect(negResult).toBeLessThanOrEqual(-90)
+ assert.ok(negResult >= -110)
+ assert.ok(negResult <= -90)
})
await it('should detect Cloud Foundry environment from VCAP_APPLICATION', () => {
const originalVcap = process.env.VCAP_APPLICATION
try {
delete process.env.VCAP_APPLICATION
- expect(isCFEnvironment()).toBe(false)
+ assert.strictEqual(isCFEnvironment(), false)
process.env.VCAP_APPLICATION = '{}'
- expect(isCFEnvironment()).toBe(true)
+ assert.strictEqual(isCFEnvironment(), true)
} finally {
if (originalVcap != null) {
process.env.VCAP_APPLICATION = originalVcap
// eslint-disable-next-line @typescript-eslint/no-empty-function -- Mock queueMicrotask with no-op to prevent actual throw
const mockFn = t.mock.method(globalThis, 'queueMicrotask', () => {})
queueMicrotaskErrorThrowing(error)
- expect(mockFn.mock.callCount()).toBe(1)
+ assert.strictEqual(mockFn.mock.callCount(), 1)
const callback = mockFn.mock.calls[0].arguments[0] as () => void
- expect(() => {
+ assert.throws(() => {
callback()
- }).toThrow(error)
+ }, error)
})
})
* @file Tests for WorkerUtils
* @description Unit tests for worker process utility functions
*/
-import { expect } from '@std/expect'
+import assert from 'node:assert/strict'
import { afterEach, describe, it } from 'node:test'
import { WorkerProcessType } from '../../src/worker/WorkerTypes.js'
await it('should validate worker process types correctly', () => {
// Valid worker process types should not throw
- expect(() => {
+ assert.doesNotThrow(() => {
checkWorkerProcessType(WorkerProcessType.dynamicPool)
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
checkWorkerProcessType(WorkerProcessType.fixedPool)
- }).not.toThrow()
- expect(() => {
+ })
+ assert.doesNotThrow(() => {
checkWorkerProcessType(WorkerProcessType.workerSet)
- }).not.toThrow()
+ })
// Invalid worker process type should throw
- expect(() => {
+ assert.throws(() => {
checkWorkerProcessType('invalidType' as WorkerProcessType)
- }).toThrow(SyntaxError)
+ }, SyntaxError)
})
await it('should return timeout object after specified delay', async t => {
const timeout = await sleepPromise
// Verify timeout object is returned
- expect(timeout).toBeDefined()
- expect(typeof timeout).toBe('object')
+ assert.notStrictEqual(timeout, undefined)
+ assert.strictEqual(typeof timeout, 'object')
// Clean up timeout
clearTimeout(timeout)
// Test successful exit (code 0)
defaultExitHandler(0)
- expect(mockConsoleInfo.mock.calls.length).toBe(1)
- expect(mockConsoleError.mock.calls.length).toBe(0)
+ assert.strictEqual(mockConsoleInfo.mock.calls.length, 1)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 0)
// Reset mocks
mockConsoleInfo.mock.resetCalls()
// Test terminated successfully (code 1)
defaultExitHandler(1)
- expect(mockConsoleInfo.mock.calls.length).toBe(1)
- expect(mockConsoleError.mock.calls.length).toBe(0)
+ assert.strictEqual(mockConsoleInfo.mock.calls.length, 1)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 0)
// Reset mocks
mockConsoleInfo.mock.resetCalls()
// Test error exit (code > 1)
defaultExitHandler(2)
- expect(mockConsoleInfo.mock.calls.length).toBe(0)
- expect(mockConsoleError.mock.calls.length).toBe(1)
+ assert.strictEqual(mockConsoleInfo.mock.calls.length, 0)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 1)
// Test another error code
mockConsoleError.mock.resetCalls()
defaultExitHandler(5)
- expect(mockConsoleError.mock.calls.length).toBe(1)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 1)
})
await it('should log error with error details', t => {
defaultErrorHandler(testError)
- expect(mockConsoleError.mock.calls.length).toBe(1)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 1)
// Test with different error types
const syntaxError = new SyntaxError('Syntax error')
defaultErrorHandler(syntaxError)
- expect(mockConsoleError.mock.calls.length).toBe(2)
+ assert.strictEqual(mockConsoleError.mock.calls.length, 2)
})
await it('should randomize delay within ±20% tolerance', () => {
results.push(randomized)
// Each result should be within ±20% of base delay
- expect(randomized).toBeGreaterThanOrEqual(baseDelay - tolerance)
- expect(randomized).toBeLessThanOrEqual(baseDelay + tolerance)
+ assert.ok(randomized >= baseDelay - tolerance)
+ assert.ok(randomized <= baseDelay + tolerance)
}
// Verify we get some variation (not all values identical)
const uniqueValues = new Set(results)
- expect(uniqueValues.size).toBeGreaterThan(1)
+ assert.ok(uniqueValues.size > 1)
// Test with zero delay
const zeroResult = randomizeDelay(0)
- expect(zeroResult).toBeGreaterThanOrEqual(-0)
- expect(zeroResult).toBeLessThanOrEqual(0)
+ assert.ok(zeroResult >= 0)
+ assert.ok(zeroResult <= 0)
// Test with negative delay (edge case)
const negativeDelay = -100
const negativeResult = randomizeDelay(negativeDelay)
const negativeTolerance = Math.abs(negativeDelay) * 0.2
- expect(negativeResult).toBeGreaterThanOrEqual(negativeDelay - negativeTolerance)
- expect(negativeResult).toBeLessThanOrEqual(negativeDelay + negativeTolerance)
+ assert.ok(negativeResult >= negativeDelay - negativeTolerance)
+ assert.ok(negativeResult <= negativeDelay + negativeTolerance)
})
})