]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp2): consolidate dual-path request architecture into single path
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 14:16:54 +0000 (15:16 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 18 Mar 2026 14:16:54 +0000 (15:16 +0100)
Remove 8 unused dedicated request methods (requestFirmwareStatusNotification,
requestGet15118EVCertificate, requestGetCertificateStatus, etc.) that bypassed
buildRequestPayload via direct sendMessage calls. All production callers
already used requestHandler exclusively, making these methods dead code.

Move SignCertificate CSR generation logic into buildRequestPayload, making
it the authoritative payload construction layer — symmetric with OCPP 1.6.
This also adds isRequestCommandSupported check and AJV schema validation
to the SignCertificate flow that the dedicated method bypassed.

Refactor 6 test files to test through requestHandler (production path)
instead of the removed dedicated methods.

src/charging-station/ocpp/2.0/OCPP20RequestService.ts
src/charging-station/ocpp/2.0/__testable__/OCPP20RequestServiceTestable.ts
tests/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-FirmwareStatusNotification.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-ISO15118.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-LogStatusNotification.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-MeterValues.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-SecurityEventNotification.test.ts
tests/charging-station/ocpp/2.0/OCPP20RequestService-SignCertificate.test.ts

index 308e2740e9230197af74277fb03158c07c2b6cd0..21ff7b6bf60546beb7b7719351e241d94fd53dc1 100644 (file)
@@ -5,34 +5,14 @@ import type { OCPPResponseService } from '../OCPPResponseService.js'
 
 import { OCPPError } from '../../../exception/index.js'
 import {
-  type CertificateActionEnumType,
   type CertificateSigningUseEnumType,
   ErrorType,
   type JsonObject,
   type JsonType,
-  type OCPP20FirmwareStatusEnumType,
-  type OCPP20FirmwareStatusNotificationRequest,
-  type OCPP20FirmwareStatusNotificationResponse,
-  type OCPP20Get15118EVCertificateRequest,
-  type OCPP20Get15118EVCertificateResponse,
-  type OCPP20GetCertificateStatusRequest,
-  type OCPP20GetCertificateStatusResponse,
-  type OCPP20LogStatusNotificationRequest,
-  type OCPP20LogStatusNotificationResponse,
-  type OCPP20MeterValue,
-  type OCPP20MeterValuesRequest,
-  type OCPP20MeterValuesResponse,
-  type OCPP20NotifyCustomerInformationRequest,
-  type OCPP20NotifyCustomerInformationResponse,
   OCPP20RequestCommand,
-  type OCPP20SecurityEventNotificationRequest,
-  type OCPP20SecurityEventNotificationResponse,
   type OCPP20SignCertificateRequest,
-  type OCPP20SignCertificateResponse,
   OCPPVersion,
-  type OCSPRequestDataType,
   type RequestParams,
-  type UploadLogStatusEnumType,
 } from '../../../types/index.js'
 import { generateUUID, logger } from '../../../utils/index.js'
 import { OCPPRequestService } from '../OCPPRequestService.js'
@@ -85,151 +65,6 @@ export class OCPP20RequestService extends OCPPRequestService {
     this.buildRequestPayload = this.buildRequestPayload.bind(this)
   }
 
-  /**
-   * Send a FirmwareStatusNotification to the CSMS.
-   *
-   * Notifies the CSMS about the progress of a firmware update on the charging station.
-   * Per OCPP 2.0.1 use case J01, the CS sends firmware status updates during the
-   * download, verification, and installation phases of a firmware update.
-   * The response is an empty object — the CSMS acknowledges receipt without data.
-   * @param chargingStation - The charging station reporting the firmware status
-   * @param status - Current firmware update status (e.g., Downloading, Installed)
-   * @param requestId - The request ID from the original UpdateFirmware request
-   * @returns Promise resolving to the empty CSMS acknowledgement response
-   */
-  public async requestFirmwareStatusNotification (
-    chargingStation: ChargingStation,
-    status: OCPP20FirmwareStatusEnumType,
-    requestId?: number
-  ): Promise<OCPP20FirmwareStatusNotificationResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestFirmwareStatusNotification: Sending FirmwareStatusNotification with status '${status}'`
-    )
-
-    const requestPayload: OCPP20FirmwareStatusNotificationRequest = {
-      status,
-      ...(requestId !== undefined && { requestId }),
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestFirmwareStatusNotification: Sending FirmwareStatusNotification request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.FIRMWARE_STATUS_NOTIFICATION
-    )) as OCPP20FirmwareStatusNotificationResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestFirmwareStatusNotification: Received response`
-    )
-
-    return response
-  }
-
-  /**
-   * Request an ISO 15118 EV certificate from the CSMS.
-   *
-   * Forwards an EXI-encoded certificate request from the EV to the CSMS.
-   * The EXI payload is passed through unmodified (base64 string) without
-   * any decoding or validation - the CSMS is responsible for processing it.
-   *
-   * This is used during ISO 15118 Plug & Charge flows when the EV requests
-   * certificate installation or update from the Mobility Operator (MO).
-   * @param chargingStation - The charging station forwarding the request
-   * @param iso15118SchemaVersion - Schema version identifier (e.g., 'urn:iso:15118:2:2013:MsgDef')
-   * @param action - The certificate action type (Install or Update)
-   * @param exiRequest - Base64-encoded EXI request from the EV (passed through unchanged)
-   * @returns Promise resolving to the CSMS response with EXI-encoded certificate data
-   */
-  public async requestGet15118EVCertificate (
-    chargingStation: ChargingStation,
-    iso15118SchemaVersion: string,
-    action: CertificateActionEnumType,
-    exiRequest: string
-  ): Promise<OCPP20Get15118EVCertificateResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGet15118EVCertificate: Requesting ISO 15118 EV certificate`
-    )
-
-    const requestPayload: OCPP20Get15118EVCertificateRequest = {
-      action,
-      exiRequest,
-      iso15118SchemaVersion,
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGet15118EVCertificate: Sending Get15118EVCertificate request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.GET_15118_EV_CERTIFICATE
-    )) as OCPP20Get15118EVCertificateResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGet15118EVCertificate: Received response with status '${response.status}'`
-    )
-
-    return response
-  }
-
-  /**
-   * Request OCSP certificate status from the CSMS.
-   *
-   * Sends an OCSP (Online Certificate Status Protocol) request to the CSMS
-   * to check the revocation status of a certificate. The CSMS will return
-   * the OCSP response data which can be used to verify certificate validity.
-   *
-   * This is used to validate certificates during ISO 15118 communication
-   * before accepting them for charging authorization.
-   *
-   * **Simulator limitation**: This is a stub implementation — no real OCSP network calls are
-   * made. The method forwards the OCSP request data to the CSMS, which provides the certificate
-   * revocation status in its response. In a production charging station, the CS or CSMS would
-   * contact an external OCSP responder to verify certificate validity in real time. Full OCSP
-   * integration would require external OCSP responder configuration and network access.
-   * @param chargingStation - The charging station requesting the status
-   * @param ocspRequestData - OCSP request data including certificate hash and responder URL
-   * @returns Promise resolving to the CSMS response with OCSP result
-   */
-  public async requestGetCertificateStatus (
-    chargingStation: ChargingStation,
-    ocspRequestData: OCSPRequestDataType
-  ): Promise<OCPP20GetCertificateStatusResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGetCertificateStatus: Requesting certificate status`
-    )
-
-    const requestPayload: OCPP20GetCertificateStatusRequest = {
-      ocspRequestData,
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGetCertificateStatus: Sending GetCertificateStatus request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.GET_CERTIFICATE_STATUS
-    )) as OCPP20GetCertificateStatusResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestGetCertificateStatus: Received response with status '${response.status}'`
-    )
-
-    return response
-  }
-
   /**
    * Handles OCPP 2.0.1 request processing with enhanced validation and comprehensive error handling
    *
@@ -302,261 +137,6 @@ export class OCPP20RequestService extends OCPPRequestService {
     throw new OCPPError(ErrorType.NOT_SUPPORTED, errorMsg, commandName, commandParams)
   }
 
-  /**
-   * Send a LogStatusNotification to the CSMS.
-   *
-   * Notifies the CSMS about the progress of a log upload on the charging station.
-   * Per OCPP 2.0.1 use case M04, the CS sends log upload status updates during
-   * the upload process. The response is an empty object — the CSMS acknowledges
-   * receipt without data.
-   * @param chargingStation - The charging station reporting the log upload status
-   * @param status - Current log upload status (e.g., Uploading, Uploaded)
-   * @param requestId - The request ID from the original GetLog request
-   * @returns Promise resolving to the empty CSMS acknowledgement response
-   */
-  public async requestLogStatusNotification (
-    chargingStation: ChargingStation,
-    status: UploadLogStatusEnumType,
-    requestId?: number
-  ): Promise<OCPP20LogStatusNotificationResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestLogStatusNotification: Sending LogStatusNotification with status '${status}'`
-    )
-
-    const requestPayload: OCPP20LogStatusNotificationRequest = {
-      status,
-      ...(requestId !== undefined && { requestId }),
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestLogStatusNotification: Sending LogStatusNotification request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.LOG_STATUS_NOTIFICATION
-    )) as OCPP20LogStatusNotificationResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestLogStatusNotification: Received response`
-    )
-
-    return response
-  }
-
-  /**
-   * Send MeterValues to the CSMS.
-   *
-   * Reports meter values for a specific EVSE to the CSMS outside of a transaction context.
-   * Per OCPP 2.0.1, the charging station may send sampled meter values (e.g., energy, power,
-   * voltage, current) at configured intervals or upon trigger. Each meter value contains
-   * one or more sampled values all taken at the same point in time.
-   * The response is an empty object — the CSMS acknowledges receipt without data.
-   * @param chargingStation - The charging station reporting the meter values
-   * @param evseId - The EVSE identifier (0 for main power meter, >0 for specific EVSE)
-   * @param meterValue - Array of meter value objects, each containing timestamped sampled values
-   * @returns Promise resolving to the empty CSMS acknowledgement response
-   */
-  public async requestMeterValues (
-    chargingStation: ChargingStation,
-    evseId: number,
-    meterValue: OCPP20MeterValue[]
-  ): Promise<OCPP20MeterValuesResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestMeterValues: Sending MeterValues for EVSE ${evseId.toString()}`
-    )
-
-    const requestPayload: OCPP20MeterValuesRequest = {
-      evseId,
-      meterValue,
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestMeterValues: Sending MeterValues request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.METER_VALUES
-    )) as OCPP20MeterValuesResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestMeterValues: Received response`
-    )
-
-    return response
-  }
-
-  /**
-   * Send NotifyCustomerInformation to the CSMS.
-   *
-   * Notifies the CSMS about customer information availability.
-   * For the simulator, this sends empty customer data as no real customer
-   * information is stored (GDPR compliance).
-   * @param chargingStation - The charging station sending the notification
-   * @param requestId - The request ID from the original CustomerInformation request
-   * @param data - Customer information data (empty string for simulator)
-   * @param seqNo - Sequence number for the notification
-   * @param generatedAt - Timestamp when the data was generated
-   * @param tbc - To be continued flag (false for simulator)
-   * @returns Promise resolving when the notification is sent
-   */
-  public async requestNotifyCustomerInformation (
-    chargingStation: ChargingStation,
-    requestId: number,
-    data: string,
-    seqNo: number,
-    generatedAt: Date,
-    tbc: boolean
-  ): Promise<OCPP20NotifyCustomerInformationResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestNotifyCustomerInformation: Sending NotifyCustomerInformation`
-    )
-
-    const requestPayload: OCPP20NotifyCustomerInformationRequest = {
-      data,
-      generatedAt,
-      requestId,
-      seqNo,
-      tbc,
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestNotifyCustomerInformation: Sending NotifyCustomerInformation request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.NOTIFY_CUSTOMER_INFORMATION
-    )) as OCPP20NotifyCustomerInformationResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestNotifyCustomerInformation: Received response`
-    )
-
-    return response
-  }
-
-  /**
-   * Send a SecurityEventNotification to the CSMS.
-   *
-   * Notifies the CSMS about a security event that occurred at the charging station.
-   * Per OCPP 2.0.1 use case A04, the CS sends security events (e.g., tamper detection,
-   * firmware validation failure, invalid certificate) to keep the CSMS informed.
-   * The response is an empty object — the CSMS acknowledges receipt without data.
-   * @param chargingStation - The charging station reporting the security event
-   * @param type - Type of the security event (from the Security events list, max 50 chars)
-   * @param timestamp - Date and time at which the event occurred
-   * @param techInfo - Optional additional technical information about the event (max 255 chars)
-   * @returns Promise resolving to the empty CSMS acknowledgement response
-   */
-  public async requestSecurityEventNotification (
-    chargingStation: ChargingStation,
-    type: string,
-    timestamp: Date,
-    techInfo?: string
-  ): Promise<OCPP20SecurityEventNotificationResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSecurityEventNotification: Sending SecurityEventNotification`
-    )
-
-    const requestPayload: OCPP20SecurityEventNotificationRequest = {
-      timestamp,
-      type,
-      ...(techInfo !== undefined && { techInfo }),
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSecurityEventNotification: Sending SecurityEventNotification request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION
-    )) as OCPP20SecurityEventNotificationResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSecurityEventNotification: Received response`
-    )
-
-    return response
-  }
-
-  /**
-   * Request certificate signing from the CSMS.
-   *
-   * Generates a PKCS#10 Certificate Signing Request (RFC 2986) with a real RSA 2048-bit
-   * key pair and SHA-256 signature, then sends it to the CSMS for signing per A02.FR.02.
-   * Supports both ChargingStationCertificate and V2GCertificate types.
-   * @param chargingStation - The charging station requesting the certificate
-   * @param certificateType - Optional certificate type (ChargingStationCertificate or V2GCertificate)
-   * @returns Promise resolving to the CSMS response with Accepted or Rejected status
-   * @throws {OCPPError} When CSR generation fails
-   */
-  public async requestSignCertificate (
-    chargingStation: ChargingStation,
-    certificateType?: CertificateSigningUseEnumType
-  ): Promise<OCPP20SignCertificateResponse> {
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSignCertificate: Requesting certificate signing`
-    )
-
-    let csr: string
-    try {
-      const configKey = chargingStation.ocppConfiguration?.configurationKey?.find(
-        key => key.key === 'SecurityCtrlr.OrganizationName'
-      )
-      const orgName = configKey?.value ?? 'Unknown'
-      const stationId = chargingStation.stationInfo?.chargingStationId ?? 'Unknown'
-
-      csr = generatePkcs10Csr(stationId, orgName)
-    } catch (error) {
-      const errorMsg = `Failed to generate CSR: ${error instanceof Error ? error.message : 'Unknown error'}`
-      logger.error(
-        `${chargingStation.logPrefix()} ${moduleName}.requestSignCertificate: ${errorMsg}`
-      )
-      throw new OCPPError(ErrorType.INTERNAL_ERROR, errorMsg, OCPP20RequestCommand.SIGN_CERTIFICATE)
-    }
-
-    const requestPayload: OCPP20SignCertificateRequest = {
-      csr,
-    }
-
-    if (certificateType != null) {
-      requestPayload.certificateType = certificateType
-    }
-
-    const messageId = generateUUID()
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSignCertificate: Sending SignCertificate request with message ID '${messageId}'`
-    )
-
-    const response = (await this.sendMessage(
-      chargingStation,
-      messageId,
-      requestPayload,
-      OCPP20RequestCommand.SIGN_CERTIFICATE
-    )) as OCPP20SignCertificateResponse
-
-    logger.debug(
-      `${chargingStation.logPrefix()} ${moduleName}.requestSignCertificate: Received response with status '${response.status}'`
-    )
-
-    return response
-  }
-
   // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
   private buildRequestPayload<Request extends JsonType>(
     chargingStation: ChargingStation,
@@ -578,12 +158,44 @@ export class OCPP20RequestService extends OCPPRequestService {
       case OCPP20RequestCommand.NOTIFY_CUSTOMER_INFORMATION:
       case OCPP20RequestCommand.NOTIFY_REPORT:
       case OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION:
-      case OCPP20RequestCommand.SIGN_CERTIFICATE:
       case OCPP20RequestCommand.STATUS_NOTIFICATION:
       case OCPP20RequestCommand.TRANSACTION_EVENT:
         return commandParams as unknown as Request
       case OCPP20RequestCommand.HEARTBEAT:
         return OCPP20Constants.OCPP_RESPONSE_EMPTY as unknown as Request
+      case OCPP20RequestCommand.SIGN_CERTIFICATE: {
+        let csr: string
+        try {
+          const configKey = chargingStation.ocppConfiguration?.configurationKey?.find(
+            key => key.key === 'SecurityCtrlr.OrganizationName'
+          )
+          const orgName = configKey?.value ?? 'Unknown'
+          const stationId = chargingStation.stationInfo?.chargingStationId ?? 'Unknown'
+
+          csr = generatePkcs10Csr(stationId, orgName)
+        } catch (error) {
+          const errorMsg = `Failed to generate CSR: ${error instanceof Error ? error.message : 'Unknown error'}`
+          logger.error(
+            `${chargingStation.logPrefix()} ${moduleName}.buildRequestPayload: ${errorMsg}`
+          )
+          throw new OCPPError(
+            ErrorType.INTERNAL_ERROR,
+            errorMsg,
+            OCPP20RequestCommand.SIGN_CERTIFICATE
+          )
+        }
+
+        const certificateType = (commandParams as JsonObject | undefined)?.certificateType as
+          | CertificateSigningUseEnumType
+          | undefined
+
+        const requestPayload: OCPP20SignCertificateRequest = {
+          csr,
+          ...(certificateType != null && { certificateType }),
+        }
+
+        return requestPayload as unknown as Request
+      }
       default: {
         // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
         const errorMsg = `Unsupported OCPP command ${commandName as string} for payload building`
index a21ac2687c2a0d20d4f35c849ae82d16ee20143d..60ddd64045a1ec149fd7fabcee7fab4c2ce30e70 100644 (file)
@@ -3,7 +3,7 @@
  *
  * This module provides type-safe testing utilities for OCPP20RequestService.
  * It enables mocking of the protected sendMessage method and provides access
- * to public request methods for testing ISO 15118 certificate and OCSP flows.
+ * to buildRequestPayload and requestHandler for testing the single-path architecture.
  * @example
  * ```typescript
  * import {
  * } from './__testable__/OCPP20RequestServiceTestable.js'
  *
  * const { sendMessageMock, service } = createTestableRequestService({
- *   sendMessageResponse: { status: Iso15118EVCertificateStatusEnumType.Accepted }
+ *   sendMessageResponse: { status: GenericStatus.Accepted }
  * })
- * const response = await service.requestGet15118EVCertificate(station, schema, action, exi)
+ * const response = await service.requestHandler(station, OCPP20RequestCommand.HEARTBEAT)
  * expect(sendMessageMock.mock.calls.length).toBe(1)
  * ```
  */
 
 import { mock } from 'node:test'
 
-import type {
-  CertificateActionEnumType,
-  CertificateSigningUseEnumType,
-  JsonType,
-  OCPP20FirmwareStatusEnumType,
-  OCPP20FirmwareStatusNotificationResponse,
-  OCPP20Get15118EVCertificateResponse,
-  OCPP20GetCertificateStatusResponse,
-  OCPP20LogStatusNotificationResponse,
-  OCPP20MeterValue,
-  OCPP20MeterValuesResponse,
-  OCPP20RequestCommand,
-  OCPP20SecurityEventNotificationResponse,
-  OCPP20SignCertificateResponse,
-  OCSPRequestDataType,
-  RequestParams,
-  UploadLogStatusEnumType,
-} from '../../../../types/index.js'
+import type { JsonType, OCPP20RequestCommand, RequestParams } from '../../../../types/index.js'
 import type { ChargingStation } from '../../../index.js'
 
 import { OCPP20RequestService } from '../OCPP20RequestService.js'
@@ -72,93 +55,25 @@ export interface SendMessageMock {
  * Interface exposing OCPP20RequestService methods for testing.
  */
 export interface TestableOCPP20RequestService {
-  /**
-   * Build request payload for OCPP 2.0 commands.
-   * Used internally to construct command-specific payloads.
-   */
   buildRequestPayload: (
     chargingStation: ChargingStation,
     commandName: OCPP20RequestCommand,
     commandParams?: JsonType
   ) => JsonType
 
-  /**
-   * Send a FirmwareStatusNotification to the CSMS.
-   * Reports firmware update progress to the CSMS.
-   */
-  requestFirmwareStatusNotification: (
+  // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+  requestHandler: <RequestType extends JsonType, ResponseType extends JsonType>(
     chargingStation: ChargingStation,
-    status: OCPP20FirmwareStatusEnumType,
-    requestId?: number
-  ) => Promise<OCPP20FirmwareStatusNotificationResponse>
-
-  /**
-   * Request an ISO 15118 EV certificate from the CSMS.
-   * Forwards EXI-encoded certificate request from EV to CSMS.
-   */
-  requestGet15118EVCertificate: (
-    chargingStation: ChargingStation,
-    iso15118SchemaVersion: string,
-    action: CertificateActionEnumType,
-    exiRequest: string
-  ) => Promise<OCPP20Get15118EVCertificateResponse>
-
-  /**
-   * Request OCSP certificate status from the CSMS.
-   * Sends OCSP request to check certificate revocation status.
-   */
-  requestGetCertificateStatus: (
-    chargingStation: ChargingStation,
-    ocspRequestData: OCSPRequestDataType
-  ) => Promise<OCPP20GetCertificateStatusResponse>
-
-  /**
-   * Send a LogStatusNotification to the CSMS.
-   * Reports the status of a log upload initiated by a GetLog request.
-   */
-  requestLogStatusNotification: (
-    chargingStation: ChargingStation,
-    status: UploadLogStatusEnumType,
-    requestId?: number
-  ) => Promise<OCPP20LogStatusNotificationResponse>
-
-  /**
-   * Send MeterValues to the CSMS.
-   * Reports meter values for a specific EVSE outside of a transaction context.
-   */
-  requestMeterValues: (
-    chargingStation: ChargingStation,
-    evseId: number,
-    meterValue: OCPP20MeterValue[]
-  ) => Promise<OCPP20MeterValuesResponse>
-  /**
-   * Send a SecurityEventNotification to the CSMS.
-   * Notifies the CSMS about a security event at the charging station (A04).
-   */
-  requestSecurityEventNotification: (
-    chargingStation: ChargingStation,
-    type: string,
-    timestamp: Date,
-    techInfo?: string
-  ) => Promise<OCPP20SecurityEventNotificationResponse>
-  /**
-   * Request certificate signing from the CSMS.
-   * Generates a CSR and sends it to CSMS for signing.
-   */
-  requestSignCertificate: (
-    chargingStation: ChargingStation,
-    certificateType?: CertificateSigningUseEnumType
-  ) => Promise<OCPP20SignCertificateResponse>
+    commandName: OCPP20RequestCommand,
+    commandParams?: RequestType,
+    params?: RequestParams
+  ) => Promise<ResponseType>
 }
 
 /**
  * Configuration options for creating a testable request service
  */
 export interface TestableRequestServiceOptions<T extends JsonType = JsonType> {
-  /**
-   * Response to return from mocked sendMessage.
-   * Can be a partial response that will be spread into the result.
-   */
   sendMessageResponse?: Partial<T>
 }
 
@@ -166,13 +81,7 @@ export interface TestableRequestServiceOptions<T extends JsonType = JsonType> {
  * Result of creating a testable request service
  */
 export interface TestableRequestServiceResult {
-  /**
-   * The mock function for sendMessage with call tracking
-   */
   sendMessageMock: SendMessageMock
-  /**
-   * The testable service with mocked sendMessage
-   */
   service: TestableOCPP20RequestService
 }
 
@@ -181,33 +90,23 @@ export interface TestableRequestServiceResult {
  *
  * This factory function creates an OCPP20RequestService instance with its
  * protected sendMessage method replaced by a mock that returns configured
- * responses. This allows testing the public request methods without making
+ * responses. This allows testing through requestHandler without making
  * actual network calls.
  * @template T - The expected response type
  * @param options - Configuration for the testable service
  * @returns Object containing the testable service and sendMessage mock
  * @example
  * ```typescript
- * // Create service with mocked response
  * const { sendMessageMock, service } = createTestableRequestService({
- *   sendMessageResponse: {
- *     status: Iso15118EVCertificateStatusEnumType.Accepted,
- *     exiResponse: 'base64EncodedResponse'
- *   }
+ *   sendMessageResponse: { status: GenericStatus.Accepted }
  * })
  *
- * // Call the public method
- * const response = await service.requestGet15118EVCertificate(
+ * const response = await service.requestHandler(
  *   mockStation,
- *   schemaVersion,
- *   CertificateActionEnumType.Install,
- *   exiRequest
+ *   OCPP20RequestCommand.HEARTBEAT
  * )
  *
- * // Verify the call
  * expect(sendMessageMock.mock.calls.length).toBe(1)
- * const sentPayload = sendMessageMock.mock.calls[0].arguments[2]
- * expect(sentPayload.action).toBe(CertificateActionEnumType.Install)
  * ```
  */
 export function createTestableRequestService<T extends JsonType = JsonType> (
index 8a7eb717aac58672ff0570f8d48f75371fb226a7..198de2a71a70d102331b50ff9167d6dc033491c5 100644 (file)
@@ -575,19 +575,21 @@ await describe('ChargingStationWorkerBroadcastChannel', async () => {
       assert.deepStrictEqual(payload, commandParams)
     })
 
-    await it('should build SIGN_CERTIFICATE payload as passthrough', () => {
+    await it('should build SIGN_CERTIFICATE payload with generated CSR', () => {
       const { station, testableRequestService } = createOCPP20RequestTestContext()
       const commandParams = {
-        csr: '-----BEGIN CERTIFICATE REQUEST-----\nMIIBkTCB...\n-----END CERTIFICATE REQUEST-----',
+        certificateType: 'ChargingStationCertificate',
       }
 
       const payload = testableRequestService.buildRequestPayload(
         station,
         RequestCommand.SIGN_CERTIFICATE,
         commandParams
-      )
+      ) as { certificateType?: string; csr: string }
 
-      assert.deepStrictEqual(payload, commandParams)
+      assert.ok(payload.csr.startsWith('-----BEGIN CERTIFICATE REQUEST-----'))
+      assert.ok(payload.csr.endsWith('-----END CERTIFICATE REQUEST-----'))
+      assert.strictEqual(payload.certificateType, 'ChargingStationCertificate')
     })
   })
 
index f4eec9a78b093a7f7b305e7b54bfb0c1c02ddc3f..098438cb4d12be7c4ecf1fdae09481471016adc7 100644 (file)
@@ -56,11 +56,10 @@ await describe('J01 - FirmwareStatusNotification', async () => {
   })
 
   await it('should send FirmwareStatusNotification with Downloading status', async () => {
-    await service.requestFirmwareStatusNotification(
-      station,
-      OCPP20FirmwareStatusEnumType.Downloading,
-      42
-    )
+    await service.requestHandler(station, OCPP20RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      requestId: 42,
+      status: OCPP20FirmwareStatusEnumType.Downloading,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -76,11 +75,10 @@ await describe('J01 - FirmwareStatusNotification', async () => {
   await it('should include requestId when provided', async () => {
     const testRequestId = 99
 
-    await service.requestFirmwareStatusNotification(
-      station,
-      OCPP20FirmwareStatusEnumType.Installed,
-      testRequestId
-    )
+    await service.requestHandler(station, OCPP20RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      requestId: testRequestId,
+      status: OCPP20FirmwareStatusEnumType.Installed,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -91,11 +89,13 @@ await describe('J01 - FirmwareStatusNotification', async () => {
   })
 
   await it('should return empty response from CSMS', async () => {
-    const response = await service.requestFirmwareStatusNotification(
-      station,
-      OCPP20FirmwareStatusEnumType.Downloaded,
-      1
-    )
+    const response = await service.requestHandler<
+      OCPP20FirmwareStatusNotificationRequest,
+      OCPP20FirmwareStatusNotificationResponse
+    >(station, OCPP20RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
+      requestId: 1,
+      status: OCPP20FirmwareStatusEnumType.Downloaded,
+    })
 
     assert.notStrictEqual(response, undefined)
     assert.strictEqual(typeof response, 'object')
index 4f18032d8332ad4fe4aedaf1ce995f8de141744e..2262a3eeca0c45f99415320e6de0a326b39db81f 100644 (file)
@@ -65,12 +65,11 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
             },
           })
 
-        await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Install,
-          MOCK_EXI_REQUEST
-        )
+        await service.requestHandler(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Install,
+          exiRequest: MOCK_EXI_REQUEST,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -91,12 +90,11 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
             },
           })
 
-        await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Update,
-          MOCK_EXI_REQUEST
-        )
+        await service.requestHandler(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Update,
+          exiRequest: MOCK_EXI_REQUEST,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         const sentPayload = sendMessageMock.mock.calls[0]
           .arguments[2] as OCPP20Get15118EVCertificateRequest
@@ -114,12 +112,14 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-        const response = await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Install,
-          MOCK_EXI_REQUEST
-        )
+        const response = await service.requestHandler<
+          OCPP20Get15118EVCertificateRequest,
+          OCPP20Get15118EVCertificateResponse
+        >(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Install,
+          exiRequest: MOCK_EXI_REQUEST,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         assert.notStrictEqual(response, undefined)
         assert.strictEqual(response.status, Iso15118EVCertificateStatusEnumType.Accepted)
@@ -137,12 +137,14 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-        const response = await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Install,
-          MOCK_EXI_REQUEST
-        )
+        const response = await service.requestHandler<
+          OCPP20Get15118EVCertificateRequest,
+          OCPP20Get15118EVCertificateResponse
+        >(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Install,
+          exiRequest: MOCK_EXI_REQUEST,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         assert.notStrictEqual(response, undefined)
         assert.strictEqual(response.status, Iso15118EVCertificateStatusEnumType.Failed)
@@ -160,12 +162,11 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
             },
           })
 
-        await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Install,
-          MOCK_EXI_REQUEST
-        )
+        await service.requestHandler(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Install,
+          exiRequest: MOCK_EXI_REQUEST,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         const sentPayload = sendMessageMock.mock.calls[0]
           .arguments[2] as OCPP20Get15118EVCertificateRequest
@@ -186,16 +187,14 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
             },
           })
 
-        await service.requestGet15118EVCertificate(
-          station,
-          MOCK_ISO15118_SCHEMA_VERSION,
-          CertificateActionEnumType.Install,
-          complexBase64EXI
-        )
+        await service.requestHandler(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+          action: CertificateActionEnumType.Install,
+          exiRequest: complexBase64EXI,
+          iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+        })
 
         const sentPayload = sendMessageMock.mock.calls[0]
           .arguments[2] as OCPP20Get15118EVCertificateRequest
-        // EXI should be passed through unchanged - no decoding/encoding
         assert.strictEqual(sentPayload.exiRequest, complexBase64EXI)
       })
     })
