import test from "node:test"; import assert from "node:assert/strict"; import { createRequire } from "node:module"; const require = createRequire(import.meta.url); const { buildPublicTrainingProfile, findPublicTrainingShare, hasPublicTrainingShare, getPublicBestDurationResult, normalizePublicTrainingSince, normalizePublicTrainingWindow, splitPublicTrainingStints, addPublicTrainingMarker, savePublicEvenLapsConfig, savePublicStepTrainingConfig, recordPublicExerciseSession, } = require("../server/public_training_profile.js"); const DRIVER_ONE_TOKEN = "driver-one-token-12345678901234567890"; const DRIVER_TWO_TOKEN = "driver-two-token-12345678901234567890"; const DISABLED_TOKEN = "disabled-token-123456789012345678901"; function buildState(now = Date.now()) { return { drivers: [ { id: "driver_1", profileId: "profile_1", name: "Driver One", brand: "Associated 2WD", classId: "class_1", transponder: "secret-transponder-one" }, { id: "driver_1_4wd", profileId: "profile_1", name: "Driver One", brand: "Associated 4WD", classId: "class_2", transponder: "secret-transponder-four" }, { id: "driver_2", profileId: "profile_2", name: "Driver Two", brand: "Xray", classId: "class_1", transponder: "secret-transponder-two" }, ], classes: [ { id: "class_1", name: "2WD" }, { id: "class_2", name: "4WD" }, ], settings: { clubName: "Test Club", clubTagline: "Practice", logoDataUrl: "javascript:alert(1)", wsUrl: "ws://secret-decoder:9000", }, training: { shares: [ { driverId: "driver_1", token: DRIVER_ONE_TOKEN, enabled: true }, { driverId: "driver_2", token: DRIVER_TWO_TOKEN, enabled: true }, { driverId: "driver_1", token: DISABLED_TOKEN, enabled: false }, ], goals: [ { driverId: "driver_1", title: "Bronze", reward: "Badge", targetLaps: 5 }, { driverId: "driver_2", title: "Other goal", reward: "Other reward", targetLaps: 10 }, ], ledger: [ { driverId: "driver_1", sessionId: "free", timestamp: now - 25 * 60000, lapMs: 19000 }, { driverId: "driver_1", sessionId: "free", timestamp: now - 120000, lapMs: 18000 }, { driverId: "driver_1", sessionId: "free", timestamp: now - 12000, lapMs: 16500 }, { driverId: "driver_1", sessionId: "free", timestamp: now - 4000, lapMs: 16000 }, { driverId: "driver_1", sessionId: "free", timestamp: now - 2000, lapMs: 500 }, { driverId: "driver_1_4wd", sessionId: "free", timestamp: now - 11000, lapMs: 14500 }, { driverId: "driver_1_4wd", sessionId: "free", timestamp: now - 5000, lapMs: 14000 }, { driverId: "driver_2", sessionId: "free", timestamp: now - 3000, lapMs: 12000 }, ], }, resultsBySession: { free: { splits: [ { driverId: "driver_1", splitMs: 7200 }, { driverId: "driver_1", splitMs: 7000 }, { driverId: "driver_1_4wd", splitMs: 6400 }, { driverId: "driver_2", splitMs: 6100 }, ], pitStops: [ { driverId: "driver_1", pitStopMs: 12500, transponder: "secret-transponder-one" }, { driverId: "driver_1_4wd", pitStopMs: 11000, transponder: "secret-transponder-four" }, { driverId: "driver_2", pitStopMs: 9500, transponder: "secret-transponder-two" }, ], }, }, }; } test("public share lookup rejects short, disabled, and unknown tokens", () => { const state = buildState(); assert.equal(findPublicTrainingShare(state, "short"), null); assert.equal(findPublicTrainingShare(state, DISABLED_TOKEN), null); assert.equal(hasPublicTrainingShare(state, "unknown-token-123456789012345678"), false); assert.equal(hasPublicTrainingShare(state, DRIVER_ONE_TOKEN), true); }); test("public mobile marker is restricted to the shared profile and selected class", () => { const state = buildState(); const marker = addPublicTrainingMarker(state, DRIVER_ONE_TOKEN, { classId: "class_2", type: "tires", note: " New rear tires " }, 123456); assert.deepEqual({ driverId: marker.driverId, classId: marker.classId, type: marker.type, note: marker.note, timestamp: marker.timestamp }, { driverId: "driver_1_4wd", classId: "class_2", type: "tires", note: "New rear tires", timestamp: 123456 }); assert.equal(state.training.markers.length, 1); assert.equal(addPublicTrainingMarker(state, DRIVER_TWO_TOKEN, { classId: "class_2" }, 123457), null); assert.equal(addPublicTrainingMarker(state, DISABLED_TOKEN, { classId: "class_1" }, 123458), null); }); test("a driver can save their own even-laps and step-training exercise from their mobile link", () => { const state = buildState(); const evenLaps = savePublicEvenLapsConfig(state, DRIVER_ONE_TOKEN, { classId: "class_2", targetMs: 14000, toleranceMs: 300, lapCount: 6 }); assert.deepEqual({ driverId: evenLaps.driverId, classId: evenLaps.classId, targetMs: evenLaps.targetMs, toleranceMs: evenLaps.toleranceMs, lapCount: evenLaps.lapCount }, { driverId: "driver_1_4wd", classId: "class_2", targetMs: 14000, toleranceMs: 300, lapCount: 6 }); assert.equal(state.training.evenLaps.length, 1); // Saving again for the same driver+class updates in place rather than adding a duplicate. savePublicEvenLapsConfig(state, DRIVER_ONE_TOKEN, { classId: "class_2", targetMs: 13500, toleranceMs: 250, lapCount: 5 }); assert.equal(state.training.evenLaps.length, 1); assert.equal(state.training.evenLaps[0].targetMs, 13500); // A different driver's token, or a class the driver isn't in, must not be able to write it. assert.equal(savePublicEvenLapsConfig(state, DRIVER_TWO_TOKEN, { classId: "class_2", targetMs: 10000, toleranceMs: 300, lapCount: 5 }), null); assert.equal(savePublicEvenLapsConfig(state, DRIVER_ONE_TOKEN, { classId: "unknown_class", targetMs: 10000, toleranceMs: 300, lapCount: 5 }), null); assert.equal(savePublicEvenLapsConfig(state, DRIVER_ONE_TOKEN, { classId: "class_2", targetMs: 0, toleranceMs: 300, lapCount: 5 }), null, "a non-positive target is rejected"); const stepTraining = savePublicStepTrainingConfig(state, DRIVER_ONE_TOKEN, { classId: "class_1", steps: [{ effort: "calm", lapCount: 3 }, { effort: "controlled", lapCount: 2 }] }); assert.equal(stepTraining.driverId, "driver_1"); assert.deepEqual(stepTraining.steps, [{ effort: "calm", lapCount: 3 }, { effort: "controlled", lapCount: 2 }]); assert.equal(savePublicStepTrainingConfig(state, DRIVER_TWO_TOKEN, { classId: "class_2", steps: [{ effort: "calm", lapCount: 3 }] }), null, "driver_2 is not in class_2, unlike class_1 which they share with driver_1"); assert.equal(savePublicStepTrainingConfig(state, DRIVER_ONE_TOKEN, { classId: "class_1", steps: [] }), null, "a sequence with no laps is rejected"); }); test("public profile computes even-laps and step-training progress from their own since-marker, never leaking raw lap rows", () => { const now = Date.now(); const state = buildState(now); state.training.evenLaps = [{ id: "e1", driverId: "driver_1", classId: "class_1", targetMs: 16000, toleranceMs: 500, lapCount: 5 }]; state.training.stepTraining = [{ id: "s1", driverId: "driver_1", classId: "class_1", steps: [{ effort: "calm", lapCount: 1 }, { effort: "controlled", lapCount: 1 }] }]; // buildState's class_1 ledger for driver_1, oldest to newest: 19000, 18000, 16500, 16000 // (the 500ms lap is dropped by the ">500ms" pace filter applied before classLaps is built). // A since-marker far enough back captures all four. const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "", 0, 0, now - 2_000_000, now - 2_000_000); const classOne = profile.classProfiles.find((item) => item.classId === "class_1"); assert.deepEqual(classOne.evenLapsConfig, { targetMs: 16000, toleranceMs: 500, lapCount: 5 }); // 16000 and 16500 are both within 16000+/-500 of the target; 18000 before them is not. assert.equal(classOne.evenLapsProgress.currentStreak, 2); assert.equal(classOne.evenLapsProgress.active, true); assert.deepEqual(classOne.stepTrainingConfig, { steps: [{ effort: "calm", lapCount: 1 }, { effort: "controlled", lapCount: 1 }] }); assert.equal(classOne.stepTrainingProgress.complete, true, "both configured laps (out of four recorded since the marker) are filled"); assert.equal(classOne.stepTrainingProgress.steps[0].averageLapMs, 19000); assert.equal(classOne.stepTrainingProgress.steps[1].averageLapMs, 18000); const serialized = JSON.stringify(profile); assert.equal(serialized.includes("driver_1"), false, "the internal driver id must never reach the public payload, including in the new exercise fields"); }); test("public profile omits exercise progress and configuration when the driver has not configured or started one", () => { const now = Date.now(); const profile = buildPublicTrainingProfile(buildState(now), DRIVER_ONE_TOKEN, "", 0, 0); const classOne = profile.classProfiles.find((item) => item.classId === "class_1"); assert.equal(classOne.evenLapsConfig, null); assert.equal(classOne.evenLapsProgress, null); assert.equal(classOne.stepTrainingConfig, null); assert.equal(classOne.stepTrainingProgress, null); }); test("public profile contains only the selected driver's sanitized aggregates", () => { const now = Date.now(); const profile = buildPublicTrainingProfile(buildState(now), DRIVER_ONE_TOKEN, "2026-08-24T12:00:00.000Z", now - 30000, 30000); assert.equal(profile.profile.name, "Driver One"); assert.equal(profile.profile.brand, "Associated 2WD"); assert.equal(profile.profile.className, "2WD"); assert.equal("clubRank" in profile.profile, false); assert.equal("rankedDrivers" in profile.profile, false); assert.equal(profile.branding.clubName, "Test Club"); assert.equal(profile.branding.logoDataUrl, ""); assert.equal(profile.stats.totalLaps, 4); assert.equal(profile.stats.bestLapMs, 16000); assert.deepEqual(profile.currentSession, { laps: 2, bestLapMs: 16000, averageLapMs: 16250, consistencySpreadPct: 0, consistencySampleReady: false }); assert.equal(profile.goals.length, 1); assert.equal(profile.goals[0].title, "Bronze"); assert.equal(profile.goals[0].percent, 80); assert.equal(profile.classProfiles.length, 2); assert.equal(profile.classProfiles[0].stats.totalLaps, 4); assert.equal(profile.classProfiles[0].stats.achievements.find((item) => item.id === "first_session").unlocked, true); assert.ok(Array.isArray(profile.classProfiles[0].stats.paceBuckets)); assert.deepEqual(profile.classProfiles[0].channelStats, { splitCount: 2, bestSplitMs: 7000, averageSplitMs: 7100, latestSplitGainMs: 200, pitStopCount: 1, totalPitMs: 12500, bestPitStopMs: 12500, averagePitStopMs: 12500, }); assert.equal(profile.classProfiles[1].className, "4WD"); assert.equal(profile.classProfiles[1].stats.totalLaps, 2); assert.equal(profile.classProfiles[1].stats.bestLapMs, 14000); assert.equal(profile.classProfiles[1].channelStats.splitCount, 1); assert.equal(profile.classProfiles[1].channelStats.pitStopCount, 1); const serialized = JSON.stringify(profile); assert.equal(serialized.includes("Driver Two"), false); assert.equal(serialized.includes("driver_1"), false); assert.equal(serialized.includes("secret-transponder"), false); assert.equal(serialized.includes("secret-decoder"), false); }); test("public profile's personal-best and lifetime totals are scoped correctly across a track layout change", () => { const now = Date.now(); const oldLayoutDay = now - 60 * 24 * 60 * 60 * 1000; const state = buildState(now); state.trackLayouts = [ { id: "layout_1", label: "Layout 1", startedAt: oldLayoutDay - 1000 }, { id: "layout_2", label: "Layout 2", startedAt: now - 24 * 60 * 60 * 1000 }, ]; // Old layout: a much faster time from a track that no longer exists. state.training.ledger.push( { driverId: "driver_1", sessionId: "old_track", timestamp: oldLayoutDay, lapMs: 9000, trackLayoutId: "layout_1" }, { driverId: "driver_1", sessionId: "old_track", timestamp: oldLayoutDay + 10000, lapMs: 9200, trackLayoutId: "layout_1" }, ); // Every lap already in buildState's fixture predates any layout tracking (no // trackLayoutId at all) - it must not be conflated with "layout_2" either. const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); const classOne = profile.classProfiles.find((item) => item.classId === "class_1"); assert.equal(classOne.stats.totalLaps, 6, "lifetime total counts the old-layout laps too"); assert.equal(classOne.stats.bestLapMs, 0, "no lap is tagged with the currently active layout yet, so there is no current-layout best"); // Tag one of the existing (previously untagged) laps as run on the current layout. state.training.ledger.find((lap) => lap.lapMs === 16000).trackLayoutId = "layout_2"; const updatedProfile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); const updatedClassOne = updatedProfile.classProfiles.find((item) => item.classId === "class_1"); assert.equal(updatedClassOne.stats.bestLapMs, 16000, "the current-layout lap is now the reported best, not the faster but incomparable old-layout lap"); assert.equal(updatedClassOne.stats.totalLaps, 6, "lifetime total is unaffected by which layout is current"); }); test("public profile visibility removes disabled sections from the API payload", () => { const state = buildState(); state.training.shares[0].visibility = { progress: false, achievements: false, performance: false, goals: false, history: false }; const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); const classProfile = profile.classProfiles[0]; assert.deepEqual(profile.visibility, { progress: false, achievements: false, performance: false, goals: false, history: false }); assert.equal(classProfile.goals.length, 0); assert.deepEqual(classProfile.channelStats, {}); assert.equal("paceHistory" in classProfile.stats, false); assert.equal("achievements" in classProfile.stats, false); assert.equal("stintSummaries" in classProfile.stats, false); assert.equal("dailySummaries" in classProfile.stats, false); assert.equal(classProfile.stats.totalLaps, 4); assert.equal(classProfile.stats.bestLapMs, 16000); }); test("public profile exposes only class-scoped development trends and achievements", () => { const now = Date.now(); const state = buildState(now); state.training.ledger = Array.from({ length: 60 }, (_, index) => ({ driverId: "driver_1", sessionId: "practice_2wd", timestamp: now - (60 - index) * 18_000, lapMs: index < 30 ? 18_000 : 17_000, })); const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); const twoWheelDrive = profile.classProfiles.find((item) => item.classId === "class_1"); const fourWheelDrive = profile.classProfiles.find((item) => item.classId === "class_2"); assert.equal(twoWheelDrive.stats.trendSampleReady, true); assert.equal(twoWheelDrive.stats.averageTrendMs, 1_000); assert.equal(twoWheelDrive.stats.achievements.find((item) => item.id === "controlled_5").unlocked, true); assert.equal(fourWheelDrive.stats.totalLaps, 0); assert.equal(fourWheelDrive.stats.trendSampleReady, false); }); test("public timer window freezes aggregates after the personal session", () => { const now = Date.now(); const profile = buildPublicTrainingProfile(buildState(now), DRIVER_ONE_TOKEN, "", now - 30 * 60000, 20 * 60000); assert.deepEqual(profile.currentSession, { laps: 1, bestLapMs: 19000, averageLapMs: 19000, consistencySpreadPct: 0, consistencySampleReady: false }); }); test("public timer window reports consistency once it has enough laps, and withholds it otherwise", () => { const now = Date.now(); const state = buildState(now); const sessionStart = now - 5 * 60000; state.training.ledger = [ { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 10000, lapMs: 16000 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 20000, lapMs: 16100 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 30000, lapMs: 16200 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 40000, lapMs: 16300 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 50000, lapMs: 16400 }, ]; const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "", sessionStart, 5 * 60000); assert.equal(profile.currentSession.laps, 5); assert.equal(profile.currentSession.consistencySampleReady, true); // mean 16200, p10 (index 0) 16000, p90 (index 4) 16400: (16400-16000)/16200*100 assert.ok(Math.abs(profile.currentSession.consistencySpreadPct - ((16400 - 16000) / 16200) * 100) < 0.001); const shortProfile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, "", sessionStart + 35000, 5 * 60000); assert.equal(shortProfile.currentSession.laps, 2, "only the last two laps fall inside this shorter window"); assert.equal(shortProfile.currentSession.consistencySampleReady, false, "fewer than 5 laps must not report a spread"); assert.equal(shortProfile.currentSession.consistencySpreadPct, 0); }); test("public profile returns null when token or driver is unavailable", () => { const state = buildState(); assert.equal(buildPublicTrainingProfile(state, DISABLED_TOKEN, ""), null); state.drivers = state.drivers.filter((driver) => driver.id !== "driver_1"); assert.equal(buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""), null); }); test("public timer query normalization bounds age and window values", () => { const before = Date.now(); const since = normalizePublicTrainingSince(undefined, "30000"); const after = Date.now(); assert.ok(since >= before - 30000 && since <= after - 30000); assert.equal(normalizePublicTrainingSince(undefined, String(25 * 60 * 60 * 1000)), 0); assert.equal(normalizePublicTrainingWindow("-100"), 0); assert.equal(normalizePublicTrainingWindow(String(25 * 60 * 60 * 1000)), 24 * 60 * 60 * 1000); }); test("public profile counts a max-boundary lap but excludes it from pace", () => { const now = Date.now(); const state = buildState(now); state.training.ledger = [ { driverId: "driver_1", sourceId: "tp_1", sessionId: "practice", timestamp: now - 127_000, lapMs: 37_000 }, { driverId: "driver_1", sourceId: "tp_1", sessionId: "practice", timestamp: now - 90_000, lapMs: 37_000 }, { driverId: "driver_1", sourceId: "tp_1", sessionId: "practice", timestamp: now, lapMs: 90_000, excludedFromPace: true, stintBoundary: true }, ]; const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); assert.equal(profile.stats.totalLaps, 3); assert.equal(profile.stats.bestLapMs, 37_000); assert.equal(profile.stats.averageLapMs, 37_000); assert.equal(profile.stats.stintCount, 2); }); test("public five-minute result never combines simultaneous transponder sources", () => { const laps = ["tp_a", "tp_b"].flatMap((sourceId) => Array.from({ length: 9 }, (_, index) => ({ sourceId, sessionId: "practice_1", timestamp: (index + 1) * 37_000, lapMs: 37_000, }))); const result = getPublicBestDurationResult(splitPublicTrainingStints(laps), 300_000); assert.equal(result.lapCount, 9); assert.equal(result.totalMs, 333_000); }); test("public profile duration result includes the lap started before five minutes", () => { const laps = Array.from({ length: 16 }, (_, index) => ({ sessionId: "practice_1", timestamp: (index + 1) * 18_000, lapMs: 18_000, })); laps.push({ sessionId: "practice_1", timestamp: 314_123, lapMs: 26_123 }); const result = getPublicBestDurationResult(splitPublicTrainingStints(laps), 300_000); assert.equal(result.lapCount, 17); assert.equal(result.totalMs, 314_123); assert.equal(result.averageLapMs, 18_478); }); function buildExerciseSessionState(now) { return { drivers: [{ id: "driver_1", profileId: "profile_1", name: "Driver One", classId: "class_1", transponder: "secret-transponder-one" }], classes: [{ id: "class_1", name: "2WD" }], settings: {}, training: { shares: [{ driverId: "driver_1", token: DRIVER_ONE_TOKEN, enabled: true }], ledger: [], }, resultsBySession: {}, }; } test("recording a completed step-training session verifies it against real ledger laps, never trusting the client's claim", () => { const now = Date.now(); const state = buildExerciseSessionState(now); state.training.stepTraining = [{ driverId: "driver_1", classId: "class_1", steps: [{ effort: "calm", lapCount: 2 }, { effort: "controlled", lapCount: 2 }] }]; const sessionStart = now - 60000; // A driver who has NOT actually completed the sequence (only 1 of the required 4 laps) cannot // have a completion recorded just by asking for one. state.training.ledger = [{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 5000, lapMs: 16000 }]; assert.equal(recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "stepTraining", startedAgoMs: now - sessionStart }, now), null); assert.equal((state.training.exerciseSessions || []).length, 0); // Once the real ledger actually has all 4 laps, the same request succeeds and the summary is // computed from that real data, not anything the client sent. state.training.ledger.push( { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 15000, lapMs: 16100 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 25000, lapMs: 20000 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 35000, lapMs: 20200 }, ); const record = recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "stepTraining", startedAgoMs: now - sessionStart }, now); assert.ok(record); assert.equal(record.kind, "stepTraining"); assert.equal(record.driverId, "driver_1"); assert.equal(record.summary.totalSteps, 2); assert.equal(record.summary.totalLaps, 4); assert.deepEqual(record.summary.controlledComparison, null, "only one controlled step exists, so there is nothing to compare"); // Calling it again for the same run must not create a second history entry. const again = recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "stepTraining", startedAgoMs: now - sessionStart }, now); assert.equal(again.id, record.id); assert.equal(state.training.exerciseSessions.length, 1); }); test("recording a completed even-laps session requires the target streak to have genuinely been reached", () => { const now = Date.now(); const state = buildExerciseSessionState(now); state.training.evenLaps = [{ driverId: "driver_1", classId: "class_1", targetMs: 16000, toleranceMs: 300, lapCount: 3 }]; const sessionStart = now - 60000; // Only 2 laps within tolerance so far - not yet a streak of 3. state.training.ledger = [ { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 5000, lapMs: 16000 }, { driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 15000, lapMs: 16100 }, ]; assert.equal(recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "evenLaps", startedAgoMs: now - sessionStart }, now), null); state.training.ledger.push({ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 25000, lapMs: 15900 }); const record = recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "evenLaps", startedAgoMs: now - sessionStart }, now); assert.ok(record); assert.equal(record.summary.streak, 3); assert.equal(record.summary.lapCount, 3); }); test("recording a completed timer session requires real elapsed time and at least one lap", () => { const now = Date.now(); const state = buildExerciseSessionState(now); const sessionStart = now - 300000; state.training.ledger = [{ driverId: "driver_1", sessionId: "free", timestamp: sessionStart + 10000, lapMs: 16000 }]; // Claiming a 5-minute session finished after only 1 minute of real elapsed time is rejected. assert.equal(recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "timer", startedAgoMs: 60000, windowMs: 300000 }, now), null); const record = recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "timer", startedAgoMs: now - sessionStart, windowMs: 300000 }, now); assert.ok(record); assert.equal(record.summary.laps, 1); assert.equal(record.summary.bestLapMs, 16000); }); test("recording an exercise session for an unknown class or invalid kind is rejected", () => { const now = Date.now(); const state = buildExerciseSessionState(now); assert.equal(recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "unknown_class", kind: "timer", startedAgoMs: 60000, windowMs: 60000 }, now), null); assert.equal(recordPublicExerciseSession(state, DRIVER_ONE_TOKEN, { classId: "class_1", kind: "not_a_real_kind", startedAgoMs: 60000 }, now), null); assert.equal(recordPublicExerciseSession(state, DISABLED_TOKEN, { classId: "class_1", kind: "timer", startedAgoMs: 60000, windowMs: 60000 }, now), null); }); test("a goal targeting completed sessions computes progress from real exerciseSessions, and history/driverId stay out of the public response", () => { const now = Date.now(); const state = buildExerciseSessionState(now); state.training.goals = [{ driverId: "driver_1", classId: "class_1", metric: "sessions", exerciseKind: "stepTraining", targetSessions: 2, title: "Coffee mug", reward: "Mug" }]; state.training.exerciseSessions = [ { id: "ex_1", driverId: "driver_1", classId: "class_1", kind: "stepTraining", startedAt: now - 100000, completedAt: new Date(now - 90000).toISOString(), summary: { totalSteps: 2, totalLaps: 4 } }, { id: "ex_2", driverId: "driver_1", classId: "class_1", kind: "evenLaps", startedAt: now - 50000, completedAt: new Date(now - 40000).toISOString(), summary: { streak: 5 } }, ]; const profile = buildPublicTrainingProfile(state, DRIVER_ONE_TOKEN, ""); const classOne = profile.classProfiles.find((item) => item.classId === "class_1"); assert.equal(classOne.goals[0].metric, "sessions"); assert.equal(classOne.goals[0].exerciseKind, "stepTraining"); assert.equal(classOne.goals[0].currentLaps, 1, "only the stepTraining session counts toward this goal, not the evenLaps one"); assert.equal(classOne.goals[0].targetLaps, 2); assert.equal(classOne.goals[0].reached, false); assert.equal(classOne.exerciseSessionHistory.length, 2); assert.deepEqual(classOne.exerciseSessionHistory.map((entry) => entry.kind).sort(), ["evenLaps", "stepTraining"]); const serialized = JSON.stringify(classOne.exerciseSessionHistory); assert.ok(!serialized.includes("driver_1"), "exercise session history must never expose driverId"); assert.ok(!serialized.includes("class_1"), "exercise session history must never expose classId"); });