]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(tests): add testable interface to eliminate eslint-disable comments
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Feb 2026 22:31:29 +0000 (23:31 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Feb 2026 22:31:29 +0000 (23:31 +0100)
- 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

tests/charging-station/ocpp/2.0/OCPP20RequestService-BootNotification.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-HeartBeat.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts
tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts

index 5835ba624ddd9f124d8d46f1446d536c8f9172c6..2e24dfd45387e200ce684044f51e58b00fd1a16f 100644 (file)
@@ -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')
index 33c096a0318c9d2489b070a8e0a2700e66ae791f..02d0d83ad5a43194ba664a8d14471a92f4e7d40b 100644 (file)
@@ -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
index cd410cdfd74008a7352700fd122deaad0724e2e8..49de14044d216de586bd78c0e608e8acf408d956 100644 (file)
@@ -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)
index c718934de12f9b65c329c376a29837d3fd2231cb..3e8487be78bf4c43f6362c360f1c1644930f91c6 100644 (file)
@@ -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.