From 0e1cda099fb2f33c75046b88d19da3335bcd90ca Mon Sep 17 00:00:00 2001 From: YANGCHUNHONG3000 Date: Sun, 26 Jul 2026 09:23:12 +0800 Subject: [PATCH 1/4] feat(scenarios): add firmware update success scenario --- .../__scenarios__/firmware-update-success.ts | 91 +++++++++++++++++++ packages/toolkit/src/scenarios/index.test.ts | 7 +- packages/toolkit/src/scenarios/index.ts | 5 + tests/external-fixture/test.mjs | 4 +- 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts diff --git a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts new file mode 100644 index 0000000..aad045d --- /dev/null +++ b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts @@ -0,0 +1,91 @@ +export default { + name: 'firmware-update-success', + description: + 'Successful firmware update: station boots, downloads firmware, installs it successfully, and continues operating normally. No failures expected.', + trace: { + traceId: 'scenario-firmware-update-success', + metadata: { + stationId: 'CS-SYNTHETIC-012', + ocppVersion: '1.6', + source: 'synthetic-scenario', + description: + 'Station boots, reports the firmware as downloaded and installed within seconds, then sends a heartbeat.', + }, + events: [ + { + timestamp: '2024-01-15T06:00:00.000Z', + direction: 'CS_TO_CSMS', + message: [ + 2, + 'msg-001', + 'BootNotification', + { + chargePointVendor: 'SyntheticVendor', + chargePointModel: 'SM-100', + chargePointSerialNumber: 'CS-SYNTHETIC-012', + firmwareVersion: '1.0.0', + }, + ], + }, + { + timestamp: '2024-01-15T06:00:00.500Z', + direction: 'CSMS_TO_CS', + message: [ + 3, + 'msg-001', + { + currentTime: '2024-01-15T06:00:00.500Z', + interval: 300, + status: 'Accepted', + }, + ], + }, + { + timestamp: '2024-01-15T06:00:30.000Z', + direction: 'CS_TO_CSMS', + message: [ + 2, + 'msg-002', + 'FirmwareStatusNotification', + { status: 'Downloaded' }, + ], + }, + { + timestamp: '2024-01-15T06:00:30.500Z', + direction: 'CSMS_TO_CS', + message: [3, 'msg-002', {}], + }, + { + timestamp: '2024-01-15T06:01:00.000Z', + direction: 'CS_TO_CSMS', + message: [ + 2, + 'msg-003', + 'FirmwareStatusNotification', + { status: 'Installed' }, + ], + }, + { + timestamp: '2024-01-15T06:01:00.500Z', + direction: 'CSMS_TO_CS', + message: [3, 'msg-003', {}], + }, + { + timestamp: '2024-01-15T06:03:00.000Z', + direction: 'CS_TO_CSMS', + message: [2, 'msg-hb-1', 'Heartbeat', {}], + }, + { + timestamp: '2024-01-15T06:03:00.500Z', + direction: 'CSMS_TO_CS', + message: [ + 3, + 'msg-hb-1', + { currentTime: '2024-01-15T06:03:00.500Z' }, + ], + }, + ], + }, + expectedFailures: [], + assertions: [{ type: 'no_failures', params: {} }], +}; diff --git a/packages/toolkit/src/scenarios/index.test.ts b/packages/toolkit/src/scenarios/index.test.ts index 92ffafd..dcf3324 100644 --- a/packages/toolkit/src/scenarios/index.test.ts +++ b/packages/toolkit/src/scenarios/index.test.ts @@ -18,6 +18,7 @@ import { shortSessionScenario, heartbeatIrregularScenario, unresponsiveCsmsScenario, + firmwareUpdateSuccessScenario, } from './index.js'; import { parseTrace, buildSessionTimeline, detectFailures } from '../core/index.js'; @@ -26,8 +27,8 @@ import { parseTrace, buildSessionTimeline, detectFailures } from '../core/index. // --------------------------------------------------------------------------- describe('scenario registry', () => { - it('exports exactly 15 scenarios', () => { - expect(scenarios).toHaveLength(15); + it('exports exactly 16 scenarios', () => { + expect(scenarios).toHaveLength(16); }); it('exports scenario names in order', () => { @@ -47,6 +48,7 @@ describe('scenario registry', () => { 'short-session', 'heartbeat-irregular', 'unresponsive-csms', + 'firmware-update-success', ]); }); @@ -75,6 +77,7 @@ describe('scenario registry', () => { expect(getScenario('short-session')).toBe(shortSessionScenario); expect(getScenario('heartbeat-irregular')).toBe(heartbeatIrregularScenario); expect(getScenario('unresponsive-csms')).toBe(unresponsiveCsmsScenario); + expect(getScenario('firmware-update-success')).toBe(firmwareUpdateSuccessScenario); }); it('getScenario returns undefined for unknown name', () => { diff --git a/packages/toolkit/src/scenarios/index.ts b/packages/toolkit/src/scenarios/index.ts index 38aad78..7fd231b 100644 --- a/packages/toolkit/src/scenarios/index.ts +++ b/packages/toolkit/src/scenarios/index.ts @@ -20,6 +20,7 @@ import meterAnomaly from './__scenarios__/meter-anomaly.js'; import shortSession from './__scenarios__/short-session.js'; import heartbeatIrregular from './__scenarios__/heartbeat-irregular.js'; import unresponsiveCsms from './__scenarios__/unresponsive-csms.js'; +import firmwareUpdateSuccess from './__scenarios__/firmware-update-success.js'; // --------------------------------------------------------------------------- // Scenarios derived from core fixtures @@ -66,6 +67,7 @@ const meterAnomalyScenario: Scenario = meterAnomaly as unknown as Scenario; const shortSessionScenario: Scenario = shortSession as unknown as Scenario; const heartbeatIrregularScenario: Scenario = heartbeatIrregular as unknown as Scenario; const unresponsiveCsmsScenario: Scenario = unresponsiveCsms as unknown as Scenario; +const firmwareUpdateSuccessScenario: Scenario = firmwareUpdateSuccess as unknown as Scenario; // --------------------------------------------------------------------------- // Registry @@ -87,6 +89,7 @@ export const scenarios = [ shortSessionScenario, heartbeatIrregularScenario, unresponsiveCsmsScenario, + firmwareUpdateSuccessScenario, ] as const; export const scenarioNames = [ @@ -105,6 +108,7 @@ export const scenarioNames = [ 'short-session', 'heartbeat-irregular', 'unresponsive-csms', + 'firmware-update-success', ] as const; export { @@ -123,6 +127,7 @@ export { shortSessionScenario, heartbeatIrregularScenario, unresponsiveCsmsScenario, + firmwareUpdateSuccessScenario, }; export { compareScenarioReports } from './compare.js'; diff --git a/tests/external-fixture/test.mjs b/tests/external-fixture/test.mjs index 337b6e9..3b64809 100644 --- a/tests/external-fixture/test.mjs +++ b/tests/external-fixture/test.mjs @@ -158,8 +158,8 @@ const scenarios = await import('@ocpp-debugkit/toolkit/scenarios'); assert(Array.isArray(scenarios.scenarios), 'scenarios is an array'); assert( - scenarios.scenarios.length === 15, - `15 scenarios exported (got ${scenarios.scenarios.length})`, + scenarios.scenarios.length === 16, + `16 scenarios exported (got ${scenarios.scenarios.length})`, ); assert(typeof scenarios.getScenario === 'function', 'getScenario is a function'); assert(scenarios.getScenario('normal-session') !== undefined, 'normal-session scenario exists'); From 61e626fe530e4f42315696bcf44dad2c7846d26b Mon Sep 17 00:00:00 2001 From: Mike Yang <250981657@qq.com> Date: Mon, 27 Jul 2026 00:40:17 +0800 Subject: [PATCH 2/4] fix: address review feedback (station ID, doc counts, changeset) --- .changeset/warm-dragons-smile.md | 5 +++++ README.md | 2 +- packages/toolkit/README.md | 2 +- .../src/scenarios/__scenarios__/firmware-update-success.ts | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .changeset/warm-dragons-smile.md diff --git a/.changeset/warm-dragons-smile.md b/.changeset/warm-dragons-smile.md new file mode 100644 index 0000000..d39165b --- /dev/null +++ b/.changeset/warm-dragons-smile.md @@ -0,0 +1,5 @@ +--- +"@ocpp-debugkit/toolkit": minor +--- + +feat(scenarios): add firmware update success scenario diff --git a/README.md b/README.md index 170cce1..46b71d3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ validate behavior against known scenarios. firmware update failures, suspicious session duration, slow CSMS responses, heartbeat interval violations, meter value anomalies, unresponsive CSMS, and repeated boot notifications. -- **Scenario Evaluator** — 15 predefined scenarios with expected failure +- **Scenario Evaluator** — 16 predefined scenarios with expected failure outcomes for testing the analysis engine. Supports external scenario files. - **Replay Engine** — Deterministic, pure replay engine with step forward/back, jump-to-event, and configurable playback speed. diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index c68dcf8..5c3c3e3 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -20,7 +20,7 @@ report generation, React components, and CLI. firmware update failures, suspicious session duration, slow CSMS responses, heartbeat interval violations, meter value anomalies, unresponsive CSMS, and repeated boot notifications. -- **Scenario Evaluator** — 15 predefined scenarios with expected failure +- **Scenario Evaluator** — 16 predefined scenarios with expected failure outcomes for testing the analysis engine. Supports external scenario files. - **Replay Engine** — Deterministic, pure replay engine with step forward/back, jump-to-event, and configurable playback speed. No timers or I/O. diff --git a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts index aad045d..4f13e64 100644 --- a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts +++ b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts @@ -5,7 +5,7 @@ export default { trace: { traceId: 'scenario-firmware-update-success', metadata: { - stationId: 'CS-SYNTHETIC-012', + stationId: 'CS-SYNTHETIC-016', ocppVersion: '1.6', source: 'synthetic-scenario', description: @@ -22,7 +22,7 @@ export default { { chargePointVendor: 'SyntheticVendor', chargePointModel: 'SM-100', - chargePointSerialNumber: 'CS-SYNTHETIC-012', + chargePointSerialNumber: 'CS-SYNTHETIC-016', firmwareVersion: '1.0.0', }, ], From cfd789d8bf7e020426101e59b41a3c5ed96685bd Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Tue, 28 Jul 2026 18:06:26 +0300 Subject: [PATCH 3/4] style(scenarios): apply Prettier to the firmware-update-success scenario --- .../__scenarios__/firmware-update-success.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts index 4f13e64..7d527c3 100644 --- a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts +++ b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-success.ts @@ -43,12 +43,7 @@ export default { { timestamp: '2024-01-15T06:00:30.000Z', direction: 'CS_TO_CSMS', - message: [ - 2, - 'msg-002', - 'FirmwareStatusNotification', - { status: 'Downloaded' }, - ], + message: [2, 'msg-002', 'FirmwareStatusNotification', { status: 'Downloaded' }], }, { timestamp: '2024-01-15T06:00:30.500Z', @@ -58,12 +53,7 @@ export default { { timestamp: '2024-01-15T06:01:00.000Z', direction: 'CS_TO_CSMS', - message: [ - 2, - 'msg-003', - 'FirmwareStatusNotification', - { status: 'Installed' }, - ], + message: [2, 'msg-003', 'FirmwareStatusNotification', { status: 'Installed' }], }, { timestamp: '2024-01-15T06:01:00.500Z', @@ -78,11 +68,7 @@ export default { { timestamp: '2024-01-15T06:03:00.500Z', direction: 'CSMS_TO_CS', - message: [ - 3, - 'msg-hb-1', - { currentTime: '2024-01-15T06:03:00.500Z' }, - ], + message: [3, 'msg-hb-1', { currentTime: '2024-01-15T06:03:00.500Z' }], }, ], }, From 6476b8e26846cbc76a7d5cf4f1cda32423609d68 Mon Sep 17 00:00:00 2001 From: Mike Yang <250981657@qq.com> Date: Wed, 29 Jul 2026 00:54:04 -0400 Subject: [PATCH 4/4] feat(scenarios): add firmware-update-failure scenario --- .changeset/QFWTPHO.md | 5 ++ README.md | 4 +- packages/toolkit/README.md | 4 +- .../__scenarios__/firmware-update-failure.ts | 79 +++++++++++++++++++ packages/toolkit/src/scenarios/index.test.ts | 15 +++- packages/toolkit/src/scenarios/index.ts | 5 ++ tests/external-fixture/test.mjs | 4 +- 7 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 .changeset/QFWTPHO.md create mode 100644 packages/toolkit/src/scenarios/__scenarios__/firmware-update-failure.ts diff --git a/.changeset/QFWTPHO.md b/.changeset/QFWTPHO.md new file mode 100644 index 0000000..8760f6d --- /dev/null +++ b/.changeset/QFWTPHO.md @@ -0,0 +1,5 @@ +--- +'@ocpp-debugkit/toolkit': patch +--- + +feat(scenarios): add firmware-update-failure scenario diff --git a/README.md b/README.md index 46b71d3..1d2cd87 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ validate behavior against known scenarios. - **Trace Inspector** — Load OCPP traces, view session timelines, inspect individual messages, and identify failures. -- **Failure Detection** — 16 detection rules (4 critical, 10 warning, 2 info) +- **Failure Detection** — 17 detection rules (4 critical, 10 warning, 2 info) covering common failure patterns: failed authorization, connector faults, station offline, heartbeat timeout, meter value gaps, invalid stop reasons, unexpected starts, status transition violations, diagnostics failures, firmware update failures, suspicious session duration, slow CSMS responses, heartbeat interval violations, meter value anomalies, unresponsive CSMS, and repeated boot notifications. -- **Scenario Evaluator** — 16 predefined scenarios with expected failure +- **Scenario Evaluator** — 17 predefined scenarios with expected failure outcomes for testing the analysis engine. Supports external scenario files. - **Replay Engine** — Deterministic, pure replay engine with step forward/back, jump-to-event, and configurable playback speed. diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index 5c3c3e3..2575037 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -13,14 +13,14 @@ report generation, React components, and CLI. `parseOpenOcppTrace()` (with `parseTrace()` auto-detection) reads it, `toOpenOcppTraceJsonl()` and the `convert` CLI command emit it, both checked against the specification's conformance fixtures. -- **Failure Detection** — 16 detection rules (4 critical, 10 warning, 2 info) +- **Failure Detection** — 17 detection rules (4 critical, 10 warning, 2 info) covering common failure patterns: failed authorization, connector faults, station offline, heartbeat timeout, meter value gaps, invalid stop reasons, unexpected starts, status transition violations, diagnostics failures, firmware update failures, suspicious session duration, slow CSMS responses, heartbeat interval violations, meter value anomalies, unresponsive CSMS, and repeated boot notifications. -- **Scenario Evaluator** — 16 predefined scenarios with expected failure +- **Scenario Evaluator** — 17 predefined scenarios with expected failure outcomes for testing the analysis engine. Supports external scenario files. - **Replay Engine** — Deterministic, pure replay engine with step forward/back, jump-to-event, and configurable playback speed. No timers or I/O. diff --git a/packages/toolkit/src/scenarios/__scenarios__/firmware-update-failure.ts b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-failure.ts new file mode 100644 index 0000000..633dd6b --- /dev/null +++ b/packages/toolkit/src/scenarios/__scenarios__/firmware-update-failure.ts @@ -0,0 +1,79 @@ +export default { + name: 'firmware-update-failure', + description: + 'Firmware update failure: station boots, firmware download initiates but install fails with InstallFailed status. Expects FIRMWARE_UPDATE_FAILURE failure.', + trace: { + traceId: 'scenario-firmware-update-failure', + metadata: { + stationId: 'CS-SYNTHETIC-019', + ocppVersion: '1.6', + source: 'synthetic-scenario', + description: + 'Station boots, reports Downloading firmware, then InstallFailed within seconds, followed by a heartbeat to confirm station is still online.', + }, + events: [ + { + timestamp: '2026-01-15T07:00:00.000Z', + direction: 'CS_TO_CSMS', + message: [ + 2, + 'msg-001', + 'BootNotification', + { + chargePointVendor: 'SyntheticVendor', + chargePointModel: 'SM-100', + chargePointSerialNumber: 'CS-SYNTHETIC-019', + firmwareVersion: '1.0.0', + }, + ], + }, + { + timestamp: '2026-01-15T07:00:00.500Z', + direction: 'CSMS_TO_CS', + message: [ + 3, + 'msg-001', + { + currentTime: '2026-01-15T07:00:00.500Z', + interval: 300, + status: 'Accepted', + }, + ], + }, + { + timestamp: '2026-01-15T07:00:30.000Z', + direction: 'CS_TO_CSMS', + message: [2, 'msg-002', 'FirmwareStatusNotification', { status: 'Downloading' }], + }, + { + timestamp: '2026-01-15T07:00:30.500Z', + direction: 'CSMS_TO_CS', + message: [3, 'msg-002', {}], + }, + { + timestamp: '2026-01-15T07:01:00.000Z', + direction: 'CS_TO_CSMS', + message: [2, 'msg-003', 'FirmwareStatusNotification', { status: 'InstallFailed' }], + }, + { + timestamp: '2026-01-15T07:01:00.500Z', + direction: 'CSMS_TO_CS', + message: [3, 'msg-003', {}], + }, + { + timestamp: '2026-01-15T07:02:00.000Z', + direction: 'CS_TO_CSMS', + message: [2, 'msg-hb-1', 'Heartbeat', {}], + }, + { + timestamp: '2026-01-15T07:02:00.500Z', + direction: 'CSMS_TO_CS', + message: [3, 'msg-hb-1', { currentTime: '2026-01-15T07:02:00.500Z' }], + }, + ], + }, + expectedFailures: ['FIRMWARE_UPDATE_FAILURE'], + assertions: [ + { type: 'failure_count', params: { code: 'FIRMWARE_UPDATE_FAILURE', min: 1 } }, + ], +}; diff --git a/packages/toolkit/src/scenarios/index.test.ts b/packages/toolkit/src/scenarios/index.test.ts index dcf3324..2b85c28 100644 --- a/packages/toolkit/src/scenarios/index.test.ts +++ b/packages/toolkit/src/scenarios/index.test.ts @@ -19,6 +19,7 @@ import { heartbeatIrregularScenario, unresponsiveCsmsScenario, firmwareUpdateSuccessScenario, + firmwareUpdateFailureScenario, } from './index.js'; import { parseTrace, buildSessionTimeline, detectFailures } from '../core/index.js'; @@ -27,8 +28,8 @@ import { parseTrace, buildSessionTimeline, detectFailures } from '../core/index. // --------------------------------------------------------------------------- describe('scenario registry', () => { - it('exports exactly 16 scenarios', () => { - expect(scenarios).toHaveLength(16); + it('exports exactly 17 scenarios', () => { + expect(scenarios).toHaveLength(17); }); it('exports scenario names in order', () => { @@ -49,6 +50,7 @@ describe('scenario registry', () => { 'heartbeat-irregular', 'unresponsive-csms', 'firmware-update-success', + 'firmware-update-failure', ]); }); @@ -78,6 +80,7 @@ describe('scenario registry', () => { expect(getScenario('heartbeat-irregular')).toBe(heartbeatIrregularScenario); expect(getScenario('unresponsive-csms')).toBe(unresponsiveCsmsScenario); expect(getScenario('firmware-update-success')).toBe(firmwareUpdateSuccessScenario); + expect(getScenario('firmware-update-failure')).toBe(firmwareUpdateFailureScenario); }); it('getScenario returns undefined for unknown name', () => { @@ -202,6 +205,14 @@ describe('scenario engine integration', () => { const failures = detectFailures(result.events, sessions); expect(failures.some((f) => f.code === 'DIAGNOSTICS_FAILURE')).toBe(true); }); + + it('firmware-update-failure: detects FIRMWARE_UPDATE_FAILURE', () => { + const trace = JSON.stringify(firmwareUpdateFailureScenario.trace); + const result = parseTrace(trace); + const sessions = buildSessionTimeline(result.events); + const failures = detectFailures(result.events, sessions); + expect(failures.some((f) => f.code === 'FIRMWARE_UPDATE_FAILURE')).toBe(true); + }); }); // --------------------------------------------------------------------------- diff --git a/packages/toolkit/src/scenarios/index.ts b/packages/toolkit/src/scenarios/index.ts index 7fd231b..6181adb 100644 --- a/packages/toolkit/src/scenarios/index.ts +++ b/packages/toolkit/src/scenarios/index.ts @@ -21,6 +21,7 @@ import shortSession from './__scenarios__/short-session.js'; import heartbeatIrregular from './__scenarios__/heartbeat-irregular.js'; import unresponsiveCsms from './__scenarios__/unresponsive-csms.js'; import firmwareUpdateSuccess from './__scenarios__/firmware-update-success.js'; +import firmwareUpdateFailure from './__scenarios__/firmware-update-failure.js'; // --------------------------------------------------------------------------- // Scenarios derived from core fixtures @@ -68,6 +69,7 @@ const shortSessionScenario: Scenario = shortSession as unknown as Scenario; const heartbeatIrregularScenario: Scenario = heartbeatIrregular as unknown as Scenario; const unresponsiveCsmsScenario: Scenario = unresponsiveCsms as unknown as Scenario; const firmwareUpdateSuccessScenario: Scenario = firmwareUpdateSuccess as unknown as Scenario; +const firmwareUpdateFailureScenario: Scenario = firmwareUpdateFailure as unknown as Scenario; // --------------------------------------------------------------------------- // Registry @@ -90,6 +92,7 @@ export const scenarios = [ heartbeatIrregularScenario, unresponsiveCsmsScenario, firmwareUpdateSuccessScenario, + firmwareUpdateFailureScenario, ] as const; export const scenarioNames = [ @@ -109,6 +112,7 @@ export const scenarioNames = [ 'heartbeat-irregular', 'unresponsive-csms', 'firmware-update-success', + 'firmware-update-failure', ] as const; export { @@ -128,6 +132,7 @@ export { heartbeatIrregularScenario, unresponsiveCsmsScenario, firmwareUpdateSuccessScenario, + firmwareUpdateFailureScenario, }; export { compareScenarioReports } from './compare.js'; diff --git a/tests/external-fixture/test.mjs b/tests/external-fixture/test.mjs index 3b64809..08896c7 100644 --- a/tests/external-fixture/test.mjs +++ b/tests/external-fixture/test.mjs @@ -158,8 +158,8 @@ const scenarios = await import('@ocpp-debugkit/toolkit/scenarios'); assert(Array.isArray(scenarios.scenarios), 'scenarios is an array'); assert( - scenarios.scenarios.length === 16, - `16 scenarios exported (got ${scenarios.scenarios.length})`, + scenarios.scenarios.length === 17, + `17 scenarios exported (got ${scenarios.scenarios.length})`, ); assert(typeof scenarios.getScenario === 'function', 'getScenario is a function'); assert(scenarios.getScenario('normal-session') !== undefined, 'normal-session scenario exists');