@@ -235,7 +234,9 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
 
         const ocspRequestData = createMockOCSPRequestData()
 
-        await service.requestGetCertificateStatus(station, ocspRequestData)
+        await service.requestHandler(station, OCPP20RequestCommand.GET_CERTIFICATE_STATUS, {
+          ocspRequestData,
+        })
 
         assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -262,10 +263,12 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-        const response = await service.requestGetCertificateStatus(
-          station,
-          createMockOCSPRequestData()
-        )
+        const response = await service.requestHandler<
+          OCPP20GetCertificateStatusRequest,
+          OCPP20GetCertificateStatusResponse
+        >(station, OCPP20RequestCommand.GET_CERTIFICATE_STATUS, {
+          ocspRequestData: createMockOCSPRequestData(),
+        })
 
         assert.notStrictEqual(response, undefined)
         assert.strictEqual(response.status, GetCertificateStatusEnumType.Accepted)
@@ -282,10 +285,12 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-        const response = await service.requestGetCertificateStatus(
-          station,
-          createMockOCSPRequestData()
-        )
+        const response = await service.requestHandler<
+          OCPP20GetCertificateStatusRequest,
+          OCPP20GetCertificateStatusResponse
+        >(station, OCPP20RequestCommand.GET_CERTIFICATE_STATUS, {
+          ocspRequestData: createMockOCSPRequestData(),
+        })
 
         assert.notStrictEqual(response, undefined)
         assert.strictEqual(response.status, GetCertificateStatusEnumType.Failed)
