From 563a87ba6d2c69e1cfbd0c3e6d9ced13ac73725f Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sat, 28 Feb 2026 22:04:13 +0100 Subject: [PATCH] refactor(tests): consolidate duplicate mock factories - Extract createMockCertificateManager to OCPP20TestUtils.ts (was duplicated 4x) - Consolidate 5 createMock*AuthorizationResult into single parameterized factory - Add createMockIdentifier with OCPP version parameter (deprecate version-specific ones) - Remove unused wrapper functions from certificate test files Reduces code duplication and improves maintainability. --- ...ngRequestService-CertificateSigned.test.ts | 41 +----- ...ngRequestService-DeleteCertificate.test.ts | 22 +--- ...Service-GetInstalledCertificateIds.test.ts | 24 +--- ...gRequestService-InstallCertificate.test.ts | 26 +--- .../ocpp/2.0/OCPP20TestUtils.ts | 81 ++++++++++++ .../ocpp/auth/helpers/MockFactories.ts | 118 ++++++++---------- 6 files changed, 148 insertions(+), 164 deletions(-) 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 ded401fa..1859e14e 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts @@ -4,15 +4,9 @@ */ import { expect } from '@std/expect' -import { afterEach, beforeEach, describe, it, mock, type Mock } from 'node:test' +import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { ChargingStation } from '../../../../src/charging-station/index.js' -import type { - DeleteCertificateResult, - GetInstalledCertificatesResult, - OCPP20CertificateManagerInterface, - StoreCertificateResult, -} from '../../../../src/charging-station/ocpp/2.0/OCPP20CertificateManager.js' import { createTestableIncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/__testable__/index.js' import { OCPP20IncomingRequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js' @@ -26,6 +20,7 @@ import { import { Constants } from '../../../../src/utils/index.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' +import { createMockCertificateManager } from './OCPP20TestUtils.js' const VALID_PEM_CERTIFICATE = `-----BEGIN CERTIFICATE----- MIIBkTCB+wIJAKHBfpvPA0GXMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnRl @@ -63,38 +58,6 @@ const INVALID_PEM_CERTIFICATE_MISSING_MARKERS = `MIIBkTCB+wIJAKHBfpvPA0GXMA0GCSq Rlc3RDQTAeFw0yNDAxMDEwMDAwMDBaFw0yOTAxMDEwMDAwMDBaMBExDzANBgNVBA MMBnRlc3RDQTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC5p8U8zTk8TT5H5s8mjx` -/** - * Mock certificate manager interface for testing - * Provides typed access to mock certificate operations - */ -interface MockCertificateManager extends OCPP20CertificateManagerInterface { - deleteCertificate: Mock<() => DeleteCertificateResult> - getInstalledCertificates: Mock<() => GetInstalledCertificatesResult> - storeCertificate: Mock<() => StoreCertificateResult> - validateCertificateFormat: Mock<(cert: string) => boolean> -} - -const createMockCertificateManager = ( - options: { - storeCertificateError?: Error - storeCertificateResult?: boolean - } = {} -): MockCertificateManager => ({ - deleteCertificate: mock.fn(), - getInstalledCertificates: mock.fn(() => ({ certificateHashDataChain: [] })), - storeCertificate: mock.fn(() => { - if (options.storeCertificateError) { - throw options.storeCertificateError - } - return { success: options.storeCertificateResult ?? true } - }), - validateCertificateFormat: mock.fn((cert: string) => { - return ( - cert.includes('-----BEGIN CERTIFICATE-----') && cert.includes('-----END CERTIFICATE-----') - ) - }), -}) - await describe('I04 - CertificateSigned', async () => { let station: ChargingStation let incomingRequestService: OCPP20IncomingRequestService 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 28ca9d66..b08c11cd 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts @@ -22,7 +22,10 @@ import { import { Constants } from '../../../../src/utils/index.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' -import { createStationWithCertificateManager } from './OCPP20TestUtils.js' +import { + createMockCertificateManager, + createStationWithCertificateManager, +} from './OCPP20TestUtils.js' const VALID_CERTIFICATE_HASH_DATA = { hashAlgorithm: HashAlgorithmEnumType.SHA256, @@ -38,23 +41,6 @@ const NONEXISTENT_CERTIFICATE_HASH_DATA = { serialNumber: 'NONEXISTENT123456', } -const createMockCertificateManager = ( - options: { - deleteCertificateError?: Error - deleteCertificateResult?: { status: 'Accepted' | 'Failed' | 'NotFound' } - } = {} -) => ({ - deleteCertificate: mock.fn(() => { - if (options.deleteCertificateError) { - throw options.deleteCertificateError - } - return options.deleteCertificateResult ?? { status: 'Accepted' } - }), - getInstalledCertificates: mock.fn(() => []), - storeCertificate: mock.fn(() => true), - validateCertificateFormat: mock.fn(() => true), -}) - await describe('I04 - DeleteCertificate', async () => { afterEach(() => { mock.restoreAll() 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 494815d4..7a53ea5a 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts @@ -24,7 +24,10 @@ import { import { Constants } from '../../../../src/utils/index.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' -import { createStationWithCertificateManager } from './OCPP20TestUtils.js' +import { + createMockCertificateManager, + createStationWithCertificateManager, +} from './OCPP20TestUtils.js' const createMockCertificateHashData = (serialNumber = '123456789'): CertificateHashDataType => ({ hashAlgorithm: HashAlgorithmEnumType.SHA256, @@ -41,25 +44,6 @@ const createMockCertificateHashDataChain = ( certificateType, }) -const createMockCertificateManager = ( - options: { - getInstalledCertificatesError?: Error - getInstalledCertificatesResult?: CertificateHashDataChainType[] - } = {} -) => ({ - deleteCertificate: mock.fn(), - getInstalledCertificates: mock.fn(() => { - if (options.getInstalledCertificatesError) { - throw options.getInstalledCertificatesError - } - return { - certificateHashDataChain: options.getInstalledCertificatesResult ?? [], - } - }), - storeCertificate: mock.fn(() => true), - validateCertificateFormat: mock.fn(() => true), -}) - await describe('I04 - GetInstalledCertificateIds', async () => { let station: ChargingStation let stationWithCertManager: ChargingStationWithCertificateManager 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 d396821d..a364726a 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts @@ -21,7 +21,10 @@ import { import { Constants } from '../../../../src/utils/index.js' import { TEST_CHARGING_STATION_BASE_NAME } from '../../ChargingStationTestConstants.js' import { createMockChargingStation } from '../../ChargingStationTestUtils.js' -import { createStationWithCertificateManager } from './OCPP20TestUtils.js' +import { + createMockCertificateManager, + createStationWithCertificateManager, +} from './OCPP20TestUtils.js' const VALID_PEM_CERTIFICATE = `-----BEGIN CERTIFICATE----- MIIBkTCB+wIJAKHBfpvPA0GXMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnRl @@ -48,27 +51,6 @@ GDAWgBRc8RqFu0nnqJdw3f9nFVXm9BxeUDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG SIb3DQEBCwUAA0EAexpired== -----END CERTIFICATE-----` -const createMockCertificateManager = ( - options: { - storeCertificateError?: Error - storeCertificateResult?: boolean - } = {} -) => ({ - deleteCertificate: mock.fn(), - getInstalledCertificates: mock.fn(() => []), - storeCertificate: mock.fn(() => { - if (options.storeCertificateError) { - throw options.storeCertificateError - } - return { success: options.storeCertificateResult ?? true } - }), - validateCertificateFormat: mock.fn((cert: string) => { - return ( - cert.includes('-----BEGIN CERTIFICATE-----') && cert.includes('-----END CERTIFICATE-----') - ) - }), -}) - await describe('I03 - InstallCertificate', async () => { afterEach(() => { mock.restoreAll() diff --git a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts index 79342b2f..a2654cf6 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts @@ -636,6 +636,87 @@ export const TransactionFlowPatterns: TransactionFlowPattern[] = [ // ChargingStationWithCertificateManager Factory // ============================================================================ +/** + * Options for configuring the mock certificate manager behavior. + */ +export interface MockCertificateManagerOptions { + /** Error to throw when deleteCertificate is called */ + deleteCertificateError?: Error + /** Result to return from deleteCertificate (default: { status: 'Accepted' }) */ + deleteCertificateResult?: { status: 'Accepted' | 'Failed' | 'NotFound' } + /** Error to throw when getInstalledCertificates is called */ + getInstalledCertificatesError?: Error + /** Result to return from getInstalledCertificates (default: []) */ + getInstalledCertificatesResult?: unknown[] + /** Error to throw when storeCertificate is called */ + storeCertificateError?: Error + /** Result to return from storeCertificate (default: { success: true }) */ + storeCertificateResult?: boolean +} + +// ============================================================================ +// Certificate Manager Mock Factory +// ============================================================================ + +/** + * Create a mock certificate manager for OCPP 2.0 certificate operation testing. + * + * This unified factory consolidates the previously duplicated createMockCertificateManager + * functions from DeleteCertificate, InstallCertificate, CertificateSigned, and + * GetInstalledCertificateIds test files. + * @param options - Configuration options for mock behavior + * @returns Mock certificate manager with configurable behavior + * @example + * ```typescript + * // Default behavior - all operations succeed + * const certManager = createMockCertificateManager() + * + * // Configure delete to return NotFound + * const certManager = createMockCertificateManager({ + * deleteCertificateResult: { status: 'NotFound' } + * }) + * + * // Configure store to throw error + * const certManager = createMockCertificateManager({ + * storeCertificateError: new Error('Storage failed') + * }) + * + * // Configure getInstalledCertificates to return specific certificates + * const certManager = createMockCertificateManager({ + * getInstalledCertificatesResult: [certificateChain1, certificateChain2] + * }) + * ``` + */ +export function createMockCertificateManager (options: MockCertificateManagerOptions = {}) { + return { + deleteCertificate: mock.fn(() => { + if (options.deleteCertificateError != null) { + throw options.deleteCertificateError + } + return options.deleteCertificateResult ?? { status: 'Accepted' } + }), + getInstalledCertificates: mock.fn(() => { + if (options.getInstalledCertificatesError != null) { + throw options.getInstalledCertificatesError + } + return { + certificateHashDataChain: options.getInstalledCertificatesResult ?? [], + } + }), + storeCertificate: mock.fn(() => { + if (options.storeCertificateError != null) { + throw options.storeCertificateError + } + return { success: options.storeCertificateResult ?? true } + }), + validateCertificateFormat: mock.fn((cert: string) => { + return ( + cert.includes('-----BEGIN CERTIFICATE-----') && cert.includes('-----END CERTIFICATE-----') + ) + }), + } +} + /** * Create a mock ChargingStation with certificate manager for testing. * This encapsulates the type casting pattern for ChargingStationWithCertificateManager. diff --git a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts index 9541da70..f3e8a3ec 100644 --- a/tests/charging-station/ocpp/auth/helpers/MockFactories.ts +++ b/tests/charging-station/ocpp/auth/helpers/MockFactories.ts @@ -27,11 +27,29 @@ import { OCPPVersion } from '../../../../../src/types/ocpp/OCPPVersion.js' * Centralizes mock creation to avoid duplication across test files */ +/** + * Create a mock UnifiedIdentifier for any OCPP version. + * @param ocppVersion - OCPP version (defaults to VERSION_16) + * @param value - Identifier token value (defaults to 'TEST-TAG-001') + * @param type - Identifier type enum value (defaults to ID_TAG) + * @returns Mock UnifiedIdentifier configured for specified OCPP version + */ +export const createMockIdentifier = ( + ocppVersion: OCPPVersion = OCPPVersion.VERSION_16, + value = 'TEST-TAG-001', + type: IdentifierType = IdentifierType.ID_TAG +): UnifiedIdentifier => ({ + ocppVersion, + type, + value, +}) + /** * Create a mock UnifiedIdentifier for OCPP 1.6 * @param value - Identifier token value (defaults to 'TEST-TAG-001') * @param type - Identifier type enum value (defaults to ID_TAG) * @returns Mock UnifiedIdentifier configured for OCPP 1.6 protocol + * @deprecated Use createMockIdentifier(OCPPVersion.VERSION_16, ...) for new code */ export const createMockOCPP16Identifier = ( value = 'TEST-TAG-001', @@ -47,6 +65,7 @@ export const createMockOCPP16Identifier = ( * @param value - Identifier token value (defaults to 'TEST-TAG-001') * @param type - Identifier type enum value (defaults to ID_TAG) * @returns Mock UnifiedIdentifier configured for OCPP 2.0 protocol + * @deprecated Use createMockIdentifier(OCPPVersion.VERSION_20, ...) for new code */ export const createMockOCPP20Identifier = ( value = 'TEST-TAG-001', @@ -66,84 +85,53 @@ export const createMockAuthRequest = (overrides?: Partial): AuthReq allowOffline: false, connectorId: 1, context: AuthContext.TRANSACTION_START, - identifier: createMockOCPP16Identifier(), + identifier: createMockIdentifier(), timestamp: new Date(), ...overrides, }) /** - * Create a mock successful AuthorizationResult + * Create a mock AuthorizationResult with configurable status. + * + * This factory consolidates what were previously 5 separate factories: + * - createMockAuthorizationResult (ACCEPTED) + * - createMockRejectedAuthorizationResult (INVALID) + * - createMockBlockedAuthorizationResult (BLOCKED) + * - createMockExpiredAuthorizationResult (EXPIRED) + * - createMockConcurrentTxAuthorizationResult (CONCURRENT_TX) + * + * @param status - Authorization status (defaults to ACCEPTED) * @param overrides - Partial AuthorizationResult properties to override defaults - * @returns Mock AuthorizationResult with ACCEPTED status from local list method + * @returns Mock AuthorizationResult with specified status from local list method + * @example + * ```typescript + * // Default: ACCEPTED status + * const accepted = createMockAuthorizationResult() + * + * // Rejected with INVALID status + * const rejected = createMockAuthorizationResult(AuthorizationStatus.INVALID) + * + * // Blocked with custom method + * const blocked = createMockAuthorizationResult(AuthorizationStatus.BLOCKED, { + * method: AuthenticationMethod.REMOTE_AUTHORIZATION + * }) + * + * // Expired with custom expiry date + * const expired = createMockAuthorizationResult(AuthorizationStatus.EXPIRED, { + * expiryDate: new Date(Date.now() - 1000) + * }) + * ``` */ export const createMockAuthorizationResult = ( + status: AuthorizationStatus = AuthorizationStatus.ACCEPTED, overrides?: Partial ): AuthorizationResult => ({ isOffline: false, method: AuthenticationMethod.LOCAL_LIST, - status: AuthorizationStatus.ACCEPTED, - timestamp: new Date(), - ...overrides, -}) - -/** - * Create a mock rejected AuthorizationResult - * @param overrides - Partial AuthorizationResult properties to override defaults - * @returns Mock AuthorizationResult with INVALID status from local list method - */ -export const createMockRejectedAuthorizationResult = ( - overrides?: Partial -): AuthorizationResult => ({ - isOffline: false, - method: AuthenticationMethod.LOCAL_LIST, - status: AuthorizationStatus.INVALID, - timestamp: new Date(), - ...overrides, -}) - -/** - * Create a mock blocked AuthorizationResult - * @param overrides - Partial AuthorizationResult properties to override defaults - * @returns Mock AuthorizationResult with BLOCKED status from local list method - */ -export const createMockBlockedAuthorizationResult = ( - overrides?: Partial -): AuthorizationResult => ({ - isOffline: false, - method: AuthenticationMethod.LOCAL_LIST, - status: AuthorizationStatus.BLOCKED, - timestamp: new Date(), - ...overrides, -}) - -/** - * Create a mock expired AuthorizationResult - * @param overrides - Partial AuthorizationResult properties to override defaults - * @returns Mock AuthorizationResult with EXPIRED status and past expiry date - */ -export const createMockExpiredAuthorizationResult = ( - overrides?: Partial -): AuthorizationResult => ({ - expiryDate: new Date(Date.now() - 1000), // Expired 1 second ago - isOffline: false, - method: AuthenticationMethod.LOCAL_LIST, - status: AuthorizationStatus.EXPIRED, - timestamp: new Date(), - ...overrides, -}) - -/** - * Create a mock concurrent transaction limit AuthorizationResult - * @param overrides - Partial AuthorizationResult properties to override defaults - * @returns Mock AuthorizationResult with CONCURRENT_TX status from local list method - */ -export const createMockConcurrentTxAuthorizationResult = ( - overrides?: Partial -): AuthorizationResult => ({ - isOffline: false, - method: AuthenticationMethod.LOCAL_LIST, - status: AuthorizationStatus.CONCURRENT_TX, + status, timestamp: new Date(), + // For expired status, include a default expiryDate in the past + ...(status === AuthorizationStatus.EXPIRED && { expiryDate: new Date(Date.now() - 1000) }), ...overrides, }) -- 2.43.0