import { Command, Option } from 'commander'
-import { OCPP20IdTokenEnumType, OCPPVersion, ProcedureName, type RequestPayload } from 'ui-common'
+import { buildAuthorizePayload, OCPPVersion, ProcedureName, type RequestPayload } from 'ui-common'
import {
handleActionErrors,
program,
hashIds
)
- switch (ocppVersion) {
- case OCPPVersion.VERSION_16:
- payload = {
- idTag: options.idTag,
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
- payload = {
- idToken: { idToken: options.idTag, type: OCPP20IdTokenEnumType.ISO14443 },
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- default:
- throw new Error(UNSUPPORTED_OCPP_VERSION_ERROR)
+ payload = {
+ ...buildAuthorizePayload(options.idTag, ocppVersion),
+ ...buildHashIdsPayload(resolvedHashIds),
}
await runAction(program, ProcedureName.AUTHORIZE, payload, undefined, config)
} else {
import { Command, Option } from 'commander'
import {
- OCPP20IdTokenEnumType,
- OCPP20TransactionEventEnumType,
+ buildStartTransactionPayload,
+ buildStopTransactionPayload,
+ isOCPP20x,
OCPPVersion,
ProcedureName,
type RequestPayload,
export const createTransactionCommands = (program: Command): Command => {
const cmd = new Command('transaction').description('Transaction management')
- const UNSUPPORTED_OCPP_VERSION_ERROR =
- 'Unsupported OCPP version for this command. ' +
- 'Use ocpp transaction-event -p to pass the payload directly.'
cmd
.command('start [hashIds...]')
program,
hashIds
)
- switch (ocppVersion) {
- case OCPPVersion.VERSION_16:
- procedureName = ProcedureName.START_TRANSACTION
- payload = {
- connectorId: options.connectorId,
- idTag: options.idTag,
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
- procedureName = ProcedureName.TRANSACTION_EVENT
- payload = {
- connectorId: options.connectorId,
- eventType: OCPP20TransactionEventEnumType.STARTED,
- ...(options.evseId != null && { evseId: options.evseId }),
- idToken: { idToken: options.idTag, type: OCPP20IdTokenEnumType.ISO14443 },
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- default:
- throw new Error(UNSUPPORTED_OCPP_VERSION_ERROR)
+ const { payload: built, procedureName: proc } = buildStartTransactionPayload(
+ options.connectorId,
+ ocppVersion,
+ { evseId: options.evseId, idTag: options.idTag }
+ )
+ procedureName =
+ proc === 'transactionEvent'
+ ? ProcedureName.TRANSACTION_EVENT
+ : ProcedureName.START_TRANSACTION
+ payload = {
+ ...built,
+ ...buildHashIdsPayload(resolvedHashIds),
}
await runAction(program, procedureName, payload, undefined, config)
} else {
program,
hashIds
)
- switch (ocppVersion) {
- case OCPPVersion.VERSION_16:
- procedureName = ProcedureName.STOP_TRANSACTION
- payload = {
- transactionId: parseInteger(
- options.transactionId,
- '--transaction-id (OCPP 1.6 requires integer)'
- ),
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- case OCPPVersion.VERSION_20:
- case OCPPVersion.VERSION_201:
- if (options.connectorId == null) {
- throw new Error('--connector-id is required for OCPP 2.0.x stations')
- }
- procedureName = ProcedureName.TRANSACTION_EVENT
- payload = {
- connectorId: options.connectorId,
- eventType: OCPP20TransactionEventEnumType.ENDED,
- transactionId: options.transactionId,
- ...buildHashIdsPayload(resolvedHashIds),
- }
- break
- default:
- throw new Error(UNSUPPORTED_OCPP_VERSION_ERROR)
+ if (isOCPP20x(ocppVersion) && options.connectorId == null) {
+ throw new Error('--connector-id is required for OCPP 2.0.x stations')
+ }
+ const { payload: built, procedureName: proc } = buildStopTransactionPayload(
+ ocppVersion === OCPPVersion.VERSION_16
+ ? parseInteger(
+ options.transactionId,
+ '--transaction-id (OCPP 1.6 requires integer)'
+ )
+ : options.transactionId,
+ ocppVersion,
+ options.connectorId
+ )
+ procedureName =
+ proc === 'transactionEvent'
+ ? ProcedureName.TRANSACTION_EVENT
+ : ProcedureName.STOP_TRANSACTION
+ payload = {
+ ...built,
+ ...buildHashIdsPayload(resolvedHashIds),
}
await runAction(program, procedureName, payload, undefined, config)
} else {
export * from './types/UIProtocol.js'
export * from './types/UUID.js'
export * from './utils/converters.js'
+export * from './utils/payloadBuilders.js'
export * from './utils/UUID.js'
export * from './utils/websocket.js'
--- /dev/null
+import type { RequestPayload } from '../types/UIProtocol.js'
+
+import {
+ OCPP20IdTokenEnumType,
+ type OCPP20IdTokenType,
+ OCPP20TransactionEventEnumType,
+ OCPPVersion,
+} from '../types/ChargingStationType.js'
+
+/**
+ * Builds an Authorize request payload adapted to the station's OCPP version.
+ * @param idTag - RFID tag identifier
+ * @param ocppVersion - Target OCPP version (1.6 format if undefined)
+ * @returns Payload with flat `idTag` for 1.6 or nested `idToken` for 2.0.x
+ */
+export function buildAuthorizePayload (
+ idTag: string,
+ ocppVersion: OCPPVersion | undefined
+): RequestPayload {
+ if (isOCPP20x(ocppVersion)) {
+ return {
+ idToken: { idToken: idTag, type: OCPP20IdTokenEnumType.ISO14443 },
+ }
+ }
+ assertOCPP16OrUndefined(ocppVersion)
+ return { idTag }
+}
+
+/**
+ * Builds an OCPP 2.0.x IdTokenType object.
+ * @param idTag - RFID tag identifier
+ * @param type - Token type enumeration value
+ * @returns IdTokenType object
+ */
+export function buildIdToken (
+ idTag: string,
+ type: OCPP20IdTokenEnumType = OCPP20IdTokenEnumType.ISO14443
+): OCPP20IdTokenType {
+ return { idToken: idTag, type }
+}
+
+/**
+ * Builds a StartTransaction/TransactionEvent payload adapted to the station's OCPP version.
+ * @param connectorId - Connector identifier
+ * @param ocppVersion - Target OCPP version
+ * @param options - Optional fields
+ * @param options.evseId - EVSE identifier (OCPP 2.0.x only)
+ * @param options.idTag - RFID tag identifier
+ * @returns Payload and procedure name to use
+ */
+export function buildStartTransactionPayload (
+ connectorId: number,
+ ocppVersion: OCPPVersion | undefined,
+ options?: { evseId?: number; idTag?: string }
+): { payload: RequestPayload; procedureName: 'startTransaction' | 'transactionEvent' } {
+ if (isOCPP20x(ocppVersion)) {
+ return {
+ payload: {
+ connectorId,
+ eventType: OCPP20TransactionEventEnumType.STARTED,
+ ...(options?.evseId != null && { evseId: options.evseId }),
+ ...(options?.idTag != null && {
+ idToken: { idToken: options.idTag, type: OCPP20IdTokenEnumType.ISO14443 },
+ }),
+ },
+ procedureName: 'transactionEvent',
+ }
+ }
+ assertOCPP16OrUndefined(ocppVersion)
+ return {
+ payload: { connectorId, ...(options?.idTag != null && { idTag: options.idTag }) },
+ procedureName: 'startTransaction',
+ }
+}
+
+/**
+ * Builds a StopTransaction/TransactionEvent payload adapted to the station's OCPP version.
+ * @param transactionId - Transaction identifier (integer for 1.6, string for 2.0.x)
+ * @param ocppVersion - Target OCPP version
+ * @param connectorId - Connector identifier (OCPP 2.0.x only)
+ * @returns Payload and procedure name to use
+ */
+export function buildStopTransactionPayload (
+ transactionId: number | string,
+ ocppVersion: OCPPVersion | undefined,
+ connectorId?: number
+): { payload: RequestPayload; procedureName: 'stopTransaction' | 'transactionEvent' } {
+ if (isOCPP20x(ocppVersion)) {
+ return {
+ payload: {
+ ...(connectorId != null && { connectorId }),
+ eventType: OCPP20TransactionEventEnumType.ENDED,
+ transactionId: transactionId.toString(),
+ },
+ procedureName: 'transactionEvent',
+ }
+ }
+ assertOCPP16OrUndefined(ocppVersion)
+ return {
+ payload: { transactionId },
+ procedureName: 'stopTransaction',
+ }
+}
+
+/**
+ * Checks whether the given OCPP version is 2.0 or 2.0.1.
+ * @param version - OCPP version to check
+ * @returns `true` if version is 2.0 or 2.0.1
+ */
+export function isOCPP20x (version: OCPPVersion | undefined): boolean {
+ return version === OCPPVersion.VERSION_20 || version === OCPPVersion.VERSION_201
+}
+
+/**
+ * Asserts that the OCPP version is 1.6 or undefined (legacy default).
+ * @param version - OCPP version to validate
+ * @throws {Error} if version is not 1.6, undefined, or a known 2.0.x variant
+ */
+function assertOCPP16OrUndefined (version: OCPPVersion | undefined): void {
+ if (version != null && version !== OCPPVersion.VERSION_16) {
+ throw new Error(`Unsupported OCPP version for payload building: ${version}`)
+ }
+}
--- /dev/null
+import assert from 'node:assert'
+import { describe, it } from 'node:test'
+
+import {
+ OCPP20IdTokenEnumType,
+ OCPP20TransactionEventEnumType,
+ OCPPVersion,
+} from '../src/types/ChargingStationType.js'
+import {
+ buildAuthorizePayload,
+ buildIdToken,
+ buildStartTransactionPayload,
+ buildStopTransactionPayload,
+ isOCPP20x,
+} from '../src/utils/payloadBuilders.js'
+
+await describe('payloadBuilders', async () => {
+ await describe('isOCPP20x', async () => {
+ await it('should return true for VERSION_20', () => {
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_20), true)
+ })
+
+ await it('should return true for VERSION_201', () => {
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_201), true)
+ })
+
+ await it('should return false for VERSION_16', () => {
+ assert.strictEqual(isOCPP20x(OCPPVersion.VERSION_16), false)
+ })
+
+ await it('should return false for undefined', () => {
+ assert.strictEqual(isOCPP20x(undefined), false)
+ })
+ })
+
+ await describe('buildAuthorizePayload', async () => {
+ await it('should build flat idTag payload for OCPP 1.6', () => {
+ const result = buildAuthorizePayload('RFID123', OCPPVersion.VERSION_16)
+ assert.deepStrictEqual(result, { idTag: 'RFID123' })
+ })
+
+ await it('should build idToken payload for OCPP 2.0', () => {
+ const result = buildAuthorizePayload('RFID123', OCPPVersion.VERSION_20)
+ assert.deepStrictEqual(result, {
+ idToken: { idToken: 'RFID123', type: OCPP20IdTokenEnumType.ISO14443 },
+ })
+ })
+
+ await it('should build idToken payload for OCPP 2.0.1', () => {
+ const result = buildAuthorizePayload('RFID123', OCPPVersion.VERSION_201)
+ assert.deepStrictEqual(result, {
+ idToken: { idToken: 'RFID123', type: OCPP20IdTokenEnumType.ISO14443 },
+ })
+ })
+
+ await it('should default to OCPP 1.6 format when version is undefined', () => {
+ const result = buildAuthorizePayload('RFID123', undefined)
+ assert.deepStrictEqual(result, { idTag: 'RFID123' })
+ })
+
+ await it('should throw on unsupported OCPP version', () => {
+ assert.throws(
+ () => buildAuthorizePayload('RFID123', '3.0' as unknown as OCPPVersion),
+ (error: Error) => error.message.includes('Unsupported OCPP version')
+ )
+ })
+ })
+
+ await describe('buildStartTransactionPayload', async () => {
+ await it('should build OCPP 1.6 payload with startTransaction procedure', () => {
+ const result = buildStartTransactionPayload(1, OCPPVersion.VERSION_16, { idTag: 'TAG1' })
+ assert.deepStrictEqual(result, {
+ payload: { connectorId: 1, idTag: 'TAG1' },
+ procedureName: 'startTransaction',
+ })
+ })
+
+ await it('should build OCPP 2.0.1 payload with transactionEvent procedure', () => {
+ const result = buildStartTransactionPayload(1, OCPPVersion.VERSION_201, { idTag: 'TAG1' })
+ assert.deepStrictEqual(result, {
+ payload: {
+ connectorId: 1,
+ eventType: OCPP20TransactionEventEnumType.STARTED,
+ idToken: { idToken: 'TAG1', type: OCPP20IdTokenEnumType.ISO14443 },
+ },
+ procedureName: 'transactionEvent',
+ })
+ })
+
+ await it('should include evseId for OCPP 2.0.x when provided', () => {
+ const result = buildStartTransactionPayload(1, OCPPVersion.VERSION_201, {
+ evseId: 2,
+ idTag: 'TAG1',
+ })
+ assert.deepStrictEqual(result, {
+ payload: {
+ connectorId: 1,
+ eventType: OCPP20TransactionEventEnumType.STARTED,
+ evseId: 2,
+ idToken: { idToken: 'TAG1', type: OCPP20IdTokenEnumType.ISO14443 },
+ },
+ procedureName: 'transactionEvent',
+ })
+ })
+
+ await it('should not include evseId for OCPP 1.6 even if provided', () => {
+ const result = buildStartTransactionPayload(1, OCPPVersion.VERSION_16, {
+ evseId: 2,
+ idTag: 'TAG1',
+ })
+ assert.deepStrictEqual(result, {
+ payload: { connectorId: 1, idTag: 'TAG1' },
+ procedureName: 'startTransaction',
+ })
+ })
+
+ await it('should omit idTag/idToken when not provided', () => {
+ const result = buildStartTransactionPayload(1, OCPPVersion.VERSION_201)
+ assert.deepStrictEqual(result, {
+ payload: {
+ connectorId: 1,
+ eventType: OCPP20TransactionEventEnumType.STARTED,
+ },
+ procedureName: 'transactionEvent',
+ })
+ })
+ })
+
+ await describe('buildStopTransactionPayload', async () => {
+ await it('should build OCPP 1.6 payload with stopTransaction procedure', () => {
+ const result = buildStopTransactionPayload(12345, OCPPVersion.VERSION_16)
+ assert.deepStrictEqual(result, {
+ payload: { transactionId: 12345 },
+ procedureName: 'stopTransaction',
+ })
+ })
+
+ await it('should build OCPP 2.0.1 payload with transactionEvent procedure', () => {
+ const result = buildStopTransactionPayload('uuid-123', OCPPVersion.VERSION_201, 1)
+ assert.deepStrictEqual(result, {
+ payload: {
+ connectorId: 1,
+ eventType: OCPP20TransactionEventEnumType.ENDED,
+ transactionId: 'uuid-123',
+ },
+ procedureName: 'transactionEvent',
+ })
+ })
+
+ await it('should convert numeric transactionId to string for OCPP 2.0.x', () => {
+ const result = buildStopTransactionPayload(99, OCPPVersion.VERSION_201, 1)
+ assert.strictEqual((result.payload as Record<string, unknown>).transactionId, '99')
+ })
+
+ await it('should omit connectorId for OCPP 2.0.x when not provided', () => {
+ const result = buildStopTransactionPayload('uuid-123', OCPPVersion.VERSION_201)
+ assert.strictEqual((result.payload as Record<string, unknown>).connectorId, undefined)
+ })
+ })
+
+ await describe('buildIdToken', async () => {
+ await it('should build idToken with default ISO14443 type', () => {
+ assert.deepStrictEqual(buildIdToken('TAG1'), {
+ idToken: 'TAG1',
+ type: OCPP20IdTokenEnumType.ISO14443,
+ })
+ })
+
+ await it('should build idToken with custom type', () => {
+ assert.deepStrictEqual(buildIdToken('TAG1', OCPP20IdTokenEnumType.CENTRAL), {
+ idToken: 'TAG1',
+ type: OCPP20IdTokenEnumType.CENTRAL,
+ })
+ })
+ })
+})
+import type { OCPPVersion } from 'ui-common'
+
import {
+ buildAuthorizePayload,
+ buildStartTransactionPayload,
+ buildStopTransactionPayload,
type ChargingStationOptions,
createBrowserWsAdapter,
- OCPP20IdTokenEnumType,
- OCPP20TransactionEventEnumType,
- type OCPP20TransactionEventRequest,
- OCPPVersion,
+ isOCPP20x,
ProcedureName,
type RequestPayload,
type ResponsePayload,
return UIClient.instance
}
- private static isOCPP20x (version: OCPPVersion | undefined): boolean {
- return version === OCPPVersion.VERSION_20 || version === OCPPVersion.VERSION_201
- }
-
public async addChargingStations (
template: string,
numberOfStations: number,
})
}
- public async authorize (hashId: string, idTag: string): Promise<ResponsePayload> {
+ public async authorize (
+ hashId: string,
+ idTag: string,
+ ocppVersion?: OCPPVersion
+ ): Promise<ResponsePayload> {
return this.sendRequest(ProcedureName.AUTHORIZE, {
hashIds: [hashId],
- idTag,
+ ...buildAuthorizePayload(idTag, ocppVersion),
})
}
ocppVersion?: OCPPVersion
}
): Promise<ResponsePayload> {
- if (UIClient.isOCPP20x(options.ocppVersion)) {
- return this.transactionEvent(hashId, {
- eventType: OCPP20TransactionEventEnumType.STARTED,
- evse:
- options.evseId != null
- ? { connectorId: options.connectorId, id: options.evseId }
- : undefined,
- idToken:
- options.idTag != null
- ? { idToken: options.idTag, type: OCPP20IdTokenEnumType.ISO14443 }
- : undefined,
- })
+ const { payload, procedureName } = buildStartTransactionPayload(
+ options.connectorId,
+ options.ocppVersion,
+ { evseId: options.evseId, idTag: options.idTag }
+ )
+ if (procedureName === 'transactionEvent') {
+ return this.transactionEvent(hashId, payload)
}
return this.sendRequest(ProcedureName.START_TRANSACTION, {
- connectorId: options.connectorId,
hashIds: [hashId],
- idTag: options.idTag,
+ ...payload,
})
}
transactionId: number | string | undefined
}
): Promise<ResponsePayload> {
- if (UIClient.isOCPP20x(options.ocppVersion)) {
- return this.transactionEvent(hashId, {
- eventType: OCPP20TransactionEventEnumType.ENDED,
- transactionId: options.transactionId?.toString(),
- })
+ if (options.transactionId == null) {
+ return {
+ responsesFailed: [
+ {
+ errorMessage: 'transactionId is required',
+ hashId,
+ status: ResponseStatus.FAILURE,
+ },
+ ],
+ status: ResponseStatus.FAILURE,
+ }
}
- if (typeof options.transactionId === 'string') {
+ if (!isOCPP20x(options.ocppVersion) && typeof options.transactionId === 'string') {
return {
responsesFailed: [
{
status: ResponseStatus.FAILURE,
}
}
+ const { payload, procedureName } = buildStopTransactionPayload(
+ options.transactionId,
+ options.ocppVersion
+ )
+ if (procedureName === 'transactionEvent') {
+ return this.transactionEvent(hashId, payload)
+ }
return this.sendRequest(ProcedureName.STOP_TRANSACTION, {
hashIds: [hashId],
- transactionId: options.transactionId,
+ ...payload,
})
}
private async transactionEvent (
hashId: string,
- payload: OCPP20TransactionEventRequest
+ payload: RequestPayload
): Promise<ResponsePayload> {
return this.sendRequest(ProcedureName.TRANSACTION_EVENT, {
hashIds: [hashId],
return false
}
try {
- await $uiClient.authorize(hashId, idTag)
+ await $uiClient.authorize(hashId, idTag, toValue(ocppVersion))
} catch (error: unknown) {
$toast.error('Error at authorizing RFID tag')
console.error('Error at authorizing RFID tag:', error)
v-if="showAuthorizeDialog"
:hash-id="showAuthorizeDialog.hashId"
:charging-station-id="showAuthorizeDialog.chargingStationId"
+ :ocpp-version="showAuthorizeDialog.ocppVersion"
@close="showAuthorizeDialog = null"
/>
</main>
const showAuthorizeDialog = ref<null | {
chargingStationId: string
hashId: string
+ ocppVersion?: OCPPVersion
}>(null)
const confirmStopSimulator = (): void => {
const emit = defineEmits<{
'need-refresh': []
- 'open-authorize': [data: { chargingStationId: string; hashId: string }]
+ 'open-authorize': [data: { chargingStationId: string; hashId: string; ocppVersion?: OCPPVersion }]
'open-set-url': [data: { chargingStationId: string; hashId: string }]
'open-start-tx': [
data: {
emit('open-authorize', {
chargingStationId: props.chargingStation.stationInfo.chargingStationId,
hashId: props.chargingStation.stationInfo.hashId,
+ ocppVersion: props.chargingStation.stationInfo.ocppVersion,
})
}
</template>
<script setup lang="ts">
+import { type OCPPVersion } from 'ui-common'
import { computed, ref } from 'vue'
import { useToast } from 'vue-toast-notification'
const props = defineProps<{
chargingStationId: string
hashId: string
+ ocppVersion?: OCPPVersion
}>()
const emit = defineEmits<{ close: [] }>()
pending.value = true
lastFailure.value = null
try {
- await $uiClient.authorize(props.hashId, idTag.value)
+ await $uiClient.authorize(props.hashId, idTag.value, props.ocppVersion)
$toast.success(`Authorized ${idTag.value}`)
close()
} catch (error: unknown) {
*/
import {
AuthenticationType,
- OCPP20TransactionEventEnumType,
OCPPVersion,
ProcedureName,
ResponseStatus,
})
describe('startTransaction', () => {
- it('should send START_TRANSACTION for OCPP 1.6', async () => {
+ it('should route to START_TRANSACTION for OCPP 1.6', async () => {
await client.startTransaction(TEST_HASH_ID, {
connectorId: 1,
idTag: TEST_ID_TAG,
ocppVersion: OCPPVersion.VERSION_16,
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.START_TRANSACTION, {
- connectorId: 1,
- hashIds: [TEST_HASH_ID],
- idTag: TEST_ID_TAG,
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.START_TRANSACTION,
+ expect.objectContaining({ connectorId: 1, hashIds: [TEST_HASH_ID], idTag: TEST_ID_TAG })
+ )
})
- it('should send TRANSACTION_EVENT with evse object for OCPP 2.0.x', async () => {
+ it('should route to TRANSACTION_EVENT for OCPP 2.0.x', async () => {
await client.startTransaction(TEST_HASH_ID, {
connectorId: 2,
evseId: 1,
ocppVersion: OCPPVersion.VERSION_20,
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.STARTED,
- evse: { connectorId: 2, id: 1 },
- hashIds: [TEST_HASH_ID],
- idToken: { idToken: TEST_ID_TAG, type: 'ISO14443' },
- })
- })
-
- it('should default to OCPP 1.6 when version is undefined', async () => {
- await client.startTransaction(TEST_HASH_ID, {
- connectorId: 1,
- idTag: TEST_ID_TAG,
- })
-
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.START_TRANSACTION, {
- connectorId: 1,
- hashIds: [TEST_HASH_ID],
- idTag: TEST_ID_TAG,
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.TRANSACTION_EVENT,
+ expect.objectContaining({ connectorId: 2, evseId: 1, hashIds: [TEST_HASH_ID] })
+ )
})
- it('should send undefined evse when evseId is not provided for OCPP 2.0.x', async () => {
+ it('should default to START_TRANSACTION when version is undefined', async () => {
await client.startTransaction(TEST_HASH_ID, {
connectorId: 1,
idTag: TEST_ID_TAG,
- ocppVersion: OCPPVersion.VERSION_20,
- })
-
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.STARTED,
- evse: undefined,
- hashIds: [TEST_HASH_ID],
- idToken: { idToken: TEST_ID_TAG, type: 'ISO14443' },
- })
- })
-
- it('should send undefined idToken when idTag is not provided for OCPP 2.0.x', async () => {
- await client.startTransaction(TEST_HASH_ID, {
- connectorId: 1,
- evseId: 1,
- ocppVersion: OCPPVersion.VERSION_20,
- })
-
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.STARTED,
- evse: { connectorId: 1, id: 1 },
- hashIds: [TEST_HASH_ID],
- idToken: undefined,
- })
- })
-
- it('should send undefined evse and idToken when both absent for OCPP 2.0.x', async () => {
- await client.startTransaction(TEST_HASH_ID, {
- connectorId: 1,
- ocppVersion: OCPPVersion.VERSION_20,
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.STARTED,
- evse: undefined,
- hashIds: [TEST_HASH_ID],
- idToken: undefined,
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.START_TRANSACTION,
+ expect.objectContaining({ connectorId: 1, hashIds: [TEST_HASH_ID], idTag: TEST_ID_TAG })
+ )
})
})
describe('stopTransaction', () => {
- it('should send STOP_TRANSACTION for OCPP 1.6', async () => {
+ it('should route to STOP_TRANSACTION for OCPP 1.6', async () => {
await client.stopTransaction(TEST_HASH_ID, {
ocppVersion: OCPPVersion.VERSION_16,
transactionId: 12345,
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.STOP_TRANSACTION, {
- hashIds: [TEST_HASH_ID],
- transactionId: 12345,
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.STOP_TRANSACTION,
+ expect.objectContaining({ hashIds: [TEST_HASH_ID], transactionId: 12345 })
+ )
})
- it('should send TRANSACTION_EVENT with Ended for OCPP 2.0.x', async () => {
+ it('should route to TRANSACTION_EVENT for OCPP 2.0.x', async () => {
await client.stopTransaction(TEST_HASH_ID, {
ocppVersion: OCPPVersion.VERSION_20,
transactionId: 'tx-uuid-123',
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.ENDED,
- hashIds: [TEST_HASH_ID],
- transactionId: 'tx-uuid-123',
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.TRANSACTION_EVENT,
+ expect.objectContaining({ hashIds: [TEST_HASH_ID], transactionId: 'tx-uuid-123' })
+ )
})
- it('should default to OCPP 1.6 when version is undefined', async () => {
+ it('should default to STOP_TRANSACTION when version is undefined', async () => {
await client.stopTransaction(TEST_HASH_ID, { transactionId: 12345 })
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.STOP_TRANSACTION, {
- hashIds: [TEST_HASH_ID],
- transactionId: 12345,
- })
+ expect(sendRequestSpy).toHaveBeenCalledWith(
+ ProcedureName.STOP_TRANSACTION,
+ expect.objectContaining({ hashIds: [TEST_HASH_ID], transactionId: 12345 })
+ )
})
- it('should send undefined transactionId for OCPP 2.0.x when not provided', async () => {
- await client.stopTransaction(TEST_HASH_ID, {
+ it('should return failure when transactionId is undefined', async () => {
+ const result = await client.stopTransaction(TEST_HASH_ID, {
ocppVersion: OCPPVersion.VERSION_20,
transactionId: undefined,
})
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.ENDED,
- hashIds: [TEST_HASH_ID],
- transactionId: undefined,
- })
- })
-
- it('should convert numeric transactionId to string for OCPP 2.0.x', async () => {
- await client.stopTransaction(TEST_HASH_ID, {
- ocppVersion: OCPPVersion.VERSION_20,
- transactionId: 12345,
- })
-
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.TRANSACTION_EVENT, {
- eventType: OCPP20TransactionEventEnumType.ENDED,
- hashIds: [TEST_HASH_ID],
- transactionId: '12345',
- })
+ expect(result.status).toBe(ResponseStatus.FAILURE)
+ expect(sendRequestSpy).not.toHaveBeenCalled()
})
it('should return failure for string transactionId with OCPP 1.6', async () => {
expect(result.status).toBe(ResponseStatus.FAILURE)
expect(sendRequestSpy).not.toHaveBeenCalled()
})
-
- it('should send undefined transactionId for OCPP 1.6 when not provided', async () => {
- await client.stopTransaction(TEST_HASH_ID, {
- ocppVersion: OCPPVersion.VERSION_16,
- transactionId: undefined,
- })
-
- expect(sendRequestSpy).toHaveBeenCalledWith(ProcedureName.STOP_TRANSACTION, {
- hashIds: [TEST_HASH_ID],
- transactionId: undefined,
- })
- })
})
})
formState.value.authorizeIdTag = true
formState.value.idTag = 'TAG001'
await submitForm()
- expect(mockAuthorize).toHaveBeenCalledWith('hash1', 'TAG001')
+ expect(mockAuthorize).toHaveBeenCalledWith('hash1', 'TAG001', undefined)
expect(mockStartTransaction).toHaveBeenCalled()
})
* @description Unit tests for classic skin action components: AddChargingStations, SetSupervisionUrl, StartTransaction.
*/
import { flushPromises, mount } from '@vue/test-utils'
+import { OCPPVersion } from 'ui-common'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ref, shallowRef } from 'vue'
const submitBtn = wrapper.findComponent(ButtonStub)
await submitBtn.trigger('click')
await flushPromises()
- expect(mockClient.authorize).toHaveBeenCalledWith(TEST_HASH_ID, 'RFID-001')
+ expect(mockClient.authorize).toHaveBeenCalledWith(
+ TEST_HASH_ID,
+ 'RFID-001',
+ OCPPVersion.VERSION_16
+ )
expect(mockClient.startTransaction).toHaveBeenCalledWith(
TEST_HASH_ID,
expect.objectContaining({
connectorId: 1,
evseId: 1,
idTag: 'RFID-001',
- ocppVersion: '1.6',
+ ocppVersion: OCPPVersion.VERSION_16,
})
)
})
* Modal is mocked to skip the Teleport so wrapper.find() reaches dialog inputs.
*/
import { flushPromises, mount } from '@vue/test-utils'
-import { ResponseStatus, ServerFailureError } from 'ui-common'
+import { OCPPVersion, ResponseStatus, ServerFailureError } from 'ui-common'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent, ref } from 'vue'
await checkbox.setValue(true)
await wrapper.findAll('.stub-modal__foot button')[1].trigger('click')
await flushPromises()
- expect(mockClient.authorize).toHaveBeenCalledWith(TEST_HASH_ID, 'RFID-01')
+ expect(mockClient.authorize).toHaveBeenCalledWith(TEST_HASH_ID, 'RFID-01', undefined)
expect(mockClient.startTransaction).toHaveBeenCalledWith(
TEST_HASH_ID,
expect.objectContaining({ connectorId: 1, idTag: 'RFID-01' })
})
it('should include evseId and ocppVersion from props', async () => {
- const wrapper = mountDialog({ evseId: 2, ocppVersion: '1.6' })
+ const wrapper = mountDialog({ evseId: 2, ocppVersion: OCPPVersion.VERSION_16 })
await wrapper.find('#modern-tx-idtag').setValue('RFID-01')
await wrapper.findAll('.stub-modal__foot button')[1].trigger('click')
await flushPromises()
expect(mockClient.startTransaction).toHaveBeenCalledWith(
TEST_HASH_ID,
- expect.objectContaining({ evseId: 2, ocppVersion: '1.6' })
+ expect.objectContaining({ evseId: 2, ocppVersion: OCPPVersion.VERSION_16 })
)
expect(wrapper.text()).toContain('EVSE 2')
})
await wrapper.find('#modern-auth-tag').setValue('GOOD')
await wrapper.findAll('.stub-modal__foot button')[1].trigger('click')
await flushPromises()
- expect(mockClient.authorize).toHaveBeenCalledWith(TEST_HASH_ID, 'GOOD')
+ expect(mockClient.authorize).toHaveBeenCalledWith(TEST_HASH_ID, 'GOOD', undefined)
expect(toastMock.success).toHaveBeenCalled()
expect(wrapper.emitted('close')).toHaveLength(1)
})
* @description Header pills, connector enumeration, start/connect/delete, supervision/authorize events.
*/
import { flushPromises, mount } from '@vue/test-utils'
-import { type ChargingStationData, OCPP16AvailabilityType } from 'ui-common'
+import { type ChargingStationData, OCPP16AvailabilityType, OCPPVersion } from 'ui-common'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { uiClientKey } from '@/core/index.js'
const btn = buttons.find(b => b.text() === 'Authorize')
await btn?.trigger('click')
expect(wrapper.emitted('open-authorize')).toEqual([
- [{ chargingStationId: TEST_STATION_ID, hashId: TEST_HASH_ID }],
+ [
+ {
+ chargingStationId: TEST_STATION_ID,
+ hashId: TEST_HASH_ID,
+ ocppVersion: OCPPVersion.VERSION_16,
+ },
+ ],
])
})