@@ -295,8 +300,6 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
 
     await describe('Stub OCSP Response', async () => {
       await it('should handle stub OCSP response correctly', async () => {
-        // This tests that the simulator doesn't make real network calls
-        // Response is stubbed/mocked at the sendMessage level
         const stubOcspResult = 'U3R1YiBPQ1NQIFJlc3BvbnNlIERhdGE='
 
         const { sendMessageMock, service } =
@@ -307,16 +310,17 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
             },
           })
 
-        const response = await service.requestGetCertificateStatus(
-          station,
-          createMockOCSPRequestData()
-        )
+        const response = await service.requestHandler<
+          OCPP20GetCertificateStatusRequest,
+          OCPP20GetCertificateStatusResponse
+        >(station, OCPP20RequestCommand.GET_CERTIFICATE_STATUS, {
+          ocspRequestData: createMockOCSPRequestData(),
+        })
 
         assert.notStrictEqual(response, undefined)
         assert.strictEqual(response.status, GetCertificateStatusEnumType.Accepted)
         assert.strictEqual(response.ocspResult, stubOcspResult)
 
-        // Verify sendMessage was called (no real network call)
         assert.strictEqual(sendMessageMock.mock.calls.length, 1)
       })
     })
@@ -353,12 +357,11 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-      await service.requestGet15118EVCertificate(
-        station,
-        MOCK_ISO15118_SCHEMA_VERSION,
-        CertificateActionEnumType.Install,
-        MOCK_EXI_REQUEST
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE, {
+        action: CertificateActionEnumType.Install,
+        exiRequest: MOCK_EXI_REQUEST,
+        iso15118SchemaVersion: MOCK_ISO15118_SCHEMA_VERSION,
+      })
 
       const commandName = sendMessageMock.mock.calls[0].arguments[3]
       assert.strictEqual(commandName, OCPP20RequestCommand.GET_15118_EV_CERTIFICATE)
