]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(tests): improve test isolation and remove dead code
authorJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Feb 2026 22:54:59 +0000 (23:54 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Fri, 27 Feb 2026 22:54:59 +0000 (23:54 +0100)
- Fix module-level state sharing in 6 OCPP 2.0 RequestService tests
- Remove unused createMockTemplate function from StationHelpers
- Remove unused TestStationHelper, TestTimerHelper, TestEnvironmentHelper classes (206 LOC)
- Add createMockAuthCache and createMockOCPPAdapter factories
- Update TEST_STYLE_GUIDE.md with test isolation best practices
- Clean up orphaned imports after dead code removal

tests/TEST_STYLE_GUIDE.md
tests/charging-station/ChargingStationTestUtils.ts
tests/charging-station/helpers/StationHelpers.ts
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-ISO15118.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-NotifyReport.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-StatusNotification.test.ts
tests/charging-station/ocpp/auth/helpers/MockFactories.ts
tests/helpers/TestLifecycleHelpers.ts

index 903ce704b089b6d636c30515ddee47859a2fec6d..2fc517bf04cb69ba15d2144607f830cc5b3a6305 100644 (file)
@@ -164,6 +164,61 @@ import { waitForCondition } from './helpers/StationHelpers.js'
 - Keep mocks focused - mock only what's necessary
 - Verify mock calls when behavior depends on them
 
+## Test Isolation (CRITICAL)
+
+**NEVER define mock instances at module level inside describe blocks.** Each test must get fresh instances.
+
+❌ **Bad (Module-Level State Sharing):**
+
+```typescript
+await describe('My Test Suite', async () => {
+  afterEach(() => {
+    mock.restoreAll()
+  })
+
+  // WRONG: These instances are SHARED across all tests!
+  const mockResponseService = new OCPP20ResponseService()
+  const requestService = new OCPP20RequestService(mockResponseService)
+  const mockChargingStation = createChargingStation({...})
+
+  await it('test 1', () => { /* uses shared state */ })
+  await it('test 2', () => { /* uses same shared state! Test pollution risk! */ })
+})
+```
+
+✅ **Good (Fresh Instances Per Test):**
+
+```typescript
+await describe('My Test Suite', async () => {
+  let mockResponseService: OCPP20ResponseService
+  let requestService: OCPP20RequestService
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    // Fresh instances for every test - proper isolation
+    mockResponseService = new OCPP20ResponseService()
+    requestService = new OCPP20RequestService(mockResponseService)
+    mockChargingStation = createChargingStation({...})
+  })
+
+  afterEach(() => {
+    mock.restoreAll()
+  })
+
+  await it('test 1', () => { /* clean state */ })
+  await it('test 2', () => { /* clean state */ })
+})
+```
+
+**Why:** Module-level state causes:
+
+- Test pollution (state leaks between tests)
+- Flaky tests (order-dependent results)
+- False positives/negatives
+- Difficult debugging
+
+**Exception:** Static constants (strings, numbers, frozen objects) CAN be at module level since they don't change.
+
 ## Cleanup Hooks
 
 **ALWAYS include `afterEach()` cleanup to prevent test pollution:**
index 66c697bc49d3d507fed806ce0ef4a890551bc00b..e53ae1abaadf3b8c4950dbc27fb8542330d3e109 100644 (file)
@@ -17,11 +17,7 @@ export {
   clearConnectorTransaction,
   setupConnectorWithTransaction,
   standardCleanup,
-  TestEnvironmentHelper,
-  TestStationHelper,
-  TestTimerHelper,
 } from '../helpers/TestLifecycleHelpers.js'
-export type { MockableTimerAPI, TimerHelperOptions } from '../helpers/TestLifecycleHelpers.js'
 
 // Re-export all helper functions and types
 export type {
@@ -35,7 +31,6 @@ export {
   cleanupChargingStation,
   createConnectorStatus,
   createMockChargingStation,
-  createMockTemplate,
   resetChargingStationState,
   waitForCondition,
 } from './helpers/StationHelpers.js'
index 68c1d7d56195496020cdda1760acefa1b5ef82b7..2dcd49ef8d1c5db77aced7f21849f2d5fd1331cd 100644 (file)
@@ -6,7 +6,6 @@
 
 import type { ChargingStation } from '../../../src/charging-station/ChargingStation.js'
 import type {
-  ChargingStationTemplate,
   ConnectorStatus,
   EvseStatus,
   StopTransactionReason,
@@ -747,24 +746,6 @@ export function createMockChargingStation (
   }
 }
 
-/**
- * Create a mock template for testing
- * @param overrides - Template properties to override
- * @returns ChargingStationTemplate for testing
- */
-export function createMockTemplate (
-  overrides: Partial<ChargingStationTemplate> = {}
-): ChargingStationTemplate {
-  return {
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    chargePointModel: 'Test Model',
-    chargePointVendor: 'Test Vendor',
-    numberOfConnectors: 2,
-    ocppVersion: OCPPVersion.VERSION_16,
-    ...overrides,
-  } as ChargingStationTemplate
-}
-
 /**
  * Reset a ChargingStation to its initial state
  *
index 2e24dfd45387e200ce684044f51e58b00fd1a16f..da9835a10c85b969296b8ba614a4524dcf2fd893 100644 (file)
@@ -3,7 +3,7 @@
  * @description Unit tests for OCPP 2.0 BootNotification request building (B01)
  */
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
 import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
 import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
@@ -15,7 +15,7 @@ import {
 } from '../../../../src/types/index.js'
 import { type ChargingStationType } from '../../../../src/types/ocpp/2.0/Common.js'
 import { Constants } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import {
   TEST_CHARGE_POINT_MODEL,
   TEST_CHARGE_POINT_SERIAL_NUMBER,
@@ -23,31 +23,40 @@ import {
   TEST_CHARGING_STATION_BASE_NAME,
   TEST_FIRMWARE_VERSION,
 } from '../../ChargingStationTestConstants.js'
-import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js'
+import {
+  createTestableOCPP20RequestService,
+  type TestableOCPP20RequestService,
+} from './OCPP20TestUtils.js'
 
 await describe('B01 - Cold Boot Charging Station', async () => {
-  afterEach(() => {
-    mock.restoreAll()
+  let mockResponseService: OCPP20ResponseService
+  let requestService: OCPP20RequestService
+  let testableRequestService: TestableOCPP20RequestService
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockResponseService = new OCPP20ResponseService()
+    requestService = new OCPP20RequestService(mockResponseService)
+    testableRequestService = createTestableOCPP20RequestService(requestService)
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        chargePointModel: TEST_CHARGE_POINT_MODEL,
+        chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
+        chargePointVendor: TEST_CHARGE_POINT_VENDOR,
+        firmwareVersion: TEST_FIRMWARE_VERSION,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
   })
 
-  const mockResponseService = new OCPP20ResponseService()
-  const requestService = new OCPP20RequestService(mockResponseService)
-  const testableRequestService = createTestableOCPP20RequestService(requestService)
-
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      chargePointModel: TEST_CHARGE_POINT_MODEL,
-      chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
-      chargePointVendor: TEST_CHARGE_POINT_VENDOR,
-      firmwareVersion: TEST_FIRMWARE_VERSION,
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+  afterEach(() => {
+    mock.restoreAll()
   })
 
   // FR: B01.FR.01
index 02d0d83ad5a43194ba664a8d14471a92f4e7d40b..cc4a119af85bc7175cbcbe6d6ed52f044910ecbf 100644 (file)
@@ -3,7 +3,7 @@
  * @description Unit tests for OCPP 2.0 Heartbeat request building (G02)
  */
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
 import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
 import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
@@ -13,7 +13,7 @@ import {
   OCPPVersion,
 } from '../../../../src/types/index.js'
 import { Constants, has } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import {
   TEST_CHARGE_POINT_MODEL,
   TEST_CHARGE_POINT_SERIAL_NUMBER,
@@ -21,31 +21,41 @@ import {
   TEST_CHARGING_STATION_BASE_NAME,
   TEST_FIRMWARE_VERSION,
 } from '../../ChargingStationTestConstants.js'
-import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js'
+import {
+  createTestableOCPP20RequestService,
+  type TestableOCPP20RequestService,
+} from './OCPP20TestUtils.js'
 
 await describe('G02 - Heartbeat', async () => {
+  let mockResponseService: OCPP20ResponseService
+  let requestService: OCPP20RequestService
+  let testableRequestService: TestableOCPP20RequestService
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockResponseService = new OCPP20ResponseService()
+    requestService = new OCPP20RequestService(mockResponseService)
+    testableRequestService = createTestableOCPP20RequestService(requestService)
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        chargePointModel: TEST_CHARGE_POINT_MODEL,
+        chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
+        chargePointVendor: TEST_CHARGE_POINT_VENDOR,
+        firmwareVersion: TEST_FIRMWARE_VERSION,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
   afterEach(() => {
     mock.restoreAll()
   })
-  const mockResponseService = new OCPP20ResponseService()
-  const requestService = new OCPP20RequestService(mockResponseService)
-  const testableRequestService = createTestableOCPP20RequestService(requestService)
-
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      chargePointModel: TEST_CHARGE_POINT_MODEL,
-      chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
-      chargePointVendor: TEST_CHARGE_POINT_VENDOR,
-      firmwareVersion: TEST_FIRMWARE_VERSION,
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-  })
 
   // FR: G02.FR.01
   await it('should build HeartBeat request payload correctly with empty object', () => {
index 506ad930462171b8a24ee035dcc85620eb2ea4f1..3836e607832a3bcd8063f240c1ebc842895e8115 100644 (file)
@@ -5,7 +5,7 @@
 /* cspell:ignore Bvbn NQIF CBCYX */
 
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
 import { createTestableRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import {
@@ -23,7 +23,7 @@ import {
   ReasonCodeEnumType,
 } from '../../../../src/types/index.js'
 import { Constants } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
 
 // Sample Base64 EXI request (mock - represents CertificateInstallationReq)
@@ -42,20 +42,25 @@ const createMockOCSPRequestData = (): OCSPRequestDataType => ({
 })
 
 await describe('M02 - Get15118EVCertificate Request', async () => {
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
   afterEach(() => {
     mock.restoreAll()
   })
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-  })
 
   await describe('EXI Install Action', async () => {
     await it('should forward EXI request unmodified for Install action', async () => {
@@ -204,16 +209,24 @@ await describe('M02 - Get15118EVCertificate Request', async () => {
 })
 
 await describe('M03 - GetCertificateStatus Request', async () => {
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
+  afterEach(() => {
+    mock.restoreAll()
   })
 
   await describe('OCSP Request Data', async () => {
@@ -313,16 +326,24 @@ await describe('M03 - GetCertificateStatus Request', async () => {
 })
 
 await describe('Request Command Names', async () => {
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 1,
-    evseConfiguration: { evsesCount: 1 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 1,
+      evseConfiguration: { evsesCount: 1 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
+  afterEach(() => {
+    mock.restoreAll()
   })
 
   await it('should send GET_15118_EV_CERTIFICATE command name', async () => {
index 6b55e96996d62b338b2c8faa31924a2dd1b79d10..82cbe448ffc75d6e378a27740e300a2d052c3e3c 100644 (file)
@@ -4,9 +4,12 @@
  */
 
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
-import { createTestableRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
+import {
+  createTestableRequestService,
+  type TestableOCPP20RequestService,
+} from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import {
   AttributeEnumType,
   DataEnumType,
@@ -19,7 +22,7 @@ import {
   type ReportDataType,
 } from '../../../../src/types/index.js'
 import { Constants } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import {
   TEST_CHARGE_POINT_MODEL,
   TEST_CHARGE_POINT_SERIAL_NUMBER,
@@ -29,26 +32,32 @@ import {
 } from '../../ChargingStationTestConstants.js'
 
 await describe('B07/B08 - NotifyReport', async () => {
+  let testableService: TestableOCPP20RequestService
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    const { service } = createTestableRequestService()
+    testableService = service
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        chargePointModel: TEST_CHARGE_POINT_MODEL,
+        chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
+        chargePointVendor: TEST_CHARGE_POINT_VENDOR,
+        firmwareVersion: TEST_FIRMWARE_VERSION,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
   afterEach(() => {
     mock.restoreAll()
   })
-  const { service: testableService } = createTestableRequestService()
-
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      chargePointModel: TEST_CHARGE_POINT_MODEL,
-      chargePointSerialNumber: TEST_CHARGE_POINT_SERIAL_NUMBER,
-      chargePointVendor: TEST_CHARGE_POINT_VENDOR,
-      firmwareVersion: TEST_FIRMWARE_VERSION,
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-  })
 
   // FR: B07.FR.03, B07.FR.04
   await it('should build NotifyReport request payload correctly with minimal required fields', () => {
index 6753286181cb2090f75583555be6d2d9291cc08b..d3e718c8924e2e5a7d85996cfd1997a6e34c22f6 100644 (file)
@@ -4,7 +4,7 @@
  */
 
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
 import { createTestableRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js'
 import {
@@ -16,31 +16,35 @@ import {
   OCPPVersion,
 } from '../../../../src/types/index.js'
 import { Constants } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js'
 
 const MOCK_ORGANIZATION_NAME = 'Test Organization Inc.'
 
 await describe('I02 - SignCertificate Request', async () => {
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockChargingStation = createChargingStation({
+      baseName: TEST_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+    // Set up configuration with OrganizationName
+    mockChargingStation.ocppConfiguration = {
+      configurationKey: [{ key: 'SecurityCtrlr.OrganizationName', value: MOCK_ORGANIZATION_NAME }],
+    }
+  })
+
   afterEach(() => {
     mock.restoreAll()
   })
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-  })
-
-  // Set up configuration with OrganizationName
-  mockChargingStation.ocppConfiguration = {
-    configurationKey: [{ key: 'SecurityCtrlr.OrganizationName', value: MOCK_ORGANIZATION_NAME }],
-  }
 
   await describe('CSR Generation', async () => {
     await it('should generate CSR with PKCS#10 PEM format', async () => {
index 49de14044d216de586bd78c0e608e8acf408d956..c4a6bdb203553a3c576fa7238568f67bc6323645 100644 (file)
@@ -3,7 +3,7 @@
  * @description Unit tests for OCPP 2.0 StatusNotification request building (G01)
  */
 import { expect } from '@std/expect'
-import { afterEach, describe, it, mock } from 'node:test'
+import { afterEach, beforeEach, describe, it, mock } from 'node:test'
 
 import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
 import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js'
@@ -14,7 +14,7 @@ import {
   OCPPVersion,
 } from '../../../../src/types/index.js'
 import { Constants } from '../../../../src/utils/index.js'
-import { createChargingStation } from '../../../ChargingStationFactory.js'
+import { createChargingStation, type TestChargingStation } from '../../../ChargingStationFactory.js'
 import {
   TEST_FIRMWARE_VERSION,
   TEST_STATUS_CHARGE_POINT_MODEL,
@@ -22,31 +22,41 @@ import {
   TEST_STATUS_CHARGE_POINT_VENDOR,
   TEST_STATUS_CHARGING_STATION_BASE_NAME,
 } from '../../ChargingStationTestConstants.js'
-import { createTestableOCPP20RequestService } from './OCPP20TestUtils.js'
+import {
+  createTestableOCPP20RequestService,
+  type TestableOCPP20RequestService,
+} from './OCPP20TestUtils.js'
 
 await describe('G01 - Status Notification', async () => {
+  let mockResponseService: OCPP20ResponseService
+  let requestService: OCPP20RequestService
+  let testableRequestService: TestableOCPP20RequestService
+  let mockChargingStation: TestChargingStation
+
+  beforeEach(() => {
+    mockResponseService = new OCPP20ResponseService()
+    requestService = new OCPP20RequestService(mockResponseService)
+    testableRequestService = createTestableOCPP20RequestService(requestService)
+    mockChargingStation = createChargingStation({
+      baseName: TEST_STATUS_CHARGING_STATION_BASE_NAME,
+      connectorsCount: 3,
+      evseConfiguration: { evsesCount: 3 },
+      heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
+      stationInfo: {
+        chargePointModel: TEST_STATUS_CHARGE_POINT_MODEL,
+        chargePointSerialNumber: TEST_STATUS_CHARGE_POINT_SERIAL_NUMBER,
+        chargePointVendor: TEST_STATUS_CHARGE_POINT_VENDOR,
+        firmwareVersion: TEST_FIRMWARE_VERSION,
+        ocppStrictCompliance: false,
+        ocppVersion: OCPPVersion.VERSION_201,
+      },
+      websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
+    })
+  })
+
   afterEach(() => {
     mock.restoreAll()
   })
-  const mockResponseService = new OCPP20ResponseService()
-  const requestService = new OCPP20RequestService(mockResponseService)
-  const testableRequestService = createTestableOCPP20RequestService(requestService)
-
-  const mockChargingStation = createChargingStation({
-    baseName: TEST_STATUS_CHARGING_STATION_BASE_NAME,
-    connectorsCount: 3,
-    evseConfiguration: { evsesCount: 3 },
-    heartbeatInterval: Constants.DEFAULT_HEARTBEAT_INTERVAL,
-    stationInfo: {
-      chargePointModel: TEST_STATUS_CHARGE_POINT_MODEL,
-      chargePointSerialNumber: TEST_STATUS_CHARGE_POINT_SERIAL_NUMBER,
-      chargePointVendor: TEST_STATUS_CHARGE_POINT_VENDOR,
-      firmwareVersion: TEST_FIRMWARE_VERSION,
-      ocppStrictCompliance: false,
-      ocppVersion: OCPPVersion.VERSION_201,
-    },
-    websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
-  })
 
   // FR: G01.FR.01
   await it('should build StatusNotification request payload correctly with Available status', () => {
index aecf8905102d6d9a805607488bd01372ad3ab67e..bb9f7ec4131a27f1989e75bbc8d4b3ad032b2dd7 100644 (file)
@@ -3,7 +3,11 @@
 import { expect } from '@std/expect'
 
 import type { ChargingStation } from '../../../../../src/charging-station/ChargingStation.js'
-import type { OCPPAuthService } from '../../../../../src/charging-station/ocpp/auth/interfaces/OCPPAuthService.js'
+import type {
+  AuthCache,
+  OCPPAuthAdapter,
+  OCPPAuthService,
+} from '../../../../../src/charging-station/ocpp/auth/interfaces/OCPPAuthService.js'
 
 import {
   type AuthConfiguration,
@@ -178,6 +182,71 @@ export const createMockAuthService = (overrides?: Partial<OCPPAuthService>): OCP
     ...overrides,
   }) as OCPPAuthService
 
+// ============================================================================
+// Cache Mocks
+// ============================================================================
+
+/**
+ * Create a mock AuthCache for testing.
+ * @param overrides - Partial AuthCache methods to override defaults
+ * @returns Mock AuthCache with stubbed async methods
+ */
+export const createMockAuthCache = (overrides?: Partial<AuthCache>): AuthCache => ({
+  clear: async () => Promise.resolve(),
+  get: async (_key: string) => Promise.resolve(undefined),
+  getStats: async () =>
+    Promise.resolve({
+      evictions: 0,
+      expiredEntries: 0,
+      hitRate: 0,
+      hits: 0,
+      memoryUsage: 0,
+      misses: 0,
+      totalEntries: 0,
+    }),
+  remove: async (_key: string) => Promise.resolve(),
+  set: async (_key: string, _value: unknown, _ttl?: number) => Promise.resolve(),
+  ...overrides,
+})
+
+// ============================================================================
+// Adapter Mocks
+// ============================================================================
+
+/**
+ * Create a mock OCPPAuthAdapter for testing.
+ * @param ocppVersion - OCPP version for this adapter
+ * @param overrides - Partial OCPPAuthAdapter methods to override defaults
+ * @returns Mock OCPPAuthAdapter with stubbed methods
+ */
+export const createMockOCPPAdapter = (
+  ocppVersion: OCPPVersion,
+  overrides?: Partial<OCPPAuthAdapter>
+): OCPPAuthAdapter => ({
+  authorizeRemote: async (_identifier: UnifiedIdentifier) =>
+    Promise.resolve(
+      createMockAuthorizationResult({
+        method: AuthenticationMethod.REMOTE_AUTHORIZATION,
+      })
+    ),
+  convertFromUnifiedIdentifier: (identifier: UnifiedIdentifier) =>
+    ocppVersion === OCPPVersion.VERSION_16
+      ? identifier.value
+      : { idToken: identifier.value, type: identifier.type },
+  convertToUnifiedIdentifier: (identifier: object | string) => ({
+    ocppVersion,
+    type: IdentifierType.ID_TAG,
+    value:
+      typeof identifier === 'string'
+        ? identifier
+        : ((identifier as { idToken?: string }).idToken ?? 'unknown'),
+  }),
+  getConfigurationSchema: () => ({}),
+  isRemoteAvailable: async () => Promise.resolve(true),
+  ocppVersion,
+  validateConfiguration: async (_config: AuthConfiguration) => Promise.resolve(true),
+  ...overrides,
+})
 // ============================================================================
 // Assertion Helpers
 // ============================================================================
index 4de31796e63aeac612ecf0e388c357449b5628c3..da06d23c059a7a3952ff611bb627f3270883c1a3 100644 (file)
 import { mock } from 'node:test'
 
 import type { ChargingStation } from '../../src/charging-station/ChargingStation.js'
-import type {
-  MockChargingStationOptions,
-  MockChargingStationResult,
-} from '../charging-station/helpers/StationHelpers.js'
 
-import {
-  cleanupChargingStation,
-  createMockChargingStation,
-} from '../charging-station/helpers/StationHelpers.js'
 import { MockIdTagsCache, MockSharedLRUCache } from '../charging-station/mocks/MockCaches.js'
 
 /**
@@ -80,213 +72,6 @@ interface MockContext {
   }
 }
 
-/**
- * Helper class for managing mock charging stations in tests
- *
- * Provides automatic cleanup of charging station resources including
- * timers, WebSocket connections, and singleton mocks.
- * @example
- * ```typescript
- * describe('MyTest', () => {
- *   const stationHelper = new TestStationHelper({ connectorsCount: 2 })
- *
- *   beforeEach(() => stationHelper.setup())
- *   afterEach(() => stationHelper.cleanup())
- *
- *   it('should test station', () => {
- *     const { station, mocks } = stationHelper.get()
- *     // test with station
- *   })
- * })
- * ```
- */
-export class TestStationHelper {
-  private readonly options: MockChargingStationOptions
-  private result: MockChargingStationResult | null = null
-
-  constructor (options: MockChargingStationOptions = {}) {
-    this.options = options
-  }
-
-  /**
-   * Clean up the charging station (call in afterEach)
-   */
-  cleanup (): void {
-    if (this.result != null) {
-      cleanupChargingStation(this.result.station)
-      this.result = null
-    }
-    // Also reset singleton mocks
-    MockSharedLRUCache.resetInstance()
-    MockIdTagsCache.resetInstance()
-  }
-
-  /**
-   * Get the current mock result (throws if not setup)
-   * @returns The mock charging station result
-   */
-  get (): MockChargingStationResult {
-    if (this.result == null) {
-      throw new Error('TestStationHelper.setup() must be called before get()')
-    }
-    return this.result
-  }
-
-  /**
-   * Get just the station (convenience method)
-   * @returns The charging station instance
-   */
-  getStation (): ChargingStation {
-    return this.get().station
-  }
-
-  /**
-   * Check if station is currently setup
-   * @returns True if station is setup
-   */
-  isSetup (): boolean {
-    return this.result != null
-  }
-
-  /**
-   * Create the mock charging station (call in beforeEach)
-   * @returns The mock charging station result
-   */
-  setup (): MockChargingStationResult {
-    this.result = createMockChargingStation(this.options)
-    return this.result
-  }
-}
-
-/**
- * Helper class for managing mock timers in tests
- *
- * Encapsulates the common pattern of enabling and resetting mock timers,
- * ensuring consistent cleanup and preventing timer leaks between tests.
- * @example
- * ```typescript
- * describe('MyTest', () => {
- *   const timerHelper = new TestTimerHelper()
- *
- *   beforeEach(() => timerHelper.setup())
- *   afterEach(() => timerHelper.cleanup())
- *
- *   it('should handle timer-based logic', () => {
- *     // Timer-dependent code works with mock timers
- *     mock.timers.tick(1000)
- *   })
- * })
- * ```
- */
-export class TestTimerHelper {
-  private readonly apis: MockableTimerAPI[]
-  private isSetup = false
-
-  constructor (options: TimerHelperOptions = {}) {
-    this.apis = options.apis ?? ['setInterval', 'setTimeout', 'setImmediate']
-  }
-
-  /**
-   * Reset mock timers (call in afterEach)
-   */
-  cleanup (): void {
-    if (this.isSetup) {
-      mock.timers.reset()
-      this.isSetup = false
-    }
-  }
-
-  /**
-   * Enable mock timers (call in beforeEach)
-   */
-  setup (): void {
-    if (!this.isSetup) {
-      mock.timers.enable({ apis: this.apis })
-      this.isSetup = true
-    }
-  }
-
-  /**
-   * Advance mock timers by specified milliseconds
-   * @param ms - Milliseconds to advance
-   */
-  tick (ms: number): void {
-    mock.timers.tick(ms)
-  }
-}
-
-/**
- * Combined helper for tests that need both timers and station
- * @example
- * ```typescript
- * describe('MyTest', () => {
- *   const helper = new TestEnvironmentHelper({ connectorsCount: 2 })
- *
- *   beforeEach(() => helper.setup())
- *   afterEach(() => helper.cleanup())
- *
- *   it('should test with timers and station', () => {
- *     const station = helper.getStation()
- *     helper.tick(1000) // Advance time
- *   })
- * })
- * ```
- */
-export class TestEnvironmentHelper {
-  private readonly stationHelper: TestStationHelper
-  private readonly timerHelper: TestTimerHelper
-
-  constructor (
-    stationOptions: MockChargingStationOptions = {},
-    timerOptions: TimerHelperOptions = {}
-  ) {
-    this.timerHelper = new TestTimerHelper(timerOptions)
-    this.stationHelper = new TestStationHelper(stationOptions)
-  }
-
-  /**
-   * Cleanup both timers and station
-   */
-  cleanup (): void {
-    this.stationHelper.cleanup()
-    this.timerHelper.cleanup()
-    mock.restoreAll()
-  }
-
-  /**
-   * Get the mock station result
-   * @returns The mock charging station result
-   */
-  get (): MockChargingStationResult {
-    return this.stationHelper.get()
-  }
-
-  /**
-   * Get just the station
-   * @returns The charging station instance
-   */
-  getStation (): ChargingStation {
-    return this.stationHelper.getStation()
-  }
-
-  /**
-   * Setup both timers and station
-   * @returns The mock charging station result
-   */
-  setup (): MockChargingStationResult {
-    this.timerHelper.setup()
-    return this.stationHelper.setup()
-  }
-
-  /**
-   * Advance mock timers
-   * @param ms - Milliseconds to advance
-   */
-  tick (ms: number): void {
-    this.timerHelper.tick(ms)
-  }
-}
-
 /**
  * Clear transaction state from a connector
  * @param station - ChargingStation instance