from __future__ import annotations from pathlib import Path import unittest REPOSITORY_ROOT = Path(__file__).resolve().parents[2] SOURCE_PATH = REPOSITORY_ROOT / "apps" / "web-console" / "self-extension.js" class SelfExtensionFrontendDelayContractTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.source = SOURCE_PATH.read_text(encoding="utf-8") hydrate_start = cls.source.index(" function hydratePlan(steps) {") hydrate_end = cls.source.index("\n function setExecutionState", hydrate_start) cls.hydrate_source = cls.source[hydrate_start:hydrate_end] execute_start = cls.source.index(" async function executePlan(plan) {") execute_end = cls.source.index("\n function addHistory", execute_start) cls.execute_source = cls.source[execute_start:execute_end] def test_hydration_recognizes_and_strictly_validates_delay_steps(self) -> None: self.assertIn('if (step.kind === "delay")', self.hydrate_source) self.assertIn("Number.isInteger(step.delay_ms)", self.hydrate_source) self.assertIn("step.delay_ms < MIN_PLAN_DELAY_MS", self.hydrate_source) self.assertIn("step.delay_ms > MAX_PLAN_DELAY_MS", self.hydrate_source) self.assertIn("totalDelayMs > MAX_TOTAL_PLAN_DELAY_MS", self.hydrate_source) self.assertIn("index === 0 || index === plan.length - 1", self.hydrate_source) self.assertIn('plan[index - 1].kind === "delay"', self.hydrate_source) self.assertIn("step.delay_ms !== undefined && step.delay_ms !== 0", self.hydrate_source) self.assertNotIn(".filter(Boolean)", self.hydrate_source) def test_hydration_displays_a_canonical_five_second_label(self) -> None: self.assertIn("`等待 ${step.delay_ms / 1000} 秒`", self.hydrate_source) self.assertIn("delayMs: step.delay_ms", self.hydrate_source) def test_execution_routes_delay_only_to_the_timer(self) -> None: delay_branch = self.execute_source.index('if (step.kind === "delay")') vehicle_branch = self.execute_source.index('else if (step.kind === "vehicle")') peripheral_branch = self.execute_source.index('await sendGatewayCommand("emit"') self.assertLess(delay_branch, vehicle_branch) self.assertLess(vehicle_branch, peripheral_branch) between_delay_and_vehicle = self.execute_source[delay_branch:vehicle_branch] self.assertIn("await delay(step.delayMs);", between_delay_and_vehicle) self.assertNotIn("executeVehicle", between_delay_and_vehicle) self.assertNotIn("sendGatewayCommand", between_delay_and_vehicle) def test_explicit_delay_does_not_stack_with_default_action_spacing(self) -> None: self.assertIn("const nextStep = plan[index + 1];", self.execute_source) self.assertIn( 'nextStep && step.kind !== "delay" && nextStep.kind !== "delay"', self.execute_source, ) self.assertIn("await delay(state.settings.actionDelayMs);", self.execute_source) if __name__ == "__main__": unittest.main()