@@ -373,7 +376,9 @@ await describe('OCPP20 ISO15118 Request Service', async () => {
           },
         })
 
-      await service.requestGetCertificateStatus(station, createMockOCSPRequestData())
+      await service.requestHandler(station, OCPP20RequestCommand.GET_CERTIFICATE_STATUS, {
+        ocspRequestData: createMockOCSPRequestData(),
+      })
 
       const commandName = sendMessageMock.mock.calls[0].arguments[3]
       assert.strictEqual(commandName, OCPP20RequestCommand.GET_CERTIFICATE_STATUS)
index 3ea8f388116a243f88292fb4e5bc67b0027b2d2e..b0063a93001df0622eeebf4d3aa25a9d051be358 100644 (file)
@@ -56,7 +56,10 @@ await describe('M04 - LogStatusNotification', async () => {
   })
 
   await it('should send LogStatusNotification with Uploading status', async () => {
-    await service.requestLogStatusNotification(station, UploadLogStatusEnumType.Uploading, 42)
+    await service.requestHandler(station, OCPP20RequestCommand.LOG_STATUS_NOTIFICATION, {
+      requestId: 42,
+      status: UploadLogStatusEnumType.Uploading,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -72,11 +75,10 @@ await describe('M04 - LogStatusNotification', async () => {
   await it('should include requestId when provided', async () => {
     const testRequestId = 99
 
-    await service.requestLogStatusNotification(
-      station,
-      UploadLogStatusEnumType.Uploaded,
-      testRequestId
-    )
+    await service.requestHandler(station, OCPP20RequestCommand.LOG_STATUS_NOTIFICATION, {
+      requestId: testRequestId,
+      status: UploadLogStatusEnumType.Uploaded,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -87,11 +89,13 @@ await describe('M04 - LogStatusNotification', async () => {
   })
 
   await it('should return empty response from CSMS', async () => {
-    const response = await service.requestLogStatusNotification(
-      station,
-      UploadLogStatusEnumType.Uploading,
-      1
-    )
+    const response = await service.requestHandler<
+      OCPP20LogStatusNotificationRequest,
+      OCPP20LogStatusNotificationResponse
+    >(station, OCPP20RequestCommand.LOG_STATUS_NOTIFICATION, {
+      requestId: 1,
+      status: UploadLogStatusEnumType.Uploading,
+    })
 
     assert.notStrictEqual(response, undefined)
     assert.strictEqual(typeof response, 'object')
index 795cbb96c2ebc15ca2df067f284ffc4ee6610412..24e34af86f3a30195e526125d71a19818393b926 100644 (file)
@@ -64,7 +64,7 @@ await describe('G01 - MeterValues', async () => {
       },
     ]
 
-    await service.requestMeterValues(station, evseId, meterValue)
+    await service.requestHandler(station, OCPP20RequestCommand.METER_VALUES, { evseId, meterValue })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -88,7 +88,7 @@ await describe('G01 - MeterValues', async () => {
       },
     ]
 
-    await service.requestMeterValues(station, evseId, meterValue)
+    await service.requestHandler(station, OCPP20RequestCommand.METER_VALUES, { evseId, meterValue })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -108,8 +108,10 @@ await describe('G01 - MeterValues', async () => {
       },
     ]
 
-    // evseId 0 = main power meter
-    const response = await service.requestMeterValues(station, 0, meterValue)
+    const response = await service.requestHandler<
+      OCPP20MeterValuesRequest,
+      OCPP20MeterValuesResponse
+    >(station, OCPP20RequestCommand.METER_VALUES, { evseId: 0, meterValue })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
index 3ec59e9171c2b1a2d0588a4a667749fec5644b14..75d78fd130996a02799435aa3f2e83adf46c4b3f 100644 (file)
@@ -58,7 +58,10 @@ await describe('A04 - SecurityEventNotification', async () => {
     const testTimestamp = new Date('2024-03-15T10:30:00.000Z')
     const testType = 'FirmwareUpdated'
 
-    await service.requestSecurityEventNotification(station, testType, testTimestamp)
+    await service.requestHandler(station, OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION, {
+      timestamp: testTimestamp,
+      type: testType,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -78,7 +81,11 @@ await describe('A04 - SecurityEventNotification', async () => {
     const testType = 'TamperDetectionActivated'
     const testTechInfo = 'Enclosure opened at connector 1'
 
-    await service.requestSecurityEventNotification(station, testType, testTimestamp, testTechInfo)
+    await service.requestHandler(station, OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION, {
+      techInfo: testTechInfo,
+      timestamp: testTimestamp,
+      type: testType,
+    })
 
     assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
@@ -91,11 +98,13 @@ await describe('A04 - SecurityEventNotification', async () => {
 
   // FR: A04.FR.03
   await it('should return empty response from CSMS', async () => {
-    const response = await service.requestSecurityEventNotification(
-      station,
-      'SettingSystemTime',
-      new Date('2024-03-15T12:00:00.000Z')
-    )
+    const response = await service.requestHandler<
+      OCPP20SecurityEventNotificationRequest,
+      OCPP20SecurityEventNotificationResponse
+    >(station, OCPP20RequestCommand.SECURITY_EVENT_NOTIFICATION, {
+      timestamp: new Date('2024-03-15T12:00:00.000Z'),
+      type: 'SettingSystemTime',
+    })
 
     assert.notStrictEqual(response, undefined)
     assert.strictEqual(typeof response, 'object')
index f64490e1ca867a30ad3f735b53e9407ba11077b5..dc432bc93cb7bd811a6de684739252d02464b9f2 100644 (file)
@@ -12,6 +12,7 @@ import { createTestableRequestService } from '../../../../src/charging-station/o
 import {
   CertificateSigningUseEnumType,
   GenericStatus,
+  type JsonType,
   OCPP20RequestCommand,
   type OCPP20SignCertificateRequest,
   type OCPP20SignCertificateResponse,
@@ -41,7 +42,6 @@ await describe('I02 - SignCertificate Request', async () => {
       websocketPingInterval: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL,
     })
     station = createdStation
-    // Set up configuration with OrganizationName
     station.ocppConfiguration = {
       configurationKey: [
         { key: 'SecurityCtrlr.OrganizationName', readonly: false, value: MOCK_ORGANIZATION_NAME },
@@ -62,9 +62,10 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      const response = await service.requestSignCertificate(
+      const response = await service.requestHandler<JsonType, OCPP20SignCertificateResponse>(
         station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
+        OCPP20RequestCommand.SIGN_CERTIFICATE,
+        { certificateType: CertificateSigningUseEnumType.ChargingStationCertificate }
       )
 
       assert.notStrictEqual(response, undefined)
@@ -86,10 +87,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       assert.ok(sentPayload.csr.startsWith('-----BEGIN CERTIFICATE REQUEST-----\n'))
@@ -103,10 +103,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       assert.ok(sentPayload.csr.endsWith('\n-----END CERTIFICATE REQUEST-----'))
@@ -120,10 +119,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       const csrLines = sentPayload.csr.split('\n')
@@ -141,10 +139,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       const csrLines = sentPayload.csr.split('\n')
@@ -166,10 +163,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       const csrLines = sentPayload.csr.split('\n')
@@ -189,10 +185,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
       const csrLines = sentPayload.csr.split('\n')
@@ -211,10 +206,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
 
@@ -234,7 +228,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(station, CertificateSigningUseEnumType.V2GCertificate)
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.V2GCertificate,
+      })
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
 
@@ -250,9 +246,10 @@ await describe('I02 - SignCertificate Request', async () => {
         },
       })
 
-      const response = await service.requestSignCertificate(
+      const response = await service.requestHandler<JsonType, OCPP20SignCertificateResponse>(
         station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
+        OCPP20RequestCommand.SIGN_CERTIFICATE,
+        { certificateType: CertificateSigningUseEnumType.ChargingStationCertificate }
       )
 
       assert.notStrictEqual(response, undefined)
@@ -269,9 +266,10 @@ await describe('I02 - SignCertificate Request', async () => {
         },
       })
 
-      const response = await service.requestSignCertificate(
+      const response = await service.requestHandler<JsonType, OCPP20SignCertificateResponse>(
         station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
+        OCPP20RequestCommand.SIGN_CERTIFICATE,
+        { certificateType: CertificateSigningUseEnumType.ChargingStationCertificate }
       )
 
       assert.notStrictEqual(response, undefined)
@@ -290,12 +288,11 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(station)
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {})
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
 
       assert.notStrictEqual(sentPayload.csr, undefined)
-      // certificateType should be undefined when not specified
       assert.strictEqual(sentPayload.certificateType, undefined)
     })
   })
@@ -309,21 +306,19 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       assert.strictEqual(sendMessageMock.mock.calls.length, 1)
 
       const sentPayload = sendMessageMock.mock.calls[0].arguments[2] as OCPP20SignCertificateRequest
 
-      // Validate payload structure
       assert.strictEqual(typeof sentPayload, 'object')
       assert.notStrictEqual(sentPayload.csr, undefined)
       assert.strictEqual(typeof sentPayload.csr, 'string')
       assert.ok(sentPayload.csr.length > 0)
-      assert.ok(sentPayload.csr.length <= 5500) // Max length per schema
+      assert.ok(sentPayload.csr.length <= 5500)
     })
 
     await it('should send SIGN_CERTIFICATE command name', async () => {
@@ -334,10 +329,9 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      await service.requestSignCertificate(
-        station,
-        CertificateSigningUseEnumType.ChargingStationCertificate
-      )
+      await service.requestHandler(station, OCPP20RequestCommand.SIGN_CERTIFICATE, {
+        certificateType: CertificateSigningUseEnumType.ChargingStationCertificate,
+      })
 
       const commandName = sendMessageMock.mock.calls[0].arguments[3]
 
@@ -372,9 +366,10 @@ await describe('I02 - SignCertificate Request', async () => {
           },
         })
 
-      const response = await service.requestSignCertificate(
+      const response = await service.requestHandler<JsonType, OCPP20SignCertificateResponse>(
         stationWithoutCertManager,
-        CertificateSigningUseEnumType.ChargingStationCertificate
+        OCPP20RequestCommand.SIGN_CERTIFICATE,
+        { certificateType: CertificateSigningUseEnumType.ChargingStationCertificate }
       )
 
       assert.notStrictEqual(response, undefined)