Files
Live_RC/tests/decoder_runtime.test.js

150 lines
5.1 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { connectDecoderHelper, processDecoderMessageHelper } from "../src/decoder_runtime.js";
import { ensureSessionResult } from "../src/timing_logic.js";
function createHarness() {
const session = {
id: "session_1",
eventId: "event_1",
mode: "race",
type: "team_race",
status: "running",
startMode: "mass",
startedAt: 1_000,
};
const state = {
events: [{
id: "event_1",
mode: "race",
teams: [{ id: "team_1", name: "Team One", driverIds: ["driver_1", "driver_2"], carIds: [] }],
}],
drivers: [
{ id: "driver_1", name: "Driver One", transponder: "123" },
{ id: "driver_2", name: "Driver Two", transponder: "456" },
],
cars: [],
resultsBySession: {},
};
let saveCount = 0;
const overlayEvents = [];
const deps = {
state,
t: (key) => key,
getActiveSession: () => session,
ensureSessionResult: (sessionId) => ensureSessionResult(sessionId, { state }),
normalizeStartMode: () => "mass",
getSessionLapWindow: () => ({ minLapMs: 0, maxLapMs: Number.POSITIVE_INFINITY }),
findEventTeamForPassing: (event, driverId) => event.teams.find((team) => team.driverIds.includes(driverId)) || null,
persistPassingToBackend: () => {},
pushOverlayEvent: (type, label) => overlayEvents.push({ type, label }),
buildLeaderboard: () => [],
formatLap: (ms) => `${ms}ms`,
announcePassing: () => {},
saveState: () => { saveCount += 1; },
lastOverlayLeaderKeyBySession: {},
lastOverlayTop3BySession: {},
lastOverlayBestLapByKey: {},
};
const passing = (timestamp, role, transponder = "123") => processDecoderMessageHelper({
msg: "PASSING",
transponder,
rtc_time: new Date(timestamp).toISOString(),
}, deps, role);
return { state, session, passing, overlayEvents, getSaveCount: () => saveCount };
}
test("split decoder records intermediate time without changing main lap state", () => {
const harness = createHarness();
harness.passing(17_000, "main");
const result = harness.state.resultsBySession.session_1;
const entry = result.competitors.team_team_1;
assert.equal(entry.laps, 1);
assert.equal(entry.lastTimestamp, 17_000);
harness.passing(25_000, "split");
assert.equal(entry.laps, 1);
assert.equal(entry.lastTimestamp, 17_000);
assert.equal(entry.lastSplitMs, 8_000);
assert.equal(entry.lastSplitLap, 2);
assert.equal(result.splits.length, 1);
assert.equal(result.passings.length, 1);
});
test("pit entry and exit create one timed stop without adding laps", () => {
const harness = createHarness();
harness.passing(17_000, "main");
harness.passing(30_000, "pit_entry");
harness.passing(31_000, "pit_entry");
harness.passing(42_500, "pit_exit", "456");
const result = harness.state.resultsBySession.session_1;
const entry = result.competitors.team_team_1;
assert.equal(entry.laps, 1);
assert.equal(entry.pitActive, false);
assert.equal(entry.pitStops, 1);
assert.equal(entry.lastPitStopMs, 12_500);
assert.equal(entry.totalPitMs, 12_500);
assert.equal(result.pitEvents.length, 2);
assert.equal(result.pitStops.length, 1);
assert.equal(result.pitStops[0].driverId, "driver_1");
assert.equal(result.pitStops[0].driverName, "Driver One");
assert.equal(result.pitStops[0].transponder, "123");
assert.equal(result.pitStops[0].exitTransponder, "456");
assert.equal(result.passings.length, 1);
});
test("connect decoder opens one websocket per enabled role", () => {
const OriginalWebSocket = globalThis.WebSocket;
class FakeWebSocket {
constructor(url) { this.url = url; }
close() {}
}
globalThis.WebSocket = FakeWebSocket;
try {
const clients = {};
const timers = {};
let disconnected = 0;
let renderCount = 0;
const state = {
settings: {
wsUrl: "ws://server:9000",
splitDecoderEnabled: true,
splitWsUrl: "ws://server:9001",
pitDecoderEnabled: true,
pitEntryWsUrl: "ws://server:9002",
pitExitWsUrl: "ws://server:9003",
},
decoder: {},
};
connectDecoderHelper({
state,
t: (key) => key,
saveState: () => {},
getWsClient: (role) => clients[role] || null,
setWsClient: (role, client) => { clients[role] = client; },
getReconnectTimer: (role) => timers[role] || null,
setReconnectTimer: (role, timer) => { timers[role] = timer; },
getCurrentView: () => "settings",
renderView: () => { renderCount += 1; },
updateConnectionBadge: () => {},
processDecoderMessage: () => {},
disconnectDecoder: () => { disconnected += 1; },
});
assert.equal(disconnected, 1);
assert.deepEqual(Object.fromEntries(Object.entries(clients).map(([role, client]) => [role, client.url])), {
main: "ws://server:9000",
split: "ws://server:9001",
pit_entry: "ws://server:9002",
pit_exit: "ws://server:9003",
});
clients.main.onopen();
clients.main.onerror();
clients.main.onclose();
assert.equal(renderCount, 0);
} finally {
globalThis.WebSocket = OriginalWebSocket;
}
});