From: Jérôme Benoit Date: Tue, 10 Mar 2026 19:44:19 +0000 (+0100) Subject: test(ocpp16): add comprehensive OCPP 1.6 unit and integration tests (#1710) X-Git-Url: https://git.piment-noir.org/?a=commitdiff_plain;h=9acc5a0aec9833fa506ef2ced4e265cb15a2d696;p=e-mobility-charging-stations-simulator.git test(ocpp16): add comprehensive OCPP 1.6 unit and integration tests (#1710) * test(ocpp16): add testable interface layer and test utilities * test(ocpp16): add constants and service utils tests * test(ocpp16): add incoming request service tests * fix(ocpp): break circular dependency in OCPPServiceUtils via dynamic imports Use dynamic await import() for OCPP16Constants and OCPP20Constants inside checkConnectorStatusTransition to break the circular dependency chain: OCPPConstants → utils/index → ErrorUtils → OCPPServiceUtils → OCPP16/20Constants → OCPPConstants This fixes OCPP16Constants.test.ts (89 tests) without breaking UIHttpServer, UIWebSocketServer, or AbstractUIService tests. * test(ocpp16): add integration tests for transactions, charging profiles, configuration, and reservations * test(ocpp16): fix lint errors, type safety, and spec traceability across all test files - Add OCPP 1.6 spec section references (§) to all 21 test files - Fix 129 no-floating-promises: add await to it()/describe() calls - Fix 17 require-await: remove async from sync callbacks - Fix 9 no-non-null-assertion: replace ! with proper null checks - Fix 8 no-unsafe-enum-comparison: use String() wrappers - Fix 13 TypeScript type errors (requestHandler mocks, SampledValueTemplate, etc.) - Remove blanket eslint-disable and 89x (t: any) in Constants test - Add @file JSDoc headers where missing Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus * test(ocpp16): migrate 21 OCPP 1.6 test files from @std/expect to node:assert/strict Replace all @std/expect imports with node:assert/strict across 21 test files (~536 expect calls), eliminating the JSR dependency that breaks CI. Migration patterns applied: - expect(a).toBe(b) → assert.strictEqual(a, b) - expect(a).toBeDefined() → assert.notStrictEqual(a, undefined) - expect(a).toStrictEqual(b) → assert.deepStrictEqual(a, b) - expect(a).toBeInstanceOf(T) → assert.ok(a instanceof T) - expect(a).toContain(x) → assert.ok(a.includes(x)) - expect(p).resolves.toBeUndefined() → await assert.doesNotReject(p) - expect(() => fn()).toThrow() → assert.throws(() => { fn() }) Added 26 null guards (if + assert.fail) after getConnectorStatus() and nullable field accesses to satisfy both TypeScript strict null checks and ESLint. All quality gates pass: 1694 tests, 0 lint errors, 0 TS errors. * test: migrate all remaining test files from @std/expect to node:assert/strict Migrate 82 remaining test files (OCPP 2.0, auth, charging-station, UI server, utils, worker, performance, exception, types) from @std/expect to node:assert/strict. Remove @std/expect JSR dependency from package.json to fix CI 404 errors on npm.jsr.io. All 1694 tests pass, lint clean, TSC clean. * chore: update lockfile after removing @std/expect dependency * docs: update test style guide and config to reflect node:assert/strict migration * test(ocpp16): eliminate as-unknown-as casts and add missing AAA comments Create typed helpers in OCPP16TestUtils (setMockRequestHandler, dispatchResponse, createMeterValuesTemplate, createCommandsSupport) to encapsulate type casts in one place instead of 26 inline casts across 8 test files. Add Arrange/Act/Assert comments to 5 tests in OCPP16ServiceUtils.test.ts that have 3+ logical setup steps. All 1694 tests pass, lint clean, TSC clean. * fix(test): use real enum value instead of as-any cast in AuthHelpers.test.ts Use AuthorizationStatus.NO_CREDIT (unhandled by getStatusMessage switch) to test the default branch, removing the as-any cast and eslint-disable comment that caused CI autofix failure. * [autofix.ci] apply automated fixes --------- Co-authored-by: Sisyphus Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- diff --git a/openspec/config.yaml b/openspec/config.yaml index d7c5fee6..1b3edaf0 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -10,7 +10,7 @@ context: | - 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 diff --git a/package.json b/package.json index b004f4a3..8c278074 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,6 @@ "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10506082..50879278 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,9 +91,6 @@ importers: '@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 @@ -1013,18 +1010,6 @@ packages: '@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==} @@ -6875,22 +6860,6 @@ snapshots: '@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)': diff --git a/src/charging-station/ocpp/1.6/__testable__/index.ts b/src/charging-station/ocpp/1.6/__testable__/index.ts new file mode 100644 index 00000000..c5b26950 --- /dev/null +++ b/src/charging-station/ocpp/1.6/__testable__/index.ts @@ -0,0 +1,296 @@ +/** + * 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 + + /** + * 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 + + /** + * 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 + + /** + * Handles OCPP 1.6 RemoteStartTransaction request from central system. + * Initiates charging transaction on specified or available connector. + */ + handleRequestRemoteStartTransaction: ( + chargingStation: ChargingStation, + commandPayload: RemoteStartTransactionRequest + ) => Promise + + /** + * 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 + + /** + * 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 + + /** + * 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), + } +} diff --git a/src/charging-station/ocpp/OCPPServiceUtils.ts b/src/charging-station/ocpp/OCPPServiceUtils.ts index ffaf5107..f6cc126f 100644 --- a/src/charging-station/ocpp/OCPPServiceUtils.ts +++ b/src/charging-station/ocpp/OCPPServiceUtils.ts @@ -67,8 +67,6 @@ import { 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' @@ -307,7 +305,7 @@ export const sendAndSetConnectorStatus = async ( return } if (options.send) { - checkConnectorStatusTransition(chargingStation, connectorId, status) + await checkConnectorStatusTransition(chargingStation, connectorId, status) await chargingStation.ocppRequestService.requestHandler< StatusNotificationRequest, StatusNotificationResponse @@ -339,15 +337,16 @@ export const restoreConnectorStatus = async ( } } -const checkConnectorStatusTransition = ( +const checkConnectorStatusTransition = async ( chargingStation: ChargingStation, connectorId: number, status: ConnectorStatusEnum -): boolean => { +): Promise => { 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( @@ -361,8 +360,10 @@ const checkConnectorStatusTransition = ( 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( @@ -376,6 +377,7 @@ const checkConnectorStatusTransition = ( transitionAllowed = true } break + } default: throw new OCPPError( ErrorType.INTERNAL_ERROR, diff --git a/tests/TEST_STYLE_GUIDE.md b/tests/TEST_STYLE_GUIDE.md index d3f8e820..01c7c5aa 100644 --- a/tests/TEST_STYLE_GUIDE.md +++ b/tests/TEST_STYLE_GUIDE.md @@ -7,7 +7,7 @@ Conventions for writing maintainable, consistent tests in the e-mobility chargin - **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 --- @@ -60,7 +60,7 @@ it('should calculate total power correctly', () => { const actualPower = station.getTotalPower() // Assert - expect(actualPower).toBe(expectedPower) + assert.strictEqual(actualPower, expectedPower) }) ``` @@ -141,12 +141,12 @@ afterEach(() => { 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/ }) }) ``` @@ -158,7 +158,7 @@ it('should timeout', async t => { 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/ }) }) }) @@ -232,15 +232,15 @@ Available constants: `tests/charging-station/ChargingStationTestConstants.ts` ```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 ``` --- @@ -263,7 +263,7 @@ testable.buildRequestPayload(station, command) ```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) ``` --- @@ -288,7 +288,7 @@ const { station, mocks } = createMockChargingStation({ }) // Verify sent messages -expect(mocks.webSocket.sentMessages).toContain(expectedMessage) +assert.ok(mocks.webSocket.sentMessages.includes(expectedMessage)) ``` --- @@ -344,7 +344,7 @@ expect(mocks.webSocket.sentMessages).toContain(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 diff --git a/tests/charging-station/AutomaticTransactionGenerator.test.ts b/tests/charging-station/AutomaticTransactionGenerator.test.ts index b3bd6eac..e6eb6943 100644 --- a/tests/charging-station/AutomaticTransactionGenerator.test.ts +++ b/tests/charging-station/AutomaticTransactionGenerator.test.ts @@ -14,7 +14,7 @@ * 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' @@ -72,7 +72,7 @@ function getConnectorStatus ( connectorId: number ): NonNullable { 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`) } @@ -86,7 +86,7 @@ function getConnectorStatus ( */ 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') } @@ -146,7 +146,7 @@ await describe('AutomaticTransactionGenerator', async () => { 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', () => { @@ -155,7 +155,7 @@ await describe('AutomaticTransactionGenerator', async () => { const atg1 = AutomaticTransactionGenerator.getInstance(station) const atg2 = AutomaticTransactionGenerator.getInstance(station) - expect(atg1).toBe(atg2) + assert.strictEqual(atg1, atg2) }) await it('should delete an instance', () => { @@ -165,7 +165,7 @@ await describe('AutomaticTransactionGenerator', async () => { AutomaticTransactionGenerator.deleteInstance(station) const atg2 = AutomaticTransactionGenerator.getInstance(station) - expect(atg1).not.toBe(atg2) + assert.notStrictEqual(atg1, atg2) }) }) @@ -177,7 +177,7 @@ await describe('AutomaticTransactionGenerator', async () => { atg.start() - expect(atg.started).toBe(true) + assert.strictEqual(atg.started, true) }) await it('should not start when station is not started', () => { @@ -186,7 +186,7 @@ await describe('AutomaticTransactionGenerator', async () => { atg.start() - expect(atg.started).toBe(false) + assert.strictEqual(atg.started, false) }) await it('should warn and not restart when already started', () => { @@ -197,7 +197,7 @@ await describe('AutomaticTransactionGenerator', async () => { atg.start() atg.start() - expect(atg.started).toBe(true) + assert.strictEqual(atg.started, true) }) }) @@ -208,10 +208,10 @@ await describe('AutomaticTransactionGenerator', async () => { 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', () => { @@ -220,7 +220,7 @@ await describe('AutomaticTransactionGenerator', async () => { atg.stop() - expect(atg.started).toBe(false) + assert.strictEqual(atg.started, false) }) }) @@ -233,16 +233,16 @@ await describe('AutomaticTransactionGenerator', async () => { 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) }) }) @@ -258,9 +258,9 @@ await describe('AutomaticTransactionGenerator', async () => { 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', () => { @@ -274,9 +274,9 @@ await describe('AutomaticTransactionGenerator', async () => { 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) }) }) }) diff --git a/tests/charging-station/ChargingStation-Configuration.test.ts b/tests/charging-station/ChargingStation-Configuration.test.ts index 089715ac..9cb52120 100644 --- a/tests/charging-station/ChargingStation-Configuration.test.ts +++ b/tests/charging-station/ChargingStation-Configuration.test.ts @@ -2,7 +2,7 @@ * @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' @@ -37,9 +37,11 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -49,7 +51,7 @@ await describe('ChargingStation Configuration Management', async () => { 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 = { @@ -59,8 +61,8 @@ await describe('ChargingStation Configuration Management', async () => { } // 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 @@ -74,8 +76,8 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -87,8 +89,8 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -98,7 +100,7 @@ await describe('ChargingStation Configuration Management', async () => { bootNotificationStatus: RegistrationStatusEnumType.PENDING, }) station = result.station - expect(station.inPendingState()).toBe(true) + assert.strictEqual(station.inPendingState(), true) // Act - Simulate receiving Rejected response station.bootNotificationResponse = { @@ -108,8 +110,8 @@ await describe('ChargingStation Configuration Management', async () => { } // 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) }) }) @@ -135,9 +137,11 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -154,11 +158,11 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -168,7 +172,7 @@ await describe('ChargingStation Configuration Management', async () => { 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 = { @@ -178,8 +182,8 @@ await describe('ChargingStation Configuration Management', async () => { } // 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 @@ -191,8 +195,8 @@ await describe('ChargingStation Configuration Management', async () => { 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 @@ -209,10 +213,10 @@ await describe('ChargingStation Configuration Management', async () => { }) // 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) @@ -235,8 +239,8 @@ await describe('ChargingStation Configuration Management', async () => { } // 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) }) }) @@ -261,7 +265,7 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -270,7 +274,7 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -279,7 +283,7 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -289,7 +293,7 @@ await describe('ChargingStation Configuration Management', async () => { // 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', () => { @@ -299,7 +303,7 @@ await describe('ChargingStation Configuration Management', async () => { // Act & Assert - getLocalAuthListEnabled returns boolean const localAuthEnabled = station.getLocalAuthListEnabled() - expect(typeof localAuthEnabled).toBe('boolean') + assert.strictEqual(typeof localAuthEnabled, 'boolean') }) // === Configuration Save Operations === @@ -310,7 +314,9 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -319,9 +325,9 @@ await describe('ChargingStation Configuration Management', async () => { 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 === @@ -331,15 +337,15 @@ await describe('ChargingStation Configuration Management', async () => { 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) @@ -352,10 +358,12 @@ await describe('ChargingStation Configuration Management', async () => { // 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) } }) @@ -367,7 +375,7 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -376,8 +384,8 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -389,10 +397,10 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -401,8 +409,8 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -415,10 +423,10 @@ await describe('ChargingStation Configuration Management', async () => { 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) }) }) @@ -444,13 +452,13 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -463,7 +471,7 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -476,7 +484,7 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -485,13 +493,13 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -504,7 +512,7 @@ await describe('ChargingStation Configuration Management', async () => { station.closeWSConnection() // Assert - no error, connection remains null - expect(station.wsConnection).toBeNull() + assert.strictEqual(station.wsConnection, null) }) // === Message Capture Tests === @@ -520,9 +528,10 @@ await describe('ChargingStation Configuration Management', async () => { 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}]' ) }) @@ -538,7 +547,10 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -548,7 +560,7 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -562,8 +574,8 @@ await describe('ChargingStation Configuration Management', async () => { // 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()', () => { @@ -575,14 +587,14 @@ await describe('ChargingStation Configuration Management', async () => { // 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 === @@ -603,8 +615,8 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -626,8 +638,8 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -646,8 +658,8 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -667,8 +679,8 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -689,8 +701,8 @@ await describe('ChargingStation Configuration Management', async () => { 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()', () => { @@ -711,8 +723,8 @@ await describe('ChargingStation Configuration Management', async () => { 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 === @@ -727,9 +739,12 @@ await describe('ChargingStation Configuration Management', async () => { 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', () => { @@ -739,9 +754,9 @@ await describe('ChargingStation Configuration Management', async () => { 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) }) }) @@ -768,8 +783,8 @@ await describe('ChargingStation Configuration Management', async () => { 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') }) }) @@ -783,7 +798,7 @@ await describe('ChargingStation Configuration Management', async () => { station.restartWebSocketPing() // Assert - should complete without error - expect(station).toBeDefined() + assert.notStrictEqual(station, undefined) }) }) }) diff --git a/tests/charging-station/ChargingStation-Connectors.test.ts b/tests/charging-station/ChargingStation-Connectors.test.ts index 00776945..adfd2747 100644 --- a/tests/charging-station/ChargingStation-Connectors.test.ts +++ b/tests/charging-station/ChargingStation-Connectors.test.ts @@ -2,7 +2,7 @@ * @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' @@ -31,18 +31,18 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -52,16 +52,16 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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()', () => { @@ -69,15 +69,15 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -85,14 +85,14 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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) }) }) @@ -114,8 +114,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -123,7 +123,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { station = result.station // Initially connector 0 is operative - expect(station.isChargingStationAvailable()).toBe(true) + assert.strictEqual(station.isChargingStationAvailable(), true) }) }) @@ -147,8 +147,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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', () => { @@ -158,8 +158,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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) }) }) @@ -183,7 +183,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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', () => { @@ -193,7 +193,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) station = result.station - expect(station.getNumberOfEvses()).toBe(1) + assert.strictEqual(station.getNumberOfEvses(), 1) }) await it('should return connector status via getConnectorStatus() in EVSE mode', () => { @@ -207,8 +207,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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()', () => { @@ -219,8 +219,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -230,7 +230,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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', () => { @@ -242,9 +242,11 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -254,7 +256,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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', () => { @@ -264,8 +266,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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', () => { @@ -275,7 +277,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { }) 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()', () => { @@ -286,7 +288,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { station = result.station // Should return total connectors across all EVSEs - expect(station.getNumberOfConnectors()).toBe(4) + assert.strictEqual(station.getNumberOfConnectors(), 4) }) }) @@ -311,10 +313,10 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -325,10 +327,10 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -339,10 +341,10 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -357,7 +359,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { } // Assert - only check inUnknownState - expect(station.inUnknownState()).toBe(true) + assert.strictEqual(station.inUnknownState(), true) }) await it('should allow state transitions from PENDING to ACCEPTED', () => { @@ -366,7 +368,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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 @@ -375,8 +377,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -385,7 +387,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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 @@ -394,8 +396,8 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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) }) }) @@ -428,9 +430,11 @@ await describe('ChargingStation Connector and EVSE State', async () => { // 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 () => { @@ -456,9 +460,11 @@ await describe('ChargingStation Connector and EVSE State', 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 () => { @@ -479,7 +485,7 @@ await describe('ChargingStation Connector and EVSE State', 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 () => { @@ -500,7 +506,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { // Assert const found = station.getReservationBy('reservationId', 401) - expect(found).toBeUndefined() + assert.strictEqual(found, undefined) }) await it('should query reservation by reservationId', async () => { @@ -519,9 +525,11 @@ await describe('ChargingStation Connector and EVSE State', 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 () => { @@ -540,9 +548,11 @@ await describe('ChargingStation Connector and EVSE State', 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 () => { @@ -561,9 +571,11 @@ await describe('ChargingStation Connector and EVSE State', 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 () => { @@ -582,7 +594,7 @@ await describe('ChargingStation Connector and EVSE State', 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', () => { @@ -594,7 +606,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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', () => { @@ -606,7 +618,7 @@ await describe('ChargingStation Connector and EVSE State', async () => { 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 () => { @@ -633,10 +645,10 @@ await describe('ChargingStation Connector and EVSE State', 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) }) }) }) diff --git a/tests/charging-station/ChargingStation-Lifecycle.test.ts b/tests/charging-station/ChargingStation-Lifecycle.test.ts index af03adac..1f2536d6 100644 --- a/tests/charging-station/ChargingStation-Lifecycle.test.ts +++ b/tests/charging-station/ChargingStation-Lifecycle.test.ts @@ -2,7 +2,7 @@ * @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' @@ -35,8 +35,8 @@ await describe('ChargingStation Lifecycle', async () => { 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', () => { @@ -51,8 +51,8 @@ await describe('ChargingStation Lifecycle', async () => { 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()', () => { @@ -62,11 +62,11 @@ await describe('ChargingStation Lifecycle', async () => { // 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 () => { @@ -74,13 +74,13 @@ await describe('ChargingStation Lifecycle', 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 () => { @@ -88,13 +88,13 @@ await describe('ChargingStation Lifecycle', 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 () => { @@ -104,14 +104,14 @@ await describe('ChargingStation Lifecycle', 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 () => { @@ -119,13 +119,13 @@ await describe('ChargingStation Lifecycle', 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 () => { @@ -133,15 +133,15 @@ await describe('ChargingStation Lifecycle', 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', () => { @@ -156,7 +156,7 @@ await describe('ChargingStation Lifecycle', async () => { // 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) @@ -180,15 +180,15 @@ await describe('ChargingStation Lifecycle', async () => { // 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 () => { @@ -196,14 +196,14 @@ await describe('ChargingStation Lifecycle', 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 () => { @@ -220,15 +220,15 @@ await describe('ChargingStation Lifecycle', 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) }) }) }) diff --git a/tests/charging-station/ChargingStation-Resilience.test.ts b/tests/charging-station/ChargingStation-Resilience.test.ts index 726e1404..d9c6394f 100644 --- a/tests/charging-station/ChargingStation-Resilience.test.ts +++ b/tests/charging-station/ChargingStation-Resilience.test.ts @@ -2,7 +2,7 @@ * @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' @@ -45,7 +45,7 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -59,7 +59,7 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -69,13 +69,13 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -84,7 +84,7 @@ await describe('ChargingStation Resilience', async () => { 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 }) @@ -100,7 +100,7 @@ await describe('ChargingStation Resilience', async () => { stationWithRetryCount.wsConnectionRetryCount = 0 // Assert - expect(stationWithRetryCount.wsConnectionRetryCount).toBe(0) + assert.strictEqual(stationWithRetryCount.wsConnectionRetryCount, 0) }) // ------------------------------------------------------------------------- @@ -118,7 +118,7 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -137,7 +137,7 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -159,7 +159,7 @@ await describe('ChargingStation Resilience', async () => { ]) // 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 @@ -175,7 +175,7 @@ await describe('ChargingStation Resilience', async () => { 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 @@ -196,8 +196,8 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -209,11 +209,14 @@ await describe('ChargingStation Resilience', async () => { 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', () => { @@ -234,8 +237,8 @@ await describe('ChargingStation Resilience', async () => { // 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) }) // ------------------------------------------------------------------------- @@ -253,7 +256,7 @@ await describe('ChargingStation Resilience', async () => { // 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', () => { @@ -270,7 +273,7 @@ await describe('ChargingStation Resilience', async () => { cleanupChargingStation(station) // Assert - Timer should be cleared - expect(station.heartbeatSetInterval).toBeUndefined() + assert.strictEqual(station.heartbeatSetInterval, undefined) }) await it('should clear pending requests on cleanup', () => { @@ -299,7 +302,7 @@ await describe('ChargingStation Resilience', async () => { cleanupChargingStation(station) // Assert - Requests should be cleared - expect(station.requests.size).toBe(0) + assert.strictEqual(station.requests.size, 0) }) }) @@ -337,9 +340,9 @@ await describe('ChargingStation Resilience', async () => { // 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', () => { @@ -359,7 +362,7 @@ await describe('ChargingStation Resilience', async () => { // 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', () => { @@ -381,11 +384,11 @@ await describe('ChargingStation Resilience', async () => { // 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', () => { @@ -410,9 +413,9 @@ await describe('ChargingStation Resilience', async () => { // 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]) } }) @@ -433,13 +436,15 @@ await describe('ChargingStation Resilience', async () => { // 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()}` + ) ) }) @@ -464,8 +469,8 @@ await describe('ChargingStation Resilience', async () => { // 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', () => { @@ -483,7 +488,7 @@ await describe('ChargingStation Resilience', async () => { 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) { @@ -491,7 +496,7 @@ await describe('ChargingStation Resilience', async () => { } // 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', () => { @@ -518,10 +523,12 @@ await describe('ChargingStation Resilience', async () => { // 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()}` + ) ) }) }) diff --git a/tests/charging-station/ChargingStation-Transactions.test.ts b/tests/charging-station/ChargingStation-Transactions.test.ts index 24f42b45..37ea1bc8 100644 --- a/tests/charging-station/ChargingStation-Transactions.test.ts +++ b/tests/charging-station/ChargingStation-Transactions.test.ts @@ -2,7 +2,7 @@ * @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' @@ -35,7 +35,7 @@ await describe('ChargingStation Transaction Management', async () => { const connectorId = station.getConnectorIdByTransactionId(12345) // Assert - expect(connectorId).toBeUndefined() + assert.strictEqual(connectorId, undefined) }) await it('should return connector id for getConnectorIdByTransactionId with active transaction', () => { @@ -53,7 +53,7 @@ await describe('ChargingStation Transaction Management', async () => { const connectorId = station.getConnectorIdByTransactionId(100) // Assert - expect(connectorId).toBe(1) + assert.strictEqual(connectorId, 1) }) await it('should return undefined for getConnectorIdByTransactionId with undefined transactionId', () => { @@ -65,7 +65,7 @@ await describe('ChargingStation Transaction Management', async () => { const connectorId = station.getConnectorIdByTransactionId(undefined) // Assert - expect(connectorId).toBeUndefined() + assert.strictEqual(connectorId, undefined) }) await it('should return undefined for getEvseIdByTransactionId in non-EVSE mode', () => { @@ -85,7 +85,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -107,7 +107,7 @@ await describe('ChargingStation Transaction Management', async () => { const evseId = station.getEvseIdByTransactionId(200) // Assert - expect(evseId).toBe(1) + assert.strictEqual(evseId, 1) }) await it('should return idTag for getTransactionIdTag with active transaction', () => { @@ -125,7 +125,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -137,7 +137,7 @@ await describe('ChargingStation Transaction Management', async () => { const idTag = station.getTransactionIdTag(999) // Assert - expect(idTag).toBeUndefined() + assert.strictEqual(idTag, undefined) }) await it('should return zero for getNumberOfRunningTransactions with no transactions', () => { @@ -149,7 +149,7 @@ await describe('ChargingStation Transaction Management', async () => { const count = station.getNumberOfRunningTransactions() // Assert - expect(count).toBe(0) + assert.strictEqual(count, 0) }) await it('should return correct count for getNumberOfRunningTransactions with active transactions', () => { @@ -172,7 +172,7 @@ await describe('ChargingStation Transaction Management', async () => { const count = station.getNumberOfRunningTransactions() // Assert - expect(count).toBe(2) + assert.strictEqual(count, 2) }) }) @@ -198,7 +198,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -216,7 +216,7 @@ await describe('ChargingStation Transaction Management', async () => { const energy = station.getEnergyActiveImportRegisterByConnectorId(1) // Assert - expect(energy).toBe(12500) + assert.strictEqual(energy, 12500) }) await it('should return rounded energy value when rounded=true', () => { @@ -234,7 +234,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -246,7 +246,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -258,7 +258,7 @@ await describe('ChargingStation Transaction Management', async () => { const energy = station.getEnergyActiveImportRegisterByTransactionId(999) // Assert - expect(energy).toBe(0) + assert.strictEqual(energy, 0) }) await it('should return energy for getEnergyActiveImportRegisterByTransactionId with active transaction', () => { @@ -276,7 +276,7 @@ await describe('ChargingStation Transaction Management', async () => { const energy = station.getEnergyActiveImportRegisterByTransactionId(400) // Assert - expect(energy).toBe(25000) + assert.strictEqual(energy, 25000) }) }) @@ -323,22 +323,22 @@ await describe('ChargingStation Transaction Management', async () => { } // 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', () => { @@ -367,19 +367,19 @@ await describe('ChargingStation Transaction Management', async () => { } // 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', () => { @@ -405,7 +405,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -427,7 +427,7 @@ await describe('ChargingStation Transaction Management', async () => { 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', () => { @@ -446,8 +446,8 @@ await describe('ChargingStation Transaction Management', async () => { 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) }) }) @@ -477,8 +477,8 @@ await describe('ChargingStation Transaction Management', async () => { 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') }) }) @@ -498,9 +498,9 @@ await describe('ChargingStation Transaction Management', async () => { 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) }) }) @@ -520,7 +520,7 @@ await describe('ChargingStation Transaction Management', async () => { const secondInterval = station.heartbeatSetInterval // Assert - interval should be same (not restarted) - expect(firstInterval).toBe(secondInterval) + assert.strictEqual(firstInterval, secondInterval) }) }) @@ -540,8 +540,8 @@ await describe('ChargingStation Transaction Management', async () => { // 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') } }) }) @@ -564,9 +564,9 @@ await describe('ChargingStation Transaction Management', async () => { 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) }) }) @@ -586,7 +586,7 @@ await describe('ChargingStation Transaction Management', async () => { station.stopMeterValues(1) // Assert - interval should be cleared - expect(connector1?.transactionSetInterval).toBeUndefined() + assert.strictEqual(connector1?.transactionSetInterval, undefined) }) }) @@ -609,8 +609,8 @@ await describe('ChargingStation Transaction Management', async () => { // 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') } }) }) @@ -634,7 +634,7 @@ await describe('ChargingStation Transaction Management', async () => { station.stopTxUpdatedInterval(1) // Assert - interval should be cleared - expect(connector1?.transactionTxUpdatedSetInterval).toBeUndefined() + assert.strictEqual(connector1?.transactionTxUpdatedSetInterval, undefined) }) }) }) diff --git a/tests/charging-station/ChargingStation.test.ts b/tests/charging-station/ChargingStation.test.ts index 0c8cecf5..d1fa3ad0 100644 --- a/tests/charging-station/ChargingStation.test.ts +++ b/tests/charging-station/ChargingStation.test.ts @@ -8,7 +8,7 @@ * - 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' @@ -39,9 +39,9 @@ await describe('ChargingStation', async () => { 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) }) @@ -51,8 +51,8 @@ await describe('ChargingStation', async () => { 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) }) @@ -64,8 +64,8 @@ await describe('ChargingStation', async () => { }) 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) }) @@ -74,9 +74,9 @@ await describe('ChargingStation', async () => { 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) }) @@ -85,10 +85,10 @@ await describe('ChargingStation', async () => { 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) }) @@ -98,21 +98,21 @@ await describe('ChargingStation', async () => { 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) }) }) @@ -137,7 +137,7 @@ await describe('ChargingStation', async () => { // Start station station.start() - expect(station.started).toBe(true) + assert.strictEqual(station.started, true) // Set up transaction const connector1 = station.getConnectorStatus(1) @@ -149,15 +149,16 @@ await describe('ChargingStation', async () => { } // 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 @@ -174,16 +175,16 @@ await describe('ChargingStation', async () => { // 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 @@ -198,8 +199,8 @@ await describe('ChargingStation', async () => { 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 = { @@ -212,12 +213,12 @@ await describe('ChargingStation', async () => { // 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 @@ -233,8 +234,8 @@ await describe('ChargingStation', async () => { 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 = { @@ -244,8 +245,8 @@ await describe('ChargingStation', async () => { } // 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 @@ -260,7 +261,7 @@ await describe('ChargingStation', async () => { // 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) @@ -271,7 +272,7 @@ await describe('ChargingStation', async () => { // 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) }) diff --git a/tests/charging-station/ConfigurationKeyUtils.test.ts b/tests/charging-station/ConfigurationKeyUtils.test.ts index cd38d713..65a93d5a 100644 --- a/tests/charging-station/ConfigurationKeyUtils.test.ts +++ b/tests/charging-station/ConfigurationKeyUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -34,7 +34,7 @@ await describe('ConfigurationKeyUtils', async () => { cs.ocppConfiguration = {} as Partial // 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)', () => { @@ -46,8 +46,11 @@ await describe('ConfigurationKeyUtils', async () => { 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)', () => { @@ -56,7 +59,7 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -68,7 +71,10 @@ await describe('ConfigurationKeyUtils', async () => { 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) }) }) @@ -83,7 +89,7 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -95,12 +101,14 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -118,9 +126,12 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -141,13 +152,16 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -174,11 +188,14 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -197,10 +214,13 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -219,8 +239,11 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -237,9 +260,9 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -251,7 +274,7 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -270,7 +293,7 @@ await describe('ConfigurationKeyUtils', async () => { ) // Assert - expect(saveMock.mock.calls.length).toBe(1) + assert.strictEqual(saveMock.mock.calls.length, 1) }) }) @@ -284,8 +307,8 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -299,8 +322,8 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -313,8 +336,8 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -326,7 +349,7 @@ await describe('ConfigurationKeyUtils', async () => { const updated = setConfigurationKeyValue(cs, MIXED_CASE_KEY.toLowerCase(), VALUE_B, true) // Assert - expect(updated?.value).toBe(VALUE_B) + assert.strictEqual(updated?.value, VALUE_B) }) }) @@ -341,7 +364,7 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -352,7 +375,7 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -365,11 +388,14 @@ await describe('ConfigurationKeyUtils', async () => { 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 => { @@ -382,8 +408,11 @@ await describe('ConfigurationKeyUtils', async () => { 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', () => { @@ -398,8 +427,11 @@ await describe('ConfigurationKeyUtils', async () => { }) // 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) }) }) @@ -414,9 +446,9 @@ await describe('ConfigurationKeyUtils', async () => { 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) }) }) }) diff --git a/tests/charging-station/Helpers.test.ts b/tests/charging-station/Helpers.test.ts index b3747d37..8ff2332c 100644 --- a/tests/charging-station/Helpers.test.ts +++ b/tests/charging-station/Helpers.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -21,7 +21,6 @@ import { hasReservationExpired, validateStationInfo, } from '../../src/charging-station/Helpers.js' -import { BaseError } from '../../src/exception/index.js' import { AvailabilityType, type ChargingStationConfiguration, @@ -61,13 +60,15 @@ await describe('Helpers', async () => { }) 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' ) }) @@ -82,9 +83,12 @@ await describe('Helpers', async () => { 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', () => { @@ -96,9 +100,12 @@ await describe('Helpers', async () => { 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', () => { @@ -109,9 +116,12 @@ await describe('Helpers', async () => { }) // 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', () => { @@ -122,9 +132,12 @@ await describe('Helpers', async () => { }) // 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', () => { @@ -139,12 +152,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -160,12 +172,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -182,12 +193,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -204,12 +214,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -227,12 +236,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -250,12 +258,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -274,12 +281,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -298,12 +304,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -323,12 +328,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -348,12 +352,11 @@ await describe('Helpers', async () => { }) // 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/ } ) }) @@ -373,9 +376,9 @@ await describe('Helpers', async () => { }) // Act & Assert - expect(() => { + assert.doesNotThrow(() => { validateStationInfo(validStation) - }).not.toThrow() + }) }) await it('should throw for OCPP 2.0 without EVSE configuration', () => { @@ -397,12 +400,14 @@ await describe('Helpers', async () => { }) // 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/, + } ) }) @@ -425,12 +430,14 @@ await describe('Helpers', async () => { }) // 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/, + } ) }) @@ -447,8 +454,8 @@ await describe('Helpers', async () => { 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 => { @@ -464,8 +471,8 @@ await describe('Helpers', async () => { 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 => { @@ -481,28 +488,28 @@ await describe('Helpers', async () => { 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 => { @@ -511,18 +518,22 @@ await describe('Helpers', async () => { 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 => { @@ -530,16 +541,20 @@ await describe('Helpers', async () => { 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 => { @@ -548,13 +563,13 @@ await describe('Helpers', async () => { 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', () => { @@ -566,7 +581,8 @@ await describe('Helpers', async () => { const connectorStatus = {} as ConnectorStatus // Act & Assert - expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( + assert.strictEqual( + getBootConnectorStatus(chargingStation, 1, connectorStatus), ConnectorStatusEnum.Available ) }) @@ -582,7 +598,8 @@ await describe('Helpers', async () => { } as ConnectorStatus // Act & Assert - expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( + assert.strictEqual( + getBootConnectorStatus(chargingStation, 1, connectorStatus), ConnectorStatusEnum.Unavailable ) }) @@ -599,7 +616,8 @@ await describe('Helpers', async () => { } as ConnectorStatus // Act & Assert - expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( + assert.strictEqual( + getBootConnectorStatus(chargingStation, 1, connectorStatus), ConnectorStatusEnum.Unavailable ) }) @@ -617,7 +635,8 @@ await describe('Helpers', async () => { } as ConnectorStatus // Act & Assert - expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( + assert.strictEqual( + getBootConnectorStatus(chargingStation, 1, connectorStatus), ConnectorStatusEnum.Unavailable ) }) @@ -635,7 +654,8 @@ await describe('Helpers', async () => { } as ConnectorStatus // Act & Assert - expect(getBootConnectorStatus(chargingStation, 1, connectorStatus)).toBe( + assert.strictEqual( + getBootConnectorStatus(chargingStation, 1, connectorStatus), ConnectorStatusEnum.Charging ) }) @@ -653,33 +673,34 @@ await describe('Helpers', async () => { } 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)', () => { @@ -687,7 +708,7 @@ await describe('Helpers', async () => { 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)', () => { @@ -702,7 +723,7 @@ await describe('Helpers', async () => { } // Act & Assert - expect(hasPendingReservations(chargingStation)).toBe(true) + assert.strictEqual(hasPendingReservations(chargingStation), true) }) await it('should return false when no reservations exist (EVSE mode)', () => { @@ -714,7 +735,7 @@ await describe('Helpers', async () => { }) // Act & Assert - expect(hasPendingReservations(chargingStation)).toBe(false) + assert.strictEqual(hasPendingReservations(chargingStation), false) }) await it('should return true when pending reservation exists (EVSE mode)', () => { @@ -731,7 +752,7 @@ await describe('Helpers', async () => { } // Act & Assert - expect(hasPendingReservations(chargingStation)).toBe(true) + assert.strictEqual(hasPendingReservations(chargingStation), true) }) await it('should return false when only expired reservations exist (EVSE mode)', () => { @@ -748,6 +769,6 @@ await describe('Helpers', async () => { } // Act & Assert - expect(hasPendingReservations(chargingStation)).toBe(false) + assert.strictEqual(hasPendingReservations(chargingStation), false) }) }) diff --git a/tests/charging-station/IdTagsCache.test.ts b/tests/charging-station/IdTagsCache.test.ts index 316a2834..6ff8d73b 100644 --- a/tests/charging-station/IdTagsCache.test.ts +++ b/tests/charging-station/IdTagsCache.test.ts @@ -9,7 +9,7 @@ * - 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' @@ -77,7 +77,7 @@ await describe('IdTagsCache', async () => { const instance1 = IdTagsCache.getInstance() const instance2 = IdTagsCache.getInstance() - expect(instance1).toBe(instance2) + assert.strictEqual(instance1, instance2) }) await it('should create new instance after reset', () => { @@ -85,7 +85,7 @@ await describe('IdTagsCache', async () => { resetIdTagsCache() const instance2 = IdTagsCache.getInstance() - expect(instance1).not.toBe(instance2) + assert.notStrictEqual(instance1, instance2) }) }) @@ -97,7 +97,7 @@ await describe('IdTagsCache', async () => { 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', () => { @@ -109,7 +109,7 @@ await describe('IdTagsCache', async () => { 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 }) @@ -121,7 +121,7 @@ await describe('IdTagsCache', async () => { const result = cache.getIdTags('') - expect(result).toStrictEqual([]) + assert.deepStrictEqual(result, []) cache.deleteIdTags('') }) }) @@ -139,9 +139,9 @@ await describe('IdTagsCache', async () => { 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', () => { @@ -158,7 +158,7 @@ await describe('IdTagsCache', async () => { const tag4 = cache.getIdTag(IdTagDistribution.ROUND_ROBIN, station, 1) - expect(tag4).toBe('TAG-001') + assert.strictEqual(tag4, 'TAG-001') }) }) @@ -177,7 +177,7 @@ await describe('IdTagsCache', async () => { } for (const tag of results) { - expect(TEST_ID_TAGS.includes(tag)).toBe(true) + assert.ok(TEST_ID_TAGS.includes(tag)) } }) }) @@ -195,7 +195,7 @@ await describe('IdTagsCache', async () => { // 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', () => { @@ -212,8 +212,8 @@ await describe('IdTagsCache', async () => { // 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') }) }) @@ -225,9 +225,9 @@ await describe('IdTagsCache', async () => { 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', () => { @@ -244,14 +244,14 @@ await describe('IdTagsCache', async () => { 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) }) }) }) diff --git a/tests/charging-station/SharedLRUCache.test.ts b/tests/charging-station/SharedLRUCache.test.ts index 66c1517b..e803d810 100644 --- a/tests/charging-station/SharedLRUCache.test.ts +++ b/tests/charging-station/SharedLRUCache.test.ts @@ -9,7 +9,7 @@ * - Cache clear */ -import { expect } from '@std/expect' +import assert from 'node:assert/strict' import { afterEach, beforeEach, describe, it } from 'node:test' import type { @@ -88,7 +88,7 @@ await describe('SharedLRUCache', async () => { const instance1 = SharedLRUCache.getInstance() const instance2 = SharedLRUCache.getInstance() - expect(instance1).toBe(instance2) + assert.strictEqual(instance1, instance2) }) await it('should create new instance after reset', () => { @@ -96,7 +96,7 @@ await describe('SharedLRUCache', async () => { resetSharedLRUCache() const instance2 = SharedLRUCache.getInstance() - expect(instance1).not.toBe(instance2) + assert.notStrictEqual(instance1, instance2) }) }) @@ -108,7 +108,7 @@ await describe('SharedLRUCache', async () => { 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', () => { @@ -116,7 +116,7 @@ await describe('SharedLRUCache', async () => { const result = cache.getChargingStationTemplate('unknown-hash') - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) await it('should report has correctly for templates', () => { @@ -125,8 +125,8 @@ await describe('SharedLRUCache', async () => { 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', () => { @@ -136,7 +136,7 @@ await describe('SharedLRUCache', async () => { cache.setChargingStationTemplate(template) cache.deleteChargingStationTemplate('template-hash-3') - expect(cache.hasChargingStationTemplate('template-hash-3')).toBe(false) + assert.strictEqual(cache.hasChargingStationTemplate('template-hash-3'), false) }) }) @@ -148,7 +148,7 @@ await describe('SharedLRUCache', async () => { 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', () => { @@ -158,7 +158,7 @@ await describe('SharedLRUCache', async () => { 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', () => { @@ -168,7 +168,7 @@ await describe('SharedLRUCache', async () => { 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', () => { @@ -177,7 +177,7 @@ await describe('SharedLRUCache', async () => { cache.setChargingStationConfiguration(config) - expect(cache.hasChargingStationConfiguration('')).toBe(false) + assert.strictEqual(cache.hasChargingStationConfiguration(''), false) }) await it('should delete a charging station configuration', () => { @@ -187,7 +187,7 @@ await describe('SharedLRUCache', async () => { cache.setChargingStationConfiguration(config) cache.deleteChargingStationConfiguration('config-hash-del') - expect(cache.hasChargingStationConfiguration('config-hash-del')).toBe(false) + assert.strictEqual(cache.hasChargingStationConfiguration('config-hash-del'), false) }) }) @@ -201,8 +201,8 @@ await describe('SharedLRUCache', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/1.6/OCPP16Constants.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Constants.test.ts new file mode 100644 index 00000000..bde3b6a4 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16Constants.test.ts @@ -0,0 +1,883 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-ChangeAvailability.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-ChangeAvailability.test.ts new file mode 100644 index 00000000..85449f37 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-ChangeAvailability.test.ts @@ -0,0 +1,140 @@ +/** + * @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) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Configuration.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Configuration.test.ts new file mode 100644 index 00000000..b9567e79 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Configuration.test.ts @@ -0,0 +1,198 @@ +/** + * @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') + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Firmware.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Firmware.test.ts new file mode 100644 index 00000000..b1056702 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Firmware.test.ts @@ -0,0 +1,155 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStartTransaction.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStartTransaction.test.ts new file mode 100644 index 00000000..e39be637 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStartTransaction.test.ts @@ -0,0 +1,162 @@ +/** + * @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) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStopUnlock.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStopUnlock.test.ts new file mode 100644 index 00000000..16c34915 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-RemoteStopUnlock.test.ts @@ -0,0 +1,162 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reservation.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reservation.test.ts new file mode 100644 index 00000000..74fb0a7a --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reservation.test.ts @@ -0,0 +1,261 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts new file mode 100644 index 00000000..91be4938 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-Reset.test.ts @@ -0,0 +1,137 @@ +/** + * @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)) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SimpleHandlers.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SimpleHandlers.test.ts new file mode 100644 index 00000000..3e7e6bb9 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SimpleHandlers.test.ts @@ -0,0 +1,85 @@ +/** + * @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') + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SmartCharging.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SmartCharging.test.ts new file mode 100644 index 00000000..e2b6c58d --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-SmartCharging.test.ts @@ -0,0 +1,369 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts new file mode 100644 index 00000000..b7a7ae62 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16IncomingRequestService-TriggerMessage.test.ts @@ -0,0 +1,162 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16Integration-ChargingProfiles.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Integration-ChargingProfiles.test.ts new file mode 100644 index 00000000..17a60205 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16Integration-ChargingProfiles.test.ts @@ -0,0 +1,336 @@ +/** + * @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) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16Integration-Configuration.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Integration-Configuration.test.ts new file mode 100644 index 00000000..dbe1216a --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16Integration-Configuration.test.ts @@ -0,0 +1,258 @@ +/** + * @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') + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16Integration-Reservations.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Integration-Reservations.test.ts new file mode 100644 index 00000000..9994f6e6 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16Integration-Reservations.test.ts @@ -0,0 +1,298 @@ +/** + * @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') + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts b/tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts new file mode 100644 index 00000000..4d9b8f88 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16Integration-Transactions.test.ts @@ -0,0 +1,423 @@ +/** + * @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) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16RequestService-Payloads.test.ts b/tests/charging-station/ocpp/1.6/OCPP16RequestService-Payloads.test.ts new file mode 100644 index 00000000..43b0d56e --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16RequestService-Payloads.test.ts @@ -0,0 +1,300 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16ResponseService-BootAuth.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-BootAuth.test.ts new file mode 100644 index 00000000..442a02f0 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-BootAuth.test.ts @@ -0,0 +1,221 @@ +/** + * @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 { + 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 { + 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) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16ResponseService-SimpleHandlers.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-SimpleHandlers.test.ts new file mode 100644 index 00000000..feadd991 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-SimpleHandlers.test.ts @@ -0,0 +1,132 @@ +/** + * @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 + ) + ) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts new file mode 100644 index 00000000..09fd6caf --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16ResponseService-Transactions.test.ts @@ -0,0 +1,360 @@ +/** + * @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 + ) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16SchemaValidation.test.ts b/tests/charging-station/ocpp/1.6/OCPP16SchemaValidation.test.ts new file mode 100644 index 00000000..ce9b85ef --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16SchemaValidation.test.ts @@ -0,0 +1,277 @@ +/** + * @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 { + return JSON.parse(readFileSync(join(SCHEMA_DIR, filename), 'utf8')) as Record +} + +/** + * 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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts new file mode 100644 index 00000000..ded3c30a --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16ServiceUtils.test.ts @@ -0,0 +1,716 @@ +/** + * @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) + }) + }) +}) diff --git a/tests/charging-station/ocpp/1.6/OCPP16TestUtils.ts b/tests/charging-station/ocpp/1.6/OCPP16TestUtils.ts new file mode 100644 index 00000000..a0bc79a4 --- /dev/null +++ b/tests/charging-station/ocpp/1.6/OCPP16TestUtils.ts @@ -0,0 +1,436 @@ +/** + * @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 +} + +export interface OCPP16RequestTestContext { + readonly requestService: OCPP16RequestService + readonly station: ChargingStation + readonly testableRequestService: TestableOCPP16RequestService +} + +export interface OCPP16RequestTestContextOptions { + readonly baseName?: string + readonly stationInfo?: Record +} + +export interface OCPP16ResponseTestContext { + readonly responseService: OCPP16ResponseService + readonly station: ChargingStation +} + +export interface OCPP16ResponseTestContextOptions { + readonly baseName?: string + readonly stationInfo?: Record +} + +// ============================================================================ +// Type Cast Helpers +// ============================================================================ + +/** + * Create a `commandsSupport` object compatible with `ChargingStationInfo` from partial records. + * Encapsulates the casts from `Partial>` 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 + outgoingCommands?: Record +}): NonNullable { + return { + incomingCommands: (config.incomingCommands ?? {}) as unknown as Record< + IncomingRequestCommand, + boolean + >, + ...(config.outgoingCommands != null && { + outgoingCommands: config.outgoingCommands as unknown as Record, + }), + } +} + +/** + * 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 { + await responseService.responseHandler( + station, + command, + payload as unknown as Parameters[2], + requestPayload as unknown as Parameters[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 +): 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 diff --git a/tests/charging-station/ocpp/2.0/OCPP20CertificateManager.test.ts b/tests/charging-station/ocpp/2.0/OCPP20CertificateManager.test.ts index 8d4f6c8c..7ad03406 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20CertificateManager.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20CertificateManager.test.ts @@ -3,7 +3,7 @@ * @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' @@ -27,9 +27,9 @@ const TEST_CERT_TYPE = InstallCertificateUseEnumType.CSMSRootCertificate // 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: '', } await describe('I02-I04 - ISO15118 Certificate Management', async () => { @@ -63,11 +63,14 @@ 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 () => { @@ -77,9 +80,12 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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 () => { @@ -89,9 +95,9 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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 () => { @@ -101,9 +107,12 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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')) }) }) @@ -123,9 +132,9 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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 () => { @@ -138,8 +147,8 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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 () => { @@ -152,8 +161,8 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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)) }) }) @@ -166,23 +175,23 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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 () => { @@ -193,8 +202,8 @@ await describe('I02-I04 - ISO15118 Certificate Management', 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)) }) }) @@ -207,34 +216,34 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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', () => { @@ -243,8 +252,8 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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', () => { @@ -253,8 +262,8 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { HashAlgorithmEnumType.SHA512 ) - expect(hashData).toBeDefined() - expect(hashData.hashAlgorithm).toBe(HashAlgorithmEnumType.SHA512) + assert.notStrictEqual(hashData, undefined) + assert.strictEqual(hashData.hashAlgorithm, HashAlgorithmEnumType.SHA512) }) }) @@ -267,32 +276,32 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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', () => { @@ -306,7 +315,7 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { const isValid = manager.validateCertificateFormat(pemWithWhitespace) - expect(isValid).toBe(true) + assert.strictEqual(isValid, true) }) }) @@ -319,12 +328,12 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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', () => { @@ -334,10 +343,13 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { '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', () => { @@ -353,17 +365,17 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { '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$/) }) }) @@ -388,9 +400,9 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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) }) }) @@ -399,7 +411,7 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { 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', () => { @@ -407,7 +419,7 @@ await describe('I02-I04 - ISO15118 Certificate Management', async () => { const path = manager.getCertificatePath(maliciousHashId, TEST_CERT_TYPE, 'SERIAL-001') - expect(path).not.toContain('..') + assert.ok(!path.includes('..')) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts index bf936d4f..ee7605c0 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts @@ -3,7 +3,7 @@ * @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' @@ -77,11 +77,11 @@ await describe('I04 - CertificateSigned', async () => { 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 () => { @@ -97,9 +97,9 @@ await describe('I04 - CertificateSigned', 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) }) }) @@ -113,11 +113,11 @@ await describe('I04 - CertificateSigned', 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() - 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') }) }) @@ -138,9 +138,9 @@ await describe('I04 - CertificateSigned', async () => { 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) }) }) @@ -161,11 +161,11 @@ await describe('I04 - CertificateSigned', async () => { 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) }) }) @@ -194,10 +194,10 @@ await describe('I04 - CertificateSigned', async () => { 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') }) }) @@ -215,10 +215,10 @@ await describe('I04 - CertificateSigned', 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) }) await it('should return Rejected status when storage throws error', async () => { @@ -234,10 +234,10 @@ await describe('I04 - CertificateSigned', 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) }) }) @@ -255,26 +255,26 @@ await describe('I04 - CertificateSigned', async () => { 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') } }) @@ -287,12 +287,13 @@ await describe('I04 - CertificateSigned', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts index 426217da..0d5247d4 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-ClearCache.test.ts @@ -3,7 +3,7 @@ * @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' @@ -47,21 +47,21 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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) @@ -87,8 +87,8 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) @@ -108,7 +108,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () try { await testableService.handleRequestClearCache(station) - expect(deleteIdTagsCalled).toBe(false) + assert.strictEqual(deleteIdTagsCalled, false) } finally { // Restore original method Object.assign(station.idTagsCache, { deleteIdTags: originalDeleteIdTags }) @@ -138,7 +138,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) @@ -165,7 +165,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) @@ -192,7 +192,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) @@ -220,7 +220,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) @@ -242,7 +242,7 @@ await describe('C11 - Clear Authorization Data in Authorization Cache', async () 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 }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts index 9a160c69..2137f7f2 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts @@ -3,7 +3,7 @@ * @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' @@ -88,12 +88,12 @@ await describe('I04 - DeleteCertificate', async () => { 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 () => { @@ -111,9 +111,9 @@ await describe('I04 - DeleteCertificate', 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 () => { @@ -131,9 +131,9 @@ await describe('I04 - DeleteCertificate', 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) }) }) @@ -150,8 +150,8 @@ await describe('I04 - DeleteCertificate', async () => { 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) }) }) @@ -168,10 +168,10 @@ await describe('I04 - DeleteCertificate', async () => { 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 () => { @@ -199,10 +199,10 @@ await describe('I04 - DeleteCertificate', 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) }) }) @@ -219,27 +219,29 @@ await describe('I04 - DeleteCertificate', async () => { 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') } }) @@ -255,12 +257,13 @@ await describe('I04 - DeleteCertificate', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts index f2c7b3b3..3bb000bf 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts @@ -1,9 +1,9 @@ +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' @@ -97,8 +97,8 @@ await describe('B07 - Get Base Report', async () => { 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 @@ -110,8 +110,8 @@ await describe('B07 - Get Base Report', async () => { 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', () => { @@ -121,12 +121,12 @@ await describe('B07 - Get Base Report', async () => { 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( @@ -134,13 +134,13 @@ await describe('B07 - Get Base Report', async () => { 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]) } }) @@ -153,8 +153,8 @@ await describe('B07 - Get Base Report', async () => { 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 @@ -166,8 +166,8 @@ await describe('B07 - Get Base Report', async () => { 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 @@ -181,8 +181,8 @@ await describe('B07 - Get Base Report', async () => { 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 @@ -196,8 +196,8 @@ await describe('B07 - Get Base Report', async () => { // 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( @@ -205,18 +205,18 @@ await describe('B07 - Get Base Report', async () => { 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) } }) @@ -224,8 +224,8 @@ await describe('B07 - Get Base Report', async () => { 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( @@ -233,9 +233,9 @@ await describe('B07 - Get Base Report', async () => { 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( @@ -243,9 +243,9 @@ await describe('B07 - Get Base Report', async () => { 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) } }) @@ -253,8 +253,8 @@ await describe('B07 - Get Base Report', async () => { 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( @@ -262,9 +262,9 @@ await describe('B07 - Get Base Report', async () => { 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) } }) @@ -291,7 +291,7 @@ await describe('B07 - Get Base Report', async () => { 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) @@ -300,15 +300,15 @@ await describe('B07 - Get Base Report', async () => { 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)) } } }) @@ -333,15 +333,15 @@ await describe('B07 - Get Base Report', async () => { 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) } }) @@ -352,7 +352,7 @@ await describe('B07 - Get Base Report', async () => { '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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts index 8c8a8198..0f428738 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts @@ -3,7 +3,7 @@ * @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' @@ -79,11 +79,11 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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) }) }) @@ -105,11 +105,15 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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 ) }) @@ -134,9 +138,9 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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) }) }) @@ -151,11 +155,11 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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 () => { @@ -170,8 +174,8 @@ await describe('I04 - GetInstalledCertificateIds', 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) }) }) @@ -186,13 +190,15 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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 () => { @@ -210,17 +216,20 @@ await describe('I04 - GetInstalledCertificateIds', 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) }) }) @@ -252,9 +261,9 @@ await describe('I04 - GetInstalledCertificateIds', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts index 980653d8..fe078261 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetVariables.test.ts @@ -1,9 +1,9 @@ +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' @@ -79,30 +79,34 @@ await describe('B06 - Get Variables', async () => { 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 @@ -122,30 +126,30 @@ await describe('B06 - Get Variables', async () => { 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 @@ -162,13 +166,13 @@ await describe('B06 - Get Variables', async () => { 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 @@ -187,9 +191,9 @@ await describe('B06 - Get Variables', async () => { ], } 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 @@ -204,9 +208,9 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -222,8 +226,8 @@ await describe('B06 - Get Variables', async () => { } 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) }) @@ -238,8 +242,8 @@ await describe('B06 - Get Variables', async () => { } 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', () => { @@ -257,10 +261,10 @@ await describe('B06 - Get Variables', async () => { ], } 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) }) @@ -280,10 +284,10 @@ await describe('B06 - Get Variables', async () => { ], } 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) }) @@ -322,11 +326,11 @@ await describe('B06 - Get Variables', async () => { 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) }) @@ -343,12 +347,12 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -362,13 +366,13 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -382,12 +386,12 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -408,16 +412,16 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -438,20 +442,23 @@ await describe('B06 - Get Variables', async () => { ], } 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}` ) }) @@ -468,11 +475,11 @@ await describe('B06 - Get Variables', async () => { ], } 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 @@ -487,10 +494,10 @@ await describe('B06 - Get Variables', async () => { ], } 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 @@ -505,10 +512,10 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -527,15 +534,15 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -549,9 +556,9 @@ await describe('B06 - Get Variables', async () => { ], } 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)', () => { @@ -565,9 +572,9 @@ await describe('B06 - Get Variables', async () => { ], } 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', () => { @@ -584,9 +591,12 @@ await describe('B06 - Get Variables', async () => { } 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts index 8a5ee14d..35f8f5c8 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts @@ -3,7 +3,7 @@ * @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' @@ -80,12 +80,12 @@ await describe('I03 - InstallCertificate', async () => { 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 () => { @@ -101,9 +101,9 @@ await describe('I03 - InstallCertificate', 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 () => { @@ -119,9 +119,9 @@ await describe('I03 - InstallCertificate', 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 () => { @@ -137,9 +137,9 @@ await describe('I03 - InstallCertificate', 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) }) }) @@ -153,11 +153,11 @@ await describe('I03 - InstallCertificate', 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() - 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 () => { @@ -174,10 +174,10 @@ await describe('I03 - InstallCertificate', 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).validateCertificateExpiry }) @@ -197,10 +197,10 @@ await describe('I03 - InstallCertificate', async () => { 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) }) }) @@ -218,27 +218,29 @@ await describe('I03 - InstallCertificate', async () => { 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') } }) @@ -251,12 +253,13 @@ await describe('I03 - InstallCertificate', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RemoteStartAuth.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RemoteStartAuth.test.ts index d06d9e5f..86769620 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RemoteStartAuth.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RemoteStartAuth.test.ts @@ -3,8 +3,7 @@ * @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' @@ -82,10 +81,10 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -100,9 +99,9 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -117,8 +116,8 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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) }) }) @@ -137,8 +136,8 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -146,8 +145,11 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { // 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) }) }) @@ -168,11 +170,11 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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') } }) @@ -192,8 +194,8 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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) }) }) @@ -212,7 +214,7 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -229,7 +231,7 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // Then: evseId should be undefined (will be rejected by handler) - expect(request.evseId).toBeUndefined() + assert.strictEqual(request.evseId, undefined) }) }) @@ -249,10 +251,13 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { // 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', () => { @@ -272,8 +277,11 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { // 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) }) }) @@ -297,15 +305,20 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -327,10 +340,14 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -345,21 +362,21 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -380,9 +397,12 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { } // 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', () => { @@ -397,7 +417,7 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { // Then: All token types should be defined tokenTypes.forEach(tokenType => { - expect(tokenType).toBeDefined() + assert.notStrictEqual(tokenType, undefined) }) }) }) @@ -405,17 +425,17 @@ await describe('G03 - Remote Start Pre-Authorization', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts index e4ceb85c..d77ee04d 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStartTransaction.test.ts @@ -2,7 +2,7 @@ * @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' @@ -79,10 +79,10 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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 @@ -119,16 +119,19 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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() }) @@ -153,9 +156,9 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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 @@ -194,9 +197,9 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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 @@ -235,8 +238,8 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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 @@ -276,8 +279,8 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { requestWithTransactionIdProfile ) - expect(response).toBeDefined() - expect(response.status).toBe(RequestStartStopStatusEnumType.Rejected) + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, RequestStartStopStatusEnumType.Rejected) }) // FR: F01.FR.07 @@ -292,9 +295,10 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { } // 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 @@ -323,9 +327,9 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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 @@ -342,17 +346,20 @@ await describe('F01 & F02 - Remote Start Transaction', async () => { 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts index 2cf6be03..96005790 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-RequestStopTransaction.test.ts @@ -3,7 +3,7 @@ * @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' @@ -116,8 +116,8 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 } @@ -138,18 +138,18 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -173,19 +173,19 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -201,11 +201,11 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -220,11 +220,11 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -241,11 +241,11 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -280,11 +280,11 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 () => { @@ -344,8 +344,8 @@ await describe('F03 - Remote Stop Transaction', 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 @@ -363,15 +363,15 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 () => { @@ -395,11 +395,11 @@ await describe('F03 - Remote Stop Transaction', 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 @@ -416,28 +416,28 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 @@ -447,7 +447,7 @@ await describe('F03 - Remote Stop Transaction', async () => { 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 } @@ -460,26 +460,28 @@ await describe('F03 - Remote Stop Transaction', async () => { 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') }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts index 56f72e43..9c1fb96b 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-Reset.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -60,15 +60,17 @@ await describe('B11 & B12 - Reset', async () => { 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 () => { @@ -81,13 +83,15 @@ await describe('B11 & B12 - Reset', 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 @@ -102,13 +106,15 @@ await describe('B11 & B12 - Reset', 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) + ) }) await it('should reject reset for non-existent EVSE when no transactions', async () => { @@ -122,11 +128,16 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -139,13 +150,13 @@ await describe('B11 & B12 - Reset', 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) } }) @@ -159,8 +170,8 @@ await describe('B11 & B12 - Reset', async () => { 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 () => { @@ -181,12 +192,17 @@ await describe('B11 & B12 - Reset', 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 @@ -208,9 +224,9 @@ await describe('B11 & B12 - Reset', async () => { 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) }) }) @@ -236,9 +252,9 @@ await describe('B11 & B12 - Reset', async () => { 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 () => { @@ -254,9 +270,9 @@ await describe('B11 & B12 - Reset', 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 @@ -274,10 +290,10 @@ await describe('B11 & B12 - Reset', async () => { 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) ) }) @@ -300,12 +316,17 @@ await describe('B11 & B12 - Reset', 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 @@ -343,9 +364,9 @@ await describe('B11 & B12 - Reset', 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 Downloaded', async () => { @@ -365,9 +386,9 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -387,9 +408,9 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -409,8 +430,8 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -430,8 +451,8 @@ await describe('B11 & B12 - Reset', async () => { resetRequest ) - expect(response).toBeDefined() - expect(response.status).toBe(ResetStatusEnumType.Accepted) + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, ResetStatusEnumType.Accepted) }) }) @@ -467,8 +488,8 @@ await describe('B11 & B12 - Reset', async () => { 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 () => { @@ -500,9 +521,9 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -518,8 +539,8 @@ await describe('B11 & B12 - Reset', async () => { resetRequest ) - expect(response).toBeDefined() - expect(response.status).toBe(ResetStatusEnumType.Accepted) + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, ResetStatusEnumType.Accepted) }) }) @@ -546,8 +567,8 @@ await describe('B11 & B12 - Reset', 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 Scheduled when multiple blocking conditions exist', async () => { @@ -579,8 +600,8 @@ await describe('B11 & B12 - Reset', async () => { resetRequest ) - expect(response).toBeDefined() - expect(response.status).toBe(ResetStatusEnumType.Scheduled) + assert.notStrictEqual(response, undefined) + assert.strictEqual(response.status, ResetStatusEnumType.Scheduled) }) }) }) @@ -603,8 +624,8 @@ await describe('B11 & B12 - Reset', async () => { 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 () => { @@ -612,7 +633,7 @@ await describe('B11 & B12 - Reset', 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 () => { @@ -620,7 +641,7 @@ await describe('B11 & B12 - Reset', 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts index cc26b82e..bf0b5f51 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts @@ -3,8 +3,8 @@ * @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' @@ -94,24 +94,24 @@ await describe('B05 - Set Variables', async () => { 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 @@ -134,13 +134,13 @@ await describe('B05 - Set Variables', async () => { 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 @@ -159,10 +159,10 @@ await describe('B05 - Set Variables', async () => { 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 @@ -181,9 +181,9 @@ await describe('B05 - Set Variables', async () => { } 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 @@ -202,10 +202,10 @@ await describe('B05 - Set Variables', async () => { 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 @@ -251,20 +251,22 @@ await describe('B05 - Set Variables', async () => { 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 @@ -281,10 +283,10 @@ await describe('B05 - Set Variables', async () => { } 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 @@ -301,10 +303,10 @@ await describe('B05 - Set Variables', async () => { } 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 @@ -343,12 +345,12 @@ await describe('B05 - Set Variables', async () => { ], }) - 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 @@ -375,7 +377,7 @@ await describe('B05 - Set Variables', async () => { }, ], }) - 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') @@ -390,7 +392,7 @@ await describe('B05 - Set Variables', async () => { }, ], }) - expect(getAfter.getVariableResult[0].attributeValue).toBe('30') // default + assert.strictEqual(getAfter.getVariableResult[0].attributeValue, '30') // default }) // FR: B07.FR.12 @@ -414,11 +416,17 @@ await describe('B05 - Set Variables', async () => { } 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) }) @@ -442,11 +450,17 @@ await describe('B05 - Set Variables', async () => { } 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) }) @@ -500,16 +514,23 @@ await describe('B05 - Set Variables', async () => { 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`) ) }) @@ -533,7 +554,10 @@ await describe('B05 - Set Variables', async () => { }, ], }) - expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted) + assert.strictEqual( + response.setVariableResult[0].attributeStatus, + SetVariableStatusEnumType.Accepted + ) response = testableService.handleRequestSetVariables(mockStation, { setVariableData: [ { @@ -544,8 +568,8 @@ await describe('B05 - Set Variables', async () => { ], }) 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) }) @@ -565,7 +589,10 @@ await describe('B05 - Set Variables', async () => { }, ], }) - expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted) + assert.strictEqual( + response.setVariableResult[0].attributeStatus, + SetVariableStatusEnumType.Accepted + ) response = testableService.handleRequestSetVariables(mockStation, { setVariableData: [ { @@ -576,8 +603,8 @@ await describe('B05 - Set Variables', async () => { ], }) 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) }) @@ -597,7 +624,10 @@ await describe('B05 - Set Variables', async () => { }, ], }) - expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted) + assert.strictEqual( + response.setVariableResult[0].attributeStatus, + SetVariableStatusEnumType.Accepted + ) response = testableService.handleRequestSetVariables(mockStation, { setVariableData: [ { @@ -608,8 +638,8 @@ await describe('B05 - Set Variables', async () => { ], }) 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) }) @@ -629,7 +659,10 @@ await describe('B05 - Set Variables', async () => { }, ], }) - expect(response.setVariableResult[0].attributeStatus).toBe(SetVariableStatusEnumType.Accepted) + assert.strictEqual( + response.setVariableResult[0].attributeStatus, + SetVariableStatusEnumType.Accepted + ) response = testableService.handleRequestSetVariables(mockStation, { setVariableData: [ { @@ -640,8 +673,8 @@ await describe('B05 - Set Variables', async () => { ], }) 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) }) @@ -661,8 +694,8 @@ await describe('B05 - Set Variables', async () => { ], }) 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) }) @@ -690,11 +723,11 @@ await describe('B05 - Set Variables', async () => { }, ], }) - 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) }) @@ -712,10 +745,10 @@ await describe('B05 - Set Variables', async () => { } 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: [ @@ -726,11 +759,11 @@ await describe('B05 - Set Variables', async () => { }, ], }) - 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts index 3a2d8ce3..3c9cf8a3 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-TriggerMessage.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -88,8 +88,8 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -102,8 +102,8 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -116,8 +116,8 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -131,8 +131,8 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -147,7 +147,7 @@ await describe('F06 - TriggerMessage', async () => { request ) - expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted) + assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted) }) }) @@ -168,10 +168,15 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -184,9 +189,9 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -199,9 +204,9 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -214,9 +219,9 @@ await describe('F06 - TriggerMessage', async () => { 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) }) }) @@ -244,10 +249,15 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -261,10 +271,15 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -277,7 +292,7 @@ await describe('F06 - TriggerMessage', async () => { request ) - expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted) + assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted) }) }) @@ -302,10 +317,15 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -322,7 +342,7 @@ await describe('F06 - TriggerMessage', async () => { request ) - expect(response.status).toBe(TriggerMessageStatusEnumType.Accepted) + assert.strictEqual(response.status, TriggerMessageStatusEnumType.Accepted) }) }) @@ -340,9 +360,9 @@ await describe('F06 - TriggerMessage', async () => { 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', () => { @@ -353,7 +373,7 @@ await describe('F06 - TriggerMessage', async () => { const result = testableService.handleRequestTriggerMessage(mockStation, request) // A Promise would have a `then` property that is a function - expect(typeof (result as unknown as Promise).then).not.toBe('function') + assert.notStrictEqual(typeof (result as unknown as Promise).then, 'function') }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts index 900ba4f4..af71d2b1 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-UnlockConnector.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -79,10 +79,15 @@ await describe('F05 - UnlockConnector', async () => { 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')) }) }) @@ -94,10 +99,15 @@ await describe('F05 - UnlockConnector', async () => { 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')) }) }) @@ -110,11 +120,16 @@ await describe('F05 - UnlockConnector', async () => { 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')) }) }) @@ -132,10 +147,15 @@ await describe('F05 - UnlockConnector', async () => { 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 () => { @@ -165,7 +185,7 @@ await describe('F05 - UnlockConnector', async () => { const response: OCPP20UnlockConnectorResponse = await testableService.handleRequestUnlockConnector(multiConnectorStation, request) - expect(response.status).toBe(UnlockStatusEnumType.Unlocked) + assert.strictEqual(response.status, UnlockStatusEnumType.Unlocked) }) }) @@ -177,8 +197,8 @@ await describe('F05 - UnlockConnector', async () => { 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 () => { @@ -188,7 +208,7 @@ await describe('F05 - UnlockConnector', 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 () => { @@ -198,7 +218,7 @@ await describe('F05 - UnlockConnector', async () => { const result = testableService.handleRequestUnlockConnector(mockStation, request) - expect(typeof (result as unknown as Promise).then).toBe('function') + assert.strictEqual(typeof (result as unknown as Promise).then, 'function') await result }) }) @@ -210,9 +230,9 @@ await describe('F05 - UnlockConnector', async () => { 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 () => { @@ -221,8 +241,8 @@ await describe('F05 - UnlockConnector', 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts index 6e617381..5d5ccbb6 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20Integration-Certificate.test.ts @@ -3,7 +3,7 @@ * @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' @@ -80,8 +80,8 @@ await describe('OCPP 2.0 Integration — Certificate install and delete lifecycl 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 () => { @@ -94,12 +94,13 @@ await describe('OCPP 2.0 Integration — Certificate install and delete lifecycl 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 () => { @@ -117,6 +118,6 @@ await describe('OCPP 2.0 Integration — Certificate install and delete lifecycl deleteRequest ) - expect(deleteResponse.status).toBeDefined() + assert.notStrictEqual(deleteResponse.status, undefined) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts index 949eb402..652b9c22 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20Integration.test.ts @@ -3,7 +3,7 @@ * @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' @@ -91,19 +91,19 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten 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', () => { @@ -118,12 +118,13 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten 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', () => { @@ -143,7 +144,7 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten } const setResponse = testableService.handleRequestSetVariables(station, setRequest) - expect(setResponse.setVariableResult).toHaveLength(2) + assert.strictEqual(setResponse.setVariableResult.length, 2) const getRequest: OCPP20GetVariablesRequest = { getVariableData: [ @@ -159,9 +160,9 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten } 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) } }) @@ -181,13 +182,14 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten } 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 = { @@ -200,11 +202,12 @@ await describe('OCPP 2.0 Integration — SetVariables → GetVariables consisten } 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 + ) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts index b466003e..0d8b3240 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts @@ -2,7 +2,7 @@ * @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' @@ -66,13 +66,13 @@ await describe('B01 - Cold Boot Charging Station', async () => { 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 @@ -95,13 +95,13 @@ await describe('B01 - Cold Boot Charging Station', async () => { 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 @@ -123,13 +123,13 @@ await describe('B01 - Cold Boot Charging Station', async () => { 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 @@ -163,9 +163,9 @@ await describe('B01 - Cold Boot Charging Station', async () => { 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) }) }) @@ -193,27 +193,27 @@ await describe('B01 - Cold Boot Charging Station', async () => { ) 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') } }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts index b3da8401..2cdbd767 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts @@ -2,7 +2,7 @@ * @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' @@ -57,9 +57,9 @@ await describe('G02 - Heartbeat', async () => { 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 @@ -70,9 +70,9 @@ await describe('G02 - Heartbeat', async () => { 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 @@ -86,11 +86,11 @@ await describe('G02 - Heartbeat', async () => { ) // 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 @@ -116,11 +116,11 @@ await describe('G02 - Heartbeat', async () => { ) // 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 @@ -150,10 +150,10 @@ await describe('G02 - Heartbeat', async () => { ) // 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 @@ -168,11 +168,11 @@ await describe('G02 - Heartbeat', async () => { // 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-ISO15118.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-ISO15118.test.ts index 5b4e52de..4f18032d 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-ISO15118.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-ISO15118.test.ts @@ -4,7 +4,7 @@ */ /* 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' @@ -72,12 +72,12 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) @@ -100,8 +100,8 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) @@ -121,9 +121,9 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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 () => { @@ -144,9 +144,9 @@ await describe('OCPP20 ISO15118 Request Service', 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) }) }) @@ -169,7 +169,7 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) @@ -196,7 +196,7 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) }) @@ -237,16 +237,19 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) @@ -264,9 +267,9 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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 () => { @@ -284,9 +287,9 @@ await describe('OCPP20 ISO15118 Request Service', 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) }) }) @@ -309,12 +312,12 @@ await describe('OCPP20 ISO15118 Request Service', async () => { 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) }) }) }) @@ -358,7 +361,7 @@ await describe('OCPP20 ISO15118 Request Service', async () => { ) 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 () => { @@ -373,7 +376,7 @@ await describe('OCPP20 ISO15118 Request Service', 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-NotifyReport.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-NotifyReport.test.ts index 6abac45d..3a363813 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-NotifyReport.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-NotifyReport.test.ts @@ -3,8 +3,7 @@ * @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' @@ -79,12 +78,12 @@ await describe('B07/B08 - NotifyReport', async () => { 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', () => { @@ -123,14 +122,14 @@ await describe('B07/B08 - NotifyReport', async () => { 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', () => { @@ -205,14 +204,14 @@ await describe('B07/B08 - NotifyReport', async () => { 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)', () => { @@ -251,14 +250,14 @@ await describe('B07/B08 - NotifyReport', async () => { 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', () => { @@ -276,14 +275,14 @@ await describe('B07/B08 - NotifyReport', async () => { 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', () => { @@ -325,12 +324,12 @@ await describe('B07/B08 - NotifyReport', async () => { 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()}`) }) }) @@ -379,13 +378,13 @@ await describe('B07/B08 - NotifyReport', async () => { 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) }) }) @@ -426,31 +425,31 @@ await describe('B07/B08 - NotifyReport', async () => { ) 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') } }) @@ -490,12 +489,12 @@ await describe('B07/B08 - NotifyReport', async () => { 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', () => { @@ -532,16 +531,16 @@ await describe('B07/B08 - NotifyReport', async () => { ) 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts index 6d67d71d..10d6eaad 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts @@ -3,7 +3,7 @@ * @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' @@ -67,15 +67,15 @@ await describe('I02 - SignCertificate Request', async () => { 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 () => { @@ -92,16 +92,16 @@ await describe('I02 - SignCertificate Request', 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.')) }) }) @@ -121,7 +121,8 @@ await describe('I02 - SignCertificate Request', async () => { const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest - expect(sentPayload.certificateType).toBe( + assert.strictEqual( + sentPayload.certificateType, CertificateSigningUseEnumType.ChargingStationCertificate ) }) @@ -140,7 +141,7 @@ await describe('I02 - SignCertificate Request', async () => { const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest - expect(sentPayload.certificateType).toBe(CertificateSigningUseEnumType.V2GCertificate) + assert.strictEqual(sentPayload.certificateType, CertificateSigningUseEnumType.V2GCertificate) }) }) @@ -157,8 +158,8 @@ await describe('I02 - SignCertificate Request', async () => { 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 () => { @@ -176,10 +177,10 @@ await describe('I02 - SignCertificate Request', 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') }) }) @@ -196,9 +197,9 @@ await describe('I02 - SignCertificate Request', async () => { 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) }) }) @@ -216,16 +217,16 @@ await describe('I02 - SignCertificate Request', async () => { 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 () => { @@ -243,7 +244,7 @@ await describe('I02 - SignCertificate Request', async () => { const commandName = sendMessageMock.mock.calls[0].arguments[3] - expect(commandName).toBe(OCPP20RequestCommand.SIGN_CERTIFICATE) + assert.strictEqual(commandName, OCPP20RequestCommand.SIGN_CERTIFICATE) }) }) @@ -279,12 +280,12 @@ await describe('I02 - SignCertificate Request', async () => { 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-----')) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts b/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts index fc17e699..9f94a1b7 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts @@ -2,7 +2,7 @@ * @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' @@ -64,11 +64,11 @@ await describe('G01 - Status Notification', async () => { 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 @@ -88,11 +88,11 @@ await describe('G01 - Status Notification', async () => { 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 @@ -112,11 +112,11 @@ await describe('G01 - Status Notification', async () => { 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 @@ -145,11 +145,11 @@ await describe('G01 - Status Notification', async () => { 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) }) }) @@ -171,24 +171,24 @@ await describe('G01 - Status Notification', async () => { ) 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 @@ -209,11 +209,11 @@ await describe('G01 - Status Notification', async () => { 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 = { @@ -229,11 +229,11 @@ await describe('G01 - Status Notification', async () => { 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 @@ -259,9 +259,9 @@ await describe('G01 - Status Notification', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts index bfbd9fac..88c40ca5 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts @@ -12,7 +12,7 @@ * - 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' @@ -88,9 +88,9 @@ await describe('B01 - BootNotificationResponse handler', async () => { 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 () => { @@ -101,9 +101,9 @@ await describe('B01 - BootNotificationResponse handler', 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 () => { @@ -114,9 +114,9 @@ await describe('B01 - BootNotificationResponse handler', 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 () => { @@ -127,10 +127,13 @@ await describe('B01 - BootNotificationResponse handler', 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 () => { @@ -140,8 +143,8 @@ await describe('B01 - BootNotificationResponse handler', 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 () => { @@ -151,6 +154,6 @@ await describe('B01 - BootNotificationResponse handler', async () => { status: 'INVALID_STATUS', } as unknown as OCPP20BootNotificationResponse await dispatch(payload) - expect(mockStation.bootNotificationResponse).toBeUndefined() + assert.strictEqual(mockStation.bootNotificationResponse, undefined) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-SimpleHandlers.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-SimpleHandlers.test.ts index 4c24b86b..cbbc9ec9 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-SimpleHandlers.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-SimpleHandlers.test.ts @@ -3,7 +3,7 @@ * @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' @@ -57,42 +57,42 @@ await describe('Simple response handlers', async () => { 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[2], {} as Parameters[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[2], {} as Parameters[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[2], {} as Parameters[3] ) - ).resolves.toBeUndefined() + ) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-TransactionEvent.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-TransactionEvent.test.ts index fcf4d6f0..257bb904 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-TransactionEvent.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-TransactionEvent.test.ts @@ -13,7 +13,7 @@ * - 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' @@ -83,17 +83,17 @@ await describe('E01-E04 - TransactionEventResponse handler', async () => { 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 () => { @@ -102,7 +102,7 @@ await describe('E01-E04 - TransactionEventResponse handler', 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 () => { @@ -111,7 +111,7 @@ await describe('E01-E04 - TransactionEventResponse handler', async () => { status: OCPP20AuthorizationStatusEnumType.Invalid, }, } - await expect(dispatch(payload)).resolves.toBeUndefined() + await assert.doesNotReject(dispatch(payload)) }) await it('should handle updatedPersonalMessage field without throwing', async () => { @@ -120,7 +120,7 @@ await describe('E01-E04 - TransactionEventResponse handler', 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 () => { @@ -137,6 +137,6 @@ await describe('E01-E04 - TransactionEventResponse handler', async () => { totalCost: 9.99, updatedPersonalMessage: message, } - await expect(dispatch(payload)).resolves.toBeUndefined() + await assert.doesNotReject(dispatch(payload)) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20SchemaValidation.test.ts b/tests/charging-station/ocpp/2.0/OCPP20SchemaValidation.test.ts index 7b25153f..f510c75a 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20SchemaValidation.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20SchemaValidation.test.ts @@ -9,9 +9,9 @@ * 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' @@ -57,147 +57,151 @@ await describe('OCPP 2.0 schema validation — negative tests', async () => { 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) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts index 6e9968f6..bb367feb 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-TransactionEvent.test.ts @@ -10,8 +10,7 @@ * - 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' @@ -125,20 +124,22 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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', () => { @@ -177,14 +178,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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', () => { @@ -213,17 +214,20 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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)', () => { @@ -241,8 +245,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) 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')) } }) @@ -287,8 +291,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { transactionId ) - expect(transactionEvent.triggerReason).toBe(triggerReason) - expect(transactionEvent.eventType).toBe(OCPP20TransactionEventEnumType.Updated) + assert.strictEqual(transactionEvent.triggerReason, triggerReason) + assert.strictEqual(transactionEvent.eventType, OCPP20TransactionEventEnumType.Updated) } }) }) @@ -308,8 +312,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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 () => { @@ -344,7 +348,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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')) } }) }) @@ -365,22 +369,22 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // 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() + }) }) }) @@ -416,29 +420,35 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { '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', () => { @@ -456,12 +466,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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) }) }) @@ -479,7 +491,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStart) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStart) }) await it('should select RemoteStop for remote_command context with RequestStopTransaction', () => { @@ -493,7 +505,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.RemoteStop) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.RemoteStop) }) await it('should select UnlockCommand for remote_command context with UnlockConnector', () => { @@ -507,7 +519,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.UnlockCommand) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.UnlockCommand) }) await it('should select ResetCommand for remote_command context with Reset', () => { @@ -521,7 +533,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ResetCommand) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ResetCommand) }) await it('should select Trigger for remote_command context with TriggerMessage', () => { @@ -535,7 +547,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger) }) await it('should select Authorized for local_authorization context with idToken', () => { @@ -549,7 +561,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized) }) await it('should select StopAuthorized for local_authorization context with stopAuthorized', () => { @@ -563,7 +575,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.StopAuthorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized) }) await it('should select Deauthorized when isDeauthorized flag is true', () => { @@ -578,7 +590,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized) }) await it('should select CablePluggedIn for cable_action context with plugged_in', () => { @@ -592,7 +604,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn) }) await it('should select EVDetected for cable_action context with detected', () => { @@ -606,7 +618,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected) }) await it('should select EVDeparted for cable_action context with unplugged', () => { @@ -620,7 +632,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted) }) await it('should select ChargingStateChanged for charging_state context', () => { @@ -637,7 +649,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.ChargingStateChanged) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.ChargingStateChanged) }) await it('should select MeterValuePeriodic for meter_value context with periodic flag', () => { @@ -651,7 +663,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValuePeriodic) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValuePeriodic) }) await it('should select MeterValueClock for meter_value context without periodic flag', () => { @@ -665,7 +677,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.MeterValueClock) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.MeterValueClock) }) await it('should select SignedDataReceived when isSignedDataReceived flag is true', () => { @@ -679,7 +691,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.SignedDataReceived) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.SignedDataReceived) }) await it('should select appropriate system events for system_event context', () => { @@ -713,7 +725,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(testCase.expected) + assert.strictEqual(triggerReason, testCase.expected) } }) @@ -727,7 +739,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EnergyLimitReached) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EnergyLimitReached) }) await it('should select TimeLimitReached for time_limit context', () => { @@ -740,7 +752,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.TimeLimitReached) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.TimeLimitReached) }) await it('should select AbnormalCondition for abnormal_condition context', () => { @@ -754,7 +766,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.AbnormalCondition) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.AbnormalCondition) }) await it('should handle priority ordering with multiple applicable contexts', () => { @@ -772,7 +784,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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', () => { @@ -785,7 +797,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger) }) await it('should fallback to Trigger for incomplete context', () => { @@ -799,7 +811,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Trigger) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Trigger) }) }) @@ -822,10 +834,13 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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', () => { @@ -852,9 +867,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 ) }) @@ -878,8 +894,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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 () => { @@ -918,7 +934,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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')) } }) }) @@ -939,9 +955,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 () => { @@ -957,8 +973,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { transactionId ) - expect(response).toBeDefined() - expect(typeof response).toBe('object') + assert.notStrictEqual(response, undefined) + assert.strictEqual(typeof response, 'object') }) }) }) @@ -981,7 +997,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { OCPP20TransactionEventEnumType.Started, startContext ) - expect(triggerReason).toBe(expectedStartTrigger) + assert.strictEqual(triggerReason, expectedStartTrigger) }) await it(`should build correct Started event for ${description}`, () => { @@ -1002,14 +1018,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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`) } }) @@ -1055,14 +1071,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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}`, () => { @@ -1111,14 +1127,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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) }) }) } @@ -1168,19 +1184,19 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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', () => { @@ -1208,11 +1224,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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) }) }) @@ -1257,14 +1273,17 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ] // 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) } }) }) @@ -1275,30 +1294,30 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // 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', () => { @@ -1306,7 +1325,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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') } @@ -1320,14 +1339,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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) }) }) @@ -1387,12 +1406,12 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // 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) } }) }) @@ -1404,7 +1423,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { TransactionContextFixtures.cablePluggedIn() ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.CablePluggedIn) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.CablePluggedIn) }) await it('should select EVDetected from cable_action context with detected state', () => { @@ -1413,7 +1432,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { TransactionContextFixtures.evDetected() ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDetected) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDetected) }) await it('should select EVDeparted from cable_action context with unplugged state', () => { @@ -1422,7 +1441,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { TransactionContextFixtures.evDeparted() ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.EVDeparted) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.EVDeparted) }) }) }) @@ -1447,7 +1466,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Authorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Authorized) }) await it('should differentiate IdToken-first from Cable-first by trigger reason', () => { @@ -1473,9 +1492,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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) }) }) @@ -1500,11 +1519,13 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { { 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)', () => { @@ -1537,8 +1558,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { { 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', () => { @@ -1562,7 +1583,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { { idToken: rfidToken } ) - expect(rfidEvent.idToken?.type).toBe(OCPP20IdTokenEnumType.ISO14443) + assert.strictEqual(rfidEvent.idToken?.type, OCPP20IdTokenEnumType.ISO14443) // Reset for eMAID test OCPP20ServiceUtils.resetTransactionSequenceNumber(mockStation, connectorId) @@ -1586,8 +1607,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { { 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') }) }) @@ -1641,27 +1662,30 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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', () => { @@ -1704,12 +1728,12 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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) }) }) @@ -1743,15 +1767,19 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 ) }) @@ -1770,7 +1798,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.Deauthorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.Deauthorized) }) await it('should handle transaction end after token revocation', () => { @@ -1802,9 +1830,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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', () => { @@ -1818,7 +1846,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { context ) - expect(triggerReason).toBe(OCPP20TriggerReasonEnumType.StopAuthorized) + assert.strictEqual(triggerReason, OCPP20TriggerReasonEnumType.StopAuthorized) }) }) @@ -1874,7 +1902,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // E03.FR.07: Sequence numbers must be continuous events.forEach((event, index) => { - expect(event.seqNo).toBe(index) + assert.strictEqual(event.seqNo, index) }) }) @@ -1887,7 +1915,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { const transaction2Id = generateUUID() // E03.FR.08: transactionId MUST be unique - expect(transaction1Id).not.toBe(transaction2Id) + assert.notStrictEqual(transaction1Id, transaction2Id) const event1 = OCPP20ServiceUtils.buildTransactionEvent( mockStation, @@ -1907,9 +1935,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 ) }) @@ -1961,15 +1990,15 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 () => { @@ -2005,21 +2034,22 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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 ) }) @@ -2039,8 +2069,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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) @@ -2061,11 +2091,9 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) 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 () => { @@ -2086,15 +2114,12 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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()) }) }) @@ -2122,15 +2147,15 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 () => { @@ -2149,14 +2174,12 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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 () => { @@ -2193,23 +2216,29 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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 () => { @@ -2218,11 +2247,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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) }) }) @@ -2241,7 +2270,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { connectorId, transactionId ) - expect(sentRequests[0].payload.seqNo).toBe(0) + assert.strictEqual(sentRequests[0].payload.seqNo, 0) setOnline(false) @@ -2265,8 +2294,8 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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, @@ -2276,10 +2305,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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) } }) }) @@ -2320,17 +2349,15 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 ) }) @@ -2363,20 +2390,22 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { await OCPP20ServiceUtils.sendQueuedTransactionEvents(mockStation, 1) - expect(sentRequests.length).toBe(1) - expect( - (sentRequests[0].payload.transactionInfo as Record).transactionId - ).toBe(transactionId1) + assert.strictEqual(sentRequests.length, 1) + assert.strictEqual( + (sentRequests[0].payload.transactionInfo as Record).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).transactionId - ).toBe(transactionId2) + assert.strictEqual(sentRequests.length, 2) + assert.strictEqual( + (sentRequests[1].payload.transactionInfo as Record).transactionId, + transactionId2 + ) }) }) @@ -2441,7 +2470,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { await OCPP20ServiceUtils.sendQueuedTransactionEvents(errorStation, connectorId) - expect(callCount).toBe(3) + assert.strictEqual(callCount, 3) }) }) }) @@ -2488,7 +2517,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // 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', () => { @@ -2496,34 +2525,34 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { // 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) }) }) @@ -2545,10 +2574,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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 ) }) @@ -2568,7 +2601,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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++) { @@ -2579,12 +2612,12 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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 () => { @@ -2603,7 +2636,10 @@ await describe('OCPP20 TransactionEvent ServiceUtils', 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 () => { @@ -2621,8 +2657,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // Verify EVSE info is present - expect(sentRequests[0].payload.evse).toBeDefined() - expect((sentRequests[0].payload.evse as Record).id).toBe(connectorId) + assert.notStrictEqual(sentRequests[0].payload.evse, undefined) + assert.strictEqual( + (sentRequests[0].payload.evse as Record).id, + connectorId + ) }) await it('should include transactionInfo with correct transactionId', async () => { @@ -2640,10 +2679,11 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // Verify transactionInfo contains the transaction ID - expect(sentRequests[0].payload.transactionInfo).toBeDefined() - expect( - (sentRequests[0].payload.transactionInfo as Record).transactionId - ).toBe(transactionId) + assert.notStrictEqual(sentRequests[0].payload.transactionInfo, undefined) + assert.strictEqual( + (sentRequests[0].payload.transactionInfo as Record).transactionId, + transactionId + ) }) }) @@ -2664,7 +2704,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { 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++) { @@ -2675,7 +2715,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { connectorId, transactionId ) - expect(updateEvent.seqNo).toBe(i) + assert.strictEqual(updateEvent.seqNo, i) } // 3. Ended event (seqNo: 4) @@ -2686,7 +2726,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { connectorId, transactionId ) - expect(endEvent.seqNo).toBe(4) + assert.strictEqual(endEvent.seqNo, 4) }) await it('should handle multiple connectors with independent timers', () => { @@ -2730,14 +2770,14 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) // 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) }) }) @@ -2776,7 +2816,7 @@ await describe('OCPP20 TransactionEvent ServiceUtils', async () => { ) 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')) } }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts index ec5a2e3b..285c8bca 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ServiceUtils-enforceMessageLimits.test.ts @@ -3,7 +3,7 @@ * @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' @@ -86,8 +86,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -105,8 +105,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { logger ) - expect(result.rejected).toBe(false) - expect(result.results).toStrictEqual([]) + assert.strictEqual(result.rejected, false) + assert.deepStrictEqual(result.results, []) }) }) @@ -127,8 +127,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -147,8 +147,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -167,11 +167,11 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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')) } }) @@ -191,10 +191,10 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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') } }) @@ -214,8 +214,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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')) }) }) @@ -236,8 +236,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -256,11 +256,11 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -279,10 +279,10 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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') } }) @@ -302,8 +302,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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')) }) }) @@ -324,9 +324,9 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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') } }) }) @@ -352,8 +352,8 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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', () => { @@ -376,10 +376,10 @@ await describe('OCPP20ServiceUtils.enforceMessageLimits', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts index dc7eccff..199b7674 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts @@ -3,8 +3,8 @@ * @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' @@ -108,8 +108,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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 () => { @@ -134,24 +134,28 @@ await describe('B05 - OCPP20VariableManager', 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)', () => { @@ -163,10 +167,10 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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)', () => { @@ -177,15 +181,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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)', () => { @@ -198,8 +202,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) } }) @@ -213,17 +217,22 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -237,17 +246,30 @@ await describe('B05 - OCPP20VariableManager', async () => { 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' + ) ) }) @@ -260,9 +282,12 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -278,18 +303,25 @@ await describe('B05 - OCPP20VariableManager', async () => { 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' + ) ) }) @@ -312,32 +344,36 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -353,16 +389,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) }) @@ -381,7 +417,7 @@ await describe('B05 - OCPP20VariableManager', async () => { // 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). @@ -390,14 +426,14 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) }) @@ -414,7 +450,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -422,7 +458,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -432,7 +468,7 @@ await describe('B05 - OCPP20VariableManager', async () => { } const isSupported = testable.isVariableSupported(component, variable) - expect(isSupported).toBe(false) + assert.strictEqual(isSupported, false) }) }) @@ -461,13 +497,13 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -481,9 +517,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -497,9 +533,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -514,9 +550,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -531,11 +573,20 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -559,8 +610,8 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -588,15 +639,41 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -608,8 +685,8 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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)', () => { @@ -621,8 +698,8 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -634,8 +711,8 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -654,9 +731,9 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -673,7 +750,7 @@ await describe('B05 - OCPP20VariableManager', async () => { variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval }, }, ])[0] - expect(beforeReset.attributeValue).toBe('99') + assert.strictEqual(beforeReset.attributeValue, '99') manager.resetRuntimeOverrides() const afterReset = manager.getVariables(station, [ { @@ -681,7 +758,7 @@ await describe('B05 - OCPP20VariableManager', async () => { variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval }, }, ])[0] - expect(afterReset.attributeValue).toBe('30') + assert.strictEqual(afterReset.attributeValue, '30') }) await it('should keep persistent ConnectionUrl after simulated restart', () => { @@ -699,9 +776,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -714,9 +791,15 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -730,8 +813,8 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ] 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', () => { @@ -756,12 +839,38 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -779,10 +888,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -800,10 +909,28 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -814,8 +941,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', @@ -837,12 +964,38 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -853,8 +1006,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', @@ -876,12 +1029,38 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -889,7 +1068,7 @@ await describe('B05 - OCPP20VariableManager', async () => { station, OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name'] ) - expect(keyBefore).toBeDefined() + assert.notStrictEqual(keyBefore, undefined) const originalValue = keyBefore?.value const first = manager.setVariables(station, [ { @@ -898,7 +1077,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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(), @@ -906,12 +1085,12 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', @@ -919,12 +1098,12 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -937,21 +1116,21 @@ await describe('B05 - OCPP20VariableManager', async () => { 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()', () => { @@ -968,7 +1147,7 @@ await describe('B05 - OCPP20VariableManager', async () => { variable: { name: OCPP20RequiredVariableName.TxUpdatedInterval }, }, ])[0] - expect(beforeReset.attributeValue).toBe('123') + assert.strictEqual(beforeReset.attributeValue, '123') manager.resetRuntimeOverrides() const afterReset = manager.getVariables(station, [ { @@ -976,8 +1155,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -988,10 +1167,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -1003,10 +1188,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -1018,10 +1209,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -1033,8 +1230,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1045,10 +1242,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -1060,10 +1263,16 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') ) }) @@ -1075,9 +1284,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1089,10 +1304,18 @@ await describe('B05 - OCPP20VariableManager', async () => { 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()})` + ) ) }) @@ -1104,14 +1327,22 @@ await describe('B05 - OCPP20VariableManager', async () => { 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()})` + ) ) }) @@ -1132,7 +1363,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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'), @@ -1140,8 +1371,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1159,7 +1393,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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'), @@ -1167,8 +1401,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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)', () => { @@ -1182,7 +1419,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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'), @@ -1190,8 +1427,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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)', () => { @@ -1205,7 +1445,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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'), @@ -1213,8 +1453,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1229,7 +1472,7 @@ await describe('B05 - OCPP20VariableManager', async () => { variable: { name: OCPP20VendorVariableName.ConnectionUrl }, }, ])[0] - expect(okRes.attributeStatus).toBe(SetVariableStatusEnumType.Accepted) + assert.strictEqual(okRes.attributeStatus, SetVariableStatusEnumType.Accepted) }) }) @@ -1276,10 +1519,10 @@ await describe('B05 - OCPP20VariableManager', async () => { ] 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) } }) @@ -1290,8 +1533,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1303,14 +1546,14 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', @@ -1318,21 +1561,21 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1343,9 +1586,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1356,7 +1605,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1395,24 +1644,30 @@ await describe('B05 - OCPP20VariableManager', async () => { 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')) } } } @@ -1428,9 +1683,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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 () => { @@ -1452,8 +1713,8 @@ await describe('B05 - OCPP20VariableManager', 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', () => { @@ -1462,8 +1723,8 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) }) @@ -1488,7 +1749,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1497,10 +1758,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) @@ -1517,7 +1778,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) @@ -1527,9 +1788,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) @@ -1553,9 +1814,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) @@ -1576,7 +1837,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) @@ -1586,9 +1847,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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) }) @@ -1607,21 +1868,21 @@ await describe('B05 - OCPP20VariableManager', async () => { 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', () => { @@ -1633,14 +1894,14 @@ await describe('B05 - OCPP20VariableManager', async () => { }, ])[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()', () => { @@ -1660,9 +1921,9 @@ await describe('B05 - OCPP20VariableManager', async () => { 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)', () => { @@ -1671,15 +1932,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1690,7 +1951,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, @@ -1698,7 +1962,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1709,7 +1976,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, @@ -1717,7 +1987,10 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1727,8 +2000,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1738,8 +2014,11 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1749,7 +2028,7 @@ await describe('B05 - OCPP20VariableManager', async () => { 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, [ @@ -1758,15 +2037,15 @@ await describe('B05 - OCPP20VariableManager', async () => { 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') }) }) }) diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts index bcf7c1ce..bf99cb91 100644 --- a/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceUtils-authorization.test.ts @@ -11,7 +11,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' @@ -62,7 +62,7 @@ await describe('OCPPServiceUtils — authorization wrappers', async () => { 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 () => { @@ -72,7 +72,7 @@ await describe('OCPPServiceUtils — authorization wrappers', 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 () => { @@ -84,8 +84,8 @@ await describe('OCPPServiceUtils — authorization wrappers', 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 () => { @@ -100,7 +100,7 @@ await describe('OCPPServiceUtils — authorization wrappers', 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 () => { @@ -115,7 +115,7 @@ await describe('OCPPServiceUtils — authorization wrappers', 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 () => { @@ -125,7 +125,7 @@ await describe('OCPPServiceUtils — authorization wrappers', async () => { setupLocalAuth(station, mocks, ['TAG-001']) const result = await isIdTagAuthorized(station, 99, 'TAG-001') - expect(result).toBe(false) + assert.strictEqual(result, false) }) }) @@ -137,7 +137,7 @@ await describe('OCPPServiceUtils — authorization wrappers', async () => { 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 () => { @@ -146,7 +146,7 @@ await describe('OCPPServiceUtils — authorization wrappers', 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 () => { @@ -155,7 +155,7 @@ await describe('OCPPServiceUtils — authorization wrappers', async () => { }) const result = await isIdTagAuthorizedUnified(station, 1, 'TAG-001') - expect(result).toBe(false) + assert.strictEqual(result, false) }) }) }) diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-connectorStatus.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-connectorStatus.test.ts index 6862da01..27e92439 100644 --- a/tests/charging-station/ocpp/OCPPServiceUtils-connectorStatus.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceUtils-connectorStatus.test.ts @@ -7,7 +7,7 @@ * - 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' @@ -50,8 +50,8 @@ await describe('OCPPServiceUtils — connector status management', async () => { 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 () => { @@ -59,7 +59,7 @@ await describe('OCPPServiceUtils — connector status management', 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 () => { @@ -69,18 +69,18 @@ await describe('OCPPServiceUtils — connector status management', 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 () => { @@ -90,7 +90,7 @@ await describe('OCPPServiceUtils — connector status management', 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 () => { @@ -100,8 +100,8 @@ await describe('OCPPServiceUtils — connector status management', 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 () => { @@ -109,7 +109,7 @@ await describe('OCPPServiceUtils — connector status management', async () => { await sendAndSetConnectorStatus(station, 1, ConnectorStatusEnum.Occupied) - expect(requestHandler.mock.calls.length).toBe(1) + assert.strictEqual(requestHandler.mock.calls.length, 1) }) }) @@ -130,7 +130,7 @@ await describe('OCPPServiceUtils — connector status management', async () => { 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 () => { @@ -143,7 +143,7 @@ await describe('OCPPServiceUtils — connector status management', 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 () => { @@ -156,8 +156,8 @@ await describe('OCPPServiceUtils — connector status management', 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) }) }) }) diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts index 79308523..ac097306 100644 --- a/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts @@ -11,7 +11,7 @@ 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' @@ -59,59 +59,66 @@ await describe('OCPPServiceUtils — pure functions', async () => { 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 ) }) @@ -122,32 +129,33 @@ await describe('OCPPServiceUtils — pure functions', async () => { const date = new Date('2025-01-15T10:30:00.000Z') const obj = { timestamp: date } as unknown as JsonType convertDateToISOString(obj) - expect((obj as Record).timestamp).toBe('2025-01-15T10:30:00.000Z') + assert.strictEqual((obj as Record).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).nested as Record).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).items).toStrictEqual(['2025-03-10T08:00:00.000Z']) + assert.deepStrictEqual((obj as Record).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' }) }) }) @@ -158,7 +166,7 @@ await describe('OCPPServiceUtils — pure functions', async () => { IncomingRequestCommand.REMOTE_START_TRANSACTION, 1 ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return true for connector ID zero', () => { @@ -167,7 +175,7 @@ await describe('OCPPServiceUtils — pure functions', async () => { IncomingRequestCommand.REMOTE_START_TRANSACTION, 0 ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return false for negative connector ID', () => { @@ -176,7 +184,7 @@ await describe('OCPPServiceUtils — pure functions', async () => { IncomingRequestCommand.REMOTE_START_TRANSACTION, -1 ) - expect(result).toBe(false) + assert.strictEqual(result, false) }) }) }) diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-validation.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-validation.test.ts index 9b959a59..0921e9f0 100644 --- a/tests/charging-station/ocpp/OCPPServiceUtils-validation.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceUtils-validation.test.ts @@ -9,7 +9,7 @@ * - 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' @@ -57,7 +57,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return false when command is explicitly disabled', () => { @@ -74,7 +74,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand ) - expect(result).toBe(false) + assert.strictEqual(result, false) }) await it('should return true when commandsSupport is undefined', () => { @@ -85,7 +85,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return true when incomingCommands is empty', () => { @@ -98,7 +98,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16IncomingRequestCommand.RESET as IncomingRequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) }) @@ -117,7 +117,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16RequestCommand.HEARTBEAT as RequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return false when command is explicitly disabled', () => { @@ -134,7 +134,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16RequestCommand.HEARTBEAT as RequestCommand ) - expect(result).toBe(false) + assert.strictEqual(result, false) }) await it('should return true when commandsSupport is undefined', () => { @@ -145,7 +145,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16RequestCommand.HEARTBEAT as RequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return true when outgoingCommands is empty', () => { @@ -158,7 +158,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16RequestCommand.HEARTBEAT as RequestCommand ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) }) @@ -175,7 +175,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16MessageTrigger.Heartbeat as MessageTrigger ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return false when trigger is explicitly disabled', () => { @@ -190,7 +190,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16MessageTrigger.Heartbeat as MessageTrigger ) - expect(result).toBe(false) + assert.strictEqual(result, false) }) await it('should return true when messageTriggerSupport is undefined', () => { @@ -201,7 +201,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16MessageTrigger.Heartbeat as MessageTrigger ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) await it('should return true when messageTriggerSupport is null', () => { @@ -214,7 +214,7 @@ await describe('OCPPServiceUtils — command/trigger validation', async () => { OCPP16MessageTrigger.Heartbeat as MessageTrigger ) - expect(result).toBe(true) + assert.strictEqual(result, true) }) }) }) diff --git a/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts b/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts index c471f5f2..5dae7cf3 100644 --- a/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts +++ b/tests/charging-station/ocpp/auth/OCPPAuthIntegration.test.ts @@ -3,7 +3,7 @@ * @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' @@ -79,11 +79,11 @@ await describe('OCPP Authentication', async () => { 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 () => { @@ -102,8 +102,8 @@ await describe('OCPP Authentication', 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) } }) @@ -114,8 +114,8 @@ await describe('OCPP Authentication', async () => { }) const result = await authService16.authorize(request) - expect(result).toBeDefined() - expect(result.timestamp).toBeInstanceOf(Date) + assert.notStrictEqual(result, undefined) + assert.ok(result.timestamp instanceof Date) }) }) @@ -135,10 +135,10 @@ await describe('OCPP Authentication', async () => { 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 () => { @@ -157,8 +157,8 @@ await describe('OCPP Authentication', 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) } }) }) @@ -184,8 +184,8 @@ await describe('OCPP Authentication', async () => { 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) }) }) @@ -212,10 +212,10 @@ await describe('OCPP Authentication', async () => { 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) } }) }) @@ -236,10 +236,10 @@ await describe('OCPP Authentication', async () => { 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) @@ -253,8 +253,8 @@ await describe('OCPP Authentication', async () => { 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() } @@ -279,12 +279,12 @@ await describe('OCPP Authentication', async () => { ) 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() } @@ -302,16 +302,16 @@ await describe('OCPP Authentication', async () => { // 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() } @@ -330,8 +330,8 @@ await describe('OCPP Authentication', async () => { 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() } @@ -361,12 +361,12 @@ await describe('OCPP Authentication', async () => { 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() } @@ -385,22 +385,22 @@ await describe('OCPP Authentication', async () => { 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() } diff --git a/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts b/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts index 9b965b5c..238b108d 100644 --- a/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts +++ b/tests/charging-station/ocpp/auth/adapters/OCPP16AuthAdapter.test.ts @@ -2,7 +2,7 @@ * @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' @@ -61,7 +61,7 @@ await describe('OCPP16AuthAdapter', async () => { 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) }) }) @@ -71,9 +71,9 @@ await describe('OCPP16AuthAdapter', async () => { 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', () => { @@ -81,9 +81,9 @@ await describe('OCPP16AuthAdapter', async () => { 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') }) }) @@ -92,7 +92,7 @@ await describe('OCPP16AuthAdapter', async () => { 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') }) }) @@ -100,13 +100,13 @@ await describe('OCPP16AuthAdapter', async () => { 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)', () => { @@ -115,7 +115,7 @@ await describe('OCPP16AuthAdapter', async () => { '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', () => { @@ -125,7 +125,7 @@ await describe('OCPP16AuthAdapter', async () => { IdentifierType.CENTRAL ) - expect(adapter.isValidIdentifier(identifier)).toBe(false) + assert.strictEqual(adapter.isValidIdentifier(identifier), false) }) }) @@ -133,26 +133,26 @@ await describe('OCPP16AuthAdapter', async () => { 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) }) }) @@ -162,10 +162,10 @@ await describe('OCPP16AuthAdapter', async () => { 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 () => { @@ -179,22 +179,22 @@ await describe('OCPP16AuthAdapter', 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', () => { @@ -203,7 +203,7 @@ await describe('OCPP16AuthAdapter', async () => { } const isAvailable = adapter.isRemoteAvailable() - expect(isAvailable).toBe(false) + assert.strictEqual(isAvailable, false) }) }) @@ -221,7 +221,7 @@ await describe('OCPP16AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(true) + assert.strictEqual(isValid, true) }) await it('should reject configuration with no auth methods', () => { @@ -237,7 +237,7 @@ await describe('OCPP16AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(false) + assert.strictEqual(isValid, false) }) await it('should reject configuration with invalid timeout', () => { @@ -253,7 +253,7 @@ await describe('OCPP16AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(false) + assert.strictEqual(isValid, false) }) }) @@ -261,11 +261,11 @@ await describe('OCPP16AuthAdapter', async () => { 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') }) }) @@ -273,14 +273,14 @@ await describe('OCPP16AuthAdapter', async () => { 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 - 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')) }) }) @@ -295,9 +295,9 @@ await describe('OCPP16AuthAdapter', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts b/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts index 0f2f3fd2..cabe073e 100644 --- a/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts +++ b/tests/charging-station/ocpp/auth/adapters/OCPP20AuthAdapter.test.ts @@ -2,7 +2,7 @@ * @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' @@ -47,7 +47,7 @@ await describe('OCPP20AuthAdapter', async () => { 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) }) }) @@ -61,19 +61,19 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -84,8 +84,8 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -100,9 +100,9 @@ await describe('OCPP20AuthAdapter', async () => { 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) }) }) @@ -116,8 +116,8 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -129,8 +129,8 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -138,7 +138,7 @@ await describe('OCPP20AuthAdapter', async () => { const result = adapter.convertFromUnifiedIdentifier(identifier) - expect(result.type).toBe(OCPP20IdTokenEnumType.Local) + assert.strictEqual(result.type, OCPP20IdTokenEnumType.Local) }) }) @@ -150,13 +150,13 @@ await describe('OCPP20AuthAdapter', async () => { 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)', () => { @@ -166,7 +166,7 @@ await describe('OCPP20AuthAdapter', async () => { IdentifierType.CENTRAL ) - expect(adapter.isValidIdentifier(identifier)).toBe(false) + assert.strictEqual(adapter.isValidIdentifier(identifier), false) }) await it('should accept all OCPP 2.0 identifier types', () => { @@ -182,7 +182,7 @@ await describe('OCPP20AuthAdapter', async () => { 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) } }) }) @@ -196,25 +196,25 @@ await describe('OCPP20AuthAdapter', async () => { '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) }) }) @@ -245,10 +245,10 @@ await describe('OCPP20AuthAdapter', async () => { 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 () => { @@ -256,8 +256,8 @@ await describe('OCPP20AuthAdapter', 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) }) }) @@ -270,7 +270,7 @@ await describe('OCPP20AuthAdapter', async () => { ) const isAvailable = adapter.isRemoteAvailable() - expect(isAvailable).toBe(true) + assert.strictEqual(isAvailable, true) }) await it('should return false when station is offline', t => { @@ -282,7 +282,7 @@ await describe('OCPP20AuthAdapter', async () => { ) const isAvailable = adapter.isRemoteAvailable() - expect(isAvailable).toBe(false) + assert.strictEqual(isAvailable, false) }) }) @@ -300,7 +300,7 @@ await describe('OCPP20AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(true) + assert.strictEqual(isValid, true) }) await it('should reject configuration with no auth methods', () => { @@ -316,7 +316,7 @@ await describe('OCPP20AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(false) + assert.strictEqual(isValid, false) }) await it('should reject configuration with invalid timeout', () => { @@ -332,7 +332,7 @@ await describe('OCPP20AuthAdapter', async () => { } const isValid = adapter.validateConfiguration(config) - expect(isValid).toBe(false) + assert.strictEqual(isValid, false) }) }) @@ -340,11 +340,11 @@ await describe('OCPP20AuthAdapter', async () => { 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)) }) }) @@ -352,14 +352,14 @@ await describe('OCPP20AuthAdapter', async () => { 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 - 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')) }) }) @@ -370,7 +370,7 @@ await describe('OCPP20AuthAdapter', async () => { }) 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', () => { @@ -386,7 +386,7 @@ await describe('OCPP20AuthAdapter', async () => { status, }) const response = adapter.convertToOCPP20Response(result) - expect(response).toBe(RequestStartStopStatusEnumType.Rejected) + assert.strictEqual(response, RequestStartStopStatusEnumType.Rejected) } }) }) @@ -420,7 +420,7 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -431,12 +431,12 @@ await describe('OCPP20AuthAdapter', async () => { 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) }) }) @@ -449,7 +449,7 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -462,7 +462,7 @@ await describe('OCPP20AuthAdapter', async () => { const isAvailable = offlineAdapter.isRemoteAvailable() // Then: Should safely return false - expect(isAvailable).toBe(false) + assert.strictEqual(isAvailable, false) }) }) @@ -470,7 +470,7 @@ await describe('OCPP20AuthAdapter', async () => { 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', () => { @@ -478,8 +478,8 @@ await describe('OCPP20AuthAdapter', async () => { 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 }) @@ -489,9 +489,9 @@ await describe('OCPP20AuthAdapter', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts b/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts index cdc4389d..69b44649 100644 --- a/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts +++ b/tests/charging-station/ocpp/auth/cache/InMemoryAuthCache.test.ts @@ -2,7 +2,7 @@ * @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' @@ -66,9 +66,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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', () => { @@ -79,9 +79,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -99,7 +99,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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) @@ -109,9 +109,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -125,7 +125,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -134,9 +134,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -154,9 +154,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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% }) }) @@ -177,8 +177,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { const result = cache.get(identifier) - expect(result).toBeDefined() - expect(result?.status).toBe(AuthorizationStatus.EXPIRED) + assert.notStrictEqual(result, undefined) + assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED) }) }) @@ -196,7 +196,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { cache.get('token-2') const stats = cache.getStats() - expect(stats.expiredEntries).toBeGreaterThanOrEqual(2) + assert.ok(stats.expiredEntries >= 2) }) }) @@ -211,8 +211,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -225,8 +225,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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) }) }) @@ -244,14 +244,14 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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', () => { @@ -260,12 +260,12 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -274,15 +274,15 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -303,10 +303,10 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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', () => { @@ -319,8 +319,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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 => { @@ -334,7 +334,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { t.mock.timers.tick(1100) const result = cache.get(identifier) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) }) @@ -348,7 +348,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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', () => { @@ -362,10 +362,10 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { } 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) }) }) @@ -388,15 +388,15 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -406,9 +406,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { } 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) }) }) @@ -427,11 +427,11 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -446,7 +446,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { const statsAfter = cache.getStats() const memoryAfter = statsAfter.memoryUsage - expect(memoryAfter).toBeGreaterThan(memoryBefore) + assert.ok(memoryAfter > memoryBefore) }) await it('should provide rate limit statistics', () => { @@ -458,10 +458,10 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -476,7 +476,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { cache.set('', mockResult) const result = cache.get('') - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) await it('should handle very long identifier strings', () => { @@ -485,7 +485,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { cache.set(longIdentifier, mockResult) const result = cache.get(longIdentifier) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) await it('should handle concurrent operations', () => { @@ -497,17 +497,17 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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', () => { @@ -515,7 +515,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { cache.set('token', mockResult, 31536000) const result = cache.get('token') - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) }) @@ -529,8 +529,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -541,7 +541,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -556,8 +556,14 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -570,8 +576,11 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -599,10 +608,10 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -627,9 +636,9 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -647,13 +656,13 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -670,8 +679,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) }) @@ -691,8 +700,8 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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) }) }) @@ -711,15 +720,15 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { // 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) }) }) }) @@ -727,12 +736,12 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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...') }) }) @@ -744,12 +753,12 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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', () => { @@ -759,7 +768,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { maxEntries: 10, rateLimit: { enabled: false }, }) - expect(noCleanupCache.hasCleanupInterval()).toBe(false) + assert.strictEqual(noCleanupCache.hasCleanupInterval(), false) noCleanupCache.dispose() }) @@ -776,23 +785,23 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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() }) }) @@ -810,7 +819,7 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { } const rateLimitsSize = boundedCache.getStats().rateLimit.rateLimitedIdentifiers - expect(rateLimitsSize).toBeLessThanOrEqual(4) + assert.ok(rateLimitsSize <= 4) boundedCache.dispose() }) }) @@ -831,17 +840,17 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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() }) @@ -858,15 +867,15 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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() }) @@ -884,14 +893,14 @@ await describe('InMemoryAuthCache - G03.FR.01 Conformance', async () => { 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() }) }) diff --git a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts index fd9c993d..c84008a2 100644 --- a/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts +++ b/tests/charging-station/ocpp/auth/factories/AuthComponentFactory.test.ts @@ -3,7 +3,7 @@ * @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' @@ -26,8 +26,8 @@ await describe('AuthComponentFactory', async () => { }) 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 () => { @@ -36,8 +36,8 @@ await describe('AuthComponentFactory', 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 () => { @@ -46,8 +46,8 @@ await describe('AuthComponentFactory', 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 () => { @@ -55,18 +55,18 @@ await describe('AuthComponentFactory', 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/, + }) }) }) @@ -84,11 +84,11 @@ await describe('AuthComponentFactory', async () => { 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') }) }) @@ -107,7 +107,7 @@ await describe('AuthComponentFactory', async () => { const result = AuthComponentFactory.createLocalAuthListManager(chargingStation, config) - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) }) @@ -125,7 +125,7 @@ await describe('AuthComponentFactory', async () => { const result = await AuthComponentFactory.createLocalStrategy(undefined, undefined, config) - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) await it('should create local strategy when enabled', async () => { @@ -141,9 +141,9 @@ await describe('AuthComponentFactory', 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) } }) }) @@ -167,7 +167,7 @@ await describe('AuthComponentFactory', async () => { const result = await AuthComponentFactory.createRemoteStrategy(adapters, undefined, config) - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) await it('should create remote strategy when enabled', async () => { @@ -188,9 +188,9 @@ await describe('AuthComponentFactory', 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) } }) }) @@ -217,8 +217,8 @@ await describe('AuthComponentFactory', async () => { config ) - expect(result).toBeDefined() - expect(result.priority).toBe(3) + assert.notStrictEqual(result, undefined) + assert.strictEqual(result.priority, 3) }) }) @@ -246,8 +246,8 @@ await describe('AuthComponentFactory', async () => { 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 () => { @@ -274,10 +274,10 @@ await describe('AuthComponentFactory', 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 }) }) @@ -295,9 +295,9 @@ await describe('AuthComponentFactory', async () => { remoteAuthorization: true, } - expect(() => { + assert.doesNotThrow(() => { AuthComponentFactory.validateConfiguration(config) - }).not.toThrow() + }) }) await it('should throw on invalid configuration', () => { @@ -312,9 +312,9 @@ await describe('AuthComponentFactory', async () => { offlineAuthorizationEnabled: false, } - expect(() => { + assert.throws(() => { AuthComponentFactory.validateConfiguration(config) - }).toThrow() + }) }) }) }) diff --git a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts index 4f204b88..b303bd9a 100644 --- a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts +++ b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts @@ -2,7 +2,7 @@ * @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 { @@ -235,10 +235,10 @@ export const expectAcceptedAuthorization = ( 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) } } @@ -251,9 +251,9 @@ export const expectRejectedAuthorization = ( 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) } // ============================================================================ diff --git a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts index 938a5a70..c1ef1868 100644 --- a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts +++ b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceFactory.test.ts @@ -2,7 +2,7 @@ * @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' @@ -31,23 +31,23 @@ await describe('OCPPAuthServiceFactory', async () => { 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 () => { @@ -59,10 +59,10 @@ await describe('OCPPAuthServiceFactory', 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')) } }) }) @@ -80,9 +80,9 @@ await describe('OCPPAuthServiceFactory', async () => { 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 () => { @@ -90,7 +90,7 @@ await describe('OCPPAuthServiceFactory', async () => { await OCPPAuthServiceFactory.createInstance(mockStation20) const finalCount = OCPPAuthServiceFactory.getCachedInstanceCount() - expect(finalCount).toBe(initialCount) + assert.strictEqual(finalCount, initialCount) }) }) @@ -113,13 +113,13 @@ await describe('OCPPAuthServiceFactory', async () => { // 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() + }) }) }) @@ -142,7 +142,7 @@ await describe('OCPPAuthServiceFactory', async () => { // Verify all cleared const count = OCPPAuthServiceFactory.getCachedInstanceCount() - expect(count).toBe(0) + assert.strictEqual(count, 0) }) }) @@ -157,17 +157,17 @@ await describe('OCPPAuthServiceFactory', async () => { }) 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) }) }) @@ -187,11 +187,11 @@ await describe('OCPPAuthServiceFactory', async () => { 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', () => { @@ -199,8 +199,8 @@ await describe('OCPPAuthServiceFactory', async () => { 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) }) }) @@ -216,17 +216,17 @@ await describe('OCPPAuthServiceFactory', async () => { 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') }) }) @@ -249,15 +249,15 @@ await describe('OCPPAuthServiceFactory', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts index 7f437325..abbbc7ed 100644 --- a/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts +++ b/tests/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.test.ts @@ -2,7 +2,7 @@ * @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' @@ -38,15 +38,15 @@ await describe('OCPPAuthServiceImpl', async () => { 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) }) }) @@ -61,10 +61,10 @@ await describe('OCPPAuthServiceImpl', async () => { 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) }) }) @@ -84,8 +84,8 @@ await describe('OCPPAuthServiceImpl', async () => { }) const config = authService.getConfiguration() - expect(config.authorizationTimeout).toBe(60) - expect(config.localAuthListEnabled).toBe(false) + assert.strictEqual(config.authorizationTimeout, 60) + assert.strictEqual(config.localAuthListEnabled, false) }) }) @@ -108,7 +108,7 @@ await describe('OCPPAuthServiceImpl', async () => { 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 () => { @@ -121,7 +121,7 @@ await describe('OCPPAuthServiceImpl', async () => { value: 'CENTRAL_ID', } - expect(authService.isSupported(centralIdentifier)).toBe(true) + assert.strictEqual(authService.isSupported(centralIdentifier), true) }) }) @@ -136,7 +136,7 @@ await describe('OCPPAuthServiceImpl', async () => { const authService = new OCPPAuthServiceImpl(mockStation) const isConnected = authService.testConnectivity() - expect(typeof isConnected).toBe('boolean') + assert.strictEqual(typeof isConnected, 'boolean') }) }) @@ -150,9 +150,9 @@ await describe('OCPPAuthServiceImpl', async () => { await it('should clear authorization cache', () => { const authService = new OCPPAuthServiceImpl(mockStation) - expect(() => { + assert.doesNotThrow(() => { authService.clearCache() - }).not.toThrow() + }) }) }) @@ -172,9 +172,9 @@ await describe('OCPPAuthServiceImpl', async () => { value: 'TAG_TO_INVALIDATE', } - expect(() => { + assert.doesNotThrow(() => { authService.invalidateCache(identifier) - }).not.toThrow() + }) }) }) @@ -189,11 +189,11 @@ await describe('OCPPAuthServiceImpl', async () => { 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) }) }) @@ -221,9 +221,9 @@ await describe('OCPPAuthServiceImpl', async () => { 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 () => { @@ -243,8 +243,8 @@ await describe('OCPPAuthServiceImpl', 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) }) }) @@ -267,7 +267,7 @@ await describe('OCPPAuthServiceImpl', async () => { 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') }) }) @@ -297,7 +297,7 @@ await describe('OCPPAuthServiceImpl', async () => { timestamp: new Date(), }) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) await it('should handle OCPP 2.0 specific identifiers', async () => { @@ -317,7 +317,7 @@ await describe('OCPPAuthServiceImpl', async () => { timestamp: new Date(), }) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) }) @@ -345,7 +345,7 @@ await describe('OCPPAuthServiceImpl', async () => { timestamp: new Date(), }) - expect(result.status).toBe(AuthorizationStatus.INVALID) + assert.strictEqual(result.status, AuthorizationStatus.INVALID) }) }) @@ -375,8 +375,8 @@ await describe('OCPPAuthServiceImpl', async () => { 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 () => { @@ -397,7 +397,7 @@ await describe('OCPPAuthServiceImpl', async () => { transactionId: 'TXN-123', }) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) await it('should handle REMOTE_START context', async () => { @@ -417,7 +417,7 @@ await describe('OCPPAuthServiceImpl', async () => { timestamp: new Date(), }) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) }) }) @@ -433,11 +433,11 @@ await describe('OCPPAuthServiceImpl', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts b/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts index c7189b5c..88c837f9 100644 --- a/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts +++ b/tests/charging-station/ocpp/auth/strategies/CertificateAuthStrategy.test.ts @@ -2,7 +2,7 @@ * @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' @@ -66,24 +66,24 @@ await describe('CertificateAuthStrategy', async () => { 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() + }) }) }) @@ -109,7 +109,7 @@ await describe('CertificateAuthStrategy', async () => { }, }) - expect(strategy.canHandle(request, config)).toBe(true) + assert.strictEqual(strategy.canHandle(request, config), true) }) await it('should return false for non-certificate identifiers', () => { @@ -122,7 +122,7 @@ await describe('CertificateAuthStrategy', async () => { }, }) - expect(strategy.canHandle(request, config)).toBe(false) + assert.strictEqual(strategy.canHandle(request, config), false) }) await it('should return false for OCPP 1.6', () => { @@ -135,7 +135,7 @@ await describe('CertificateAuthStrategy', async () => { }, }) - expect(strategy.canHandle(request, config)).toBe(false) + assert.strictEqual(strategy.canHandle(request, config), false) }) await it('should return false when certificate auth is disabled', () => { @@ -154,7 +154,7 @@ await describe('CertificateAuthStrategy', async () => { }, }) - expect(strategy.canHandle(request, config)).toBe(false) + assert.strictEqual(strategy.canHandle(request, config), false) }) await it('should return false when missing certificate data', () => { @@ -167,7 +167,7 @@ await describe('CertificateAuthStrategy', async () => { }, }) - expect(strategy.canHandle(request, config)).toBe(false) + assert.strictEqual(strategy.canHandle(request, config), false) }) }) @@ -195,9 +195,9 @@ await describe('CertificateAuthStrategy', async () => { 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 () => { @@ -218,8 +218,8 @@ await describe('CertificateAuthStrategy', 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 () => { @@ -240,8 +240,8 @@ await describe('CertificateAuthStrategy', 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 () => { @@ -256,8 +256,8 @@ await describe('CertificateAuthStrategy', 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 () => { @@ -278,8 +278,8 @@ await describe('CertificateAuthStrategy', 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 () => { @@ -300,8 +300,8 @@ await describe('CertificateAuthStrategy', 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) }) }) @@ -309,10 +309,10 @@ await describe('CertificateAuthStrategy', async () => { 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 () => { @@ -336,8 +336,8 @@ await describe('CertificateAuthStrategy', 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) }) }) @@ -347,7 +347,7 @@ await describe('CertificateAuthStrategy', async () => { strategy.cleanup() const stats = strategy.getStats() - expect(stats.isInitialized).toBe(false) + assert.strictEqual(stats.isInitialized, false) }) }) }) diff --git a/tests/charging-station/ocpp/auth/strategies/LocalAuthStrategy.test.ts b/tests/charging-station/ocpp/auth/strategies/LocalAuthStrategy.test.ts index 9e5aa139..f6534e1d 100644 --- a/tests/charging-station/ocpp/auth/strategies/LocalAuthStrategy.test.ts +++ b/tests/charging-station/ocpp/auth/strategies/LocalAuthStrategy.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -46,13 +46,13 @@ await describe('LocalAuthStrategy', async () => { 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') }) }) @@ -62,9 +62,9 @@ await describe('LocalAuthStrategy', async () => { authorizationCacheEnabled: true, localAuthListEnabled: true, }) - expect(() => { + assert.doesNotThrow(() => { strategy.initialize(config) - }).not.toThrow() + }) }) }) @@ -74,7 +74,7 @@ await describe('LocalAuthStrategy', async () => { 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', () => { @@ -82,7 +82,7 @@ await describe('LocalAuthStrategy', async () => { 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', () => { @@ -90,7 +90,7 @@ await describe('LocalAuthStrategy', async () => { 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) }) }) @@ -129,9 +129,9 @@ await describe('LocalAuthStrategy', async () => { 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 () => { @@ -153,9 +153,9 @@ await describe('LocalAuthStrategy', 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 () => { @@ -172,10 +172,10 @@ await describe('LocalAuthStrategy', 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 () => { @@ -189,7 +189,7 @@ await describe('LocalAuthStrategy', async () => { }) const result = await strategy.authenticate(request, config) - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) }) @@ -205,7 +205,7 @@ await describe('LocalAuthStrategy', async () => { }) strategy.cacheResult('TEST_TAG', result, 300) - expect(cachedValue).toBeDefined() + assert.notStrictEqual(cachedValue, undefined) }) await it('should handle cache errors gracefully', () => { @@ -217,9 +217,9 @@ await describe('LocalAuthStrategy', async () => { method: AuthenticationMethod.REMOTE_AUTHORIZATION, }) - expect(() => { + assert.doesNotThrow(() => { strategy.cacheResult('TEST_TAG', result) - }).not.toThrow() + }) }) }) @@ -231,7 +231,7 @@ await describe('LocalAuthStrategy', async () => { } strategy.invalidateCache('TEST_TAG') - expect(removedKey).toBe('TEST_TAG') + assert.strictEqual(removedKey, 'TEST_TAG') }) }) @@ -245,7 +245,7 @@ await describe('LocalAuthStrategy', async () => { }) }) - 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 () => { @@ -254,7 +254,7 @@ await describe('LocalAuthStrategy', async () => { resolve(undefined) }) - await expect(strategy.isInLocalList('UNKNOWN_TAG')).resolves.toBe(false) + assert.strictEqual(await strategy.isInLocalList('UNKNOWN_TAG'), false) }) }) @@ -262,12 +262,12 @@ await describe('LocalAuthStrategy', async () => { 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) }) }) @@ -275,7 +275,7 @@ await describe('LocalAuthStrategy', async () => { await it('should reset strategy state', () => { strategy.cleanup() const stats = strategy.getStats() - expect(stats.isInitialized).toBe(false) + assert.strictEqual(stats.isInitialized, false) }) }) }) diff --git a/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts b/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts index f9c3204e..9ebbb81f 100644 --- a/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts +++ b/tests/charging-station/ocpp/auth/strategies/RemoteAuthStrategy.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -57,32 +57,32 @@ await describe('RemoteAuthStrategy', async () => { 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() + }) }) }) @@ -96,7 +96,7 @@ await describe('RemoteAuthStrategy', async () => { 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', () => { @@ -110,7 +110,7 @@ await describe('RemoteAuthStrategy', async () => { 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', () => { @@ -123,7 +123,7 @@ await describe('RemoteAuthStrategy', async () => { IdentifierType.ID_TAG ), }) - expect(strategyNoAdapters.canHandle(request, config)).toBe(false) + assert.strictEqual(strategyNoAdapters.canHandle(request, config), false) }) }) @@ -145,9 +145,9 @@ await describe('RemoteAuthStrategy', 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 authenticate using OCPP 2.0 adapter', async () => { @@ -162,9 +162,9 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -186,7 +186,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -217,7 +217,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -248,7 +248,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -279,7 +279,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -301,7 +301,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -317,7 +317,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -331,7 +331,7 @@ await describe('RemoteAuthStrategy', async () => { }) const result = await strategy.authenticate(request, config) - expect(result).toBeUndefined() + assert.strictEqual(result, undefined) }) await it('should handle remote authorization errors gracefully', async () => { @@ -349,7 +349,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -384,7 +384,7 @@ await describe('RemoteAuthStrategy', 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 () => { @@ -412,7 +412,7 @@ await describe('RemoteAuthStrategy', async () => { }) await strategy.authenticate(request, config) - expect(cachedKey).toBe('REMOTE_AUTH_TAG') + assert.strictEqual(cachedKey, 'REMOTE_AUTH_TAG') }) }) @@ -426,7 +426,7 @@ await describe('RemoteAuthStrategy', async () => { 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', () => { @@ -437,7 +437,7 @@ await describe('RemoteAuthStrategy', async () => { identifier: createMockIdentifier(OCPPVersion.VERSION_16, 'TEST', IdentifierType.ID_TAG), }) - expect(strategy.canHandle(request, config)).toBe(false) + assert.strictEqual(strategy.canHandle(request, config), false) }) }) @@ -445,13 +445,13 @@ await describe('RemoteAuthStrategy', async () => { 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 () => { @@ -460,26 +460,25 @@ await describe('RemoteAuthStrategy', 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) }) }) @@ -487,8 +486,8 @@ await describe('RemoteAuthStrategy', async () => { 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) }) }) }) diff --git a/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts b/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts index 37aee643..999afcdc 100644 --- a/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts +++ b/tests/charging-station/ocpp/auth/types/AuthTypes.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -37,31 +37,31 @@ await describe('AuthTypes', async () => { }) 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) }) }) @@ -69,81 +69,84 @@ await describe('AuthTypes', async () => { 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 ) }) @@ -152,17 +155,20 @@ await describe('AuthTypes', async () => { 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 ) }) @@ -171,22 +177,22 @@ await describe('AuthTypes', async () => { 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) }) }) }) @@ -195,11 +201,11 @@ await describe('AuthTypes', async () => { 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', () => { @@ -209,9 +215,9 @@ await describe('AuthTypes', async () => { 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', () => { @@ -220,7 +226,7 @@ await describe('AuthTypes', async () => { cause, }) - expect(error.cause).toBe(cause) + assert.strictEqual(error.cause, cause) }) await it('should support all error codes', () => { @@ -239,7 +245,7 @@ await describe('AuthTypes', async () => { for (const code of errorCodes) { const error = new AuthenticationError('Test', code) - expect(error.code).toBe(code) + assert.strictEqual(error.code, code) } }) }) @@ -252,9 +258,9 @@ await describe('AuthTypes', async () => { 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', () => { @@ -268,11 +274,11 @@ await describe('AuthTypes', async () => { 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', () => { @@ -288,43 +294,43 @@ await describe('AuthTypes', async () => { 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') }) }) }) diff --git a/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts b/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts index 2811dcdb..b8fc286c 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthHelpers.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -24,29 +24,29 @@ await describe('AuthHelpers', async () => { 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) }) }) @@ -61,12 +61,12 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -80,7 +80,7 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -94,7 +94,7 @@ await describe('AuthHelpers', async () => { const request = AuthHelpers.createAuthRequest(identifier, context, undefined, metadata) - expect(request.metadata).toStrictEqual({ source: 'test' }) + assert.deepStrictEqual(request.metadata, { source: 'test' }) }) }) @@ -105,11 +105,11 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -119,9 +119,9 @@ await describe('AuthHelpers', async () => { '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' }) }) }) @@ -136,9 +136,9 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -151,68 +151,81 @@ await describe('AuthHelpers', async () => { 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' + ) }) }) @@ -225,7 +238,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isPermanentFailure(result)).toBe(true) + assert.strictEqual(AuthHelpers.isPermanentFailure(result), true) }) await it('should return true for EXPIRED status', () => { @@ -236,7 +249,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isPermanentFailure(result)).toBe(true) + assert.strictEqual(AuthHelpers.isPermanentFailure(result), true) }) await it('should return true for INVALID status', () => { @@ -247,7 +260,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isPermanentFailure(result)).toBe(true) + assert.strictEqual(AuthHelpers.isPermanentFailure(result), true) }) await it('should return false for ACCEPTED status', () => { @@ -258,7 +271,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isPermanentFailure(result)).toBe(false) + assert.strictEqual(AuthHelpers.isPermanentFailure(result), false) }) await it('should return false for PENDING status', () => { @@ -269,7 +282,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isPermanentFailure(result)).toBe(false) + assert.strictEqual(AuthHelpers.isPermanentFailure(result), false) }) }) @@ -282,7 +295,7 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -293,7 +306,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isResultValid(result)).toBe(true) + assert.strictEqual(AuthHelpers.isResultValid(result), true) }) await it('should return false for expired ACCEPTED result', () => { @@ -305,7 +318,7 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -317,7 +330,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isResultValid(result)).toBe(true) + assert.strictEqual(AuthHelpers.isResultValid(result), true) }) }) @@ -330,7 +343,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isTemporaryFailure(result)).toBe(true) + assert.strictEqual(AuthHelpers.isTemporaryFailure(result), true) }) await it('should return true for UNKNOWN status', () => { @@ -341,7 +354,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isTemporaryFailure(result)).toBe(true) + assert.strictEqual(AuthHelpers.isTemporaryFailure(result), true) }) await it('should return false for BLOCKED status', () => { @@ -352,7 +365,7 @@ await describe('AuthHelpers', async () => { timestamp: new Date(), } - expect(AuthHelpers.isTemporaryFailure(result)).toBe(false) + assert.strictEqual(AuthHelpers.isTemporaryFailure(result), false) }) await it('should return false for ACCEPTED status', () => { @@ -363,14 +376,14 @@ await describe('AuthHelpers', async () => { 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', () => { @@ -396,8 +409,8 @@ await describe('AuthHelpers', async () => { ] 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', () => { @@ -417,10 +430,10 @@ await describe('AuthHelpers', async () => { ] 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, }) @@ -444,7 +457,7 @@ await describe('AuthHelpers', async () => { const sanitized = AuthHelpers.sanitizeForLogging(result) - expect(sanitized).toStrictEqual({ + assert.deepStrictEqual(sanitized, { hasExpiryDate: true, hasGroupId: true, hasPersonalMessage: true, @@ -465,7 +478,7 @@ await describe('AuthHelpers', async () => { const sanitized = AuthHelpers.sanitizeForLogging(result) - expect(sanitized).toStrictEqual({ + assert.deepStrictEqual(sanitized, { hasExpiryDate: false, hasGroupId: false, hasPersonalMessage: false, diff --git a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts index 509830a8..80c6b81b 100644 --- a/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts +++ b/tests/charging-station/ocpp/auth/utils/AuthValidators.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -20,125 +20,126 @@ await describe('AuthValidators', async () => { }) 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(''), '') }) }) @@ -155,17 +156,17 @@ await describe('AuthValidators', async () => { 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', () => { @@ -179,7 +180,7 @@ await describe('AuthValidators', async () => { // certificateAuthEnabled missing } as Partial - expect(AuthValidators.validateAuthConfiguration(config)).toBe(false) + assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false) }) await it('should return false for non-positive authorization timeout', () => { @@ -193,7 +194,7 @@ await describe('AuthValidators', async () => { offlineAuthorizationEnabled: false, } - expect(AuthValidators.validateAuthConfiguration(config)).toBe(false) + assert.strictEqual(AuthValidators.validateAuthConfiguration(config), false) }) await it('should return false for negative cache lifetime', () => { @@ -208,7 +209,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -223,7 +224,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -239,19 +240,19 @@ await describe('AuthValidators', async () => { 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', () => { @@ -261,7 +262,7 @@ await describe('AuthValidators', async () => { value: '', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(false) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), false) }) await it('should return false for ID_TAG exceeding 20 characters', () => { @@ -271,7 +272,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -281,7 +282,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -291,7 +292,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -301,7 +302,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -311,7 +312,7 @@ await describe('AuthValidators', async () => { value: 'CENTRAL_TOKEN', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(true) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), true) }) await it('should return true for E_MAID type', () => { @@ -321,7 +322,7 @@ await describe('AuthValidators', async () => { value: 'DE-ABC-123456', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(true) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), true) }) await it('should return true for ISO14443 type', () => { @@ -331,7 +332,7 @@ await describe('AuthValidators', async () => { value: '04A2B3C4D5E6F7', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(true) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), true) }) await it('should return true for KEY_CODE type', () => { @@ -341,7 +342,7 @@ await describe('AuthValidators', async () => { value: '1234', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(true) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), true) }) await it('should return true for MAC_ADDRESS type', () => { @@ -351,7 +352,7 @@ await describe('AuthValidators', async () => { 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', () => { @@ -361,7 +362,7 @@ await describe('AuthValidators', async () => { value: 'NO_AUTH', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(true) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), true) }) await it('should return false for unsupported type', () => { @@ -372,7 +373,7 @@ await describe('AuthValidators', async () => { value: 'VALUE', } - expect(AuthValidators.validateIdentifier(identifier)).toBe(false) + assert.strictEqual(AuthValidators.validateIdentifier(identifier), false) }) }) }) diff --git a/tests/charging-station/ocpp/auth/utils/ConfigValidator.test.ts b/tests/charging-station/ocpp/auth/utils/ConfigValidator.test.ts index 915a43f9..a1ebec8b 100644 --- a/tests/charging-station/ocpp/auth/utils/ConfigValidator.test.ts +++ b/tests/charging-station/ocpp/auth/utils/ConfigValidator.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -35,9 +35,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.doesNotThrow(() => { AuthConfigValidator.validate(config) - }).not.toThrow() + }) }) await it('should reject negative authorizationCacheLifetime', () => { @@ -56,9 +56,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject zero authorizationCacheLifetime', () => { @@ -77,9 +77,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject non-integer authorizationCacheLifetime', () => { @@ -98,9 +98,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject negative maxCacheEntries', () => { @@ -119,9 +119,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject zero maxCacheEntries', () => { @@ -140,9 +140,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject non-integer maxCacheEntries', () => { @@ -161,9 +161,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject negative authorizationTimeout', () => { @@ -182,9 +182,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject zero authorizationTimeout', () => { @@ -203,9 +203,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should reject non-integer authorizationTimeout', () => { @@ -224,9 +224,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.throws(() => { AuthConfigValidator.validate(config) - }).toThrow(AuthenticationError) + }, AuthenticationError) }) await it('should accept configuration with cache disabled', () => { @@ -245,9 +245,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.doesNotThrow(() => { AuthConfigValidator.validate(config) - }).not.toThrow() + }) }) await it('should accept minimal valid values', () => { @@ -266,9 +266,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.doesNotThrow(() => { AuthConfigValidator.validate(config) - }).not.toThrow() + }) }) await it('should accept large valid values', () => { @@ -287,9 +287,9 @@ await describe('AuthConfigValidator', async () => { unknownIdAuthorization: AuthorizationStatus.INVALID, } - expect(() => { + assert.doesNotThrow(() => { AuthConfigValidator.validate(config) - }).not.toThrow() + }) }) }) }) diff --git a/tests/charging-station/ui-server/UIHttpServer.test.ts b/tests/charging-station/ui-server/UIHttpServer.test.ts index 8d4a5950..c4f794f6 100644 --- a/tests/charging-station/ui-server/UIHttpServer.test.ts +++ b/tests/charging-station/ui-server/UIHttpServer.test.ts @@ -3,7 +3,7 @@ * @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' @@ -61,19 +61,19 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -82,9 +82,9 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -96,7 +96,7 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -105,7 +105,7 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -114,24 +114,24 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -144,10 +144,10 @@ await describe('UIHttpServer', async () => { 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 - 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', () => { @@ -161,15 +161,15 @@ await describe('UIHttpServer', async () => { 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 - 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', () => { @@ -183,7 +183,7 @@ await describe('UIHttpServer', async () => { }) ) - expect(serverCustom).toBeDefined() + assert.notStrictEqual(serverCustom, undefined) }) await describe('Gzip compression', async () => { @@ -200,8 +200,8 @@ await describe('UIHttpServer', 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', () => { @@ -211,8 +211,8 @@ await describe('UIHttpServer', async () => { 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', () => { @@ -226,7 +226,7 @@ await describe('UIHttpServer', async () => { 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 () => { @@ -238,9 +238,9 @@ await describe('UIHttpServer', 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 () => { @@ -253,14 +253,14 @@ await describe('UIHttpServer', 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 - 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', () => { @@ -269,8 +269,8 @@ await describe('UIHttpServer', async () => { 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 () => { @@ -278,13 +278,13 @@ await describe('UIHttpServer', 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) }) }) }) diff --git a/tests/charging-station/ui-server/UIServerSecurity.test.ts b/tests/charging-station/ui-server/UIServerSecurity.test.ts index 1ff5be00..43694a71 100644 --- a/tests/charging-station/ui-server/UIServerSecurity.test.ts +++ b/tests/charging-station/ui-server/UIServerSecurity.test.ts @@ -3,7 +3,7 @@ * @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 { @@ -21,20 +21,20 @@ await describe('UIServerSecurity', async () => { }) 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) }) }) @@ -44,19 +44,19 @@ await describe('UIServerSecurity', async () => { 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) }) }) @@ -67,7 +67,7 @@ await describe('UIServerSecurity', async () => { 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) } }) @@ -76,7 +76,7 @@ await describe('UIServerSecurity', async () => { 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 => { @@ -84,60 +84,60 @@ await describe('UIServerSecurity', async () => { 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) }) }) }) diff --git a/tests/charging-station/ui-server/UIWebSocketServer.test.ts b/tests/charging-station/ui-server/UIWebSocketServer.test.ts index 11558288..7f8e1ec4 100644 --- a/tests/charging-station/ui-server/UIWebSocketServer.test.ts +++ b/tests/charging-station/ui-server/UIWebSocketServer.test.ts @@ -3,7 +3,7 @@ * @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' @@ -29,12 +29,12 @@ await describe('UIWebSocketServer', async () => { 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', () => { @@ -43,7 +43,7 @@ await describe('UIWebSocketServer', async () => { 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', () => { @@ -55,8 +55,8 @@ await describe('UIWebSocketServer', async () => { 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', () => { @@ -71,7 +71,7 @@ await describe('UIWebSocketServer', async () => { 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 () => { @@ -90,10 +90,10 @@ await describe('UIWebSocketServer', 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 () => { @@ -114,7 +114,7 @@ await describe('UIWebSocketServer', 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 () => { @@ -132,7 +132,7 @@ await describe('UIWebSocketServer', async () => { // Expected error } - expect(server.getResponseHandlersSize()).toBe(1) + assert.strictEqual(server.getResponseHandlersSize(), 1) }) await it('should clean up response handlers after each response', () => { @@ -144,13 +144,13 @@ await describe('UIWebSocketServer', async () => { 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', () => { @@ -159,18 +159,18 @@ await describe('UIWebSocketServer', async () => { 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', () => { @@ -182,6 +182,6 @@ await describe('UIWebSocketServer', async () => { }) const server = new TestableUIWebSocketServer(config) - expect(server).toBeDefined() + assert.notStrictEqual(server, undefined) }) }) diff --git a/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts b/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts index 68d0889c..d42ae92e 100644 --- a/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts +++ b/tests/charging-station/ui-server/ui-services/AbstractUIService.test.ts @@ -3,7 +3,7 @@ * @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' @@ -27,13 +27,13 @@ await describe('AbstractUIService', async () => { 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 () => { @@ -48,16 +48,16 @@ await describe('AbstractUIService', 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() } @@ -75,18 +75,20 @@ await describe('AbstractUIService', async () => { 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() } @@ -102,15 +104,15 @@ await describe('AbstractUIService', async () => { 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() } @@ -124,9 +126,9 @@ await describe('AbstractUIService', async () => { 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() } }) @@ -139,10 +141,10 @@ await describe('AbstractUIService', async () => { 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) } }) @@ -156,14 +158,14 @@ await describe('AbstractUIService', async () => { 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() } @@ -177,7 +179,7 @@ await describe('AbstractUIService', async () => { const service = server.getUIService(ProtocolVersion['0.0.1']) - expect(service).toBeDefined() + assert.notStrictEqual(service, undefined) if (service != null) { service.stop() } @@ -192,7 +194,7 @@ await describe('AbstractUIService', async () => { const uiServicesMap = Reflect.get(server, 'uiServices') as Map - expect(uiServicesMap.size).toBe(1) + assert.strictEqual(uiServicesMap.size, 1) const service = server.getUIService(ProtocolVersion['0.0.1']) if (service != null) { diff --git a/tests/exception/BaseError.test.ts b/tests/exception/BaseError.test.ts index 79adfd73..d9a5335d 100644 --- a/tests/exception/BaseError.test.ts +++ b/tests/exception/BaseError.test.ts @@ -2,7 +2,7 @@ * @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' @@ -14,45 +14,45 @@ await describe('BaseError', async () => { }) 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) }) }) diff --git a/tests/exception/OCPPError.test.ts b/tests/exception/OCPPError.test.ts index 9dcf94f6..4c97b2dc 100644 --- a/tests/exception/OCPPError.test.ts +++ b/tests/exception/OCPPError.test.ts @@ -2,7 +2,7 @@ * @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' @@ -18,37 +18,37 @@ await describe('OCPPError', async () => { 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) }) }) diff --git a/tests/performance/storage/MikroOrmStorage.test.ts b/tests/performance/storage/MikroOrmStorage.test.ts index 1f92edb9..494d070e 100644 --- a/tests/performance/storage/MikroOrmStorage.test.ts +++ b/tests/performance/storage/MikroOrmStorage.test.ts @@ -3,7 +3,7 @@ * @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' @@ -104,13 +104,13 @@ await describe('MikroOrmStorage', async () => { 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 => { @@ -123,7 +123,7 @@ await describe('MikroOrmStorage', async () => { 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 () => { @@ -135,7 +135,7 @@ await describe('MikroOrmStorage', 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 () => { @@ -157,7 +157,7 @@ await describe('MikroOrmStorage', async () => { await storage.close() // Assert - expect(errorMock.mock.calls.length).toBe(1) + assert.strictEqual(errorMock.mock.calls.length, 1) }) }) @@ -172,15 +172,15 @@ await describe('MikroOrmStorage', async () => { await storage.storePerformanceStatistics(stats) // Assert - expect(upsertCalls.length).toBe(1) + assert.strictEqual(upsertCalls.length, 1) const call = upsertCalls[0] as { data: Record; entity: unknown } - expect(call.entity).toBe(PerformanceRecord) + assert.strictEqual(call.entity, PerformanceRecord) const statsArray = call.data.statisticsData as Record[] - 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 () => { @@ -195,8 +195,8 @@ await describe('MikroOrmStorage', async () => { const call = upsertCalls[0] as { data: Record } const statsArray = call.data.statisticsData as Record[] 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 () => { @@ -209,7 +209,7 @@ await describe('MikroOrmStorage', 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 () => { @@ -222,8 +222,8 @@ await describe('MikroOrmStorage', 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 () => { @@ -237,9 +237,9 @@ await describe('MikroOrmStorage', 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 () => { @@ -258,10 +258,10 @@ await describe('MikroOrmStorage', async () => { // Assert const call = upsertCalls[0] as { data: Record } const statsArray = call.data.statisticsData as Record[] - 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) }) }) @@ -274,7 +274,7 @@ await describe('MikroOrmStorage', async () => { 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 () => { @@ -283,8 +283,8 @@ await describe('MikroOrmStorage', 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 => { @@ -305,7 +305,7 @@ await describe('MikroOrmStorage', async () => { await storage.storePerformanceStatistics(buildTestStatistics('station-1')) // Assert - expect(errorMock.mock.calls.length).toBe(1) + assert.strictEqual(errorMock.mock.calls.length, 1) }) }) @@ -313,7 +313,7 @@ await describe('MikroOrmStorage', async () => { 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 () => { @@ -330,9 +330,9 @@ await describe('MikroOrmStorage', 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() } @@ -353,8 +353,8 @@ await describe('MikroOrmStorage', async () => { }) 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() } @@ -377,13 +377,15 @@ await describe('MikroOrmStorage', async () => { }) 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() } @@ -411,8 +413,8 @@ await describe('MikroOrmStorage', async () => { }) 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() @@ -435,9 +437,9 @@ await describe('MikroOrmStorage', async () => { }) 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() } diff --git a/tests/performance/storage/MongoDBStorage.test.ts b/tests/performance/storage/MongoDBStorage.test.ts index 1ca2984d..c65bdfd5 100644 --- a/tests/performance/storage/MongoDBStorage.test.ts +++ b/tests/performance/storage/MongoDBStorage.test.ts @@ -2,7 +2,7 @@ * @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' @@ -111,11 +111,11 @@ await describe('MongoDBStorage', async () => { 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) }) }) @@ -126,13 +126,13 @@ await describe('MongoDBStorage', async () => { 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 => { @@ -146,7 +146,7 @@ await describe('MongoDBStorage', async () => { 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 () => { @@ -159,7 +159,7 @@ await describe('MongoDBStorage', 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 => { @@ -172,7 +172,7 @@ await describe('MongoDBStorage', async () => { 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 => { @@ -190,7 +190,7 @@ await describe('MongoDBStorage', async () => { await storage.close() // Assert - expect(errorMock.mock.calls.length).toBe(1) + assert.strictEqual(errorMock.mock.calls.length, 1) }) }) @@ -205,8 +205,8 @@ await describe('MongoDBStorage', async () => { 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 => { @@ -220,7 +220,7 @@ await describe('MongoDBStorage', async () => { 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 => { @@ -237,7 +237,7 @@ await describe('MongoDBStorage', async () => { await storage.open() // Assert - expect(errorMock.mock.calls.length).toBe(1) + assert.strictEqual(errorMock.mock.calls.length, 1) }) }) @@ -253,14 +253,14 @@ await describe('MongoDBStorage', async () => { await storage.storePerformanceStatistics(stats) // Assert - expect(replaceOneCalls.length).toBe(1) + assert.strictEqual(replaceOneCalls.length, 1) const replacement = replaceOneCalls[0].replacement as Record const statsArray = replacement.statisticsData as Record[] - 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 () => { @@ -276,8 +276,8 @@ await describe('MongoDBStorage', async () => { const replacement = replaceOneCalls[0].replacement as Record const statsArray = replacement.statisticsData as Record[] 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 () => { @@ -290,9 +290,9 @@ await describe('MongoDBStorage', 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 () => { @@ -305,8 +305,8 @@ await describe('MongoDBStorage', 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 () => { @@ -320,8 +320,8 @@ await describe('MongoDBStorage', 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 () => { @@ -336,9 +336,9 @@ await describe('MongoDBStorage', 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 () => { @@ -358,10 +358,10 @@ await describe('MongoDBStorage', async () => { // Assert const replacement = replaceOneCalls[0].replacement as Record const statsArray = replacement.statisticsData as Record[] - 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) }) }) @@ -374,7 +374,7 @@ await describe('MongoDBStorage', async () => { 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 () => { @@ -383,8 +383,8 @@ await describe('MongoDBStorage', 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 => { @@ -405,7 +405,7 @@ await describe('MongoDBStorage', async () => { await storage.storePerformanceStatistics(buildTestStatistics('station-1')) // Assert - expect(errorMock.mock.calls.length).toBe(1) + assert.strictEqual(errorMock.mock.calls.length, 1) }) }) }) diff --git a/tests/types/ConfigurationData.test.ts b/tests/types/ConfigurationData.test.ts index f0bed3ca..81d9d6ca 100644 --- a/tests/types/ConfigurationData.test.ts +++ b/tests/types/ConfigurationData.test.ts @@ -2,7 +2,7 @@ * @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 { @@ -18,20 +18,23 @@ await describe('ConfigurationData', async () => { }) 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') }) }) diff --git a/tests/utils/AsyncLock.test.ts b/tests/utils/AsyncLock.test.ts index cabd1b2f..5b5422a6 100644 --- a/tests/utils/AsyncLock.test.ts +++ b/tests/utils/AsyncLock.test.ts @@ -2,7 +2,7 @@ * @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' @@ -25,7 +25,10 @@ await describe('AsyncLock', async () => { 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 () => { @@ -45,29 +48,34 @@ await describe('AsyncLock', 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 () => { @@ -85,12 +93,12 @@ await describe('AsyncLock', 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) }) }) diff --git a/tests/utils/ChargingStationConfigurationUtils.test.ts b/tests/utils/ChargingStationConfigurationUtils.test.ts index 5772acd7..3168b7ed 100644 --- a/tests/utils/ChargingStationConfigurationUtils.test.ts +++ b/tests/utils/ChargingStationConfigurationUtils.test.ts @@ -4,7 +4,7 @@ * 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' @@ -50,8 +50,8 @@ await describe('ChargingStationConfigurationUtils', async () => { 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') }) }) @@ -80,13 +80,13 @@ await describe('ChargingStationConfigurationUtils', async () => { 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) @@ -96,7 +96,7 @@ await describe('ChargingStationConfigurationUtils', async () => { 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', () => { @@ -115,10 +115,10 @@ await describe('ChargingStationConfigurationUtils', async () => { 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) }) }) @@ -146,10 +146,10 @@ await describe('ChargingStationConfigurationUtils', async () => { 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 - 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', () => { @@ -173,10 +173,10 @@ await describe('ChargingStationConfigurationUtils', async () => { const evse1 = result[0] as Record 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', () => { @@ -202,10 +202,10 @@ await describe('ChargingStationConfigurationUtils', async () => { 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 - 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', () => { @@ -218,16 +218,16 @@ await describe('ChargingStationConfigurationUtils', async () => { const station = createMockStationForConfigUtils({ evses }) const result = buildEvsesStatus(station) - expect(result.length).toBe(1) + assert.strictEqual(result.length, 1) const evse = result[0] as Record - 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', () => { @@ -238,9 +238,9 @@ await describe('ChargingStationConfigurationUtils', async () => { }) const station = createMockStationForConfigUtils({ evses }) - expect(() => { + assert.throws(() => { buildEvsesStatus(station, 'unknown' as OutputFormat) - }).toThrow(RangeError) + }, RangeError) }) }) @@ -255,10 +255,10 @@ await describe('ChargingStationConfigurationUtils', async () => { }) 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', () => { @@ -269,8 +269,8 @@ await describe('ChargingStationConfigurationUtils', async () => { }) 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', () => { @@ -280,8 +280,8 @@ await describe('ChargingStationConfigurationUtils', async () => { }) 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', () => { @@ -294,8 +294,8 @@ await describe('ChargingStationConfigurationUtils', async () => { }) const result = buildChargingStationAutomaticTransactionGeneratorConfiguration(station) - expect(result.automaticTransactionGenerator).toStrictEqual(atgConfig) - expect(result.automaticTransactionGeneratorStatuses).toBeUndefined() + assert.deepStrictEqual(result.automaticTransactionGenerator, atgConfig) + assert.strictEqual(result.automaticTransactionGeneratorStatuses, undefined) }) }) }) diff --git a/tests/utils/Configuration.test.ts b/tests/utils/Configuration.test.ts index aba888a7..281278e6 100644 --- a/tests/utils/Configuration.test.ts +++ b/tests/utils/Configuration.test.ts @@ -10,7 +10,7 @@ * - 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 { @@ -62,86 +62,87 @@ await describe('Configuration', async () => { 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(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(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( 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( 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( 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( 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', () => { @@ -153,7 +154,7 @@ await describe('Configuration', async () => { try { const distribution = Configuration.getSupervisionUrlDistribution() - expect(distribution).toBe(SupervisionUrlDistribution.ROUND_ROBIN) + assert.strictEqual(distribution, SupervisionUrlDistribution.ROUND_ROBIN) } finally { internals.configurationData = originalData resetSectionCache() @@ -161,21 +162,21 @@ await describe('Configuration', async () => { }) 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) }) }) diff --git a/tests/utils/ConfigurationUtils.test.ts b/tests/utils/ConfigurationUtils.test.ts index e33f405d..e217c470 100644 --- a/tests/utils/ConfigurationUtils.test.ts +++ b/tests/utils/ConfigurationUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -20,56 +20,56 @@ await describe('ConfigurationUtils', async () => { }) 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) }) }) diff --git a/tests/utils/ElectricUtils.test.ts b/tests/utils/ElectricUtils.test.ts index a9cd9ac4..f7ab0a4b 100644 --- a/tests/utils/ElectricUtils.test.ts +++ b/tests/utils/ElectricUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -18,94 +18,94 @@ await describe('ElectricUtils', async () => { }) 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) }) }) diff --git a/tests/utils/ErrorUtils.test.ts b/tests/utils/ErrorUtils.test.ts index fc27f004..8976c3d8 100644 --- a/tests/utils/ErrorUtils.test.ts +++ b/tests/utils/ErrorUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -49,47 +49,47 @@ await describe('ErrorUtils', async () => { 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 => { @@ -112,47 +112,47 @@ await describe('ErrorUtils', async () => { 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, @@ -160,29 +160,29 @@ await describe('ErrorUtils', async () => { 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 => { @@ -192,11 +192,12 @@ await describe('ErrorUtils', async () => { 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) }) }) diff --git a/tests/utils/FileUtils.test.ts b/tests/utils/FileUtils.test.ts index e3fdfef9..ef9eb051 100644 --- a/tests/utils/FileUtils.test.ts +++ b/tests/utils/FileUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -27,8 +27,8 @@ await describe('FileUtils', async () => { 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 => { @@ -36,10 +36,10 @@ await describe('FileUtils', async () => { 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 => { @@ -52,8 +52,8 @@ await describe('FileUtils', async () => { 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', () => { @@ -64,7 +64,7 @@ await describe('FileUtils', async () => { try { const result = watchJsonFile(tmpFile, FileType.Authorization, 'test |', noop) - expect(result).toBeDefined() + assert.notStrictEqual(result, undefined) result?.close() } finally { rmSync(tmpDir, { recursive: true }) @@ -84,10 +84,10 @@ await describe('FileUtils', async () => { 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 }) } diff --git a/tests/utils/MessageChannelUtils.test.ts b/tests/utils/MessageChannelUtils.test.ts index a5e02b7a..c673820e 100644 --- a/tests/utils/MessageChannelUtils.test.ts +++ b/tests/utils/MessageChannelUtils.test.ts @@ -1,9 +1,9 @@ +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' @@ -73,62 +73,62 @@ await describe('MessageChannelUtils', async () => { 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', () => { @@ -155,17 +155,17 @@ await describe('MessageChannelUtils', async () => { 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', () => { @@ -187,7 +187,7 @@ await describe('MessageChannelUtils', async () => { 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', () => { @@ -205,8 +205,8 @@ await describe('MessageChannelUtils', async () => { 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') }) }) diff --git a/tests/utils/StatisticUtils.test.ts b/tests/utils/StatisticUtils.test.ts index 7b75a891..e6e54395 100644 --- a/tests/utils/StatisticUtils.test.ts +++ b/tests/utils/StatisticUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -13,69 +13,69 @@ await describe('StatisticUtils', async () => { 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)) }) }) diff --git a/tests/utils/Utils.test.ts b/tests/utils/Utils.test.ts index ad95f566..c05a7b75 100644 --- a/tests/utils/Utils.test.ts +++ b/tests/utils/Utils.test.ts @@ -1,10 +1,10 @@ +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' @@ -61,38 +61,38 @@ await describe('Utils', async () => { }) 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 => { @@ -101,154 +101,179 @@ await describe('Utils', async () => { 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(Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY) - ) - ).toStrictEqual([]) + ), + [] + ) const circularBuffer = new CircularBuffer( Array, Constants.DEFAULT_CIRCULAR_BUFFER_CAPACITY @@ -256,7 +281,7 @@ await describe('Utils', async () => { 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', () => { @@ -300,12 +325,12 @@ await describe('Utils', async () => { 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 */ @@ -321,45 +346,60 @@ await describe('Utils', async () => { } 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('# could not be cloned.')) + assert.throws( + () => { + clone(weakMap) + }, + { message: /# could not be cloned./ } + ) const weakSet = new WeakSet([{ 1: 1 }, { 2: 2 }]) - expect(() => clone(weakSet)).toThrow(new Error('# could not be cloned.')) + assert.throws( + () => { + clone(weakSet) + }, + { message: /# could not be cloned./ } + ) }) await it('should execute function only once regardless of call count', () => { @@ -367,124 +407,144 @@ await describe('Utils', async () => { 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([], (a, b) => a - b)).toBe(true) - expect(isArraySorted([1], (a, b) => a - b)).toBe(true) - expect(isArraySorted([1, 2, 3, 4, 5], (a, b) => a - b)).toBe(true) - expect(isArraySorted([1, 2, 3, 5, 4], (a, b) => a - b)).toBe(false) - expect(isArraySorted([2, 1, 3, 4, 5], (a, b) => a - b)).toBe(false) + assert.strictEqual( + isArraySorted([], (a, b) => a - b), + true + ) + assert.strictEqual( + isArraySorted([1], (a, b) => a - b), + true + ) + assert.strictEqual( + isArraySorted([1, 2, 3, 4, 5], (a, b) => a - b), + true + ) + assert.strictEqual( + isArraySorted([1, 2, 3, 5, 4], (a, b) => a - b), + false + ) + assert.strictEqual( + isArraySorted([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) }) // ------------------------------------------------------------------------- @@ -497,39 +557,39 @@ await describe('Utils', async () => { // 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', () => { @@ -547,8 +607,8 @@ await describe('Utils', async () => { 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) } }) @@ -566,7 +626,7 @@ await describe('Utils', async () => { // 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', () => { @@ -581,27 +641,27 @@ await describe('Utils', async () => { 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', () => { @@ -610,122 +670,129 @@ await describe('Utils', async () => { // 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 @@ -740,10 +807,10 @@ await describe('Utils', async () => { // 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) }) }) diff --git a/tests/worker/WorkerUtils.test.ts b/tests/worker/WorkerUtils.test.ts index 942dc34c..799b39e4 100644 --- a/tests/worker/WorkerUtils.test.ts +++ b/tests/worker/WorkerUtils.test.ts @@ -2,7 +2,7 @@ * @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' @@ -22,20 +22,20 @@ await describe('WorkerUtils', async () => { 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 => { @@ -46,8 +46,8 @@ await describe('WorkerUtils', async () => { 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) @@ -60,8 +60,8 @@ await describe('WorkerUtils', async () => { // 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() @@ -69,8 +69,8 @@ await describe('WorkerUtils', async () => { // 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() @@ -78,13 +78,13 @@ await describe('WorkerUtils', async () => { // 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 => { @@ -93,12 +93,12 @@ await describe('WorkerUtils', async () => { 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', () => { @@ -112,24 +112,24 @@ await describe('WorkerUtils', async () => { 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) }) })