From f7e682fd3a4cf972957a9dffb6cbc85da855a628 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 27 Mar 2026 12:31:06 +0100 Subject: [PATCH] fix: use Component.Variable[.Instance] key format for OCPP 2.0 variable persistence The previous format used bare variable names (e.g., 'Enabled'), causing collisions across components (9 components define Enabled, 8+ define Available, etc.). Now uses the spec's (Component, Variable, Instance) triplet as the persisted configuration key. - Rewrite computeConfigurationKeyName to return Component.Variable format - Remove shouldFlattenInstance (no longer needed) - Extract buildConfigKey helper in ConfigurationKeyUtils, export via barrel - Add MaxCertificateChainSize to OCPP20OptionalVariableName enum - Update all direct config key lookups with component prefix - Update keba-ocpp2 template keys to new format - Update OCPP2_PARAMETER_KEY_MAP resolved values - Replace all enum string literals in log messages with enum references - Update all tests to use new key format --- .../keba-ocpp2.station-template.json | 16 ++-- src/charging-station/ConfigurationKeyUtils.ts | 42 +++++++-- src/charging-station/index.ts | 1 + .../ocpp/2.0/OCPP20IncomingRequestService.ts | 15 +++- .../ocpp/2.0/OCPP20ResponseService.ts | 13 ++- .../ocpp/2.0/OCPP20ServiceUtils.ts | 12 ++- .../ocpp/2.0/OCPP20VariableManager.ts | 30 ++++--- .../ocpp/2.0/OCPP20VariableRegistry.ts | 7 +- src/types/ocpp/2.0/Variables.ts | 1 + .../ConfigurationKeyUtils.test.ts | 29 +++++-- ...ngRequestService-CertificateSigned.test.ts | 28 +++++- ...comingRequestService-GetBaseReport.test.ts | 10 ++- ...ncomingRequestService-SetVariables.test.ts | 23 +++-- ...20ResponseService-BootNotification.test.ts | 10 ++- .../ocpp/2.0/OCPP20TestUtils.ts | 46 +++++++--- .../ocpp/2.0/OCPP20VariableManager.test.ts | 85 ++++++++++++++----- 16 files changed, 282 insertions(+), 86 deletions(-) diff --git a/src/assets/station-templates/keba-ocpp2.station-template.json b/src/assets/station-templates/keba-ocpp2.station-template.json index dabea003..351d6117 100644 --- a/src/assets/station-templates/keba-ocpp2.station-template.json +++ b/src/assets/station-templates/keba-ocpp2.station-template.json @@ -17,42 +17,42 @@ "Configuration": { "configurationKey": [ { - "key": "TxUpdatedMeasurands", + "key": "SampledDataCtrlr.TxUpdatedMeasurands", "readonly": false, "value": "Energy.Active.Import.Register,Power.Active.Import,Current.Import,Voltage" }, { - "key": "TxUpdatedInterval", + "key": "SampledDataCtrlr.TxUpdatedInterval", "readonly": false, "value": "30" }, { - "key": "AuthorizeRemoteStart", + "key": "AuthCtrlr.AuthorizeRemoteStart", "readonly": false, "value": "false" }, { - "key": "LocalPreAuthorization", + "key": "AuthCtrlr.LocalPreAuthorization", "readonly": false, "value": "false" }, { - "key": "OfflineTxForUnknownIdEnabled", + "key": "AuthCtrlr.OfflineTxForUnknownIdEnabled", "readonly": false, "value": "false" }, { - "key": "MessageAttempts.TransactionEvent", + "key": "OCPPCommCtrlr.MessageAttempts.TransactionEvent", "readonly": false, "value": "3" }, { - "key": "MessageAttemptInterval.TransactionEvent", + "key": "OCPPCommCtrlr.MessageAttemptInterval.TransactionEvent", "readonly": false, "value": "20" }, { - "key": "WebSocketPingInterval", + "key": "ChargingStation.WebSocketPingInterval", "readonly": false, "value": "60" } diff --git a/src/charging-station/ConfigurationKeyUtils.ts b/src/charging-station/ConfigurationKeyUtils.ts index 8b08d03d..891255a2 100644 --- a/src/charging-station/ConfigurationKeyUtils.ts +++ b/src/charging-station/ConfigurationKeyUtils.ts @@ -3,11 +3,17 @@ import type { ChargingStation } from './ChargingStation.js' import { type ConfigurationKey, type ConfigurationKeyType, + OCPP20ComponentName, OCPPVersion, StandardParametersKey, } from '../types/index.js' import { logger, once } from '../utils/index.js' +export const buildConfigKey = (component: string, variable: string, instance?: string): string => { + const base = `${component}.${variable}` + return instance != null ? `${base}.${instance}` : base +} + const OCPP2_PARAMETER_KEY_MAP = new Map< ConfigurationKeyType, { @@ -17,15 +23,39 @@ const OCPP2_PARAMETER_KEY_MAP = new Map< >( ( [ - [StandardParametersKey.AuthorizeRemoteTxRequests, StandardParametersKey.AuthorizeRemoteStart], - [StandardParametersKey.ConnectionTimeOut, StandardParametersKey.EVConnectionTimeOut], + [ + StandardParametersKey.AuthorizeRemoteTxRequests, + buildConfigKey(OCPP20ComponentName.AuthCtrlr, StandardParametersKey.AuthorizeRemoteStart), + ], + [ + StandardParametersKey.ConnectionTimeOut, + buildConfigKey(OCPP20ComponentName.TxCtrlr, StandardParametersKey.EVConnectionTimeOut), + ], [ StandardParametersKey.LocalAuthorizeOffline, - StandardParametersKey.LocalAuthorizationOffline, + buildConfigKey( + OCPP20ComponentName.AuthCtrlr, + StandardParametersKey.LocalAuthorizationOffline + ), + ], + [ + StandardParametersKey.LocalPreAuthorize, + buildConfigKey(OCPP20ComponentName.AuthCtrlr, StandardParametersKey.LocalPreAuthorization), + ], + [ + StandardParametersKey.MeterValueSampleInterval, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + StandardParametersKey.TxUpdatedInterval + ), + ], + [ + StandardParametersKey.MeterValuesSampledData, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + StandardParametersKey.TxUpdatedMeasurands + ), ], - [StandardParametersKey.LocalPreAuthorize, StandardParametersKey.LocalPreAuthorization], - [StandardParametersKey.MeterValueSampleInterval, StandardParametersKey.TxUpdatedInterval], - [StandardParametersKey.MeterValuesSampledData, StandardParametersKey.TxUpdatedMeasurands], ] as [ConfigurationKeyType, ConfigurationKeyType][] ).map(([from, to]) => [ from, diff --git a/src/charging-station/index.ts b/src/charging-station/index.ts index 7a48f729..0d25e222 100644 --- a/src/charging-station/index.ts +++ b/src/charging-station/index.ts @@ -2,6 +2,7 @@ export { Bootstrap } from './Bootstrap.js' export type { ChargingStation } from './ChargingStation.js' export { addConfigurationKey, + buildConfigKey, getConfigurationKey, setConfigurationKeyValue, } from './ConfigurationKeyUtils.js' diff --git a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts index bc7e9328..7871b9f2 100644 --- a/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20IncomingRequestService.ts @@ -80,6 +80,7 @@ import { type OCPP20NotifyReportRequest, type OCPP20NotifyReportResponse, OCPP20OperationalStatusEnumType, + OCPP20OptionalVariableName, OCPP20ReasonEnumType, OCPP20RequestCommand, type OCPP20RequestStartTransactionRequest, @@ -130,7 +131,7 @@ import { truncateId, validateUUID, } from '../../../utils/index.js' -import { getConfigurationKey } from '../../ConfigurationKeyUtils.js' +import { buildConfigKey, getConfigurationKey } from '../../ConfigurationKeyUtils.js' import { getIdTagsFile, hasPendingReservation, @@ -1251,19 +1252,25 @@ export class OCPP20IncomingRequestService extends OCPPIncomingRequestService { } // A02.FR.16: Enforce MaxCertificateChainSize — reject if chain exceeds configured limit - const maxChainSizeKey = getConfigurationKey(chargingStation, 'MaxCertificateChainSize') + const maxChainSizeKey = getConfigurationKey( + chargingStation, + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20OptionalVariableName.MaxCertificateChainSize + ) + ) if (maxChainSizeKey?.value != null) { const maxChainSize = parseInt(maxChainSizeKey.value, 10) if (!isNaN(maxChainSize) && maxChainSize > 0) { const chainByteSize = Buffer.byteLength(certificateChain, 'utf8') if (chainByteSize > maxChainSize) { logger.warn( - `${chargingStation.logPrefix()} ${moduleName}.handleRequestCertificateSigned: Certificate chain size ${chainByteSize.toString()} bytes exceeds MaxCertificateChainSize ${maxChainSize.toString()} bytes` + `${chargingStation.logPrefix()} ${moduleName}.handleRequestCertificateSigned: Certificate chain size ${chainByteSize.toString()} bytes exceeds ${OCPP20OptionalVariableName.MaxCertificateChainSize as string} ${maxChainSize.toString()} bytes` ) return { status: GenericStatus.Rejected, statusInfo: { - additionalInfo: `Certificate chain size (${chainByteSize.toString()} bytes) exceeds MaxCertificateChainSize (${maxChainSize.toString()} bytes)`, + additionalInfo: `Certificate chain size (${chainByteSize.toString()} bytes) exceeds ${OCPP20OptionalVariableName.MaxCertificateChainSize as string} (${maxChainSize.toString()} bytes)`, reasonCode: ReasonCodeEnumType.InvalidCertificate, }, } diff --git a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts index c96d516d..fcc8923a 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ResponseService.ts @@ -1,6 +1,10 @@ import type { ValidateFunction } from 'ajv' -import { addConfigurationKey, type ChargingStation } from '../../../charging-station/index.js' +import { + addConfigurationKey, + buildConfigKey, + type ChargingStation, +} from '../../../charging-station/index.js' import { ChargingStationEvents, ConnectorStatusEnum, @@ -8,6 +12,7 @@ import { OCPP20AuthorizationStatusEnumType, type OCPP20AuthorizeResponse, type OCPP20BootNotificationResponse, + OCPP20ComponentName, type OCPP20DataTransferResponse, type OCPP20FirmwareStatusNotificationResponse, type OCPP20Get15118EVCertificateResponse, @@ -37,7 +42,6 @@ import { mapOCPP20TokenType, OCPPAuthServiceFactory } from '../auth/index.js' import { OCPPResponseService } from '../OCPPResponseService.js' import { sendAndSetConnectorStatus } from '../OCPPServiceUtils.js' import { OCPP20ServiceUtils } from './OCPP20ServiceUtils.js' - const moduleName = 'OCPP20ResponseService' /** @@ -199,7 +203,10 @@ export class OCPP20ResponseService extends OCPPResponseService { ) addConfigurationKey( chargingStation, - OCPP20OptionalVariableName.HeartbeatInterval, + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20OptionalVariableName.HeartbeatInterval + ), payload.interval.toString(), {}, { overwrite: true, save: true } diff --git a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts index 6936629e..7cd6adc7 100644 --- a/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts +++ b/src/charging-station/ocpp/2.0/OCPP20ServiceUtils.ts @@ -37,7 +37,7 @@ import { logger, validateIdentifierString, } from '../../../utils/index.js' -import { getConfigurationKey } from '../../ConfigurationKeyUtils.js' +import { buildConfigKey, getConfigurationKey } from '../../ConfigurationKeyUtils.js' import { buildMeterValue, OCPPServiceUtils, @@ -292,11 +292,17 @@ export class OCPP20ServiceUtils extends OCPPServiceUtils { try { const itemsCfg = getConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ItemsPerMessage + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ItemsPerMessage + ) )?.value const bytesCfg = getConfigurationKey( chargingStation, - OCPP20RequiredVariableName.BytesPerMessage + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.BytesPerMessage + ) )?.value if (itemsCfg && /^\d+$/.test(itemsCfg)) { itemsLimit = convertToIntOrNaN(itemsCfg) diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts index 0b43690b..b42fa61f 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableManager.ts @@ -23,6 +23,7 @@ import { Constants, convertToIntOrNaN, logger } from '../../../utils/index.js' import { type ChargingStation } from '../../ChargingStation.js' import { addConfigurationKey, + buildConfigKey, getConfigurationKey, setConfigurationKeyValue, } from '../../ConfigurationKeyUtils.js' @@ -44,15 +45,9 @@ const isOCPP20RequiredVariableName = (name: string): name is OCPP20RequiredVaria return Object.values(OCPP20RequiredVariableName).includes(name as OCPP20RequiredVariableName) } -const shouldFlattenInstance = (variableMetadata: VariableMetadata): boolean => { - // TODO: Generalize instance flattening via registry metadata - return variableMetadata.variable === (OCPP20RequiredVariableName.MessageAttemptInterval as string) -} -const computeConfigurationKeyName = (variableMetadata: VariableMetadata): string => { - return variableMetadata.instance != null && !shouldFlattenInstance(variableMetadata) - ? `${variableMetadata.variable}.${variableMetadata.instance}` - : variableMetadata.variable -} +const computeConfigurationKeyName = (variableMetadata: VariableMetadata): string => + buildConfigKey(variableMetadata.component, variableMetadata.variable, variableMetadata.instance) + export class OCPP20VariableManager { private static instance: null | OCPP20VariableManager = null @@ -522,12 +517,18 @@ export class OCPP20VariableManager { let valueSize: string | undefined let reportingValueSize: string | undefined if (!invalidVariables.has(valueSizeKey)) { - valueSize = getConfigurationKey(chargingStation, OCPP20RequiredVariableName.ValueSize)?.value + valueSize = getConfigurationKey( + chargingStation, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize) + )?.value } if (!invalidVariables.has(reportingValueSizeKey)) { reportingValueSize = getConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ReportingValueSize + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ReportingValueSize + ) )?.value } // Apply ValueSize first then ReportingValueSize @@ -890,13 +891,16 @@ export class OCPP20VariableManager { if (!invalidVariables.has(configurationValueSizeKey)) { configurationValueSizeRaw = getConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ConfigurationValueSize + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ConfigurationValueSize + ) )?.value } if (!invalidVariables.has(valueSizeKey)) { valueSizeRaw = getConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ValueSize + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize) )?.value } const cfgLimit = convertToIntOrNaN(configurationValueSizeRaw ?? '') diff --git a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts index 4ff7a366..9a19930d 100644 --- a/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts +++ b/src/charging-station/ocpp/2.0/OCPP20VariableRegistry.ts @@ -1967,7 +1967,10 @@ export const VARIABLE_REGISTRY: Record = { supportedAttributes: [AttributeEnumType.Actual], variable: 'Identity', }, - [buildRegistryKey(OCPP20ComponentName.SecurityCtrlr as string, 'MaxCertificateChainSize')]: { + [buildRegistryKey( + OCPP20ComponentName.SecurityCtrlr as string, + OCPP20OptionalVariableName.MaxCertificateChainSize + )]: { component: OCPP20ComponentName.SecurityCtrlr as string, dataType: DataEnumType.integer, description: @@ -1976,7 +1979,7 @@ export const VARIABLE_REGISTRY: Record = { mutability: MutabilityEnumType.ReadWrite, persistence: PersistenceEnumType.Persistent, supportedAttributes: [AttributeEnumType.Actual], - variable: 'MaxCertificateChainSize', + variable: OCPP20OptionalVariableName.MaxCertificateChainSize as string, }, [buildRegistryKey( OCPP20ComponentName.SecurityCtrlr as string, diff --git a/src/types/ocpp/2.0/Variables.ts b/src/types/ocpp/2.0/Variables.ts index 22e0b313..ee419459 100644 --- a/src/types/ocpp/2.0/Variables.ts +++ b/src/types/ocpp/2.0/Variables.ts @@ -34,6 +34,7 @@ export enum OCPP20DeviceInfoVariableName { export enum OCPP20OptionalVariableName { HeartbeatInterval = 'HeartbeatInterval', + MaxCertificateChainSize = 'MaxCertificateChainSize', WebSocketPingInterval = 'WebSocketPingInterval', } diff --git a/tests/charging-station/ConfigurationKeyUtils.test.ts b/tests/charging-station/ConfigurationKeyUtils.test.ts index ebf989e6..e9aca27e 100644 --- a/tests/charging-station/ConfigurationKeyUtils.test.ts +++ b/tests/charging-station/ConfigurationKeyUtils.test.ts @@ -9,11 +9,12 @@ import type { ChargingStationOcppConfiguration } from '../../src/types/index.js' import { addConfigurationKey, + buildConfigKey, deleteConfigurationKey, getConfigurationKey, setConfigurationKeyValue, } from '../../src/charging-station/ConfigurationKeyUtils.js' -import { OCPPVersion, StandardParametersKey } from '../../src/types/index.js' +import { OCPP20ComponentName, OCPPVersion, StandardParametersKey } from '../../src/types/index.js' import { logger } from '../../src/utils/index.js' import { standardCleanup } from '../helpers/TestLifecycleHelpers.js' import { createMockChargingStation } from './ChargingStationTestUtils.js' @@ -95,7 +96,13 @@ await describe('ConfigurationKeyUtils', async () => { if (k == null) { assert.fail('Expected configuration key to be found') } - assert.strictEqual(k.key, StandardParametersKey.TxUpdatedMeasurands) + assert.strictEqual( + k.key, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + StandardParametersKey.TxUpdatedMeasurands + ) + ) assert.strictEqual(k.value, VALUE_A) }) @@ -113,7 +120,10 @@ await describe('ConfigurationKeyUtils', async () => { if (k == null) { assert.fail('Expected configuration key to be found') } - assert.strictEqual(k.key, StandardParametersKey.EVConnectionTimeOut) + assert.strictEqual( + k.key, + buildConfigKey(OCPP20ComponentName.TxCtrlr, StandardParametersKey.EVConnectionTimeOut) + ) assert.strictEqual(k.value, '30') }) @@ -384,7 +394,10 @@ await describe('ConfigurationKeyUtils', async () => { if (k == null) { assert.fail('Expected configuration key to be found') } - assert.strictEqual(k.key, StandardParametersKey.AuthorizeRemoteStart) + assert.strictEqual( + k.key, + buildConfigKey(OCPP20ComponentName.AuthCtrlr, StandardParametersKey.AuthorizeRemoteStart) + ) assert.strictEqual(k.value, 'false') }) @@ -402,7 +415,13 @@ await describe('ConfigurationKeyUtils', async () => { if (k == null) { assert.fail('Expected configuration key to be found') } - assert.strictEqual(k.key, StandardParametersKey.TxUpdatedInterval) + assert.strictEqual( + k.key, + buildConfigKey( + OCPP20ComponentName.SampledDataCtrlr, + StandardParametersKey.TxUpdatedInterval + ) + ) assert.strictEqual(k.value, '60') }) }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts index 9ce65d53..dd29f8cd 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-CertificateSigned.test.ts @@ -9,7 +9,7 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { ChargingStation } from '../../../../src/charging-station/index.js' import type { ChargingStationWithCertificateManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20CertificateManager.js' -import { addConfigurationKey } from '../../../../src/charging-station/ConfigurationKeyUtils.js' +import { addConfigurationKey, buildConfigKey } from '../../../../src/charging-station/index.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 { @@ -17,6 +17,8 @@ import { GenericStatus, type OCPP20CertificateSignedRequest, type OCPP20CertificateSignedResponse, + OCPP20ComponentName, + OCPP20OptionalVariableName, OCPP20RequestCommand, OCPPVersion, ReasonCodeEnumType, @@ -306,7 +308,14 @@ await describe('I04 - CertificateSigned', async () => { storeCertificateResult: true, }) - addConfigurationKey(station, 'MaxCertificateChainSize', '10') + addConfigurationKey( + station, + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20OptionalVariableName.MaxCertificateChainSize + ), + '10' + ) const request: OCPP20CertificateSignedRequest = { certificateChain: VALID_PEM_CERTIFICATE, @@ -321,7 +330,11 @@ await describe('I04 - CertificateSigned', async () => { assert.strictEqual(response.status, GenericStatus.Rejected) assert.notStrictEqual(response.statusInfo, undefined) assert.strictEqual(response.statusInfo?.reasonCode, 'InvalidCertificate') - assert.ok(response.statusInfo.additionalInfo?.includes('MaxCertificateChainSize')) + assert.ok( + response.statusInfo.additionalInfo?.includes( + OCPP20OptionalVariableName.MaxCertificateChainSize as string + ) + ) }) await it('should accept certificate chain within MaxCertificateChainSize', async () => { @@ -330,7 +343,14 @@ await describe('I04 - CertificateSigned', async () => { storeCertificateResult: true, }) - addConfigurationKey(station, 'MaxCertificateChainSize', '100000') + addConfigurationKey( + station, + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20OptionalVariableName.MaxCertificateChainSize + ), + '100000' + ) const request: OCPP20CertificateSignedRequest = { certificateChain: VALID_PEM_CERTIFICATE, diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts index 690c68b9..aeb892f1 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-GetBaseReport.test.ts @@ -10,11 +10,14 @@ import type { ChargingStation } from '../../../../src/charging-station/index.js' import { addConfigurationKey, + buildConfigKey, setConfigurationKeyValue, } from '../../../../src/charging-station/ConfigurationKeyUtils.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 { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js' +import { + OCPP20VariableManager, +} from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js' import { AttributeEnumType, GenericDeviceModelStatusEnumType, @@ -273,7 +276,10 @@ await describe('B07 - Get Base Report', async () => { // ReportingValueSize truncation test await it('should truncate long SequenceList/MemberList values per ReportingValueSize', () => { // Ensure ReportingValueSize is at a small value (default is Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH). We will override configuration key if absent. - const reportingSizeKey = StandardParametersKey.ReportingValueSize + const reportingSizeKey = buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + StandardParametersKey.ReportingValueSize + ) // Add or lower configuration key to 10 to force truncation addConfigurationKey(station, reportingSizeKey, '10', undefined, { overwrite: true, diff --git a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts index bf0b5f51..00acc59b 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20IncomingRequestService-SetVariables.test.ts @@ -7,8 +7,7 @@ import { millisecondsToSeconds } from 'date-fns' import assert from 'node:assert/strict' import { afterEach, beforeEach, describe, it } from 'node:test' -import type { ChargingStation } from '../../../../src/charging-station/index.js' - +import { buildConfigKey, type ChargingStation } from '../../../../src/charging-station/index.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 { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js' @@ -510,7 +509,10 @@ await describe('B05 - Set Variables', async () => { const postCalcLimit = preEstimate + 10 upsertConfigurationKey( mockStation, - OCPP20RequiredVariableName.BytesPerMessage, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.BytesPerMessage + ), postCalcLimit.toString(), false ) @@ -541,7 +543,11 @@ await describe('B05 - Set Variables', async () => { await it('should enforce ConfigurationValueSize when ValueSize unset (service propagation)', () => { resetValueSizeLimits(mockStation) setConfigurationValueSize(mockStation, 100) - upsertConfigurationKey(mockStation, OCPP20RequiredVariableName.ValueSize, '') + upsertConfigurationKey( + mockStation, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize), + '' + ) const prefix = 'wss://example.com/' const withinLimit = prefix + 'a'.repeat(100 - prefix.length) const overLimit = prefix + 'a'.repeat(100 - prefix.length + 1) @@ -575,7 +581,14 @@ await describe('B05 - Set Variables', async () => { await it('should enforce ValueSize when ConfigurationValueSize unset (service propagation)', () => { resetValueSizeLimits(mockStation) - upsertConfigurationKey(mockStation, OCPP20RequiredVariableName.ConfigurationValueSize, '') + upsertConfigurationKey( + mockStation, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ConfigurationValueSize + ), + '' + ) setValueSize(mockStation, 120) const prefix = 'wss://example.com/' const withinLimit = prefix + 'b'.repeat(120 - prefix.length) diff --git a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts index 88c40ca5..16ba3781 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20ResponseService-BootNotification.test.ts @@ -17,10 +17,12 @@ import { afterEach, beforeEach, describe, it, mock } from 'node:test' import type { MockChargingStation } from '../../ChargingStationTestUtils.js' +import { buildConfigKey } from '../../../../src/charging-station/index.js' import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js' import { ChargingStationEvents, type OCPP20BootNotificationResponse, + OCPP20ComponentName, OCPP20OptionalVariableName, OCPP20RequestCommand, OCPPVersion, @@ -132,7 +134,13 @@ await describe('B01 - BootNotificationResponse handler', async () => { assert.fail('Expected configKey to be defined') } assert.strictEqual(configKey.length, 1) - assert.strictEqual(configKey[0].key, OCPP20OptionalVariableName.HeartbeatInterval) + assert.strictEqual( + configKey[0].key, + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20OptionalVariableName.HeartbeatInterval + ) + ) assert.strictEqual(configKey[0].value, '300') }) diff --git a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts index fd1c7ca4..64971750 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20TestUtils.ts @@ -18,12 +18,14 @@ import type { } from '../../../../src/types/index.js' import type { OCPP20RequestCommand } from '../../../../src/types/index.js' +import { buildConfigKey } from '../../../../src/charging-station/index.js' import { OCPP20RequestService } from '../../../../src/charging-station/ocpp/2.0/OCPP20RequestService.js' import { OCPP20ResponseService } from '../../../../src/charging-station/ocpp/2.0/OCPP20ResponseService.js' import { ConnectorStatusEnum, DeleteCertificateStatusEnumType, HashAlgorithmEnumType, + OCPP20ComponentName, OCPP20IdTokenEnumType, OCPP20RequiredVariableName, OCPPVersion, @@ -257,8 +259,16 @@ export function resetConnectorTransactionState (chargingStation: ChargingStation * @param chargingStation Charging station test instance whose configuration limits are reset. */ export function resetLimits (chargingStation: ChargingStation) { - upsertConfigurationKey(chargingStation, OCPP20RequiredVariableName.ItemsPerMessage, '100') - upsertConfigurationKey(chargingStation, OCPP20RequiredVariableName.BytesPerMessage, '10000') + upsertConfigurationKey( + chargingStation, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ItemsPerMessage), + '100' + ) + upsertConfigurationKey( + chargingStation, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.BytesPerMessage), + '10000' + ) } /** @@ -268,7 +278,10 @@ export function resetLimits (chargingStation: ChargingStation) { export function resetReportingValueSize (chargingStation: ChargingStation) { upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ReportingValueSize, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ReportingValueSize + ), Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString() ) } @@ -281,12 +294,15 @@ export function resetReportingValueSize (chargingStation: ChargingStation) { export function resetValueSizeLimits (chargingStation: ChargingStation) { upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ConfigurationValueSize, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ConfigurationValueSize + ), Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString() ) upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ValueSize, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize), Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH.toString() ) } @@ -299,7 +315,10 @@ export function resetValueSizeLimits (chargingStation: ChargingStation) { export function setConfigurationValueSize (chargingStation: ChargingStation, size: number) { upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ConfigurationValueSize, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ConfigurationValueSize + ), size.toString() ) } @@ -312,7 +331,10 @@ export function setConfigurationValueSize (chargingStation: ChargingStation, siz export function setReportingValueSize (chargingStation: ChargingStation, size: number) { upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ReportingValueSize, + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ReportingValueSize + ), size.toString() ) } @@ -330,12 +352,12 @@ export function setStrictLimits ( ) { upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.ItemsPerMessage, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ItemsPerMessage), itemsLimit.toString() ) upsertConfigurationKey( chargingStation, - OCPP20RequiredVariableName.BytesPerMessage, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.BytesPerMessage), bytesLimit.toString() ) } @@ -346,7 +368,11 @@ export function setStrictLimits ( * @param size Desired stored value size limit. */ export function setValueSize (chargingStation: ChargingStation, size: number) { - upsertConfigurationKey(chargingStation, OCPP20RequiredVariableName.ValueSize, size.toString()) + upsertConfigurationKey( + chargingStation, + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize), + size.toString() + ) } /** diff --git a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts index 1bd20781..eb043680 100644 --- a/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts +++ b/tests/charging-station/ocpp/2.0/OCPP20VariableManager.test.ts @@ -10,6 +10,7 @@ import { afterEach, beforeEach, describe, it } from 'node:test' import type { ChargingStation } from '../../../../src/charging-station/index.js' import { + buildConfigKey, deleteConfigurationKey, getConfigurationKey, } from '../../../../src/charging-station/ConfigurationKeyUtils.js' @@ -79,12 +80,18 @@ await describe('B05 - OCPP20VariableManager', async () => { ocppConfiguration: { configurationKey: [ { - key: StandardParametersKey.HeartbeatInterval, + key: buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + StandardParametersKey.HeartbeatInterval + ), readonly: false, value: millisecondsToSeconds(Constants.DEFAULT_HEARTBEAT_INTERVAL).toString(), }, { - key: StandardParametersKey.WebSocketPingInterval, + key: buildConfigKey( + OCPP20ComponentName.ChargingStation, + StandardParametersKey.WebSocketPingInterval + ), readonly: false, value: Constants.DEFAULT_WEBSOCKET_PING_INTERVAL.toString(), }, @@ -1067,7 +1074,10 @@ await describe('B05 - OCPP20VariableManager', async () => { await it('should avoid duplicate persistence operations when value unchanged', () => { const keyBefore = getConfigurationKey( station, - OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20OptionalVariableName.HeartbeatInterval + ) ) assert.notStrictEqual(keyBefore, undefined) const originalValue = keyBefore?.value @@ -1089,7 +1099,10 @@ await describe('B05 - OCPP20VariableManager', async () => { assert.strictEqual(changed.attributeStatus, SetVariableStatusEnumType.Accepted) const keyAfterChange = getConfigurationKey( station, - OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20OptionalVariableName.HeartbeatInterval + ) ) assert.notStrictEqual(keyAfterChange?.value, originalValue) const reverted = manager.setVariables(station, [ @@ -1102,7 +1115,10 @@ await describe('B05 - OCPP20VariableManager', async () => { assert.strictEqual(reverted.attributeStatus, SetVariableStatusEnumType.Accepted) const keyAfterRevert = getConfigurationKey( station, - OCPP20OptionalVariableName.HeartbeatInterval as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20OptionalVariableName.HeartbeatInterval + ) ) assert.strictEqual(keyAfterRevert?.value, originalValue) }) @@ -1110,12 +1126,12 @@ await describe('B05 - OCPP20VariableManager', async () => { await it('should add missing configuration key with default during self-check', () => { deleteConfigurationKey( station, - OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name'], + buildConfigKey(OCPP20ComponentName.TxCtrlr, OCPP20RequiredVariableName.EVConnectionTimeOut), { save: false } ) const before = getConfigurationKey( station, - OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name'] + buildConfigKey(OCPP20ComponentName.TxCtrlr, OCPP20RequiredVariableName.EVConnectionTimeOut) ) assert.strictEqual(before, undefined) const res = manager.getVariables(station, [ @@ -1129,7 +1145,7 @@ await describe('B05 - OCPP20VariableManager', async () => { assert.strictEqual(res.attributeValue, Constants.DEFAULT_EV_CONNECTION_TIMEOUT.toString()) const after = getConfigurationKey( station, - OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name'] + buildConfigKey(OCPP20ComponentName.TxCtrlr, OCPP20RequiredVariableName.EVConnectionTimeOut) ) assert.notStrictEqual(after, undefined) }) @@ -1354,7 +1370,7 @@ await describe('B05 - OCPP20VariableManager', async () => { // remove ValueSize to simulate unset deleteConfigurationKey( station, - OCPP20RequiredVariableName.ValueSize as unknown as VariableType['name'], + buildConfigKey(OCPP20ComponentName.DeviceDataCtrlr, OCPP20RequiredVariableName.ValueSize), { save: false } ) const okRes = manager.setVariables(station, [ @@ -1384,7 +1400,10 @@ await describe('B05 - OCPP20VariableManager', async () => { setValueSize(station, 40) deleteConfigurationKey( station, - OCPP20RequiredVariableName.ConfigurationValueSize as unknown as VariableType['name'], + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ConfigurationValueSize + ), { save: false } ) const okRes = manager.setVariables(station, [ @@ -1552,7 +1571,10 @@ await describe('B05 - OCPP20VariableManager', async () => { const beforeCfg = getConfigurationKey( station, - OCPP20RequiredVariableName.FileTransferProtocols as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20RequiredVariableName.FileTransferProtocols + ) ) assert.strictEqual(beforeCfg?.value, 'HTTPS,FTPS,SFTP') const rejected = manager.setVariables(station, [ @@ -1574,7 +1596,10 @@ await describe('B05 - OCPP20VariableManager', async () => { assert.strictEqual(afterGet.attributeValue, 'HTTPS,FTPS,SFTP') const afterCfg = getConfigurationKey( station, - OCPP20RequiredVariableName.FileTransferProtocols as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20RequiredVariableName.FileTransferProtocols + ) ) assert.strictEqual(afterCfg?.value, beforeCfg.value) }) @@ -1737,7 +1762,10 @@ await describe('B05 - OCPP20VariableManager', async () => { // Ensure ReportingValueSize unset deleteConfigurationKey( station, - OCPP20RequiredVariableName.ReportingValueSize as unknown as VariableType['name'], + buildConfigKey( + OCPP20ComponentName.DeviceDataCtrlr, + OCPP20RequiredVariableName.ReportingValueSize + ), { save: false } ) // Temporarily set large ValueSize to allow storing long value @@ -1803,7 +1831,7 @@ await describe('B05 - OCPP20VariableManager', async () => { const overLongValue = buildWsExampleUrl(3000, 'c') upsertConfigurationKey( station, - OCPP20VendorVariableName.ConnectionUrl as unknown as VariableType['name'], + buildConfigKey(OCPP20ComponentName.ChargingStation, OCPP20VendorVariableName.ConnectionUrl), overLongValue ) // Set generous ValueSize (1500) and ReportingValueSize (1400) so only absolute cap applies (since both < Constants.OCPP_VALUE_ABSOLUTE_MAX_LENGTH) @@ -1862,12 +1890,18 @@ await describe('B05 - OCPP20VariableManager', async () => { await it('should auto-create persistent OrganizationName configuration key during self-check', () => { deleteConfigurationKey( station, - OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name'], + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20RequiredVariableName.OrganizationName + ), { save: false } ) const before = getConfigurationKey( station, - OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20RequiredVariableName.OrganizationName + ) ) assert.strictEqual(before, undefined) const res = manager.getVariables(station, [ @@ -1880,7 +1914,10 @@ await describe('B05 - OCPP20VariableManager', async () => { assert.strictEqual(res.attributeValue, 'ChangeMeOrg') const after = getConfigurationKey( station, - OCPP20RequiredVariableName.OrganizationName as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.SecurityCtrlr, + OCPP20RequiredVariableName.OrganizationName + ) ) assert.notStrictEqual(after, undefined) assert.strictEqual(after?.value, 'ChangeMeOrg') @@ -1931,7 +1968,11 @@ await describe('B05 - OCPP20VariableManager', async () => { // Ensure no configuration key exists before operations const cfgBefore = getConfigurationKey( station, - OCPP20RequiredVariableName.MessageAttemptInterval as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20RequiredVariableName.MessageAttemptInterval, + 'TransactionEvent' + ) ) assert.strictEqual(cfgBefore, undefined) const initialGet = manager.getVariables(station, [ @@ -2043,7 +2084,11 @@ await describe('B05 - OCPP20VariableManager', async () => { const cfgAfter = getConfigurationKey( station, - OCPP20RequiredVariableName.MessageAttemptInterval as unknown as VariableType['name'] + buildConfigKey( + OCPP20ComponentName.OCPPCommCtrlr, + OCPP20RequiredVariableName.MessageAttemptInterval, + 'TransactionEvent' + ) ) assert.notStrictEqual(cfgAfter, undefined) assert.strictEqual(cfgAfter?.value, '7') @@ -2170,7 +2215,7 @@ await describe('B05 - OCPP20VariableManager', async () => { try { deleteConfigurationKey( stationA, - OCPP20RequiredVariableName.EVConnectionTimeOut as unknown as VariableType['name'], + buildConfigKey(OCPP20ComponentName.TxCtrlr, OCPP20RequiredVariableName.EVConnectionTimeOut), { save: false } ) VARIABLE_REGISTRY[registryKey].defaultValue = undefined -- 2.53.0