- 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.
*/
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'
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
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
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,
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()
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,
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
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
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()
// 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.
* 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',
* @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',
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,
})