]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(tests): consolidate duplicate mock factories
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Feb 2026 21:04:13 +0000 (22:04 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 28 Feb 2026 21:04:13 +0000 (22:04 +0100)
- 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.

tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-DeleteCertificate.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetInstalledCertificateIds.test.ts
tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-InstallCertificate.test.ts
tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts
tests/charging-station/ocpp/auth/helpers/MockFactories.ts

index ded401fa6116e6c38e5705feecb31532fb76789f..1859e14e5385ff6b0624f3b1c0a5d622f2789c6c 100644 (file)
@@ -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
index 28ca9d66f216eeef341f8a514b2a31a5856631a6..b08c11cd5b8d265e57233e0ac9bdd81ed496a1a4 100644 (file)
@@ -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()
index 494815d4d09f0f08001e8d7ff64da9c52c5fff80..7a53ea5a649b5b452baa0a080e0d9e1d284aa226 100644 (file)
@@ -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
index d396821da27d0c34f4050b08008cb8bf01e45128..a364726aee7394649b957236aa964e6e77f03872 100644 (file)
@@ -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()
index 79342b2fa1eebc016e7cdc33f794a3a45eabb17c..a2654cf6cc94ec883bcf49460801f4ef7cbc32fa 100644 (file)
@@ -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.
index 9541da7054e6ff07243a29ff4e8828dcb89e0b6d..f3e8a3eca60d0c23f13805752c25010796bcba00 100644 (file)
@@ -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<AuthRequest>): 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>
 ): 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>
-): 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>
-): 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>
-): 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>
-): 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,
 })