]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(ocpp): extract version-specific logic from consumer layer
authorJérôme Benoit <jerome.benoit@sap.com>
Tue, 31 Mar 2026 00:30:22 +0000 (02:30 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Tue, 31 Mar 2026 00:30:22 +0000 (02:30 +0200)
Move remaining version-specific logic from charging-station/ consumer
layer into the ocpp/ component:

- Create OCPPServiceFactory.ts with createOCPPServices() factory that
  instantiates version-specific OCPP services, removing 6 version-
  specific class imports from ChargingStation.ts
- Move createBootNotificationRequest from Helpers.ts into version-
  specific buildBootNotificationRequest methods in OCPP16ServiceUtils
  and OCPP20ServiceUtils, with dispatcher in OCPPServiceOperations.ts

ChargingStation.ts no longer has any direct knowledge of OCPP version-
specific service classes. All version dispatch is handled by the ocpp/
component through factories and dispatchers.

Add unit tests for createOCPPServices (4 tests) and
buildBootNotificationRequest (6 tests) in existing test files.

src/charging-station/ChargingStation.ts
src/charging-station/Helpers.ts
src/charging-station/ocpp/1.6/OCPP16ServiceUtils.ts
src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts
src/charging-station/ocpp/OCPPServiceFactory.ts [new file with mode: 0644]
src/charging-station/ocpp/OCPPServiceOperations.ts
src/charging-station/ocpp/index.ts
tests/charging-station/ocpp/OCPPServiceFactory.test.ts [new file with mode: 0644]
tests/charging-station/ocpp/OCPPServiceOperations.test.ts

index 4dab126ac2f6c4800236a999242b98c269560f58..37bd6ab3ee0f9aaba563b75ee3589129835acccd 100644 (file)
@@ -43,7 +43,7 @@ import {
   type IncomingRequestCommand,
   MessageType,
   MeterValueMeasurand,
-  OCPPVersion,
+  type OCPPVersion,
   type OutgoingRequest,
   PowerUnits,
   RegistrationStatusEnumType,
@@ -122,7 +122,6 @@ import {
   checkEvsesConfiguration,
   checkStationInfoConnectorStatus,
   checkTemplate,
-  createBootNotificationRequest,
   createSerialNumber,
   getAmperageLimitationUnitDivider,
   getBootConnectorStatus,
@@ -146,13 +145,9 @@ import {
 } from './Helpers.js'
 import { IdTagsCache } from './IdTagsCache.js'
 import {
+  buildBootNotificationRequest,
+  createOCPPServices,
   flushQueuedTransactionMessages,
-  OCPP16IncomingRequestService,
-  OCPP16RequestService,
-  OCPP16ResponseService,
-  OCPP20IncomingRequestService,
-  OCPP20RequestService,
-  OCPP20ResponseService,
   OCPPAuthServiceFactory,
   type OCPPIncomingRequestService,
   type OCPPRequestService,
@@ -1757,7 +1752,7 @@ export class ChargingStation extends EventEmitter {
         this.configuredSupervisionUrl
       )
     }
-    const bootNotificationRequest = createBootNotificationRequest(this.stationInfo)
+    const bootNotificationRequest = buildBootNotificationRequest(this.stationInfo)
     if (bootNotificationRequest == null) {
       const errorMsg = 'Error while creating boot notification request'
       logger.error(`${this.logPrefix()} ${errorMsg}`)
@@ -2097,25 +2092,19 @@ export class ChargingStation extends EventEmitter {
 
   private initializeOcppServices (): void {
     const ocppVersion = this.stationInfo?.ocppVersion
-    switch (ocppVersion) {
-      case OCPPVersion.VERSION_16:
-        this.ocppIncomingRequestService =
-          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>()
-        this.ocppRequestService = OCPP16RequestService.getInstance<OCPP16RequestService>(
-          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
-        )
-        break
-      case OCPPVersion.VERSION_20:
-      case OCPPVersion.VERSION_201:
-        this.ocppIncomingRequestService =
-          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>()
-        this.ocppRequestService = OCPP20RequestService.getInstance<OCPP20RequestService>(
-          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
-        )
-        break
-      default:
+    if (ocppVersion == null) {
+      this.handleUnsupportedVersion(ocppVersion)
+      return
+    }
+    try {
+      const services = createOCPPServices(ocppVersion)
+      this.ocppIncomingRequestService = services.incomingRequestService
+      this.ocppRequestService = services.requestService
+    } catch (error) {
+      if (error instanceof OCPPError && error.code === ErrorType.INTERNAL_ERROR) {
         this.handleUnsupportedVersion(ocppVersion)
-        break
+      }
+      throw error
     }
   }
 
index ae29991ef597e7e9206a7587aefade6f4b330e8a..70e7abb1c21ad080c7845627a7d45340c0a8c807 100644 (file)
@@ -27,8 +27,6 @@ import { BaseError } from '../exception/index.js'
 import {
   AmpereUnits,
   AvailabilityType,
-  type BootNotificationRequest,
-  BootReasonEnumType,
   type ChargingProfile,
   ChargingProfileKindType,
   ChargingProfilePurposeType,
@@ -564,58 +562,6 @@ export const prepareConnectorStatus = (connectorStatus: ConnectorStatus): Connec
   return connectorStatus
 }
 
-export const createBootNotificationRequest = (
-  stationInfo: ChargingStationInfo,
-  bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
-): BootNotificationRequest | undefined => {
-  const ocppVersion = stationInfo.ocppVersion
-  switch (ocppVersion) {
-    case OCPPVersion.VERSION_16:
-      return {
-        chargePointModel: stationInfo.chargePointModel,
-        chargePointVendor: stationInfo.chargePointVendor,
-        ...(stationInfo.chargeBoxSerialNumber != null && {
-          chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
-        }),
-        ...(stationInfo.chargePointSerialNumber != null && {
-          chargePointSerialNumber: stationInfo.chargePointSerialNumber,
-        }),
-        ...(stationInfo.firmwareVersion != null && {
-          firmwareVersion: stationInfo.firmwareVersion,
-        }),
-        ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
-        ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
-        ...(stationInfo.meterSerialNumber != null && {
-          meterSerialNumber: stationInfo.meterSerialNumber,
-        }),
-        ...(stationInfo.meterType != null && {
-          meterType: stationInfo.meterType,
-        }),
-      } satisfies BootNotificationRequest
-    case OCPPVersion.VERSION_20:
-    case OCPPVersion.VERSION_201:
-      return {
-        chargingStation: {
-          model: stationInfo.chargePointModel,
-          vendorName: stationInfo.chargePointVendor,
-          ...(stationInfo.firmwareVersion != null && {
-            firmwareVersion: stationInfo.firmwareVersion,
-          }),
-          ...(stationInfo.chargeBoxSerialNumber != null && {
-            serialNumber: stationInfo.chargeBoxSerialNumber,
-          }),
-          ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
-            modem: {
-              ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
-              ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
-            },
-          }),
-        },
-        reason: bootReason,
-      } satisfies BootNotificationRequest
-  }
-}
-
 export const warnTemplateKeysDeprecation = (
   stationTemplate: ChargingStationTemplate,
   logPrefix: string,
index 7f6aa814746cbdcef3566abe760aead80b737b6b..230d1c8f72ac8f165369a11bc1721e2dd20f655d 100644 (file)
@@ -16,12 +16,14 @@ import {
 import { BaseError } from '../../../exception/index.js'
 import {
   ChargePointErrorCode,
+  type ChargingStationInfo,
   type ConfigurationKey,
   type GenericResponse,
   type MeterValuesRequest,
   type MeterValuesResponse,
   OCPP16AuthorizationStatus,
   type OCPP16AvailabilityType,
+  type OCPP16BootNotificationRequest,
   type OCPP16ChangeAvailabilityResponse,
   OCPP16ChargePointStatus,
   type OCPP16ChargingProfile,
@@ -105,6 +107,37 @@ export class OCPP16ServiceUtils {
     [OCPP16RequestCommand.STOP_TRANSACTION, 'StopTransaction'],
   ]
 
+  /**
+   * Builds an OCPP 1.6 BootNotification request payload from station info.
+   * @param stationInfo - Charging station information
+   * @returns Formatted OCPP 1.6 BootNotification request payload
+   */
+  public static buildBootNotificationRequest (
+    stationInfo: ChargingStationInfo
+  ): OCPP16BootNotificationRequest {
+    return {
+      chargePointModel: stationInfo.chargePointModel,
+      chargePointVendor: stationInfo.chargePointVendor,
+      ...(stationInfo.chargeBoxSerialNumber != null && {
+        chargeBoxSerialNumber: stationInfo.chargeBoxSerialNumber,
+      }),
+      ...(stationInfo.chargePointSerialNumber != null && {
+        chargePointSerialNumber: stationInfo.chargePointSerialNumber,
+      }),
+      ...(stationInfo.firmwareVersion != null && {
+        firmwareVersion: stationInfo.firmwareVersion,
+      }),
+      ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+      ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+      ...(stationInfo.meterSerialNumber != null && {
+        meterSerialNumber: stationInfo.meterSerialNumber,
+      }),
+      ...(stationInfo.meterType != null && {
+        meterType: stationInfo.meterType,
+      }),
+    } satisfies OCPP16BootNotificationRequest
+  }
+
   /**
    * @param commandParams - Status notification parameters
    * @returns Formatted OCPP 1.6 StatusNotification request payload
index 64e304dbc901d698776ef8fab937583f59d65f00..391f0ba10074a77e802497734796ae287e560a32 100644 (file)
@@ -3,9 +3,12 @@ import { secondsToMilliseconds } from 'date-fns'
 import { type ChargingStation, resetConnectorStatus } from '../../../charging-station/index.js'
 import { OCPPError } from '../../../exception/index.js'
 import {
+  BootReasonEnumType,
+  type ChargingStationInfo,
   type ConnectorStatus,
   ConnectorStatusEnum,
   ErrorType,
+  type OCPP20BootNotificationRequest,
   OCPP20ChargingStateEnumType,
   OCPP20ComponentName,
   type OCPP20ConnectorStatusEnumType,
@@ -102,6 +105,37 @@ export class OCPP20ServiceUtils {
     [OCPP20RequestCommand.TRANSACTION_EVENT, 'TransactionEvent'],
   ]
 
+  /**
+   * Builds an OCPP 2.0 BootNotification request payload from station info.
+   * @param stationInfo - Charging station information
+   * @param bootReason - Reason for the boot notification
+   * @returns Formatted OCPP 2.0 BootNotification request payload
+   */
+  public static buildBootNotificationRequest (
+    stationInfo: ChargingStationInfo,
+    bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
+  ): OCPP20BootNotificationRequest {
+    return {
+      chargingStation: {
+        model: stationInfo.chargePointModel,
+        vendorName: stationInfo.chargePointVendor,
+        ...(stationInfo.firmwareVersion != null && {
+          firmwareVersion: stationInfo.firmwareVersion,
+        }),
+        ...(stationInfo.chargeBoxSerialNumber != null && {
+          serialNumber: stationInfo.chargeBoxSerialNumber,
+        }),
+        ...((stationInfo.iccid != null || stationInfo.imsi != null) && {
+          modem: {
+            ...(stationInfo.iccid != null && { iccid: stationInfo.iccid }),
+            ...(stationInfo.imsi != null && { imsi: stationInfo.imsi }),
+          },
+        }),
+      },
+      reason: bootReason,
+    } satisfies OCPP20BootNotificationRequest
+  }
+
   /**
    * @param chargingStation - Target charging station for EVSE resolution
    * @param commandParams - Status notification parameters
diff --git a/src/charging-station/ocpp/OCPPServiceFactory.ts b/src/charging-station/ocpp/OCPPServiceFactory.ts
new file mode 100644 (file)
index 0000000..62bddd5
--- /dev/null
@@ -0,0 +1,49 @@
+import type { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
+import type { OCPPRequestService } from './OCPPRequestService.js'
+
+import { OCPPError } from '../../exception/index.js'
+import { ErrorType, OCPPVersion } from '../../types/index.js'
+import { OCPP16IncomingRequestService } from './1.6/OCPP16IncomingRequestService.js'
+import { OCPP16RequestService } from './1.6/OCPP16RequestService.js'
+import { OCPP16ResponseService } from './1.6/OCPP16ResponseService.js'
+import { OCPP20IncomingRequestService } from './2.0/OCPP20IncomingRequestService.js'
+import { OCPP20RequestService } from './2.0/OCPP20RequestService.js'
+import { OCPP20ResponseService } from './2.0/OCPP20ResponseService.js'
+
+/**
+ * Creates OCPP request and incoming request service instances for the given OCPP version.
+ * @param ocppVersion - OCPP protocol version to create services for.
+ * @returns An object containing the incoming request service and request service instances.
+ * @throws {OCPPError} If the OCPP version is not supported.
+ */
+export const createOCPPServices = (
+  ocppVersion: OCPPVersion
+): {
+  incomingRequestService: OCPPIncomingRequestService
+  requestService: OCPPRequestService
+} => {
+  switch (ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      return {
+        incomingRequestService:
+          OCPP16IncomingRequestService.getInstance<OCPP16IncomingRequestService>(),
+        requestService: OCPP16RequestService.getInstance<OCPP16RequestService>(
+          OCPP16ResponseService.getInstance<OCPP16ResponseService>()
+        ),
+      }
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      return {
+        incomingRequestService:
+          OCPP20IncomingRequestService.getInstance<OCPP20IncomingRequestService>(),
+        requestService: OCPP20RequestService.getInstance<OCPP20RequestService>(
+          OCPP20ResponseService.getInstance<OCPP20ResponseService>()
+        ),
+      }
+    default:
+      throw new OCPPError(
+        ErrorType.INTERNAL_ERROR,
+        `Unsupported OCPP version '${ocppVersion as string}'`
+      )
+  }
+}
index 074d5e869a6f255fc839e248d64bc29a7b760130..57db83dfcfa088152acf3ea96bc634c6ff0d1558 100644 (file)
@@ -1,9 +1,11 @@
-import type { StopTransactionReason } from '../../types/index.js'
+import type { BootReasonEnumType, StopTransactionReason } from '../../types/index.js'
 
 import { type ChargingStation } from '../../charging-station/index.js'
 import { OCPPError } from '../../exception/index.js'
 import {
   AuthorizationStatus,
+  type BootNotificationRequest,
+  type ChargingStationInfo,
   ErrorType,
   OCPP20AuthorizationStatusEnumType,
   OCPP20IdTokenEnumType,
@@ -244,3 +246,24 @@ export const flushQueuedTransactionMessages = async (
       break
   }
 }
+
+/**
+ * Builds an OCPP BootNotification request using the appropriate version-specific handler.
+ * @param stationInfo - Charging station information
+ * @param bootReason - Optional boot reason (OCPP 2.0 only)
+ * @returns The BootNotification request payload, or undefined if the OCPP version is unsupported
+ */
+export const buildBootNotificationRequest = (
+  stationInfo: ChargingStationInfo,
+  bootReason?: BootReasonEnumType
+): BootNotificationRequest | undefined => {
+  switch (stationInfo.ocppVersion) {
+    case OCPPVersion.VERSION_16:
+      return OCPP16ServiceUtils.buildBootNotificationRequest(stationInfo)
+    case OCPPVersion.VERSION_20:
+    case OCPPVersion.VERSION_201:
+      return OCPP20ServiceUtils.buildBootNotificationRequest(stationInfo, bootReason)
+    default:
+      return undefined
+  }
+}
index 60d32f077b333492d88ab8e6afbbefb4c48cba92..8e3bc7aabcbace929d0769ffee44dd854c63ddd6 100644 (file)
@@ -11,7 +11,9 @@ export { OCPPAuthServiceFactory } from './auth/index.js'
 export { isIdTagAuthorized } from './IdTagAuthorization.js'
 export { OCPPIncomingRequestService } from './OCPPIncomingRequestService.js'
 export { OCPPRequestService } from './OCPPRequestService.js'
+export { createOCPPServices } from './OCPPServiceFactory.js'
 export {
+  buildBootNotificationRequest,
   flushQueuedTransactionMessages,
   startTransactionOnConnector,
   stopRunningTransactions,
diff --git a/tests/charging-station/ocpp/OCPPServiceFactory.test.ts b/tests/charging-station/ocpp/OCPPServiceFactory.test.ts
new file mode 100644 (file)
index 0000000..d1e1ed8
--- /dev/null
@@ -0,0 +1,57 @@
+/**
+ * @file Tests for OCPPServiceFactory
+ * @description Verifies that createOCPPServices returns correct service instances for each OCPP
+ *   version and throws OCPPError for unsupported versions.
+ */
+
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import { OCPP16IncomingRequestService } from '../../../src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.js'
+import { OCPP16RequestService } from '../../../src/charging-station/ocpp/1.6/OCPP16RequestService.js'
+import { OCPP20IncomingRequestService } from '../../../src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.js'
+import { OCPP20RequestService } from '../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js'
+import { createOCPPServices } from '../../../src/charging-station/ocpp/OCPPServiceFactory.js'
+import { OCPPError } from '../../../src/exception/index.js'
+import { OCPPVersion } from '../../../src/types/index.js'
+import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
+
+await describe('OCPPServiceFactory', async () => {
+  afterEach(() => {
+    standardCleanup()
+  })
+
+  await it('should return OCPP 1.6 services for VERSION_16', () => {
+    const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_16)
+
+    assert.ok(incomingRequestService instanceof OCPP16IncomingRequestService)
+    assert.ok(requestService instanceof OCPP16RequestService)
+  })
+
+  await it('should return OCPP 2.0 services for VERSION_20', () => {
+    const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_20)
+
+    assert.ok(incomingRequestService instanceof OCPP20IncomingRequestService)
+    assert.ok(requestService instanceof OCPP20RequestService)
+  })
+
+  await it('should return OCPP 2.0 services for VERSION_201', () => {
+    const { incomingRequestService, requestService } = createOCPPServices(OCPPVersion.VERSION_201)
+
+    assert.ok(incomingRequestService instanceof OCPP20IncomingRequestService)
+    assert.ok(requestService instanceof OCPP20RequestService)
+  })
+
+  await it('should throw OCPPError for unsupported version', () => {
+    assert.throws(
+      () => {
+        createOCPPServices('3.0' as unknown as OCPPVersion)
+      },
+      (error: unknown) => {
+        assert.ok(error instanceof OCPPError)
+        assert.ok(error.message.includes('Unsupported OCPP version'))
+        return true
+      }
+    )
+  })
+})
index 4f99f56462112d586da88f6e6287c3cec3cb3060..f83a43bdfa70fa4c05c785a77612d06384695992 100644 (file)
@@ -1,23 +1,29 @@
 /**
  * @file Tests for OCPPServiceOperations version-dispatching functions
  * @description Verifies startTransactionOnConnector, stopTransactionOnConnector,
- *              stopRunningTransactions, and flushQueuedTransactionMessages
- *              cross-version dispatchers
+ *              stopRunningTransactions, flushQueuedTransactionMessages, and
+ *              buildBootNotificationRequest cross-version dispatchers
  */
 
 import assert from 'node:assert/strict'
 import { afterEach, describe, it, mock } from 'node:test'
 
 import type { ChargingStation } from '../../../src/charging-station/index.js'
+import type { ChargingStationInfo } from '../../../src/types/index.js'
 import type { MockChargingStationOptions } from '../helpers/StationHelpers.js'
 
 import {
+  buildBootNotificationRequest,
   flushQueuedTransactionMessages,
   startTransactionOnConnector,
   stopRunningTransactions,
   stopTransactionOnConnector,
 } from '../../../src/charging-station/ocpp/OCPPServiceOperations.js'
-import { type OCPP20TransactionEventRequest, OCPPVersion } from '../../../src/types/index.js'
+import {
+  BootReasonEnumType,
+  type OCPP20TransactionEventRequest,
+  OCPPVersion,
+} from '../../../src/types/index.js'
 import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js'
 import { createMockChargingStation } from '../ChargingStationTestUtils.js'
 
@@ -306,4 +312,139 @@ await describe('OCPPServiceOperations', async () => {
       assert.strictEqual(connectorStatus.transactionEventQueue.length, 0)
     })
   })
+
+  await describe('buildBootNotificationRequest', async () => {
+    await describe('OCPP 1.6', async () => {
+      await it('should build OCPP 1.6 boot notification with required fields', () => {
+        const stationInfo = {
+          chargePointModel: 'TestModel',
+          chargePointVendor: 'TestVendor',
+          ocppVersion: OCPPVersion.VERSION_16,
+        } as unknown as ChargingStationInfo
+
+        const result = buildBootNotificationRequest(stationInfo)
+
+        assert.notStrictEqual(result, undefined)
+        assert.deepStrictEqual(result, {
+          chargePointModel: 'TestModel',
+          chargePointVendor: 'TestVendor',
+        })
+      })
+
+      await it('should build OCPP 1.6 boot notification with optional fields', () => {
+        // Arrange
+        const stationInfo = {
+          chargeBoxSerialNumber: 'CB-001',
+          chargePointModel: 'TestModel',
+          chargePointSerialNumber: 'CP-001',
+          chargePointVendor: 'TestVendor',
+          firmwareVersion: '1.0.0',
+          iccid: '8901234567890',
+          imsi: '310150123456789',
+          meterSerialNumber: 'M-001',
+          meterType: 'ACMeter',
+          ocppVersion: OCPPVersion.VERSION_16,
+        } as unknown as ChargingStationInfo
+
+        // Act
+        const result = buildBootNotificationRequest(stationInfo)
+
+        // Assert
+        assert.deepStrictEqual(result, {
+          chargeBoxSerialNumber: 'CB-001',
+          chargePointModel: 'TestModel',
+          chargePointSerialNumber: 'CP-001',
+          chargePointVendor: 'TestVendor',
+          firmwareVersion: '1.0.0',
+          iccid: '8901234567890',
+          imsi: '310150123456789',
+          meterSerialNumber: 'M-001',
+          meterType: 'ACMeter',
+        })
+      })
+    })
+
+    await describe('OCPP 2.0', async () => {
+      await it('should build OCPP 2.0 boot notification with required fields', () => {
+        const stationInfo = {
+          chargePointModel: 'TestModel',
+          chargePointVendor: 'TestVendor',
+          ocppVersion: OCPPVersion.VERSION_20,
+        } as unknown as ChargingStationInfo
+
+        const result = buildBootNotificationRequest(stationInfo)
+
+        assert.notStrictEqual(result, undefined)
+        assert.deepStrictEqual(result, {
+          chargingStation: {
+            model: 'TestModel',
+            vendorName: 'TestVendor',
+          },
+          reason: BootReasonEnumType.PowerUp,
+        })
+      })
+
+      await it('should build OCPP 2.0 boot notification with optional fields and modem', () => {
+        // Arrange
+        const stationInfo = {
+          chargeBoxSerialNumber: 'CB-001',
+          chargePointModel: 'TestModel',
+          chargePointVendor: 'TestVendor',
+          firmwareVersion: '2.0.0',
+          iccid: '8901234567890',
+          imsi: '310150123456789',
+          ocppVersion: OCPPVersion.VERSION_201,
+        } as unknown as ChargingStationInfo
+
+        // Act
+        const result = buildBootNotificationRequest(stationInfo)
+
+        // Assert
+        assert.deepStrictEqual(result, {
+          chargingStation: {
+            firmwareVersion: '2.0.0',
+            model: 'TestModel',
+            modem: {
+              iccid: '8901234567890',
+              imsi: '310150123456789',
+            },
+            serialNumber: 'CB-001',
+            vendorName: 'TestVendor',
+          },
+          reason: BootReasonEnumType.PowerUp,
+        })
+      })
+
+      await it('should build OCPP 2.0 boot notification with custom boot reason', () => {
+        const stationInfo = {
+          chargePointModel: 'TestModel',
+          chargePointVendor: 'TestVendor',
+          ocppVersion: OCPPVersion.VERSION_20,
+        } as unknown as ChargingStationInfo
+
+        const result = buildBootNotificationRequest(stationInfo, BootReasonEnumType.RemoteReset)
+
+        assert.notStrictEqual(result, undefined)
+        assert.deepStrictEqual(result, {
+          chargingStation: {
+            model: 'TestModel',
+            vendorName: 'TestVendor',
+          },
+          reason: BootReasonEnumType.RemoteReset,
+        })
+      })
+    })
+
+    await it('should return undefined for unsupported version', () => {
+      const stationInfo = {
+        chargePointModel: 'TestModel',
+        chargePointVendor: 'TestVendor',
+        ocppVersion: '3.0',
+      } as unknown as ChargingStationInfo
+
+      const result = buildBootNotificationRequest(stationInfo)
+
+      assert.strictEqual(result, undefined)
+    })
+  })
 })