From 00051db475ef42a203c653b5de29a3d327623073 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 27 Feb 2026 23:31:29 +0100 Subject: [PATCH] refactor(tests): add testable interface to eliminate eslint-disable comments - Add TestableOCPP20RequestService interface and factory function - Remove file-level eslint-disable from 3 OCPP20RequestService test files - Use type assertions instead of generic type parameters for type safety - Follows TEST_STYLE_GUIDE.md recommendation for testable interfaces --- ...P20RequestService-BootNotification.test.ts | 28 +++++----- .../OCPP20RequestService-HeartBeat.test.ts | 24 ++++----- ...0RequestService-StatusNotification.test.ts | 42 +++++++-------- .../ocpp/2.0/OCPP20TestUtils.ts | 54 +++++++++++++++++++ 4 files changed, 95 insertions(+), 53 deletions(-) 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 5835ba62..2e24dfd4 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts @@ -2,11 +2,6 @@ * @file Tests for OCPP20RequestService BootNotification * @description Unit tests for OCPP 2.0 BootNotification request building (B01) */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { expect } from '@std/expect' import { afterEach, describe, it, mock } from 'node:test' @@ -28,6 +23,7 @@ import { TEST_CHARGING_STATION_BASE_NAME, TEST_FIRMWARE_VERSION, } from '../../ChargingStationTestConstants.js' +import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js' await describe('B01 - Cold Boot Charging Station', async () => { afterEach(() => { @@ -36,6 +32,7 @@ await describe('B01 - Cold Boot Charging Station', async () => { const mockResponseService = new OCPP20ResponseService() const requestService = new OCPP20RequestService(mockResponseService) + const testableRequestService = createTestableOCPP20RequestService(requestService) const mockChargingStation = createChargingStation({ baseName: TEST_CHARGING_STATION_BASE_NAME, @@ -67,12 +64,11 @@ await describe('B01 - Cold Boot Charging Station', async () => { reason: BootReasonEnumType.PowerUp, } - // Access the private buildRequestPayload method via type assertion - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.BOOT_NOTIFICATION, requestParams - ) + ) as OCPP20BootNotificationRequest expect(payload).toBeDefined() expect(payload.chargingStation).toBeDefined() @@ -97,11 +93,11 @@ await describe('B01 - Cold Boot Charging Station', async () => { reason: BootReasonEnumType.ApplicationReset, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.BOOT_NOTIFICATION, requestParams - ) + ) as OCPP20BootNotificationRequest expect(payload).toBeDefined() expect(payload.chargingStation).toBeDefined() @@ -125,11 +121,11 @@ await describe('B01 - Cold Boot Charging Station', async () => { reason: BootReasonEnumType.FirmwareUpdate, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.BOOT_NOTIFICATION, requestParams - ) + ) as OCPP20BootNotificationRequest expect(payload).toBeDefined() expect(payload.chargingStation).toBeDefined() @@ -165,11 +161,11 @@ await describe('B01 - Cold Boot Charging Station', async () => { reason, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.BOOT_NOTIFICATION, requestParams - ) + ) as OCPP20BootNotificationRequest expect(payload).toBeDefined() expect(payload.reason).toBe(reason) @@ -194,11 +190,11 @@ await describe('B01 - Cold Boot Charging Station', async () => { reason: BootReasonEnumType.PowerUp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.BOOT_NOTIFICATION, requestParams - ) + ) as OCPP20BootNotificationRequest // Validate that the payload has the exact structure of OCPP20BootNotificationRequest expect(typeof payload).toBe('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 33c096a0..02d0d83a 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts @@ -2,11 +2,6 @@ * @file Tests for OCPP20RequestService HeartBeat * @description Unit tests for OCPP 2.0 Heartbeat request building (G02) */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { expect } from '@std/expect' import { afterEach, describe, it, mock } from 'node:test' @@ -26,6 +21,7 @@ import { TEST_CHARGING_STATION_BASE_NAME, TEST_FIRMWARE_VERSION, } from '../../ChargingStationTestConstants.js' +import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js' await describe('G02 - Heartbeat', async () => { afterEach(() => { @@ -33,6 +29,7 @@ await describe('G02 - Heartbeat', async () => { }) const mockResponseService = new OCPP20ResponseService() const requestService = new OCPP20RequestService(mockResponseService) + const testableRequestService = createTestableOCPP20RequestService(requestService) const mockChargingStation = createChargingStation({ baseName: TEST_CHARGING_STATION_BASE_NAME, @@ -54,8 +51,7 @@ await describe('G02 - Heartbeat', async () => { await it('should build HeartBeat request payload correctly with empty object', () => { const requestParams: OCPP20HeartbeatRequest = {} - // Access the private buildRequestPayload method via type assertion - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams @@ -69,7 +65,7 @@ await describe('G02 - Heartbeat', async () => { // FR: G02.FR.02 await it('should build HeartBeat request payload correctly without parameters', () => { // Test without passing any request parameters - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT ) @@ -83,7 +79,7 @@ await describe('G02 - Heartbeat', async () => { await it('should validate payload structure matches OCPP20HeartbeatRequest interface', () => { const requestParams: OCPP20HeartbeatRequest = {} - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams @@ -102,19 +98,19 @@ await describe('G02 - Heartbeat', async () => { const requestParams: OCPP20HeartbeatRequest = {} // Call buildRequestPayload multiple times to ensure consistency - const payload1 = (requestService as any).buildRequestPayload( + const payload1 = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams ) - const payload2 = (requestService as any).buildRequestPayload( + const payload2 = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams ) - const payload3 = (requestService as any).buildRequestPayload( + const payload3 = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT ) @@ -147,7 +143,7 @@ await describe('G02 - Heartbeat', async () => { const requestParams: OCPP20HeartbeatRequest = {} - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( alternativeChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams @@ -164,7 +160,7 @@ await describe('G02 - Heartbeat', async () => { await it('should build empty HeartBeat request conforming to OCPP 2.0 specification', () => { const requestParams: OCPP20HeartbeatRequest = {} - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.HEARTBEAT, requestParams 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 cd410cdf..49de1404 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts @@ -2,11 +2,6 @@ * @file Tests for OCPP20RequestService StatusNotification * @description Unit tests for OCPP 2.0 StatusNotification request building (G01) */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - import { expect } from '@std/expect' import { afterEach, describe, it, mock } from 'node:test' @@ -27,6 +22,7 @@ import { TEST_STATUS_CHARGE_POINT_VENDOR, TEST_STATUS_CHARGING_STATION_BASE_NAME, } from '../../ChargingStationTestConstants.js' +import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js' await describe('G01 - Status Notification', async () => { afterEach(() => { @@ -34,6 +30,7 @@ await describe('G01 - Status Notification', async () => { }) const mockResponseService = new OCPP20ResponseService() const requestService = new OCPP20RequestService(mockResponseService) + const testableRequestService = createTestableOCPP20RequestService(requestService) const mockChargingStation = createChargingStation({ baseName: TEST_STATUS_CHARGING_STATION_BASE_NAME, @@ -62,12 +59,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - // Access the private buildRequestPayload method via type assertion - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest expect(payload).toBeDefined() expect(payload.connectorId).toBe(1) @@ -87,11 +83,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest expect(payload).toBeDefined() expect(payload.connectorId).toBe(2) @@ -111,11 +107,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest expect(payload).toBeDefined() expect(payload.connectorId).toBe(1) @@ -144,11 +140,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest expect(payload).toBeDefined() expect(payload.connectorStatus).toBe(status) @@ -169,11 +165,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest // Validate that the payload has the exact structure of OCPP20StatusNotificationRequest expect(typeof payload).toBe('object') @@ -208,11 +204,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payloadConnector0 = (requestService as any).buildRequestPayload( + const payloadConnector0 = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParamsConnector0 - ) + ) as OCPP20StatusNotificationRequest expect(payloadConnector0).toBeDefined() expect(payloadConnector0.connectorId).toBe(0) @@ -228,11 +224,11 @@ await describe('G01 - Status Notification', async () => { timestamp: testTimestamp, } - const payloadEvse0 = (requestService as any).buildRequestPayload( + const payloadEvse0 = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParamsEvse0 - ) + ) as OCPP20StatusNotificationRequest expect(payloadEvse0).toBeDefined() expect(payloadEvse0.connectorId).toBe(1) @@ -250,7 +246,7 @@ await describe('G01 - Status Notification', async () => { new Date('2024-06-15T12:30:45.678Z'), // Mid-year with milliseconds ] - testCases.forEach((timestamp, index) => { + testCases.forEach((timestamp, _index) => { const requestParams: OCPP20StatusNotificationRequest = { connectorId: 1, connectorStatus: OCPP20ConnectorStatusEnumType.Available, @@ -258,11 +254,11 @@ await describe('G01 - Status Notification', async () => { timestamp, } - const payload = (requestService as any).buildRequestPayload( + const payload = testableRequestService.buildRequestPayload( mockChargingStation, OCPP20RequestCommand.STATUS_NOTIFICATION, requestParams - ) + ) as OCPP20StatusNotificationRequest expect(payload).toBeDefined() expect(payload.timestamp).toBe(timestamp) diff --git a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts index c718934d..3e8487be 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts @@ -1,6 +1,8 @@ import type { ChargingStation } from '../../../../src/charging-station/ChargingStation.js' +import type { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js' import type { ConfigurationKey } from '../../../../src/types/ChargingStationOcppConfiguration.js' import type { EmptyObject } from '../../../../src/types/EmptyObject.js' +import type { JsonType, OCPP20RequestCommand } from '../../../../src/types/index.js' import { ConnectorStatusEnum, @@ -11,6 +13,33 @@ import { Constants } from '../../../../src/utils/index.js' import { createChargingStation } from '../../../ChargingStationFactory.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' +// ============================================================================ +// Testable Interfaces +// ============================================================================ +// These interfaces provide type-safe access to private methods for testing +// purposes, eliminating the need for `as any` casts and eslint-disable comments. +// ============================================================================ + +/** + * Interface exposing private methods of OCPP20RequestService for testing. + * This allows type-safe testing without `as any` casts. + */ +export interface TestableOCPP20RequestService { + /** + * Build a request payload for the given OCPP 2.0 command. + * Exposes the private `buildRequestPayload` method for testing. + * @param chargingStation - The charging station instance + * @param commandName - The OCPP 2.0 request command + * @param commandParams - Optional command parameters + * @returns The built request payload + */ + buildRequestPayload: ( + chargingStation: ChargingStation, + commandName: OCPP20RequestCommand, + commandParams?: JsonType + ) => JsonType +} + /** * Create a mock ChargingStation for OCPP 2.0 transaction event testing. * Provides standard configuration used across all transaction event test files. @@ -33,6 +62,31 @@ export function createMockOCPP20TransactionTestStation (): ChargingStation { }) } +/** + * Create a testable wrapper for OCPP20RequestService that exposes private methods. + * + * This function provides type-safe access to private methods that need to be tested, + * following the pattern recommended in TEST_STYLE_GUIDE.md to avoid `as any` casts. + * @param requestService - The OCPP20RequestService instance to wrap + * @returns A testable interface with access to private methods + * @example + * ```typescript + * const testable = createTestableOCPP20RequestService(requestService) + * const payload = testable.buildRequestPayload(station, OCPP20RequestCommand.HEARTBEAT) + * ``` + */ +export function createTestableOCPP20RequestService ( + requestService: OCPP20RequestService +): TestableOCPP20RequestService { + // Use type assertion at the boundary only, providing type-safe interface to tests + const service = requestService as unknown as { + buildRequestPayload: TestableOCPP20RequestService['buildRequestPayload'] + } + return { + buildRequestPayload: service.buildRequestPayload.bind(requestService), + } +} + /** * Reset connector transaction state for all connectors in the charging station. * This ensures test isolation by clearing any transaction state from previous tests. -- 2.53.0