isEmpty,
isNotEmptyArray,
isNotEmptyString,
+ isOCPP20x,
JSONStringify,
logger,
logPrefix,
this.wsConnection.on('pong', this.onPong.bind(this))
}
+ /**
+ * Records a request statistic for the given command, but only when statistics
+ * collection is enabled on the station (`stationInfo.enableStatistics`).
+ * @param command - OCPP command the statistic is recorded against.
+ * @param messageType - Message type of the recorded exchange.
+ */
+ public recordRequestStatistic (
+ command: IncomingRequestCommand | RequestCommand,
+ messageType: MessageType
+ ): void {
+ if (this.stationInfo?.enableStatistics === true) {
+ this.performanceStatistics?.addRequestStatistic(command, messageType)
+ }
+ }
+
/**
* Removes a reservation and restores the connector to its previous status.
* @param reservation - The reservation to remove
}
private getReconnectDelay (): number {
- if (
- this.stationInfo?.ocppVersion === OCPPVersion.VERSION_20 ||
- this.stationInfo?.ocppVersion === OCPPVersion.VERSION_201
- ) {
+ if (isOCPP20x(this.stationInfo?.ocppVersion)) {
return OCPP20ServiceUtils.computeReconnectDelay(this, this.wsConnectionRetryCount)
}
return this.stationInfo?.reconnectExponentialDelay === true
commandPayload
)
}
- if (this.stationInfo?.enableStatistics === true) {
- this.performanceStatistics?.addRequestStatistic(commandName, messageType)
- }
+ this.recordRequestStatistic(commandName, messageType)
logger.debug(
`${this.logPrefix()} ${moduleName}.handleIncomingMessage: << Command '${commandName}' received request payload: ${JSON.stringify(
request
type ConfigurationKey,
type ConfigurationKeyType,
OCPP20ComponentName,
- OCPPVersion,
StandardParametersKey,
VendorParametersKey,
} from '../types/index.js'
-import { logger } from '../utils/index.js'
+import { isOCPP20x, logger } from '../utils/index.js'
export const buildConfigKey = (component: string, variable: string, instance?: string): string => {
const base = `${component}.${variable}`
key: ConfigurationKeyType
): ConfigurationKeyType => {
const version = chargingStation.stationInfo?.ocppVersion
- if (version === OCPPVersion.VERSION_20 || version === OCPPVersion.VERSION_201) {
+ if (isOCPP20x(version)) {
return OCPP2_PARAMETER_KEY_MAP.get(key) ?? key
}
return key
export const warnOnOCPP16TemplateKeys = (chargingStation: ChargingStation): void => {
const version = chargingStation.stationInfo?.ocppVersion
- if (version !== OCPPVersion.VERSION_20 && version !== OCPPVersion.VERSION_201) {
+ if (!isOCPP20x(version)) {
return
}
const keys = chargingStation.ocppConfiguration?.configurationKey
}
})
-/**
- * StrictTemplateSchema — rejects unknown keys. For CI strict mode.
- */
-export const StrictTemplateSchema = BaseTemplateSchema.strict()
-
// ---------------------------------------------------------------
// Exported sub-schemas for reuse
// ---------------------------------------------------------------
getErrorMessage,
isAsyncFunction,
isEmpty,
+ isOCPP20x,
logger,
} from '../../utils/index.js'
import { getConfigurationKey } from '../ConfigurationKeyUtils.js'
)
}
const transactionId = this.chargingStation.getConnectorStatus(connectorId)?.transactionId
- const isOcpp2 =
- this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_20 ||
- this.chargingStation.stationInfo?.ocppVersion === OCPPVersion.VERSION_201
+ const isOcpp2 = isOCPP20x(this.chargingStation.stationInfo?.ocppVersion)
const interval = isOcpp2
? OCPP20ServiceUtils.getAlignedDataInterval(this.chargingStation)
: (() => {
// Already completed and released by the normal response path.
return
}
- try {
- this.uiService.sendResponse(uuid, this.buildTimeoutResponsePayload(uuid))
- } finally {
- this.responses.delete(uuid)
- this.uiService.deleteBroadcastChannelRequest(uuid)
- }
+ this.releaseRequest(uuid, this.buildTimeoutResponsePayload(uuid))
}
/**
}
private completeRequest (uuid: UUIDv4): void {
- // Always release aggregation state, even if downstream sendResponse throws.
- try {
- this.uiService.sendResponse(uuid, this.buildResponsePayload(uuid))
- } finally {
- this.responses.delete(uuid)
- this.uiService.deleteBroadcastChannelRequest(uuid)
- }
+ this.releaseRequest(uuid, this.buildResponsePayload(uuid))
}
private messageErrorHandler (messageEvent: MessageEvent): void {
)
}
+ /**
+ * Sends the given response payload for a broadcast request and always
+ * releases its aggregation state afterwards, even if the downstream
+ * sendResponse throws.
+ * @param uuid - Request identifier.
+ * @param responsePayload - Aggregated response payload to send.
+ */
+ private releaseRequest (uuid: UUIDv4, responsePayload: ResponsePayload): void {
+ try {
+ this.uiService.sendResponse(uuid, responsePayload)
+ } finally {
+ this.responses.delete(uuid)
+ this.uiService.deleteBroadcastChannelRequest(uuid)
+ }
+ }
+
private responseHandler (messageEvent: MessageEvent): void {
const validatedMessageEvent = this.validateMessageEvent(messageEvent)
if (validatedMessageEvent === false) {
export type { CoherentSession } from './meter-values/index.js'
export { SharedLRUCache } from './SharedLRUCache.js'
export { applyMigration, coerceVersion, CURRENT_SCHEMA_VERSION } from './TemplateMigrations.js'
-export { StrictTemplateSchema, TemplateSchema } from './TemplateSchema.js'
+export { TemplateSchema } from './TemplateSchema.js'
export { TemplateValidationError, validateTemplate } from './TemplateValidation.js'
type OCPP16StartTransactionRequest,
type OCPP16StatusNotificationRequest,
OCPPVersion,
- type RequestParams,
} from '../../../types/index.js'
-import { assertIsJsonObject, generateUUID, logger } from '../../../utils/index.js'
+import { assertIsJsonObject, logger } from '../../../utils/index.js'
import { sendAndSetConnectorStatus } from '../OCPPConnectorStatusOperations.js'
import { OCPPRequestService } from '../OCPPRequestService.js'
-import { createPayloadValidatorMap, isRequestCommandSupported } from '../OCPPServiceUtils.js'
+import { createPayloadValidatorMap } from '../OCPPServiceUtils.js'
import { OCPP16Constants } from './OCPP16Constants.js'
import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
* @param ocppResponseService - The response service instance for handling responses
*/
public constructor (ocppResponseService: OCPPResponseService) {
- super(OCPPVersion.VERSION_16, ocppResponseService)
+ super(OCPPVersion.VERSION_16, ocppResponseService, moduleName)
this.payloadValidatorFunctions = createPayloadValidatorMap(
OCPP16ServiceUtils.createRequestPayloadConfigs(),
OCPP16ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
this.ajv
)
- this.buildRequestPayload = this.buildRequestPayload.bind(this)
- }
-
- /**
- * Handles OCPP 1.6 request processing with full validation and error handling
- *
- * This method serves as the main entry point for all outgoing OCPP 1.6 requests.
- * It performs the following operations:
- * - Validates that the requested command is supported by the charging station
- * - Builds and validates the request payload according to OCPP 1.6 schemas
- * - Sends the request to the Central System with proper error handling
- * - Processes responses with comprehensive logging and error recovery
- *
- * The method ensures type safety through generic type parameters while maintaining
- * backward compatibility with the OCPP 1.6 specification.
- * @template RequestType - The expected type of the request parameters
- * @template ResponseType - The expected type of the response from the Central System
- * @param chargingStation - The charging station instance making the request
- * @param commandName - The OCPP 1.6 command to execute (e.g., 'StartTransaction', 'StopTransaction')
- * @param commandParams - Optional parameters specific to the command being executed
- * @param params - Optional request parameters for controlling request behavior
- * @returns Promise resolving to the typed response from the Central System
- * @throws {OCPPError} When the command is not supported or validation fails
- */
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
- public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
- chargingStation: ChargingStation,
- commandName: OCPP16RequestCommand,
- commandParams?: RequestType,
- params?: RequestParams
- ): Promise<ResponseType> {
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
- )
- if (isRequestCommandSupported(chargingStation, commandName)) {
- try {
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Building request payload for '${commandName}'`
- )
- const requestPayload =
- params?.rawPayload === true
- ? (commandParams as RequestType)
- : this.buildRequestPayload(chargingStation, commandName, commandParams)
- const messageId = generateUUID()
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Sending '${commandName}' request with message ID '${messageId}'`
- )
- // Pre request actions hook
- switch (commandName) {
- case OCPP16RequestCommand.START_TRANSACTION:
- await sendAndSetConnectorStatus(chargingStation, {
- connectorId: (commandParams as OCPP16StartTransactionRequest).connectorId,
- status: OCPP16ChargePointStatus.Preparing,
- })
- break
- }
- const response = (await this.sendMessage(
- chargingStation,
- messageId,
- requestPayload,
- commandName,
- params
- )) as ResponseType
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: '${commandName}' request completed successfully`
- )
- return response
- } catch (error) {
- this.logRequestHandlerError(chargingStation, moduleName, commandName, error)
- throw error
- }
- }
- // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
- const errorMsg = `Unsupported OCPP command ${commandName}`
- logger.error(`${chargingStation.logPrefix()} ${moduleName}.requestHandler: ${errorMsg}`)
- throw new OCPPError(ErrorType.NOT_SUPPORTED, errorMsg, commandName, commandParams)
}
/**
* @param commandParams - Optional parameters provided by the caller for payload construction
* @returns The fully constructed and validated request payload ready for transmission
*/
- private buildRequestPayload (
+ protected buildRequestPayload (
chargingStation: ChargingStation,
commandName: OCPP16RequestCommand,
commandParams?: JsonType
}
}
}
+
+ /**
+ * Runs the OCPP 1.6 pre-request actions hook: for StartTransaction it sends
+ * and sets the connector status to Preparing before the request is sent.
+ * @param chargingStation - The charging station instance making the request
+ * @param commandName - The OCPP 1.6 command being sent
+ * @param commandParams - Optional parameters provided by the caller
+ */
+ protected override async preRequestHook (
+ chargingStation: ChargingStation,
+ commandName: OCPP16RequestCommand,
+ commandParams?: JsonType
+ ): Promise<void> {
+ switch (commandName) {
+ case OCPP16RequestCommand.START_TRANSACTION:
+ await sendAndSetConnectorStatus(chargingStation, {
+ connectorId: (commandParams as OCPP16StartTransactionRequest).connectorId,
+ status: OCPP16ChargePointStatus.Preparing,
+ })
+ break
+ }
+ }
}
} from '../../index.js'
import {
AuthContext,
- AuthorizationStatus,
+ AuthResultStatus,
type DifferentialAuthEntry,
type LocalAuthEntry,
mapOCPP20TokenType,
timestamp: new Date(),
})
- if (authResult.status !== AuthorizationStatus.ACCEPTED) {
+ if (authResult.status !== AuthResultStatus.ACCEPTED) {
logger.warn(
`${chargingStation.logPrefix()} ${moduleName}.authorizeToken: ${tokenLabel} '${truncateId(tokenValue)}' is not authorized`
)
}
- return authResult.status === AuthorizationStatus.ACCEPTED
+ return authResult.status === AuthResultStatus.ACCEPTED
}
private buildReportData (
type OCPP20SignCertificateRequest,
type OCPP20TransactionEventOptions,
OCPPVersion,
- type RequestParams,
type StatusNotificationOptions,
} from '../../../types/index.js'
-import { generateUUID, getErrorMessage, logger } from '../../../utils/index.js'
+import { getErrorMessage, logger } from '../../../utils/index.js'
import { OCPPRequestService } from '../OCPPRequestService.js'
-import { createPayloadValidatorMap, isRequestCommandSupported } from '../OCPPServiceUtils.js'
+import { createPayloadValidatorMap } from '../OCPPServiceUtils.js'
import { generatePkcs10Csr } from './Asn1DerUtils.js'
import { OCPP20Constants } from './OCPP20Constants.js'
import { buildTransactionEvent, OCPP20ServiceUtils } from './OCPP20ServiceUtils.js'
* @param ocppResponseService - The response service instance for handling OCPP 2.0.1 responses
*/
public constructor (ocppResponseService: OCPPResponseService) {
- super(OCPPVersion.VERSION_201, ocppResponseService)
+ super(OCPPVersion.VERSION_201, ocppResponseService, moduleName)
this.payloadValidatorFunctions = createPayloadValidatorMap(
OCPP20ServiceUtils.createRequestPayloadConfigs(),
OCPP20ServiceUtils.createPayloadOptions(moduleName, 'constructor'),
this.ajv
)
- this.buildRequestPayload = this.buildRequestPayload.bind(this)
}
- /**
- * Handles OCPP 2.0.1 request processing with enhanced validation and comprehensive error handling
- *
- * This method serves as the main entry point for all outgoing OCPP 2.0.1 requests to the CSMS.
- * It performs advanced operations including:
- * - Validates that the requested command is supported by the charging station configuration
- * - Builds and validates request payloads according to strict OCPP 2.0.1 schemas
- * - Handles OCPP 2.0.1-specific features like component/variable management and enhanced security
- * - Sends requests with comprehensive error handling and detailed logging
- * - Processes responses with full support for OCPP 2.0.1's enhanced status reporting
- * - Manages advanced OCPP 2.0.1 concepts like EVSE management and transaction UUIDs
- *
- * The method ensures full compliance with OCPP 2.0.1 specification while providing
- * enhanced type safety and detailed error reporting for debugging and monitoring.
- * @template RequestType - The expected type of the request parameters
- * @template ResponseType - The expected type of the response from the CSMS
- * @param chargingStation - The charging station instance making the request
- * @param commandName - The OCPP 2.0.1 command to execute (e.g., 'Authorize', 'TransactionEvent')
- * @param commandParams - Optional parameters specific to the command being executed
- * @param params - Optional request parameters for controlling request behavior
- * @returns Promise resolving to the typed response from the CSMS
- * @throws {OCPPError} When the command is not supported, validation fails, or CSMS returns an error
- */
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
- public async requestHandler<RequestType extends JsonType, ResponseType extends JsonType>(
- chargingStation: ChargingStation,
- commandName: OCPP20RequestCommand,
- commandParams?: RequestType,
- params?: RequestParams
- ): Promise<ResponseType> {
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Processing '${commandName}' request`
- )
- if (isRequestCommandSupported(chargingStation, commandName)) {
- try {
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Building request payload for '${commandName}'`
- )
- const requestPayload =
- params?.rawPayload === true
- ? (commandParams as RequestType)
- : this.buildRequestPayload(chargingStation, commandName, commandParams)
- const messageId = generateUUID()
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: Sending '${commandName}' request with message ID '${messageId}'`
- )
- const response = (await this.sendMessage(
- chargingStation,
- messageId,
- requestPayload,
- commandName,
- params
- )) as ResponseType
- logger.debug(
- `${chargingStation.logPrefix()} ${moduleName}.requestHandler: '${commandName}' request completed successfully`
- )
- return response
- } catch (error) {
- this.logRequestHandlerError(chargingStation, moduleName, commandName, error)
- throw error
- }
- }
- // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
- const errorMsg = `Unsupported OCPP command ${commandName}`
- logger.error(`${chargingStation.logPrefix()} ${moduleName}.requestHandler: ${errorMsg}`)
- throw new OCPPError(ErrorType.NOT_SUPPORTED, errorMsg, commandName, commandParams)
- }
-
- private buildRequestPayload (
+ protected buildRequestPayload (
chargingStation: ChargingStation,
commandName: OCPP20RequestCommand,
commandParams?: JsonType
type ComponentType,
DataEnumType,
GetVariableStatusEnumType,
- MutabilityEnumType,
OCPP20ComponentName,
type OCPP20GetVariableDataType,
type OCPP20GetVariableResultType,
OCPP20RequiredVariableName,
type OCPP20SetVariableDataType,
type OCPP20SetVariableResultType,
- PersistenceEnumType,
ReasonCodeEnumType,
SetVariableStatusEnumType,
type VariableType,
buildCaseInsensitiveCompositeKey,
enforceReportingValueSize,
getVariableMetadata,
+ isPersistent,
+ isReadOnly,
+ isVolatile,
+ isWriteOnly,
resolveValue,
validateValue,
VARIABLE_REGISTRY,
for (const metaKey of Object.keys(VARIABLE_REGISTRY)) {
const variableMetadata = VARIABLE_REGISTRY[metaKey]
// Enforce persistent non-write-only variables across components
- if (variableMetadata.persistence !== PersistenceEnumType.Persistent) {
+ if (!isPersistent(variableMetadata)) {
continue
}
- if (variableMetadata.mutability === MutabilityEnumType.WriteOnly) {
+ if (isWriteOnly(variableMetadata)) {
continue
}
// Instance-scoped persistent variables are also auto-created when defaultValue is defined
variable.instance ?? component.instance
)
if (
- variableMetadata?.mutability === MutabilityEnumType.WriteOnly &&
+ variableMetadata != null &&
+ isWriteOnly(variableMetadata) &&
resolvedAttributeType === AttributeEnumType.Actual
) {
return this.rejectGet(
let value = resolveValue(chargingStation, variableMetadata)
- if (
- variableMetadata.persistence === PersistenceEnumType.Persistent &&
- variableMetadata.mutability !== MutabilityEnumType.WriteOnly
- ) {
+ if (isPersistent(variableMetadata) && !isWriteOnly(variableMetadata)) {
const configurationKeyName = computeConfigurationKeyName(variableMetadata)
let cfg = getConfigurationKey(chargingStation, configurationKeyName)
}
}
- if (
- variableMetadata.persistence === PersistenceEnumType.Volatile &&
- variableMetadata.mutability !== MutabilityEnumType.ReadOnly
- ) {
+ if (isVolatile(variableMetadata) && !isReadOnly(variableMetadata)) {
const stationId = this.getStationId(chargingStation)
const override = this.getRuntimeOverrides(stationId).get(compositeKey)
if (override != null) {
variable.name
)
if (invalidVariables.has(variableKey) && resolvedAttributeType === AttributeEnumType.Actual) {
- if (variableMetadata.mutability !== MutabilityEnumType.WriteOnly) {
+ if (!isWriteOnly(variableMetadata)) {
return this.rejectSet(
variable,
component,
}
}
- if (variableMetadata.mutability === MutabilityEnumType.ReadOnly) {
+ if (isReadOnly(variableMetadata)) {
return this.rejectSet(
variable,
component,
const configurationKeyName = computeConfigurationKeyName(variableMetadata)
const previousValue = getConfigurationKey(chargingStation, configurationKeyName)?.value
- if (
- variableMetadata.persistence === PersistenceEnumType.Persistent &&
- variableMetadata.mutability !== MutabilityEnumType.WriteOnly
- ) {
+ if (isPersistent(variableMetadata) && !isWriteOnly(variableMetadata)) {
const configKey = getConfigurationKey(chargingStation, configurationKeyName)
if (configKey == null) {
addConfigurationKey(chargingStation, configurationKeyName, attributeValue, undefined, {
chargingStation.restartWebSocketPing()
}
// Apply volatile runtime override generically (single location)
- if (variableMetadata.persistence === PersistenceEnumType.Volatile) {
+ if (isVolatile(variableMetadata)) {
this.getRuntimeOverrides(stationId).set(variableKey, attributeValue)
}
return variableMetadata.mutability === MutabilityEnumType.ReadOnly
}
+/**
+ * Check if variable metadata is volatile.
+ * @param variableMetadata - Variable metadata entry.
+ * @returns `true` when persistence is Volatile.
+ */
+export function isVolatile (variableMetadata: VariableMetadata): boolean {
+ return variableMetadata.persistence === PersistenceEnumType.Volatile
+}
+
/**
* Check if variable metadata is write-only.
* @param variableMetadata - Variable metadata entry.
import {
ensureError,
formatDurationMilliSeconds,
+ generateUUID,
getErrorMessage,
getMessageTypeString,
handleSendMessageError,
logger,
} from '../../utils/index.js'
import { OCPPConstants } from './OCPPConstants.js'
-import { type Ajv, createAjv, validatePayload } from './OCPPServiceUtils.js'
+import {
+ type Ajv,
+ createAjv,
+ isRequestCommandSupported,
+ validatePayload,
+} from './OCPPServiceUtils.js'
const defaultRequestParams: RequestParams = {
skipBufferingOnError: false,
>()
protected readonly ajv: Ajv
+ protected readonly moduleName: string
protected abstract payloadValidatorFunctions: Map<RequestCommand, ValidateFunction<JsonType>>
private readonly ocppResponseService: OCPPResponseService
private readonly version: OCPPVersion
- protected constructor (version: OCPPVersion, ocppResponseService: OCPPResponseService) {
+ protected constructor (
+ version: OCPPVersion,
+ ocppResponseService: OCPPResponseService,
+ moduleName: string
+ ) {
this.version = version
+ this.moduleName = moduleName
this.ajv = createAjv()
this.ocppResponseService = ocppResponseService
this.requestHandler = this.requestHandler.bind(this)
+ this.buildRequestPayload = this.buildRequestPayload.bind(this)
this.sendMessage = this.sendMessage.bind(this)
this.sendResponse = this.sendResponse.bind(this)
this.sendError = this.sendError.bind(this)
* @returns Response payload from the Central System.
*/
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
- public abstract requestHandler<ReqType extends JsonType, ResType extends JsonType>(
+ public async requestHandler<ReqType extends JsonType, ResType extends JsonType>(
chargingStation: ChargingStation,
commandName: RequestCommand,
commandParams?: ReqType,
params?: RequestParams
- ): Promise<ResType>
+ ): Promise<ResType> {
+ logger.debug(
+ `${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: Processing '${commandName}' request`
+ )
+ if (isRequestCommandSupported(chargingStation, commandName)) {
+ try {
+ logger.debug(
+ `${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: Building request payload for '${commandName}'`
+ )
+ const requestPayload =
+ params?.rawPayload === true
+ ? (commandParams as ReqType)
+ : this.buildRequestPayload(chargingStation, commandName, commandParams)
+ const messageId = generateUUID()
+ logger.debug(
+ `${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: Sending '${commandName}' request with message ID '${messageId}'`
+ )
+ await this.preRequestHook(chargingStation, commandName, commandParams)
+ const response = (await this.sendMessage(
+ chargingStation,
+ messageId,
+ requestPayload,
+ commandName,
+ params
+ )) as ResType
+ logger.debug(
+ `${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: '${commandName}' request completed successfully`
+ )
+ return response
+ } catch (error) {
+ this.logRequestHandlerError(chargingStation, commandName, error)
+ throw error
+ }
+ }
+ // OCPPError usage here is debatable: it's an error in the OCPP stack but not targeted to sendError().
+ const errorMsg = `Unsupported OCPP command ${commandName}`
+ logger.error(`${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: ${errorMsg}`)
+ throw new OCPPError(ErrorType.NOT_SUPPORTED, errorMsg, commandName, commandParams)
+ }
public async sendError (
chargingStation: ChargingStation,
}
}
+ protected abstract buildRequestPayload (
+ chargingStation: ChargingStation,
+ commandName: RequestCommand,
+ commandParams?: JsonType
+ ): JsonType
+
protected logRequestHandlerError (
chargingStation: ChargingStation,
- subclassModuleName: string,
commandName: RequestCommand,
error: unknown
): void {
logger.error(
- `${chargingStation.logPrefix()} ${subclassModuleName}.requestHandler: Error processing '${commandName}' request:`,
+ `${chargingStation.logPrefix()} ${this.moduleName}.requestHandler: Error processing '${commandName}' request:`,
error
)
}
+ /**
+ * Pre-request actions hook run after message id generation and before the
+ * request is sent. Base implementation is a no-op; overridden per OCPP
+ * version that needs a version-specific step (e.g. OCPP 1.6 StartTransaction).
+ * @param _chargingStation - Target charging station.
+ * @param _commandName - OCPP request command name.
+ * @param _commandParams - Optional request payload.
+ */
+ protected preRequestHook (
+ _chargingStation: ChargingStation,
+ _commandName: RequestCommand,
+ _commandParams?: JsonType
+ ): Promise<void> | void {
+ /* No-op by default */
+ }
+
protected async sendMessage (
chargingStation: ChargingStation,
messageId: string,
* @param requestPayload - The original request payload
*/
const responseCallback = (payload: JsonType, requestPayload: JsonType): void => {
- if (chargingStation.stationInfo?.enableStatistics === true) {
- chargingStation.performanceStatistics?.addRequestStatistic(
- commandName,
- MessageType.CALL_RESULT_MESSAGE
- )
- }
+ chargingStation.recordRequestStatistic(commandName, MessageType.CALL_RESULT_MESSAGE)
self.ocppResponseService
.responseHandler(
chargingStation,
* @param requestStatistic - Whether to record request statistics
*/
const errorCallback = (ocppError: OCPPError, requestStatistic = true): void => {
- if (requestStatistic && chargingStation.stationInfo?.enableStatistics === true) {
- chargingStation.performanceStatistics?.addRequestStatistic(
- commandName,
- MessageType.CALL_ERROR_MESSAGE
- )
+ if (requestStatistic) {
+ chargingStation.recordRequestStatistic(commandName, MessageType.CALL_ERROR_MESSAGE)
}
logger.error(
`${chargingStation.logPrefix()} Error occurred at ${getMessageTypeString(
reject(ocppError)
}
- if (chargingStation.stationInfo?.enableStatistics === true) {
- chargingStation.performanceStatistics?.addRequestStatistic(commandName, messageType)
- }
+ chargingStation.recordRequestStatistic(commandName, messageType)
const messageToSend = this.buildMessageToSend(
chargingStation,
messageId,
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus as AuthStatus,
+ AuthResultStatus as AuthStatus,
IdentifierType,
OCPPAuthServiceFactory,
} from './auth/index.js'
* @returns Parsed JSON schema object
* @throws {NodeJS.ErrnoException} If the schema file cannot be read or parsed
*/
-export function parseJsonSchemaFile<T extends JsonType> (
+function parseJsonSchemaFile<T extends JsonType> (
relativePath: string,
ocppVersion: OCPPVersion,
moduleName?: string,
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
mapOCPP16Status,
mapToOCPP16Status,
},
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
mapOCPP20AuthorizationStatus,
mapOCPP20TokenType,
},
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
},
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
},
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
import type { AuthorizationResult } from '../types/AuthTypes.js'
import { Constants, isEmpty, logger, roundTo, truncateId } from '../../../../utils/index.js'
-import { AuthorizationStatus } from '../types/AuthTypes.js'
+import { AuthResultStatus } from '../types/AuthTypes.js'
const moduleName = 'InMemoryAuthCache'
if (now >= authCacheEntry.expiresAt) {
this.stats.expired++
// Transition to EXPIRED status instead of deleting (C10.FR.08)
- authCacheEntry.result = { ...authCacheEntry.result, status: AuthorizationStatus.EXPIRED }
+ authCacheEntry.result = { ...authCacheEntry.result, status: AuthResultStatus.EXPIRED }
// Apply absolute lifetime cap to expired-transition TTL refresh (default-TTL entries only)
if (!authCacheEntry.hasExplicitTtl) {
const absoluteDeadline = authCacheEntry.createdAt + this.maxAbsoluteLifetimeMs
const now = Date.now()
for (const [key, entry] of this.cache.entries()) {
if (now >= entry.expiresAt) {
- if (entry.result.status === AuthorizationStatus.EXPIRED) {
+ if (entry.result.status === AuthResultStatus.EXPIRED) {
// Already transitioned by get() — delete on second expiration cycle
this.cache.delete(key)
this.lruOrder.delete(key)
} else {
// First expiration — transition to EXPIRED status (consistent with get() C10.FR.08 semantics)
- entry.result = { ...entry.result, status: AuthorizationStatus.EXPIRED }
+ entry.result = { ...entry.result, status: AuthResultStatus.EXPIRED }
if (!entry.hasExplicitTtl) {
const absoluteDeadline = entry.createdAt + this.maxAbsoluteLifetimeMs
if (absoluteDeadline > now) {
for (const [identifier, accessTime] of this.lruOrder.entries()) {
const authCacheEntry = this.cache.get(identifier)
if (
- authCacheEntry?.result.status !== AuthorizationStatus.ACCEPTED &&
+ authCacheEntry?.result.status !== AuthResultStatus.ACCEPTED &&
accessTime < candidateTime
) {
candidateTime = accessTime
AuthenticationMethod,
AuthErrorCode,
type AuthorizationResult,
- AuthorizationStatus,
type AuthRequest,
+ AuthResultStatus,
type CertificateHashData,
type Identifier,
IdentifierType,
import type {
AuthConfiguration,
AuthorizationResult,
- AuthorizationStatus,
AuthRequest,
+ AuthResultStatus,
Identifier,
IdentifierType,
} from '../types/AuthTypes.js'
*/
updateCacheEntry(
identifier: string,
- status: AuthorizationStatus,
+ status: AuthResultStatus,
expiryDate?: Date | string,
identifierType?: IdentifierType
): void
AuthContext,
AuthenticationMethod,
type AuthorizationResult,
- AuthorizationStatus,
type AuthRequest,
+ AuthResultStatus,
type Identifier,
IdentifierType,
} from '../types/AuthTypes.js'
},
isOffline: false,
method: AuthenticationMethod.NONE,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
public updateCacheEntry (
identifier: string,
- status: AuthorizationStatus,
+ status: AuthResultStatus,
expiryDate?: Date | string,
identifierType?: IdentifierType
): void {
ocppVersion: this.chargingStation.stationInfo?.ocppVersion,
offlineAuthorizationEnabled: true,
remoteAuthorization: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
}
): void {
this.metrics.totalResponseTime += duration
- if (result.status === AuthorizationStatus.ACCEPTED) {
+ if (result.status === AuthResultStatus.ACCEPTED) {
this.metrics.successfulAuth++
} else {
this.metrics.failedAuth++
import { OCPPVersion } from '../../../../types/index.js'
import { isNotEmptyString, logger, sleep } from '../../../../utils/index.js'
-import { AuthenticationMethod, AuthorizationStatus, IdentifierType } from '../types/AuthTypes.js'
+import { AuthenticationMethod, AuthResultStatus, IdentifierType } from '../types/AuthTypes.js'
const moduleName = 'CertificateAuthStrategy'
`${moduleName}: Certificate validation failed: ${String(certValidation.reason)}`
)
return this.createFailureResult(
- AuthorizationStatus.INVALID,
+ AuthResultStatus.INVALID,
certValidation.reason ?? 'Certificate validation failed',
request.identifier,
startTime
// Unreachable when the `canHandle` contract holds; defensive fallback
// for unsupported OCPP versions.
return this.createFailureResult(
- AuthorizationStatus.INVALID,
+ AuthResultStatus.INVALID,
`Certificate authentication not supported for OCPP ${this.adapter.ocppVersion}`,
request.identifier,
startTime
} catch (error) {
logger.error(`${moduleName}: Certificate authorization error:`, error)
return this.createFailureResult(
- AuthorizationStatus.INVALID,
+ AuthResultStatus.INVALID,
'Certificate authorization failed',
request.identifier,
startTime
* @returns Authorization result with failure status and diagnostic information
*/
private createFailureResult (
- status: AuthorizationStatus,
+ status: AuthResultStatus,
reason: string,
identifier: Identifier,
startTime: number
* @param startTime - Request start timestamp for response time calculation
*/
private updateStatistics (result: AuthorizationResult, startTime: number): void {
- if (result.status === AuthorizationStatus.ACCEPTED) {
+ if (result.status === AuthResultStatus.ACCEPTED) {
this.stats.successfulAuths++
} else {
this.stats.failedAuths++
expiryDate: this.calculateCertificateExpiry(request.identifier),
isOffline: false,
method: AuthenticationMethod.CERTIFICATE_BASED,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
return successResult
} else {
return this.createFailureResult(
- AuthorizationStatus.BLOCKED,
+ AuthResultStatus.BLOCKED,
'Certificate validation failed',
request.identifier,
startTime
} catch (error) {
logger.error(`${moduleName}: OCPP 2.0.1 certificate validation error:`, error)
return this.createFailureResult(
- AuthorizationStatus.INVALID,
+ AuthResultStatus.INVALID,
'Certificate validation error',
request.identifier,
startTime
AuthenticationError,
AuthenticationMethod,
AuthErrorCode,
- AuthorizationStatus,
+ AuthResultStatus,
enhanceAuthResult,
} from '../types/AuthTypes.js'
expiryDate: entry.expiryDate,
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.EXPIRED,
+ status: AuthResultStatus.EXPIRED,
timestamp: new Date(),
}
}
additionalInfo: { reason: 'Transaction stop - offline mode' },
isOffline: true,
method: AuthenticationMethod.OFFLINE_FALLBACK,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
}
// For unknown IDs, check configuration
if (config.allowOfflineTxForUnknownId) {
- const status = config.unknownIdAuthorization ?? AuthorizationStatus.ACCEPTED
+ const status = config.unknownIdAuthorization ?? AuthResultStatus.ACCEPTED
return {
additionalInfo: { reason: 'Unknown ID allowed in offline mode' },
additionalInfo: { reason: 'Unknown ID not allowed in offline mode' },
isOffline: true,
method: AuthenticationMethod.OFFLINE_FALLBACK,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
}
* @param status - Status string from local auth list entry
* @returns Authorization status corresponding to the entry status
*/
- private mapEntryStatus (status: string): AuthorizationStatus {
+ private mapEntryStatus (status: string): AuthResultStatus {
switch (status.toLowerCase()) {
case 'accepted':
case 'authorized':
case 'valid':
- return AuthorizationStatus.ACCEPTED
+ return AuthResultStatus.ACCEPTED
case 'blocked':
case 'disabled':
- return AuthorizationStatus.BLOCKED
+ return AuthResultStatus.BLOCKED
case 'concurrent':
case 'concurrent_tx':
- return AuthorizationStatus.CONCURRENT_TX
+ return AuthResultStatus.CONCURRENT_TX
case 'expired':
- return AuthorizationStatus.EXPIRED
+ return AuthResultStatus.EXPIRED
case 'invalid':
case 'unauthorized':
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
default:
logger.warn(`${moduleName}: Unknown entry status: ${status}, defaulting to INVALID`)
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
}
}
result: AuthorizationResult,
config: AuthConfiguration
): boolean {
- return result.status !== AuthorizationStatus.ACCEPTED && config.disablePostAuthorize !== true
+ return result.status !== AuthResultStatus.ACCEPTED && config.disablePostAuthorize !== true
}
}
/**
* Authorization status combining OCPP 1.6 and 2.0 statuses
*/
-export enum AuthorizationStatus {
+export enum AuthResultStatus {
// Common statuses across versions
ACCEPTED = 'Accepted',
BLOCKED = 'Blocked',
remoteAuthorization?: boolean
/** Default authorization status for unknown IDs */
- unknownIdAuthorization?: AuthorizationStatus
+ unknownIdAuthorization?: AuthResultStatus
}
/**
}
/** Authorization status */
- readonly status: AuthorizationStatus
+ readonly status: AuthResultStatus
/** Timestamp of authorization */
readonly timestamp: Date
* @example
* ```typescript
* const status = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
- * // Returns: AuthorizationStatus.ACCEPTED
+ * // Returns: AuthResultStatus.ACCEPTED
* ```
*/
-export const mapOCPP16Status = (status: OCPP16AuthorizationStatus): AuthorizationStatus => {
+export const mapOCPP16Status = (status: OCPP16AuthorizationStatus): AuthResultStatus => {
switch (status) {
case OCPP16AuthorizationStatus.ACCEPTED:
- return AuthorizationStatus.ACCEPTED
+ return AuthResultStatus.ACCEPTED
case OCPP16AuthorizationStatus.BLOCKED:
- return AuthorizationStatus.BLOCKED
+ return AuthResultStatus.BLOCKED
case OCPP16AuthorizationStatus.CONCURRENT_TX:
- return AuthorizationStatus.CONCURRENT_TX
+ return AuthResultStatus.CONCURRENT_TX
case OCPP16AuthorizationStatus.EXPIRED:
- return AuthorizationStatus.EXPIRED
+ return AuthResultStatus.EXPIRED
case OCPP16AuthorizationStatus.INVALID:
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
default:
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
}
}
* @example
* ```typescript
* const status = mapOCPP20AuthorizationStatus(OCPP20AuthorizationStatusEnumType.Accepted)
- * // Returns: AuthorizationStatus.ACCEPTED
+ * // Returns: AuthResultStatus.ACCEPTED
* ```
*/
export const mapOCPP20AuthorizationStatus = (
status: OCPP20AuthorizationStatusEnumType
-): AuthorizationStatus => {
+): AuthResultStatus => {
switch (status) {
case OCPP20AuthorizationStatusEnumType.Accepted:
- return AuthorizationStatus.ACCEPTED
+ return AuthResultStatus.ACCEPTED
case OCPP20AuthorizationStatusEnumType.Blocked:
- return AuthorizationStatus.BLOCKED
+ return AuthResultStatus.BLOCKED
case OCPP20AuthorizationStatusEnumType.ConcurrentTx:
- return AuthorizationStatus.CONCURRENT_TX
+ return AuthResultStatus.CONCURRENT_TX
case OCPP20AuthorizationStatusEnumType.Expired:
- return AuthorizationStatus.EXPIRED
+ return AuthResultStatus.EXPIRED
case OCPP20AuthorizationStatusEnumType.Invalid:
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
case OCPP20AuthorizationStatusEnumType.NoCredit:
- return AuthorizationStatus.NO_CREDIT
+ return AuthResultStatus.NO_CREDIT
case OCPP20AuthorizationStatusEnumType.NotAllowedTypeEVSE:
- return AuthorizationStatus.NOT_ALLOWED_TYPE_EVSE
+ return AuthResultStatus.NOT_ALLOWED_TYPE_EVSE
case OCPP20AuthorizationStatusEnumType.NotAtThisLocation:
- return AuthorizationStatus.NOT_AT_THIS_LOCATION
+ return AuthResultStatus.NOT_AT_THIS_LOCATION
case OCPP20AuthorizationStatusEnumType.NotAtThisTime:
- return AuthorizationStatus.NOT_AT_THIS_TIME
+ return AuthResultStatus.NOT_AT_THIS_TIME
case OCPP20AuthorizationStatusEnumType.Unknown:
- return AuthorizationStatus.UNKNOWN
+ return AuthResultStatus.UNKNOWN
default:
- return AuthorizationStatus.INVALID
+ return AuthResultStatus.INVALID
}
}
* @returns OCPP 1.6 authorization status
* @example
* ```typescript
- * const ocpp16Status = mapToOCPP16Status(AuthorizationStatus.ACCEPTED)
+ * const ocpp16Status = mapToOCPP16Status(AuthResultStatus.ACCEPTED)
* // Returns: OCPP16AuthorizationStatus.ACCEPTED
* ```
*/
-export const mapToOCPP16Status = (status: AuthorizationStatus): OCPP16AuthorizationStatus => {
+export const mapToOCPP16Status = (status: AuthResultStatus): OCPP16AuthorizationStatus => {
switch (status) {
- case AuthorizationStatus.ACCEPTED:
+ case AuthResultStatus.ACCEPTED:
return OCPP16AuthorizationStatus.ACCEPTED
- case AuthorizationStatus.BLOCKED:
+ case AuthResultStatus.BLOCKED:
return OCPP16AuthorizationStatus.BLOCKED
- case AuthorizationStatus.CONCURRENT_TX:
+ case AuthResultStatus.CONCURRENT_TX:
return OCPP16AuthorizationStatus.CONCURRENT_TX
- case AuthorizationStatus.EXPIRED:
+ case AuthResultStatus.EXPIRED:
return OCPP16AuthorizationStatus.EXPIRED
- case AuthorizationStatus.INVALID:
- case AuthorizationStatus.NOT_AT_THIS_LOCATION:
- case AuthorizationStatus.NOT_AT_THIS_TIME:
- case AuthorizationStatus.PENDING:
- case AuthorizationStatus.UNKNOWN:
+ case AuthResultStatus.INVALID:
+ case AuthResultStatus.NOT_AT_THIS_LOCATION:
+ case AuthResultStatus.NOT_AT_THIS_TIME:
+ case AuthResultStatus.PENDING:
+ case AuthResultStatus.UNKNOWN:
default:
return OCPP16AuthorizationStatus.INVALID
}
* @returns OCPP 2.0.1 RequestStartStopStatus
* @example
* ```typescript
- * const ocpp20Status = mapToOCPP20Status(AuthorizationStatus.ACCEPTED)
+ * const ocpp20Status = mapToOCPP20Status(AuthResultStatus.ACCEPTED)
* // Returns: RequestStartStopStatusEnumType.Accepted
* ```
*/
-export const mapToOCPP20Status = (status: AuthorizationStatus): RequestStartStopStatusEnumType => {
+export const mapToOCPP20Status = (status: AuthResultStatus): RequestStartStopStatusEnumType => {
switch (status) {
- case AuthorizationStatus.ACCEPTED:
+ case AuthResultStatus.ACCEPTED:
return RequestStartStopStatusEnumType.Accepted
- case AuthorizationStatus.BLOCKED:
- case AuthorizationStatus.CONCURRENT_TX:
- case AuthorizationStatus.EXPIRED:
- case AuthorizationStatus.INVALID:
- case AuthorizationStatus.NOT_AT_THIS_LOCATION:
- case AuthorizationStatus.NOT_AT_THIS_TIME:
- case AuthorizationStatus.PENDING:
- case AuthorizationStatus.UNKNOWN:
+ case AuthResultStatus.BLOCKED:
+ case AuthResultStatus.CONCURRENT_TX:
+ case AuthResultStatus.EXPIRED:
+ case AuthResultStatus.INVALID:
+ case AuthResultStatus.NOT_AT_THIS_LOCATION:
+ case AuthResultStatus.NOT_AT_THIS_TIME:
+ case AuthResultStatus.PENDING:
+ case AuthResultStatus.UNKNOWN:
default:
return RequestStartStopStatusEnumType.Rejected
}
} from '../types/AuthTypes.js'
import { isEmpty, truncateId } from '../../../../utils/index.js'
-import { AuthorizationStatus } from '../types/AuthTypes.js'
+import { AuthResultStatus } from '../types/AuthTypes.js'
/**
* @param expiryDate - Expiry timestamp to compute TTL from
* @returns AuthorizationResult with isOffline=false
*/
function createRejectedResult (
- status: AuthorizationStatus,
+ status: AuthResultStatus,
method: AuthenticationMethod,
reason?: string
): AuthorizationResult {
* @param status - Authorization status to describe
* @returns Descriptive message for the status
*/
-function getStatusMessage (status: AuthorizationStatus): string {
+function getStatusMessage (status: AuthResultStatus): string {
switch (status) {
- case AuthorizationStatus.ACCEPTED:
+ case AuthResultStatus.ACCEPTED:
return 'Authorization accepted'
- case AuthorizationStatus.BLOCKED:
+ case AuthResultStatus.BLOCKED:
return 'Identifier is blocked'
- case AuthorizationStatus.CONCURRENT_TX:
+ case AuthResultStatus.CONCURRENT_TX:
return 'Concurrent transaction in progress'
- case AuthorizationStatus.EXPIRED:
+ case AuthResultStatus.EXPIRED:
return 'Authorization has expired'
- case AuthorizationStatus.INVALID:
+ case AuthResultStatus.INVALID:
return 'Invalid identifier'
- case AuthorizationStatus.NOT_AT_THIS_LOCATION:
+ case AuthResultStatus.NOT_AT_THIS_LOCATION:
return 'Not authorized at this location'
- case AuthorizationStatus.NOT_AT_THIS_TIME:
+ case AuthResultStatus.NOT_AT_THIS_TIME:
return 'Not authorized at this time'
- case AuthorizationStatus.PENDING:
+ case AuthResultStatus.PENDING:
return 'Authorization pending'
- case AuthorizationStatus.UNKNOWN:
+ case AuthResultStatus.UNKNOWN:
return 'Unknown authorization status'
default:
return 'Authorization failed'
* @returns `true` if BLOCKED, EXPIRED, or INVALID
*/
function isPermanentFailure (result: AuthorizationResult): boolean {
- return [
- AuthorizationStatus.BLOCKED,
- AuthorizationStatus.EXPIRED,
- AuthorizationStatus.INVALID,
- ].includes(result.status)
+ return [AuthResultStatus.BLOCKED, AuthResultStatus.EXPIRED, AuthResultStatus.INVALID].includes(
+ result.status
+ )
}
/**
* @returns `true` if ACCEPTED and expiry date has not passed
*/
function isResultValid (result: AuthorizationResult): boolean {
- if (result.status !== AuthorizationStatus.ACCEPTED) {
+ if (result.status !== AuthResultStatus.ACCEPTED) {
return false
}
* @returns `true` if PENDING or UNKNOWN
*/
function isTemporaryFailure (result: AuthorizationResult): boolean {
- if (result.status === AuthorizationStatus.PENDING) {
+ if (result.status === AuthResultStatus.PENDING) {
return true
}
- if (result.status === AuthorizationStatus.UNKNOWN) {
+ if (result.status === AuthResultStatus.UNKNOWN) {
return true
}
return undefined
}
- const acceptedResult = results.find(r => r.status === AuthorizationStatus.ACCEPTED)
+ const acceptedResult = results.find(r => r.status === AuthResultStatus.ACCEPTED)
if (acceptedResult) {
return acceptedResult
}
getErrorMessage,
isEmpty,
isNotEmptyArray,
+ isOCPP20x,
JSONStringify,
logger,
} from '../../utils/index.js'
if (expectedVersion === OCPPVersion.VERSION_16) {
return s.version !== OCPPVersion.VERSION_16
}
- return s.version !== OCPPVersion.VERSION_20 && s.version !== OCPPVersion.VERSION_201
+ return !isOCPP20x(s.version)
})
if (isNotEmptyArray(mismatched)) {
const ids = mismatched.map(s => s.hashId).join(', ')
isEmpty,
isLoopback,
isNotEmptyArray,
+ isNotEmptyString,
normalizeHost,
normalizeIPAddress,
UI_SERVER_ACCESS_POLICY_DEFAULTS,
}
const nonEmpty = (value: string | undefined): string | undefined =>
- value == null || value.trim() === '' ? undefined : value
+ isNotEmptyString(value) ? value : undefined
// RFC 7239 §6: "unknown" and obfuscated node identifiers ("_" + token chars).
// Optional ":port" suffix is stripped before comparison.
Constants,
ensureError,
getErrorMessage,
+ isEmpty,
isNotEmptyArray,
JSONStringify,
logger,
}
if (
requestContext.outstandingHashIds.delete(hashId) &&
- requestContext.outstandingHashIds.size === 0
+ isEmpty(requestContext.outstandingHashIds)
) {
this.uiServiceWorkerBroadcastChannel.completeReconciledRequest(uuid)
}
) {
return 'untracked'
}
- return requestContext.outstandingHashIds.size === 0 ? 'completed' : 'outstanding'
+ return isEmpty(requestContext.outstandingHashIds) ? 'completed' : 'outstanding'
}
public async requestHandler (
? payload.hashIds
: this.uiServer.getChargingStationHashIds()
)
- if (outstandingHashIds.size === 0) {
+ if (isEmpty(outstandingHashIds)) {
throw new BaseError('No charging station is available to handle the broadcast request')
}
// Safety-net timeout: if not all expected worker responses arrive (e.g. a
}
try {
return watch(Configuration.configurationFile, (event, filename): void => {
- if ((filename?.trim().length ?? 0) === 0 || event !== 'change') {
+ if (!isNotEmptyString(filename) || event !== 'change') {
return
}
if (Configuration.configurationFileReloading) {
import { WorkerProcessType } from '../worker/index.js'
import { CURRENT_CONFIGURATION_SCHEMA_VERSION } from './ConfigurationMigrations.js'
import { isHostLiteralWithoutPort } from './HostUtils.js'
-import { has } from './Utils.js'
+import { has, isNotEmptyArray } from './Utils.js'
// ---------------------------------------------------------------
// Sub-schemas
.optional(),
})
.strict()
- .refine(
- value =>
- !(
- value.allowLoopbackProxy === true &&
- (value.trustedProxies == null || value.trustedProxies.length === 0)
- ),
- {
- message: "'allowLoopbackProxy' requires at least one entry in 'trustedProxies'",
- path: ['trustedProxies'],
- }
- )
+ .refine(value => !(value.allowLoopbackProxy === true && !isNotEmptyArray(value.trustedProxies)), {
+ message: "'allowLoopbackProxy' requires at least one entry in 'trustedProxies'",
+ path: ['trustedProxies'],
+ })
/**
* UIServerListenOptionsObjectSchema — typed object layer for `node:net`
type JsonObject,
MapStringifyFormat,
MessageType,
+ OCPPVersion,
type TimestampedData,
type UUIDv4,
WebSocketCloseEventStatusString,
return false
}
+export const isOCPP20x = (version: OCPPVersion | undefined): boolean =>
+ version === OCPPVersion.VERSION_20 || version === OCPPVersion.VERSION_201
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
export const mergeDeepRight = <T extends object, S extends object>(target: T, source: S): T => {
const output: Record<string, unknown> = { ...(target as Record<string, unknown>) }
isJsonObject,
isNotEmptyArray,
isNotEmptyString,
+ isOCPP20x,
isValidDate,
isValidRandomIntBounds,
JSONStringify,
import type { Worker } from 'node:worker_threads'
-import { type PoolEvent, PoolEvents, type ThreadPoolOptions } from 'poolifier'
+import { type ThreadPoolOptions } from 'poolifier'
export enum WorkerMessageEvents {
addedWorkerElement = 'addedWorkerElement',
stack?: string
}
-export const WorkerEvents = {
- ...PoolEvents,
- ...WorkerSetEvents,
-} as const
-// eslint-disable-next-line @typescript-eslint/no-redeclare
-export type WorkerEvents = PoolEvent | WorkerSetEvents
-
export interface WorkerMessage<T extends WorkerData> {
data: T
event: WorkerMessageEvents
export {
type WorkerData,
type WorkerDataError,
- WorkerEvents,
type WorkerMessage,
WorkerMessageEvents,
WorkerProcessType,
--- /dev/null
+/**
+ * @file Tests for ChargingStation.recordRequestStatistic
+ * @description Verifies the single-sourced `enableStatistics` gate applied before
+ * delegating to performanceStatistics.addRequestStatistic.
+ */
+import assert from 'node:assert/strict'
+import { afterEach, describe, it, mock } from 'node:test'
+
+import type { ChargingStation } from '../../src/charging-station/index.js'
+
+import { ChargingStation as ChargingStationClass } from '../../src/charging-station/ChargingStation.js'
+import { MessageType, RequestCommand } from '../../src/types/index.js'
+import { standardCleanup } from '../helpers/TestLifecycleHelpers.js'
+
+interface RecordRequestStatisticContext {
+ performanceStatistics?: { addRequestStatistic: (...args: unknown[]) => void }
+ stationInfo?: { enableStatistics?: boolean }
+}
+
+const callRecordRequestStatistic = (
+ context: RecordRequestStatisticContext,
+ command: RequestCommand,
+ messageType: MessageType
+): void => {
+ ChargingStationClass.prototype.recordRequestStatistic.call(
+ context as unknown as ChargingStation,
+ command,
+ messageType
+ )
+}
+
+await describe('ChargingStation.recordRequestStatistic', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await it('should record the statistic when enableStatistics is true', () => {
+ const addRequestStatistic = mock.fn()
+ callRecordRequestStatistic(
+ { performanceStatistics: { addRequestStatistic }, stationInfo: { enableStatistics: true } },
+ RequestCommand.HEARTBEAT,
+ MessageType.CALL_MESSAGE
+ )
+ assert.strictEqual(addRequestStatistic.mock.callCount(), 1)
+ assert.deepStrictEqual(addRequestStatistic.mock.calls[0].arguments, [
+ RequestCommand.HEARTBEAT,
+ MessageType.CALL_MESSAGE,
+ ])
+ })
+
+ await it('should not record the statistic when enableStatistics is not true', () => {
+ const addRequestStatistic = mock.fn()
+ callRecordRequestStatistic(
+ { performanceStatistics: { addRequestStatistic }, stationInfo: { enableStatistics: false } },
+ RequestCommand.HEARTBEAT,
+ MessageType.CALL_RESULT_MESSAGE
+ )
+ callRecordRequestStatistic(
+ { performanceStatistics: { addRequestStatistic }, stationInfo: {} },
+ RequestCommand.HEARTBEAT,
+ MessageType.CALL_RESULT_MESSAGE
+ )
+ assert.strictEqual(addRequestStatistic.mock.callCount(), 0)
+ })
+
+ await it('should not throw when performanceStatistics is undefined and enableStatistics is true', () => {
+ assert.doesNotThrow(() => {
+ callRecordRequestStatistic(
+ { stationInfo: { enableStatistics: true } },
+ RequestCommand.HEARTBEAT,
+ MessageType.CALL_ERROR_MESSAGE
+ )
+ })
+ })
+})
})
})
+ await describe('preRequestHook — OCPP 1.6 StartTransaction connector status transition', async () => {
+ await it('should set the connector status to Preparing before sending StartTransaction', async () => {
+ const connectorStatus = station.getConnectorStatus(1)
+ assert.strictEqual(connectorStatus?.status, OCPP16ChargePointStatus.Available)
+
+ await requestService.requestHandler(station, OCPP16RequestCommand.START_TRANSACTION, {
+ connectorId: 1,
+ idTag: 'TEST001',
+ })
+
+ assert.strictEqual(station.getConnectorStatus(1)?.status, OCPP16ChargePointStatus.Preparing)
+ })
+
+ await it('should not change the connector status for a command without a pre-request hook', async () => {
+ const connectorStatus = station.getConnectorStatus(1)
+ assert.strictEqual(connectorStatus?.status, OCPP16ChargePointStatus.Available)
+
+ await requestService.requestHandler(station, OCPP16RequestCommand.AUTHORIZE, {
+ idTag: 'TEST001',
+ })
+
+ assert.strictEqual(station.getConnectorStatus(1)?.status, OCPP16ChargePointStatus.Available)
+ })
+ })
+
await describe('HEARTBEAT — no builder, empty payload', async () => {
await it('should send empty payload for Heartbeat', async () => {
await requestService.requestHandler(station, OCPP16RequestCommand.HEARTBEAT)
import { OCPP16ServiceUtils } from '../../../../src/charging-station/ocpp/1.6/OCPP16ServiceUtils.js'
import {
- AuthorizationStatus,
+ AuthResultStatus,
OCPPAuthServiceFactory,
} from '../../../../src/charging-station/ocpp/auth/index.js'
import {
const authCache = getTestAuthCache(authService)
const cached = authCache.get(TEST_ID_TAG)
assert.ok(cached != null)
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('should update auth cache with rejected status', () => {
const authCache = getTestAuthCache(authService)
const cached = authCache.get(TEST_ID_TAG)
assert.ok(cached != null)
- assert.strictEqual(cached.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(cached.status, AuthResultStatus.BLOCKED)
})
await it('should set TTL from expiryDate when in future', () => {
const authCache = getTestAuthCache(authService)
const cached = authCache.get(TEST_ID_TAG)
assert.ok(cached != null, 'Cache entry should exist with future TTL')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('should skip caching when expiryDate is in the past', () => {
const authCache = getTestAuthCache(authService)
const cached = authCache.get(TEST_ID_TAG)
assert.ok(cached != null, 'Cache entry should exist without TTL')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
})
})
import { OCPP20VariableManager } from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableManager.js'
import {
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
OCPPAuthServiceFactory,
} from '../../../../src/charging-station/ocpp/auth/index.js'
import {
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
})
),
})
return Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
)
}
return Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
)
},
import type { AuthCache } from '../../../../src/charging-station/ocpp/auth/interfaces/OCPPAuthService.js'
import {
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
OCPPAuthServiceFactory,
} from '../../../../src/charging-station/ocpp/auth/index.js'
// Act
authService.updateCacheEntry(
TEST_TOKEN_ISO14443,
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
undefined,
IdentifierType.ISO14443
)
// Assert
const cached = authCache.get(TEST_TOKEN_ISO14443)
assert.ok(cached != null, 'Cache entry should exist')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('C10.FR.09 - should use cacheExpiryDateTime as TTL when present in idTokenInfo', () => {
// Act
authService.updateCacheEntry(
TEST_TOKEN_ISO14443,
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
futureDate,
IdentifierType.ISO14443
)
// Assert
const cached = authCache.get(TEST_TOKEN_ISO14443)
assert.ok(cached != null, 'Cache entry should exist with explicit TTL')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('C10.FR.08 - should use AuthCacheLifeTime as TTL when cacheExpiryDateTime absent', () => {
// Act — no expiryDate, uses config.authorizationCacheLifetime
authService.updateCacheEntry(
TEST_TOKEN_ISO14443,
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
undefined,
IdentifierType.ISO14443
)
// Assert
const cached = authCache.get(TEST_TOKEN_ISO14443)
assert.ok(cached != null, 'Cache entry should exist with default TTL')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('C02.FR.03 - should NOT cache NoAuthorization token type', () => {
// Act
authService.updateCacheEntry(
'',
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
undefined,
IdentifierType.NO_AUTHORIZATION
)
// Act
authService.updateCacheEntry(
'CENTRAL_TOKEN_001',
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
undefined,
IdentifierType.CENTRAL
)
// Act
authService.updateCacheEntry(
'BLOCKED_TOKEN',
- AuthorizationStatus.BLOCKED,
+ AuthResultStatus.BLOCKED,
undefined,
IdentifierType.ISO14443
)
authService.updateCacheEntry(
'EXPIRED_TOKEN',
- AuthorizationStatus.EXPIRED,
+ AuthResultStatus.EXPIRED,
undefined,
IdentifierType.ISO14443
)
// Assert
const cachedBlocked = authCache.get('BLOCKED_TOKEN')
assert.ok(cachedBlocked != null, 'Blocked status should be cached')
- assert.strictEqual(cachedBlocked.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(cachedBlocked.status, AuthResultStatus.BLOCKED)
const cachedExpired = authCache.get('EXPIRED_TOKEN')
assert.ok(cachedExpired != null, 'Expired status should be cached')
- assert.strictEqual(cachedExpired.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(cachedExpired.status, AuthResultStatus.EXPIRED)
})
await it('should skip caching when expiryDate is in the past', () => {
// Act
authService.updateCacheEntry(
TEST_TOKEN_ISO14443,
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
pastDate,
IdentifierType.ISO14443
)
// Act
disabledService.updateCacheEntry(
TEST_TOKEN_ISO14443,
- AuthorizationStatus.ACCEPTED,
+ AuthResultStatus.ACCEPTED,
undefined,
IdentifierType.ISO14443
)
import { OCPP20ServiceUtils } from '../../../../src/charging-station/ocpp/2.0/OCPP20ServiceUtils.js'
import {
- AuthorizationStatus,
+ AuthResultStatus,
OCPPAuthServiceFactory,
} from '../../../../src/charging-station/ocpp/auth/index.js'
import { OCPPAuthServiceImpl } from '../../../../src/charging-station/ocpp/auth/services/OCPPAuthServiceImpl.js'
// Assert
const cached = authCache.get(TEST_TOKEN_ISO14443)
assert.ok(cached != null, 'AuthorizeResponse should update the cache')
- assert.strictEqual(cached.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cached.status, AuthResultStatus.ACCEPTED)
})
await it('C10.FR.01 - should cache non-Accepted status through utility helper', () => {
const cached = authCache.get('BLOCKED_TOKEN_001')
assert.ok(cached != null, 'Blocked status should be cached per C10.FR.01')
- assert.strictEqual(cached.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(cached.status, AuthResultStatus.BLOCKED)
})
await it('should handle auth service initialization failure gracefully', () => {
--- /dev/null
+/**
+ * @file Tests for OCPP 2.0 VariableMetadata predicates
+ * @description Unit tests for the isPersistent/isVolatile/isReadOnly/isWriteOnly
+ * predicates consumed by OCPP20VariableManager.
+ */
+import assert from 'node:assert/strict'
+import { afterEach, describe, it } from 'node:test'
+
+import {
+ isPersistent,
+ isReadOnly,
+ isVolatile,
+ isWriteOnly,
+ type VariableMetadata,
+} from '../../../../src/charging-station/ocpp/2.0/OCPP20VariableRegistry.js'
+import { MutabilityEnumType, PersistenceEnumType } from '../../../../src/types/index.js'
+import { standardCleanup } from '../../../helpers/TestLifecycleHelpers.js'
+
+const buildMetadata = (
+ mutability: MutabilityEnumType,
+ persistence: PersistenceEnumType
+): VariableMetadata => ({ mutability, persistence }) as unknown as VariableMetadata
+
+await describe('OCPP20VariableRegistry metadata predicates', async () => {
+ afterEach(() => {
+ standardCleanup()
+ })
+
+ await it('should detect persistent versus volatile persistence', () => {
+ assert.strictEqual(
+ isPersistent(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Persistent)),
+ true
+ )
+ assert.strictEqual(
+ isPersistent(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Volatile)),
+ false
+ )
+ assert.strictEqual(
+ isVolatile(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Volatile)),
+ true
+ )
+ assert.strictEqual(
+ isVolatile(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Persistent)),
+ false
+ )
+ })
+
+ await it('should detect read-only and write-only mutability', () => {
+ assert.strictEqual(
+ isReadOnly(buildMetadata(MutabilityEnumType.ReadOnly, PersistenceEnumType.Persistent)),
+ true
+ )
+ assert.strictEqual(
+ isReadOnly(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Persistent)),
+ false
+ )
+ assert.strictEqual(
+ isReadOnly(buildMetadata(MutabilityEnumType.WriteOnly, PersistenceEnumType.Persistent)),
+ false
+ )
+ assert.strictEqual(
+ isWriteOnly(buildMetadata(MutabilityEnumType.WriteOnly, PersistenceEnumType.Persistent)),
+ true
+ )
+ assert.strictEqual(
+ isWriteOnly(buildMetadata(MutabilityEnumType.ReadOnly, PersistenceEnumType.Persistent)),
+ false
+ )
+ assert.strictEqual(
+ isWriteOnly(buildMetadata(MutabilityEnumType.ReadWrite, PersistenceEnumType.Persistent)),
+ false
+ )
+ })
+})
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
OCPPAuthServiceFactory,
} from '../../../src/charging-station/ocpp/auth/index.js'
import {
})
injectMockAuthService(station, {
authorize: () =>
- Promise.resolve(createMockAuthorizationResult({ status: AuthorizationStatus.INVALID })),
+ Promise.resolve(createMockAuthorizationResult({ status: AuthResultStatus.INVALID })),
})
// Act
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.CACHE,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.OFFLINE_FALLBACK,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
),
})
return Promise.resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
)
},
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { OCPPVersion } from '../../../../src/types/index.js'
assert.ok(result.timestamp instanceof Date)
assert.strictEqual(typeof result.isOffline, 'boolean')
// Status should be one of the valid authorization statuses
- assert.ok(Object.values(AuthorizationStatus).includes(result.status))
+ assert.ok(Object.values(AuthResultStatus).includes(result.status))
})
await it('should handle multiple auth contexts', async () => {
assert.notStrictEqual(result, undefined)
assert.ok(result.timestamp instanceof Date)
assert.strictEqual(typeof result.isOffline, 'boolean')
- assert.ok(Object.values(AuthorizationStatus).includes(result.status))
+ assert.ok(Object.values(AuthResultStatus).includes(result.status))
})
await it('should handle all auth contexts', async () => {
// Should return a result (not throw) with non-ACCEPTED status
assert.notStrictEqual(result, undefined)
- assert.notStrictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(result.status, AuthResultStatus.ACCEPTED)
})
})
type AuthConfiguration,
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { OCPP16AuthorizationStatus, OCPPVersion } from '../../../../../src/types/index.js'
const result = await adapter.authorizeRemote(identifier, 1, 123)
- assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.status, AuthResultStatus.ACCEPTED)
assert.notStrictEqual(result.method, undefined)
assert.strictEqual(result.isOffline, false)
assert.ok(result.timestamp instanceof Date)
const result = await adapter.authorizeRemote(identifier, 1)
- assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result.status, AuthResultStatus.INVALID)
assert.notStrictEqual(result.additionalInfo?.error, undefined)
})
})
type AuthConfiguration,
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import {
const result = await adapter.authorizeRemote(identifier, 1, 'tx_123')
- assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
assert.strictEqual(result.isOffline, false)
assert.ok(result.timestamp instanceof Date)
const result = await adapter.authorizeRemote(identifier, 1)
- assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result.status, AuthResultStatus.INVALID)
assert.notStrictEqual(result.additionalInfo?.error, undefined)
})
})
await it('should convert rejection statuses to OCPP 2.0 Rejected', () => {
const statuses = [
- AuthorizationStatus.BLOCKED,
- AuthorizationStatus.INVALID,
- AuthorizationStatus.EXPIRED,
+ AuthResultStatus.BLOCKED,
+ AuthResultStatus.INVALID,
+ AuthResultStatus.EXPIRED,
]
for (const status of statuses) {
import { InMemoryAuthCache } from '../../../../../src/charging-station/ocpp/auth/cache/InMemoryAuthCache.js'
import {
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { standardCleanup, withMockTimers } from '../../../../helpers/TestLifecycleHelpers.js'
import { createMockAuthorizationResult } from '../helpers/MockFactories.js'
beforeEach(() => {
mockResult = createMockAuthorizationResult({
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
})
const cachedResult = cache.get(identifier)
assert.notStrictEqual(cachedResult, undefined)
- assert.strictEqual(cachedResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cachedResult?.status, AuthResultStatus.ACCEPTED)
assert.deepStrictEqual(cachedResult.timestamp, mockResult.timestamp)
})
const result = cache.get(identifier)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result?.status, AuthResultStatus.EXPIRED)
})
})
const result = cacheWithShortTTL.get('token')
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result?.status, AuthResultStatus.EXPIRED)
})
})
const result = cache.get('token')
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result?.status, AuthResultStatus.EXPIRED)
})
await it('should handle very large TTL values', () => {
await it('should cache ACCEPTED authorization results', () => {
const mockResult = createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
cache.set('valid-token', mockResult)
const result = cache.get('valid-token')
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await it('should handle BLOCKED authorization results', () => {
const mockResult = createMockAuthorizationResult({
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
cache.set('blocked-token', mockResult)
const result = cache.get('blocked-token')
- assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthResultStatus.BLOCKED)
})
await it('should preserve authorization result metadata', () => {
customField: 'test-value',
reason: 'test-reason',
},
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
cache.set('token', mockResult)
const mockResult = createMockAuthorizationResult({
isOffline: true,
method: AuthenticationMethod.OFFLINE_FALLBACK,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
})
cache.set('offline-token', mockResult)
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
- const blocked = createMockAuthorizationResult({ status: AuthorizationStatus.BLOCKED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
+ const blocked = createMockAuthorizationResult({ status: AuthResultStatus.BLOCKED })
lruCache.set('valid-token', accepted)
lruCache.set('blocked-token', blocked)
const newResult = lruCache.get('new-token')
assert.notStrictEqual(validResult, undefined)
- assert.strictEqual(validResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(validResult?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(blockedResult, undefined)
assert.notStrictEqual(newResult, undefined)
})
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
lruCache.set('token-a', accepted)
lruCache.set('token-b', accepted)
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
shortCache.set('token', accepted)
t.mock.timers.tick(50)
const midResult = shortCache.get('token')
assert.notStrictEqual(midResult, undefined)
- assert.strictEqual(midResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(midResult?.status, AuthResultStatus.ACCEPTED)
t.mock.timers.tick(50)
const lateResult = shortCache.get('token')
assert.notStrictEqual(lateResult, undefined)
- assert.strictEqual(lateResult?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(lateResult?.status, AuthResultStatus.ACCEPTED)
})
})
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
shortCache.set('token', accepted)
t.mock.timers.tick(200)
const result = shortCache.get('token')
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result?.status, AuthResultStatus.EXPIRED)
})
})
})
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
shortCache.set('token', accepted)
t.mock.timers.tick(10)
const result = shortCache.get('token')
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result?.status, AuthResultStatus.EXPIRED)
})
})
rateLimit: { enabled: false },
})
- const accepted = createMockAuthorizationResult({ status: AuthorizationStatus.ACCEPTED })
+ const accepted = createMockAuthorizationResult({ status: AuthResultStatus.ACCEPTED })
shortCache.set('token', accepted)
t.mock.timers.tick(10)
// First access transitions to EXPIRED
const first = shortCache.get('token')
- assert.strictEqual(first?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(first?.status, AuthResultStatus.EXPIRED)
// Second access should still return the entry (now with refreshed TTL as EXPIRED)
const second = shortCache.get('token')
assert.notStrictEqual(second, undefined)
- assert.strictEqual(second?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(second?.status, AuthResultStatus.EXPIRED)
const stats = shortCache.getStats()
assert.strictEqual(stats.totalEntries, 1)
AuthContext,
AuthenticationMethod,
type AuthorizationResult,
- AuthorizationStatus,
type AuthRequest,
+ AuthResultStatus,
type Identifier,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
/**
* Create a mock AuthorizationResult with configurable properties.
*
- * Supports all AuthorizationStatus values: ACCEPTED, INVALID, BLOCKED, EXPIRED, CONCURRENT_TX.
+ * Supports all AuthResultStatus values: ACCEPTED, INVALID, BLOCKED, EXPIRED, CONCURRENT_TX.
* @param overrides - Partial AuthorizationResult properties to override defaults
* @returns Mock AuthorizationResult with specified properties (defaults to ACCEPTED from local list)
* @example
* const accepted = createMockAuthorizationResult()
*
* // Rejected with INVALID status
- * const rejected = createMockAuthorizationResult({ status: AuthorizationStatus.INVALID })
+ * const rejected = createMockAuthorizationResult({ status: AuthResultStatus.INVALID })
*
* // Blocked with custom method
* const blocked = createMockAuthorizationResult({
- * status: AuthorizationStatus.BLOCKED,
+ * status: AuthResultStatus.BLOCKED,
* method: AuthenticationMethod.REMOTE_AUTHORIZATION
* })
*
* // Expired with custom expiry date
* const expired = createMockAuthorizationResult({
- * status: AuthorizationStatus.EXPIRED,
+ * status: AuthResultStatus.EXPIRED,
* expiryDate: new Date(Date.now() - 1000)
* })
* ```
export const createMockAuthorizationResult = (
overrides?: Partial<AuthorizationResult>
): AuthorizationResult => {
- const status = overrides?.status ?? AuthorizationStatus.ACCEPTED
+ const status = overrides?.status ?? AuthResultStatus.ACCEPTED
return {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
status,
timestamp: new Date(),
// For expired status, include a default expiryDate in the past
- ...(status === AuthorizationStatus.EXPIRED && { expiryDate: new Date(Date.now() - 1000) }),
+ ...(status === AuthResultStatus.EXPIRED && { expiryDate: new Date(Date.now() - 1000) }),
...overrides,
}
}
result: AuthorizationResult,
expectedMethod?: AuthenticationMethod
): void => {
- assert.strictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result.status, AuthResultStatus.ACCEPTED)
assert.ok(result.timestamp instanceof Date)
if (expectedMethod !== undefined) {
assert.strictEqual(result.method, expectedMethod)
*/
export const expectRejectedAuthorization = (
result: AuthorizationResult,
- expectedStatus: AuthorizationStatus = AuthorizationStatus.INVALID
+ expectedStatus: AuthResultStatus = AuthResultStatus.INVALID
): void => {
assert.strictEqual(result.status, expectedStatus)
- assert.notStrictEqual(result.status, AuthorizationStatus.ACCEPTED)
+ assert.notStrictEqual(result.status, AuthResultStatus.ACCEPTED)
assert.ok(result.timestamp instanceof Date)
}
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
type Identifier,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
timestamp: new Date(),
})
- assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result.status, AuthResultStatus.INVALID)
assert.strictEqual(result.method, AuthenticationMethod.NONE)
})
})
timestamp: new Date(),
})
- assert.strictEqual(result.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result.status, AuthResultStatus.INVALID)
})
})
import {
AuthenticationMethod,
type AuthorizationResult,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { OCPPVersion } from '../../../../../src/types/index.js'
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.CERTIFICATE_BASED)
})
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthResultStatus.BLOCKED)
})
await it('should reject revoked certificates', async () => {
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthResultStatus.BLOCKED)
})
await it('should handle missing certificate data', async () => {
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result?.status, AuthResultStatus.INVALID)
})
await it('should handle invalid hash algorithm', async () => {
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result?.status, AuthResultStatus.INVALID)
})
await it('should handle invalid hash format', async () => {
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(result?.status, AuthResultStatus.INVALID)
})
})
import { LocalAuthStrategy } from '../../../../../src/charging-station/ocpp/auth/strategies/LocalAuthStrategy.js'
import {
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js'
// Arrange
const blockedResult = createMockAuthorizationResult({
method: AuthenticationMethod.CACHE,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
const mockAuthCache = createMockAuthCache({
get: () => blockedResult,
// Assert
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthResultStatus.BLOCKED)
assert.strictEqual(result.method, AuthenticationMethod.CACHE)
})
// Arrange
const blockedResult = createMockAuthorizationResult({
method: AuthenticationMethod.CACHE,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
const mockAuthCache = createMockAuthCache({
get: () => blockedResult,
const result = strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result?.status, AuthResultStatus.BLOCKED)
assert.strictEqual(result.method, AuthenticationMethod.LOCAL_LIST)
})
})
// Arrange
const blockedResult = createMockAuthorizationResult({
method: AuthenticationMethod.CACHE,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
const mockAuthCache = createMockAuthCache({
get: () => blockedResult,
import {
AuthContext,
AuthenticationMethod,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js'
const result = strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.LOCAL_LIST)
})
const result = strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.CACHE)
})
const result = strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.OFFLINE_FALLBACK)
assert.strictEqual(result.isOffline, true)
})
import {
AuthenticationMethod,
type AuthorizationResult,
- AuthorizationStatus,
+ AuthResultStatus,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { OCPPVersion } from '../../../../../src/types/index.js'
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
await strategy.authenticate(request, config)
assert.strictEqual(cachedKey, 'CACHE_TAG')
- assert.strictEqual(cachedValue?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cachedValue?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(cachedTtl, 300)
})
resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
})
)
})
await strategy.authenticate(request, config)
assert.strictEqual(cachedKey, 'BLOCKED_TAG')
- assert.strictEqual(cachedValue?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(cachedValue?.status, AuthResultStatus.BLOCKED)
assert.strictEqual(cachedTtl, 300)
})
resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.EXPIRED,
+ status: AuthResultStatus.EXPIRED,
})
)
})
await strategy.authenticate(request, config)
assert.strictEqual(cachedKey, 'EXPIRED_TAG')
- assert.strictEqual(cachedValue?.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(cachedValue?.status, AuthResultStatus.EXPIRED)
assert.strictEqual(cachedTtl, 300)
})
resolve(
createMockAuthorizationResult({
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
})
)
})
await strategy.authenticate(request, config)
assert.strictEqual(cachedKey, TEST_ID_TAG_INVALID)
- assert.strictEqual(cachedValue?.status, AuthorizationStatus.INVALID)
+ assert.strictEqual(cachedValue?.status, AuthResultStatus.INVALID)
assert.strictEqual(cachedTtl, 300)
})
await strategy.authenticate(request, config)
assert.strictEqual(cachedKey, 'ACCEPTED_TAG')
- assert.strictEqual(cachedValue?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(cachedValue?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(cachedTtl, 300)
})
const result = await strategy.authenticate(request, config)
assert.notStrictEqual(result, undefined)
- assert.strictEqual(result?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result?.status, AuthResultStatus.ACCEPTED)
})
await it('C13.FR.01.T01 - should not cache identifier that is in local auth list', async () => {
AuthContext,
AuthenticationError,
AuthErrorCode,
- AuthorizationStatus,
+ AuthResultStatus,
type Identifier,
IdentifierType,
isCertificateBased,
await describe('OCPP 1.6 Status Mapping', async () => {
await it('should map OCPP 1.6 ACCEPTED to ACCEPTED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.ACCEPTED)
- assert.strictEqual(result, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(result, AuthResultStatus.ACCEPTED)
})
await it('should map OCPP 1.6 BLOCKED to BLOCKED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.BLOCKED)
- assert.strictEqual(result, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result, AuthResultStatus.BLOCKED)
})
await it('should map OCPP 1.6 EXPIRED to EXPIRED', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.EXPIRED)
- assert.strictEqual(result, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result, AuthResultStatus.EXPIRED)
})
await it('should map OCPP 1.6 INVALID to INVALID', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.INVALID)
- assert.strictEqual(result, AuthorizationStatus.INVALID)
+ assert.strictEqual(result, AuthResultStatus.INVALID)
})
await it('should map OCPP 1.6 CONCURRENT_TX to CONCURRENT_TX', () => {
const result = mapOCPP16Status(OCPP16AuthorizationStatus.CONCURRENT_TX)
- assert.strictEqual(result, AuthorizationStatus.CONCURRENT_TX)
+ assert.strictEqual(result, AuthResultStatus.CONCURRENT_TX)
})
})
await describe('Auth to OCPP 1.6 Status Mapping', async () => {
await it('should map ACCEPTED to OCPP 1.6 ACCEPTED', () => {
- const result = mapToOCPP16Status(AuthorizationStatus.ACCEPTED)
+ const result = mapToOCPP16Status(AuthResultStatus.ACCEPTED)
assert.strictEqual(result, OCPP16AuthorizationStatus.ACCEPTED)
})
await it('should map BLOCKED to OCPP 1.6 BLOCKED', () => {
- const result = mapToOCPP16Status(AuthorizationStatus.BLOCKED)
+ const result = mapToOCPP16Status(AuthResultStatus.BLOCKED)
assert.strictEqual(result, OCPP16AuthorizationStatus.BLOCKED)
})
await it('should map EXPIRED to OCPP 1.6 EXPIRED', () => {
- const result = mapToOCPP16Status(AuthorizationStatus.EXPIRED)
+ const result = mapToOCPP16Status(AuthResultStatus.EXPIRED)
assert.strictEqual(result, OCPP16AuthorizationStatus.EXPIRED)
})
await it('should map unsupported statuses to OCPP 1.6 INVALID', () => {
assert.strictEqual(
- mapToOCPP16Status(AuthorizationStatus.PENDING),
+ mapToOCPP16Status(AuthResultStatus.PENDING),
OCPP16AuthorizationStatus.INVALID
)
assert.strictEqual(
- mapToOCPP16Status(AuthorizationStatus.UNKNOWN),
+ mapToOCPP16Status(AuthResultStatus.UNKNOWN),
OCPP16AuthorizationStatus.INVALID
)
assert.strictEqual(
- mapToOCPP16Status(AuthorizationStatus.NOT_AT_THIS_LOCATION),
+ mapToOCPP16Status(AuthResultStatus.NOT_AT_THIS_LOCATION),
OCPP16AuthorizationStatus.INVALID
)
})
await describe('Auth to OCPP 2.0 Status Mapping', async () => {
await it('should map ACCEPTED to OCPP 2.0 Accepted', () => {
- const result = mapToOCPP20Status(AuthorizationStatus.ACCEPTED)
+ const result = mapToOCPP20Status(AuthResultStatus.ACCEPTED)
assert.strictEqual(result, RequestStartStopStatusEnumType.Accepted)
})
await it('should map rejection statuses to OCPP 2.0 Rejected', () => {
assert.strictEqual(
- mapToOCPP20Status(AuthorizationStatus.BLOCKED),
+ mapToOCPP20Status(AuthResultStatus.BLOCKED),
RequestStartStopStatusEnumType.Rejected
)
assert.strictEqual(
- mapToOCPP20Status(AuthorizationStatus.INVALID),
+ mapToOCPP20Status(AuthResultStatus.INVALID),
RequestStartStopStatusEnumType.Rejected
)
assert.strictEqual(
- mapToOCPP20Status(AuthorizationStatus.EXPIRED),
+ mapToOCPP20Status(AuthResultStatus.EXPIRED),
RequestStartStopStatusEnumType.Rejected
)
})
import {
type AuthConfiguration,
AuthenticationError,
- AuthorizationStatus,
+ AuthResultStatus,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
import { AuthConfigValidator } from '../../../../../src/charging-station/ocpp/auth/utils/AuthConfigValidator.js'
import { standardCleanup } from '../../../../helpers/TestLifecycleHelpers.js'
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.doesNotThrow(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: -1,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 0,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000.5,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.throws(() => {
localPreAuthorize: false,
maxCacheEntries: 1000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.doesNotThrow(() => {
localPreAuthorize: false,
maxCacheEntries: 1,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.doesNotThrow(() => {
localPreAuthorize: false,
maxCacheEntries: 10000,
offlineAuthorizationEnabled: true,
- unknownIdAuthorization: AuthorizationStatus.INVALID,
+ unknownIdAuthorization: AuthResultStatus.INVALID,
}
assert.doesNotThrow(() => {
AuthContext,
AuthenticationMethod,
type AuthorizationResult,
- AuthorizationStatus,
+ AuthResultStatus,
type Identifier,
IdentifierType,
} from '../../../../../src/charging-station/ocpp/auth/types/AuthTypes.js'
await describe('createRejectedResult', async () => {
await it('should create rejected result without reason', () => {
const result = AuthHelpers.createRejectedResult(
- AuthorizationStatus.BLOCKED,
+ AuthResultStatus.BLOCKED,
AuthenticationMethod.LOCAL_LIST
)
- assert.strictEqual(result.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(result.status, AuthResultStatus.BLOCKED)
assert.strictEqual(result.method, AuthenticationMethod.LOCAL_LIST)
assert.strictEqual(result.isOffline, false)
assert.ok(result.timestamp instanceof Date)
await it('should create rejected result with reason', () => {
const result = AuthHelpers.createRejectedResult(
- AuthorizationStatus.EXPIRED,
+ AuthResultStatus.EXPIRED,
AuthenticationMethod.REMOTE_AUTHORIZATION,
'Token expired on 2024-01-01'
)
- assert.strictEqual(result.status, AuthorizationStatus.EXPIRED)
+ assert.strictEqual(result.status, AuthResultStatus.EXPIRED)
assert.strictEqual(result.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
assert.deepStrictEqual(result.additionalInfo, { reason: 'Token expired on 2024-01-01' })
})
await describe('getStatusMessage', async () => {
await it('should return message for ACCEPTED status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.ACCEPTED),
+ AuthHelpers.getStatusMessage(AuthResultStatus.ACCEPTED),
'Authorization accepted'
)
})
await it('should return message for BLOCKED status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.BLOCKED),
+ AuthHelpers.getStatusMessage(AuthResultStatus.BLOCKED),
'Identifier is blocked'
)
})
await it('should return message for EXPIRED status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.EXPIRED),
+ AuthHelpers.getStatusMessage(AuthResultStatus.EXPIRED),
'Authorization has expired'
)
})
await it('should return message for INVALID status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.INVALID),
+ AuthHelpers.getStatusMessage(AuthResultStatus.INVALID),
'Invalid identifier'
)
})
await it('should return message for CONCURRENT_TX status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.CONCURRENT_TX),
+ AuthHelpers.getStatusMessage(AuthResultStatus.CONCURRENT_TX),
'Concurrent transaction in progress'
)
})
await it('should return message for NOT_AT_THIS_LOCATION status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_LOCATION),
+ AuthHelpers.getStatusMessage(AuthResultStatus.NOT_AT_THIS_LOCATION),
'Not authorized at this location'
)
})
await it('should return message for NOT_AT_THIS_TIME status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.NOT_AT_THIS_TIME),
+ AuthHelpers.getStatusMessage(AuthResultStatus.NOT_AT_THIS_TIME),
'Not authorized at this time'
)
})
await it('should return message for PENDING status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.PENDING),
+ AuthHelpers.getStatusMessage(AuthResultStatus.PENDING),
'Authorization pending'
)
})
await it('should return message for UNKNOWN status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.UNKNOWN),
+ AuthHelpers.getStatusMessage(AuthResultStatus.UNKNOWN),
'Unknown authorization status'
)
})
await it('should return generic message for unknown status', () => {
assert.strictEqual(
- AuthHelpers.getStatusMessage(AuthorizationStatus.NO_CREDIT),
+ AuthHelpers.getStatusMessage(AuthResultStatus.NO_CREDIT),
'Authorization failed'
)
})
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.EXPIRED,
+ status: AuthResultStatus.EXPIRED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.INVALID,
+ status: AuthResultStatus.INVALID,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.PENDING,
+ status: AuthResultStatus.PENDING,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
expiryDate: new Date(Date.now() - 1000),
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
expiryDate: new Date(Date.now() + 10000),
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.PENDING,
+ status: AuthResultStatus.PENDING,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.UNKNOWN,
+ status: AuthResultStatus.UNKNOWN,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date(),
}
const result: AuthorizationResult = {
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
}
{
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date(),
},
{
isOffline: false,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
},
{
isOffline: false,
method: AuthenticationMethod.CERTIFICATE_BASED,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date(),
},
]
const merged = AuthHelpers.mergeAuthResults(results)
- assert.strictEqual(merged?.status, AuthorizationStatus.ACCEPTED)
+ assert.strictEqual(merged?.status, AuthResultStatus.ACCEPTED)
assert.strictEqual(merged.method, AuthenticationMethod.REMOTE_AUTHORIZATION)
})
{
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date(),
},
{
isOffline: true,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.EXPIRED,
+ status: AuthResultStatus.EXPIRED,
timestamp: new Date(),
},
]
const merged = AuthHelpers.mergeAuthResults(results)
- assert.strictEqual(merged?.status, AuthorizationStatus.BLOCKED)
+ assert.strictEqual(merged?.status, AuthResultStatus.BLOCKED)
assert.strictEqual(merged.method, AuthenticationMethod.LOCAL_LIST)
assert.strictEqual(merged.isOffline, true)
assert.deepStrictEqual(merged.additionalInfo, {
content: 'Welcome',
format: OCPP20MessageFormatEnumType.ASCII,
},
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: new Date('2024-01-01T00:00:00Z'),
}
hasPersonalMessage: true,
isOffline: false,
method: AuthenticationMethod.LOCAL_LIST,
- status: AuthorizationStatus.ACCEPTED,
+ status: AuthResultStatus.ACCEPTED,
timestamp: '2024-01-01T00:00:00.000Z',
})
})
const result: AuthorizationResult = {
isOffline: true,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: new Date('2024-06-15T12:30:45Z'),
}
hasPersonalMessage: false,
isOffline: true,
method: AuthenticationMethod.REMOTE_AUTHORIZATION,
- status: AuthorizationStatus.BLOCKED,
+ status: AuthResultStatus.BLOCKED,
timestamp: '2024-06-15T12:30:45.000Z',
})
})
import type { TimestampedData } from '../../src/types/index.js'
import { JSRuntime, runtime } from '../../scripts/runtime.js'
-import { MapStringifyFormat, MessageType } from '../../src/types/index.js'
+import { MapStringifyFormat, MessageType, OCPPVersion } from '../../src/types/index.js'
import { Constants } from '../../src/utils/index.js'
import {
clampToSafeTimerValue,
isEmpty,
isNotEmptyArray,
isNotEmptyString,
+ isOCPP20x,
isValidDate,
JSONStringify,
logPrefix,
assert.strictEqual(isEmpty(new WeakSet()), false)
})
+ await it('should detect OCPP 2.0.x versions', () => {
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_20), true)
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_201), true)
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_16), false)
+ assert.strictEqual(isOCPP20x(undefined), false)
+ })
+
await it('should detect non-empty strings correctly', () => {
assert.strictEqual(isNotEmptyString(''), false)
assert.strictEqual(isNotEmptyString(' '), false)
import { randomUUID, validateUUID } from '../utils/UUID.js'
import { WebSocketReadyState } from './types.js'
-export { ServerFailureError } from '../errors.js'
-
export class WebSocketClient {
public get connected (): boolean {
return this.ws?.readyState === WebSocketReadyState.OPEN
import type { WebSocketFactory } from '../src/client/types.js'
import type { ResponsePayload } from '../src/types/UIProtocol.js'
-import { ServerFailureError, WebSocketClient } from '../src/client/WebSocketClient.js'
+import { WebSocketClient } from '../src/client/WebSocketClient.js'
import { DEFAULT_HOST, DEFAULT_PORT } from '../src/constants.js'
+import { ServerFailureError } from '../src/errors.js'
import {
AuthenticationType,
ProcedureName,