From e6d943576e1d66329c704fbc67745d4568986ad6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Tue, 3 Mar 2026 19:38:07 +0100 Subject: [PATCH] test(ocpp): add OCPPServiceUtils pure function tests MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Test getMessageTypeString, ajvErrorsToErrorType, convertDateToISOString, isConnectorIdValid — 19 tests covering the shared base layer with zero mocking. --- .../ocpp/OCPPServiceUtils-pure.test.ts | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts diff --git a/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts b/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts new file mode 100644 index 00000000..6c954c0c --- /dev/null +++ b/tests/charging-station/ocpp/OCPPServiceUtils-pure.test.ts @@ -0,0 +1,180 @@ +/** + * @file Tests for OCPPServiceUtils pure functions + * @description Verifies pure utility functions exported from OCPPServiceUtils + * + * Covers: + * - getMessageTypeString — converts MessageType enum to human-readable string + * - ajvErrorsToErrorType — maps AJV validation errors to OCPP ErrorType + * - convertDateToISOString — recursively converts Date objects to ISO strings in-place + * - OCPPServiceUtils.isConnectorIdValid — validates connector ID ranges + */ + +import type { ErrorObject } from 'ajv' + +import { expect } from '@std/expect' +import { afterEach, describe, it } from 'node:test' + +import type { ChargingStation } from '../../../src/charging-station/ChargingStation.js' + +import { + ajvErrorsToErrorType, + convertDateToISOString, + getMessageTypeString, +} from '../../../src/charging-station/ocpp/OCPPServiceUtils.js' +import { OCPPServiceUtils } from '../../../src/charging-station/ocpp/OCPPServiceUtils.js' +import { + ErrorType, + IncomingRequestCommand, + type JsonType, + MessageType, +} from '../../../src/types/index.js' +import { standardCleanup } from '../../helpers/TestLifecycleHelpers.js' + +/** + * + * @param keyword + */ +function makeAjvError (keyword: string): ErrorObject { + return { + instancePath: '', + keyword, + params: {}, + schemaPath: `#/${keyword}`, + } as ErrorObject +} + +/** + * + */ +function makeStationStub (): ChargingStation { + return { logPrefix: () => '[test-station]' } as unknown as ChargingStation +} + +await describe('OCPPServiceUtils — pure functions', async () => { + afterEach(() => { + standardCleanup() + }) + + await describe('getMessageTypeString', async () => { + await it('should return "request" for MessageType.CALL_MESSAGE', () => { + expect(getMessageTypeString(MessageType.CALL_MESSAGE)).toBe('request') + }) + + await it('should return "response" for MessageType.CALL_RESULT_MESSAGE', () => { + expect(getMessageTypeString(MessageType.CALL_RESULT_MESSAGE)).toBe('response') + }) + + await it('should return "error" for MessageType.CALL_ERROR_MESSAGE', () => { + expect(getMessageTypeString(MessageType.CALL_ERROR_MESSAGE)).toBe('error') + }) + + await it('should return "unknown" for undefined', () => { + expect(getMessageTypeString(undefined)).toBe('unknown') + }) + }) + + await describe('ajvErrorsToErrorType', async () => { + await it('should return FormatViolation for null errors', () => { + expect(ajvErrorsToErrorType(null)).toBe(ErrorType.FORMAT_VIOLATION) + }) + + await it('should return FormatViolation for undefined errors', () => { + expect(ajvErrorsToErrorType(undefined)).toBe(ErrorType.FORMAT_VIOLATION) + }) + + await it('should return FormatViolation for empty errors array', () => { + expect(ajvErrorsToErrorType([])).toBe(ErrorType.FORMAT_VIOLATION) + }) + + await it('should return TypeConstraintViolation for type keyword', () => { + expect(ajvErrorsToErrorType([makeAjvError('type')])).toBe(ErrorType.TYPE_CONSTRAINT_VIOLATION) + }) + + await it('should return OccurrenceConstraintViolation for required keyword', () => { + expect(ajvErrorsToErrorType([makeAjvError('required')])).toBe( + ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION + ) + }) + + await it('should return OccurrenceConstraintViolation for dependencies keyword', () => { + expect(ajvErrorsToErrorType([makeAjvError('dependencies')])).toBe( + ErrorType.OCCURRENCE_CONSTRAINT_VIOLATION + ) + }) + + await it('should return PropertyConstraintViolation for format keyword', () => { + expect(ajvErrorsToErrorType([makeAjvError('format')])).toBe( + ErrorType.PROPERTY_CONSTRAINT_VIOLATION + ) + }) + + await it('should return PropertyConstraintViolation for pattern keyword', () => { + expect(ajvErrorsToErrorType([makeAjvError('pattern')])).toBe( + ErrorType.PROPERTY_CONSTRAINT_VIOLATION + ) + }) + }) + + await describe('convertDateToISOString', async () => { + await it('should convert a Date property to an ISO string', () => { + const date = new Date('2025-01-15T10:30:00.000Z') + const obj = { timestamp: date } as unknown as JsonType + convertDateToISOString(obj) + expect((obj as Record).timestamp).toBe('2025-01-15T10:30:00.000Z') + }) + + await it('should convert nested Date properties recursively', () => { + const date = new Date('2025-06-01T12:00:00.000Z') + const obj = { nested: { deep: { created: date } } } as unknown as JsonType + convertDateToISOString(obj) + expect( + ((obj as Record).nested as Record).deep as Record< + string, + unknown + > + ).toStrictEqual({ created: '2025-06-01T12:00:00.000Z' }) + }) + + await it('should convert Date values inside arrays', () => { + const date = new Date('2025-03-10T08:00:00.000Z') + const obj = { items: [date] } as unknown as JsonType + convertDateToISOString(obj) + expect((obj as Record).items).toStrictEqual(['2025-03-10T08:00:00.000Z']) + }) + + await it('should not modify non-Date properties', () => { + const obj = { active: true, count: 42, name: 'test' } as unknown as JsonType + convertDateToISOString(obj) + expect(obj).toStrictEqual({ active: true, count: 42, name: 'test' }) + }) + }) + + await describe('OCPPServiceUtils.isConnectorIdValid', async () => { + await it('should return true for connector ID greater than zero', () => { + const result = OCPPServiceUtils.isConnectorIdValid( + makeStationStub(), + IncomingRequestCommand.REMOTE_START_TRANSACTION, + 1 + ) + expect(result).toBe(true) + }) + + await it('should return true for connector ID zero', () => { + const result = OCPPServiceUtils.isConnectorIdValid( + makeStationStub(), + IncomingRequestCommand.REMOTE_START_TRANSACTION, + 0 + ) + expect(result).toBe(true) + }) + + await it('should return false for negative connector ID', () => { + const result = OCPPServiceUtils.isConnectorIdValid( + makeStationStub(), + IncomingRequestCommand.REMOTE_START_TRANSACTION, + -1 + ) + expect(result).toBe(false) + }) + }) +}) -- 2.53.0