From be2949ef748ec51c3837d5247fc53d769e9e5147 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 2 Apr 2026 21:48:40 +0200 Subject: [PATCH] refactor(tests): consolidate duplicate test helpers Extract createStationWithRequestHandler to shared TestLifecycleHelpers, eliminating duplication between OCPPConnectorStatusOperations and OCPPServiceOperations tests. Extend setupConnectorWithTransaction to support string transactionId and pending mode, replacing local setupTransaction/setupPendingTransaction helpers. --- .../OCPPConnectorStatusOperations.test.ts | 25 +----- .../ocpp/OCPPServiceOperations.test.ts | 89 ++++--------------- tests/helpers/TestLifecycleHelpers.ts | 32 ++++++- 3 files changed, 48 insertions(+), 98 deletions(-) diff --git a/tests/charging-station/ocpp/OCPPConnectorStatusOperations.test.ts b/tests/charging-station/ocpp/OCPPConnectorStatusOperations.test.ts index 3aad3422..530fa077 100644 --- a/tests/charging-station/ocpp/OCPPConnectorStatusOperations.test.ts +++ b/tests/charging-station/ocpp/OCPPConnectorStatusOperations.test.ts @@ -10,9 +10,7 @@ import assert from 'node:assert/strict' import { afterEach, describe, it, mock } from 'node:test' -import type { ChargingStation } from '../../../src/charging-station/index.js' import type { Reservation } from '../../../src/types/index.js' -import type { MockChargingStationOptions } from '../helpers/StationHelpers.js' import { restoreConnectorStatus, @@ -24,25 +22,10 @@ import { type OCPP20StatusNotificationRequest, OCPPVersion, } from '../../../src/types/index.js' -import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' -import { createMockChargingStation } from '../ChargingStationTestUtils.js' - -/** - * Creates a mock station with a spied requestHandler for verifying OCPP requests. - * @param opts - Additional mock station options to merge - * @returns The station and its requestHandler spy - */ -function createStationWithRequestHandler (opts?: Partial): { - requestHandler: ReturnType - station: ChargingStation -} { - const requestHandler = mock.fn(() => Promise.resolve({})) - const { station } = createMockChargingStation({ - ocppRequestService: { requestHandler }, - ...opts, - }) - return { requestHandler, station } -} +import { + createStationWithRequestHandler, + standardCleanup, +} from '../../helpers/TestLifecycleHelpers.js' await describe('OCPPConnectorStatusOperations', async () => { afterEach(() => { diff --git a/tests/charging-station/ocpp/OCPPServiceOperations.test.ts b/tests/charging-station/ocpp/OCPPServiceOperations.test.ts index 474e078a..8154038c 100644 --- a/tests/charging-station/ocpp/OCPPServiceOperations.test.ts +++ b/tests/charging-station/ocpp/OCPPServiceOperations.test.ts @@ -6,10 +6,7 @@ */ import assert from 'node:assert/strict' -import { afterEach, describe, it, mock } from 'node:test' - -import type { ChargingStation } from '../../../src/charging-station/index.js' -import type { MockChargingStationOptions } from '../helpers/StationHelpers.js' +import { afterEach, describe, it } from 'node:test' import { AuthContext, @@ -25,30 +22,17 @@ import { stopTransactionOnConnector, } from '../../../src/charging-station/ocpp/OCPPServiceOperations.js' import { type OCPP20TransactionEventRequest, OCPPVersion } from '../../../src/types/index.js' -import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' +import { + createStationWithRequestHandler, + setupConnectorWithTransaction, + standardCleanup, +} from '../../helpers/TestLifecycleHelpers.js' import { createMockChargingStation } from '../ChargingStationTestUtils.js' import { createMockAuthorizationResult, createMockAuthService, } from './auth/helpers/MockFactories.js' -/** - * Creates a mock charging station with a tracked request handler for testing - * @param opts - optional charging station configuration options - * @returns object with the mock station and the mock request handler function - */ -function createStationWithRequestHandler (opts?: Partial): { - requestHandler: ReturnType - station: ChargingStation -} { - const requestHandler = mock.fn(async (..._args: unknown[]) => Promise.resolve({})) - const { station } = createMockChargingStation({ - ocppRequestService: { requestHandler }, - ...opts, - }) - return { requestHandler, station } -} - /** * Registers a mock auth service for the given station in OCPPAuthServiceFactory. * @param station - Mock charging station instance @@ -65,49 +49,6 @@ function injectMockAuthService ( return mockService } -/** - * Configures a connector with a pending (not started) transaction for testing - * @param station - the charging station mock - * @param connectorId - the connector ID to configure - * @param txId - the transaction ID to assign - */ -function setupPendingTransaction ( - station: ChargingStation, - connectorId: number, - txId: string -): void { - const connectorStatus = station.getConnectorStatus(connectorId) - if (connectorStatus == null) { - throw new Error(`Connector ${String(connectorId)} not found`) - } - connectorStatus.transactionPending = true - connectorStatus.transactionStarted = false - connectorStatus.transactionId = txId - connectorStatus.transactionStart = new Date() -} - -/** - * Configures a connector with a started transaction for testing - * @param station - the charging station mock - * @param connectorId - the connector ID to configure - * @param txId - the transaction ID to assign - */ -function setupTransaction ( - station: ChargingStation, - connectorId: number, - txId: number | string -): void { - const connectorStatus = station.getConnectorStatus(connectorId) - if (connectorStatus == null) { - throw new Error(`Connector ${String(connectorId)} not found`) - } - connectorStatus.transactionStarted = true - connectorStatus.transactionId = txId - connectorStatus.transactionIdTag = `TAG-${String(txId)}` - connectorStatus.transactionStart = new Date() - connectorStatus.idTagAuthorized = true -} - await describe('OCPPServiceOperations', async () => { afterEach(() => { OCPPAuthServiceFactory.clearAllInstances() @@ -120,7 +61,7 @@ await describe('OCPPServiceOperations', async () => { requestHandler.mock.mockImplementation(async (..._args: unknown[]) => Promise.resolve({ idTagInfo: { status: 'Accepted' } }) ) - setupTransaction(station, 1, 100) + setupConnectorWithTransaction(station, 1, { transactionId: 100 }) const result = await stopTransactionOnConnector(station, 1) @@ -140,7 +81,7 @@ await describe('OCPPServiceOperations', async () => { requestHandler.mock.mockImplementation(async (..._args: unknown[]) => Promise.resolve({ idTokenInfo: { status: 'Accepted' } }) ) - setupTransaction(station, 1, 'tx-uuid-001') + setupConnectorWithTransaction(station, 1, { transactionId: 'tx-uuid-001' }) const result = await stopTransactionOnConnector(station, 1) @@ -179,8 +120,8 @@ await describe('OCPPServiceOperations', async () => { sentCommands.push(args[1] as string) return Promise.resolve({ idTagInfo: { status: 'Accepted' } }) }) - setupTransaction(station, 1, 101) - setupTransaction(station, 2, 102) + setupConnectorWithTransaction(station, 1, { transactionId: 101 }) + setupConnectorWithTransaction(station, 2, { transactionId: 102 }) await stopRunningTransactions(station) @@ -203,8 +144,8 @@ await describe('OCPPServiceOperations', async () => { }) return Promise.resolve({ idTokenInfo: { status: 'Accepted' } }) }) - setupTransaction(station, 1, 'tx-001') - setupTransaction(station, 2, 'tx-002') + setupConnectorWithTransaction(station, 1, { transactionId: 'tx-001' }) + setupConnectorWithTransaction(station, 2, { transactionId: 'tx-002' }) await stopRunningTransactions(station) @@ -230,8 +171,8 @@ await describe('OCPPServiceOperations', async () => { }) return Promise.resolve({ idTokenInfo: { status: 'Accepted' } }) }) - setupTransaction(station, 1, 'tx-started') - setupPendingTransaction(station, 2, 'tx-pending') + setupConnectorWithTransaction(station, 1, { transactionId: 'tx-started' }) + setupConnectorWithTransaction(station, 2, { pending: true, transactionId: 'tx-pending' }) await stopRunningTransactions(station) @@ -248,7 +189,7 @@ await describe('OCPPServiceOperations', async () => { requestHandler.mock.mockImplementation(async () => Promise.reject(new Error('Simulated network error')) ) - setupTransaction(station, 1, 'tx-fail') + setupConnectorWithTransaction(station, 1, { transactionId: 'tx-fail' }) await assert.doesNotReject(() => stopRunningTransactions(station)) }) diff --git a/tests/helpers/TestLifecycleHelpers.ts b/tests/helpers/TestLifecycleHelpers.ts index 370c91d4..0d11ab68 100644 --- a/tests/helpers/TestLifecycleHelpers.ts +++ b/tests/helpers/TestLifecycleHelpers.ts @@ -28,7 +28,9 @@ import { mock } from 'node:test' import type { ChargingStation } from '../../src/charging-station/index.js' +import type { MockChargingStationOptions } from '../charging-station/helpers/StationHelpers.js' +import { createMockChargingStation } from '../charging-station/ChargingStationTestUtils.js' import { MockIdTagsCache, MockSharedLRUCache } from '../charging-station/mocks/MockCaches.js' /** @@ -194,6 +196,23 @@ export function createLoggerMocks ( } } +/** + * Creates a mock charging station with a spied requestHandler for verifying OCPP requests. + * @param opts - Additional mock station options to merge + * @returns Object with the station and its requestHandler spy + */ +export function createStationWithRequestHandler (opts?: Partial): { + requestHandler: ReturnType + station: ChargingStation +} { + const requestHandler = mock.fn(async (..._args: unknown[]) => Promise.resolve({})) + const { station } = createMockChargingStation({ + ocppRequestService: { requestHandler }, + ...opts, + }) + return { requestHandler, station } +} + /** * Create a timer scope for manual control over timer mocking. * @@ -244,6 +263,7 @@ export function createTimerScope ( * @param station - ChargingStation instance * @param connectorId - Connector to setup * @param options - Transaction options + * @param options.pending - Whether transaction is pending (not started) (default: false) * @param options.transactionId - Transaction ID to set * @param options.idTag - ID tag for the transaction (default: TAG-{transactionId}) * @param options.energyImport - Energy import value in Wh (default: 0) @@ -263,8 +283,9 @@ export function setupConnectorWithTransaction ( options: { energyImport?: number idTag?: string + pending?: boolean remoteStarted?: boolean - transactionId: number + transactionId: number | string } ): void { const connectorStatus = station.getConnectorStatus(connectorId) @@ -272,13 +293,18 @@ export function setupConnectorWithTransaction ( throw new Error(`Connector ${String(connectorId)} not found`) } - connectorStatus.transactionStarted = true + if (options.pending === true) { + connectorStatus.transactionPending = true + connectorStatus.transactionStarted = false + } else { + connectorStatus.transactionStarted = true + } connectorStatus.transactionId = options.transactionId connectorStatus.transactionIdTag = options.idTag ?? `TAG-${String(options.transactionId)}` connectorStatus.transactionEnergyActiveImportRegisterValue = options.energyImport ?? 0 connectorStatus.transactionRemoteStarted = options.remoteStarted ?? false connectorStatus.transactionStart = new Date() - connectorStatus.idTagAuthorized = true + connectorStatus.idTagAuthorized = options.pending !== true } /** -- 2.43.0