import { expect, test } from "@playwright/test"; import { createSimulationActivityWatchdog, isSimulationSolverActivityPhase, observeSimulationActivityWatchdog, } from "../../src/App"; const TEST_STALL_TIMEOUT_MS = 1_000; test("首个含 activitySequence 的慢步心跳只建立活动基线", () => { const state = createSimulationActivityWatchdog(0); const watched = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 42 }, TEST_STALL_TIMEOUT_MS, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("slow-active"); expect(watched.observation.acceptedProgressIdleMs).toBe(TEST_STALL_TIMEOUT_MS); expect(watched.observation.activityIdleMs).toBe(0); }); test("accepted time 停滞但 activitySequence 持续增长时保持慢步运行", () => { let state = createSimulationActivityWatchdog(0); ({ state } = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 1, acceptedStepSequence: 10, acceptedTime: 0.048, }, 100, TEST_STALL_TIMEOUT_MS, )); let watched = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 2, acceptedStepSequence: 10, acceptedTime: 0.048, }, 1_000, TEST_STALL_TIMEOUT_MS, ); state = watched.state; expect(watched.observation.status).toBe("slow-active"); watched = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 3, acceptedStepSequence: 10, acceptedTime: 0.048, }, 2_500, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("slow-active"); expect(watched.observation.acceptedProgressIdleMs).toBe(2_500); expect(watched.observation.activityIdleMs).toBe(0); }); test("旧后端缺少活动字段时只报告遥测不可用且不误判 stalled", () => { const state = createSimulationActivityWatchdog(0); const watched = observeSimulationActivityWatchdog( state, { heartbeat: true }, 20_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("slow-telemetry-unavailable"); expect(watched.observation.activityIdleMs).toBeNull(); expect(watched.observation.activityTelemetryAvailable).toBe(false); }); test("旧后端缺少活动字段时使用 15 分钟兼容兜底", () => { const state = createSimulationActivityWatchdog(0); const watched = observeSimulationActivityWatchdog( state, { heartbeat: true }, 900_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("stalled"); expect(watched.observation.activityTelemetryAvailable).toBe(false); }); test("接受步和明确可用的活动序号都长期不变时才报告 stalled", () => { let state = createSimulationActivityWatchdog(0); ({ state } = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 7 }, 0, TEST_STALL_TIMEOUT_MS, )); const beforeDeadline = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 7 }, 999, TEST_STALL_TIMEOUT_MS, ); expect(beforeDeadline.observation.status).toBe("progressing"); const atDeadline = observeSimulationActivityWatchdog( beforeDeadline.state, { heartbeat: true, activitySequence: 7 }, 1_000, TEST_STALL_TIMEOUT_MS, ); expect(atDeadline.observation.status).toBe("stalled"); }); test("heartbeat 报告新的 acceptedStepSequence 时重置接受步计时", () => { let state = createSimulationActivityWatchdog(0); ({ state } = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 1, acceptedStepSequence: 10, acceptedTime: 0.048, }, 0, TEST_STALL_TIMEOUT_MS, )); const watched = observeSimulationActivityWatchdog( state, { heartbeat: true, activitySequence: 2, acceptedStepSequence: 11, acceptedTime: 0.049, }, 5_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("progressing"); expect(watched.observation.acceptedProgressIdleMs).toBe(0); expect(watched.state.lastAcceptedProgressAt).toBe(5_000); }); test("validation 和 compilation 阶段不参与求解器停滞判定", () => { let state = createSimulationActivityWatchdog(0); let watched = observeSimulationActivityWatchdog( state, { phase: "validation", heartbeat: true }, 90_000, TEST_STALL_TIMEOUT_MS, ); state = watched.state; expect(watched.observation.status).toBe("progressing"); expect(watched.observation.acceptedProgressIdleMs).toBe(0); expect(watched.state.integratingPhaseActive).toBe(false); watched = observeSimulationActivityWatchdog( state, { phase: "compilation", heartbeat: true, activitySequence: 7 }, 180_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("progressing"); expect(watched.observation.activityIdleMs).toBeNull(); expect(isSimulationSolverActivityPhase("compilation")).toBe(false); }); test("进入 integrating 时重新建立计时基线", () => { let state = createSimulationActivityWatchdog(0); ({ state } = observeSimulationActivityWatchdog( state, { phase: "initialization", heartbeat: true }, 120_000, TEST_STALL_TIMEOUT_MS, )); let watched = observeSimulationActivityWatchdog( state, { phase: "integrating", heartbeat: true, activitySequence: 11, acceptedStepSequence: 3, acceptedTime: 0.048, }, 180_000, TEST_STALL_TIMEOUT_MS, ); state = watched.state; expect(watched.observation.status).toBe("progressing"); expect(watched.observation.acceptedProgressIdleMs).toBe(0); expect(watched.observation.activityIdleMs).toBe(0); expect(watched.state.integratingPhaseActive).toBe(true); expect(isSimulationSolverActivityPhase("integrating")).toBe(true); watched = observeSimulationActivityWatchdog( state, { phase: "integrating", heartbeat: true, activitySequence: 11, acceptedStepSequence: 3, acceptedTime: 0.048, }, 181_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("stalled"); }); test("离开 integrating 后清除旧积分阶段的停滞状态", () => { let state = createSimulationActivityWatchdog(0); ({ state } = observeSimulationActivityWatchdog( state, { phase: "integrating", heartbeat: true, activitySequence: 5, }, 0, TEST_STALL_TIMEOUT_MS, )); ({ state } = observeSimulationActivityWatchdog( state, { phase: "integrating", heartbeat: true, activitySequence: 5, }, TEST_STALL_TIMEOUT_MS, TEST_STALL_TIMEOUT_MS, )); const watched = observeSimulationActivityWatchdog( state, { phase: "postprocessing", heartbeat: true }, 60_000, TEST_STALL_TIMEOUT_MS, ); expect(watched.observation.status).toBe("progressing"); expect(watched.observation.acceptedProgressIdleMs).toBe(0); expect(watched.state.integratingPhaseActive).toBe(false); expect(watched.state.lastActivitySequence).toBeNull(); });