feat(web-console): rebuild intelligent cockpit sync interface

This commit is contained in:
vee
2026-08-18 13:24:36 +08:00
parent 344f662970
commit 9b24d5c8c9
30 changed files with 4100 additions and 12594 deletions
+104 -103
View File
@@ -174,37 +174,81 @@ operations:
finally:
self.service.mode_profiles = original_registry
def test_head_unit_display_sync_tracks_tasks_and_usb_catalog(self) -> None:
task_request = urllib.request.Request(
f"{self.base_url}/api/requests",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps(
{
"utterance": "副驾有点热,有点闷",
"provider": "local",
"conversation_id": "conv-head-unit-display-sync",
"vehicle_id": "denza_n7",
"execution_mode": "dry_run",
"client_type": "head_unit",
},
ensure_ascii=False,
).encode("utf-8"),
)
with self.opener.open(task_request, timeout=3) as response:
task_result = json.loads(response.read().decode("utf-8"))
peripheral = {
"id": "cap-usb-light-on",
"device_id": "usb-light-1",
"device_name": "USB 氛围灯条",
"device_type": "light",
"type_label": "灯光外设",
"action": "开启灯带",
}
extension_request = urllib.request.Request(
f"{self.base_url}/api/extension/display-sync",
method="POST",
headers={"Content-Type": "application/json"},
data=json.dumps(
{"device": {"id": "usb-light-1"}, "peripherals": [peripheral]},
ensure_ascii=False,
).encode("utf-8"),
)
with self.opener.open(extension_request, timeout=3) as response:
extension_result = json.loads(response.read().decode("utf-8"))
with self.opener.open(
f"{self.base_url}/api/vehicle-display-sync", timeout=3
) as response:
snapshot = json.loads(response.read().decode("utf-8"))
self.assertEqual(task_result["status"], "succeeded")
self.assertTrue(extension_result["success"])
self.assertEqual(snapshot["task"]["utterance"], "副驾有点热,有点闷")
self.assertEqual(snapshot["task"]["result"]["request_id"], task_result["request_id"])
self.assertEqual(snapshot["extension"]["catalog"], [peripheral])
self.assertEqual(snapshot["extension"]["last_action"]["type"], "hardware_detected")
self.assertIn("extension_health", snapshot)
def test_static_console_and_runtime_endpoint(self) -> None:
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-82", timeout=3) as response:
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-85", timeout=3) as response:
app_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/gesture-control.js?v=android-pad-7", timeout=3
f"{self.base_url}/gesture-control.js?v=android-pad-8", timeout=3
) as response:
gesture_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.js?v=desktop-refactor-36", timeout=3
f"{self.base_url}/console-v2.js?v=sync-layout-11", timeout=3
) as response:
console_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/console-v2.css?v=desktop-refactor-107", timeout=3
f"{self.base_url}/console-v2.css?v=sync-layout-11", timeout=3
) as response:
console_styles = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-evolution.js?v=seat-weighing-2", timeout=3
) as response:
evolution_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-evolution.css?v=seat-weighing-5", timeout=3
) as response:
evolution_styles = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-extension.js?v=extension-21", timeout=3
f"{self.base_url}/self-extension.js?v=sync-layout-11", timeout=3
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/self-extension.css?v=extension-19", timeout=3
f"{self.base_url}/self-extension.css?v=sync-layout-11", timeout=3
) as response:
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
with self.opener.open(
@@ -231,75 +275,48 @@ operations:
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
self.assertNotIn("透明车体 · 实体设备", html)
self.assertIn('src="/app.js?v=dual-seat-82"', html)
self.assertIn('src="/app.js?v=dual-seat-85"', html)
self.assertIn('href="/styles.css?v=dual-seat-9"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-7"', html)
self.assertIn('src="/gesture-control.js?v=android-pad-8"', html)
self.assertNotIn('class="vehicle-system-hud"', html)
self.assertIn('src="/console-v2.js?v=desktop-refactor-36"', html)
self.assertIn('href="/console-v2.css?v=desktop-refactor-107"', html)
self.assertIn('src="/self-evolution.js?v=seat-weighing-2"', html)
self.assertIn('href="/self-evolution.css?v=seat-weighing-5"', html)
self.assertIn('src="/self-extension.js?v=extension-21"', html)
self.assertIn('href="/self-extension.css?v=extension-19"', html)
self.assertIn('src="/console-v2.js?v=sync-layout-11"', html)
self.assertIn('href="/console-v2.css?v=sync-layout-11"', html)
self.assertIn('src="/self-extension.js?v=sync-layout-11"', html)
self.assertIn('href="/self-extension.css?v=sync-layout-11"', html)
self.assertNotIn("self-calibration", html)
self.assertNotIn("self-repair", html)
self.assertNotIn("self-evolution", html)
self.assertNotIn("滚动查看更多", html)
self.assertIn('aria-label="执行车舱控制指令"', html)
self.assertIn('sessionStorage.getItem("cognitive-agent.demo-role")', console_script)
self.assertNotIn('"admin"', console_script)
self.assertNotIn("v2RoleSelect", console_script)
self.assertIn('id="v2SidebarToggle"', console_script)
self.assertIn('id = "v2ModuleSwitcher"', console_script)
self.assertIn(
'panelHeading.classList.add("v2-operation-heading", "v2-module-heading")',
console_script,
)
self.assertIn('kicker.textContent = "SELF-ORCHESTRATION"', console_script)
self.assertIn(
self.assertIn('kicker.textContent = "REMOTE ORCHESTRATION · LIVE SYNC"', console_script)
self.assertIn('fetch("/api/vehicle-display-sync"', console_script)
self.assertIn('document.body.classList.add("v2-passive-display", "v2-interactive-sync")', console_script)
self.assertIn('input.readOnly = false', console_script)
self.assertIn('function buildTwinDeviceInteractions()', console_script)
self.assertIn('id="v2SyncLogList"', console_script)
self.assertIn('data-panel-toggle="devices"', console_script)
self.assertIn('data-panel-toggle="extensions"', console_script)
self.assertIn('id="vehicleExtensionSync"', console_script)
self.assertNotIn(
'if (ui.activeModule === "self-extension" && ui.role !== "user")',
console_script,
)
self.assertIn(
'if (ui.activeModule === "self-extension" && role !== "user" && !allowModuleTransition)',
self.assertNotIn(
'if (ui.activeModule === "self-extension" && role !== "user"',
console_script,
)
self.assertIn('label: "自编排", available: true', console_script)
self.assertIn('id: "self-calibration"', console_script)
self.assertIn('label: "自标定"', console_script)
self.assertLess(
console_script.index('label: "自标定"'),
console_script.index('label: "自编排"'),
)
self.assertIn("adminOnly: true", console_script)
self.assertIn(
"$('.v2-module-tab[data-module=\"self-calibration\"]')",
console_script,
)
self.assertIn('label: "自扩展", available: true', console_script)
self.assertIn('aria-label="自扩展面向所有登录用户"', console_script)
self.assertIn('511工程-智能座舱', console_script)
self.assertIn('网页控制链路', console_script)
self.assertIn('<span>Hi,</span><strong>user</strong>', console_script)
self.assertIn('id: "self-repair"', console_script)
self.assertIn('label: "自修复"', console_script)
self.assertIn('id: "self-evolution", label: "自进化", available: true', console_script)
self.assertIn(
'window.CognitiveAgentSelfEvolution?.mount(mainPanel)',
console_script,
)
self.assertIn('caption.textContent = evolutionMode ? "进化视图" : "控制方式"', console_script)
self.assertIn('{ label: "座椅称重进化", icon: "scale"', console_script)
self.assertIn('{ label: "系统能力日志", icon: "scroll-text"', console_script)
self.assertIn('aria-label", "自进化 · 座椅称重个性化模型进化"', evolution_script)
self.assertIn('SELF-EVOLUTION · SEAT WEIGHING', evolution_script)
self.assertIn('座椅称重模型进化', evolution_script)
self.assertIn('系统能力日志', evolution_script)
self.assertIn('data-family-model="${model.id}"', evolution_script)
self.assertIn('用户 A', evolution_script)
self.assertIn('用户 B', evolution_script)
self.assertIn('用户 C', evolution_script)
self.assertIn('访客', evolution_script)
self.assertIn('退化保护已触发', evolution_script)
self.assertIn('本次未参与发布', evolution_script)
self.assertIn('演示进化过程', evolution_script)
self.assertIn('家庭体脂秤', evolution_script)
self.assertIn('目标误差', evolution_script)
self.assertIn('["self-orchestration", "self-extension", "self-evolution"]', console_script)
self.assertIn('roleSelect.disabled = ui.activeModule === "self-repair"', console_script)
self.assertIn('["self-orchestration", "self-extension"]', console_script)
self.assertNotIn('label: "自诊断"', console_script)
self.assertIn('<small>手势模型</small>', console_script)
self.assertIn('id="v2GestureModelValue">MediaPipe GR</strong>', console_script)
@@ -316,13 +333,12 @@ operations:
self.assertIn('const dryRunButton = $(\'[data-execution-mode="dry_run"]\')', console_script)
self.assertIn('if (!dryRunButton.classList.contains("active")) dryRunButton.click()', console_script)
self.assertIn('userTargetText.textContent = "执行目标:虚拟 CAN"', console_script)
self.assertIn("真实台架仅由管理员显式启用", console_script)
self.assertIn("user 固定使用虚拟 CAN 安全执行环境", console_script)
self.assertNotIn("user 演示固定使用真实台架", console_script)
self.assertIn('grid-template-columns: clamp(245px, 18.5vw, 310px)', console_styles)
self.assertIn('.v2-control-switch {', console_styles)
self.assertIn('.v2-module-switcher {', console_styles)
self.assertIn('grid-template-columns: repeat(5, minmax(0, 1fr))', console_styles)
self.assertIn('[data-module="self-evolution"] .v2-self-evolution', evolution_styles)
self.assertIn('grid-template-columns: repeat(2, minmax(0, 1fr))', console_styles)
self.assertIn(
'.console-v2 .v2-module-heading.v2-module-heading h1',
console_styles,
@@ -334,22 +350,6 @@ operations:
'grid-template-rows: 36px clamp(236px, 33dvh, 246px) minmax(0, 1fr)',
console_styles,
)
self.assertIn(
'[data-module="self-calibration"] .v2-self-calibration',
console_styles,
)
self.assertIn(
'[data-module="self-repair"] .v2-self-repair',
console_styles,
)
self.assertIn('.v2-evolution-version.active', evolution_styles)
self.assertIn('[data-evolution-scope="admin"]', evolution_styles)
self.assertIn('.v2-weighing-workspace', evolution_styles)
self.assertIn('.v2-family-model.active', evolution_styles)
self.assertIn('.v2-personal-line', evolution_styles)
self.assertIn('.v2-linkage-guard', evolution_styles)
self.assertIn('.v2-parameter-grid', evolution_styles)
self.assertIn('[data-demo-role="admin"] [data-evolution-scope="admin"]', evolution_styles)
self.assertIn(
'color-mix(in srgb, var(--v2-accent-soft) 86%, white)',
console_styles,
@@ -362,14 +362,13 @@ operations:
self.assertIn('background: color-mix(in srgb, var(--v2-accent) 30%, var(--v2-accent-soft))', console_styles)
self.assertIn('width: clamp(48px, 45%, 72px)', console_styles)
self.assertIn('font-weight: 800', console_styles)
self.assertIn('width: clamp(300px, 28vw, 360px)', console_styles)
self.assertIn('white-space: nowrap', console_styles)
self.assertIn('.v2-gesture-model-field', console_styles)
self.assertIn('.v2-inline-gesture .v2-camera-toggle', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-action-rail', console_styles)
self.assertIn('grid-template-rows: 42px 10px 42px', console_styles)
self.assertIn('background: var(--v2-accent);', console_styles)
self.assertIn('[data-demo-role="user"] .v2-inline-gesture #gestureArmButton svg', console_styles)
self.assertNotIn("gestureArmButton", console_styles)
self.assertIn('.v2-inline-gesture #gestureStopButton', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-result-stack', console_styles)
self.assertIn('.v2-inline-gesture .v2-gesture-execution-slot', console_styles)
@@ -380,7 +379,7 @@ operations:
self.assertIn('.v2-edge-fade-right', console_styles)
self.assertIn('.mode-group-divider', console_styles)
self.assertIn('.joint-twin-shell > .cabin-band', console_styles)
self.assertIn('[data-demo-role="user"] .v2-page-index', console_styles)
self.assertIn('.console-v2 .v2-page-index', console_styles)
self.assertIn('.joint-twin-shell .twin-readout', console_styles)
self.assertIn('.joint-seat-panel .thermal-states', console_styles)
self.assertIn('.joint-twin-shell .domain-readouts', console_styles)
@@ -421,7 +420,7 @@ operations:
self.assertIn("function styleReferencePassiveGlass", app_script)
self.assertIn("function styleReferencePassiveRearExterior", app_script)
self.assertIn('twinRole: "reference_passive_rear_bumper"', app_script)
self.assertIn('"cognitive-agent:demo-role-change"', console_script)
self.assertNotIn('"cognitive-agent:demo-role-change"', console_script)
self.assertIn('resultStack.id = "v2GestureResultStack"', console_script)
self.assertIn('executionSlot.id = "v2GestureExecutionSlot"', console_script)
self.assertIn('statusTarget.append(requestStatus)', console_script)
@@ -450,9 +449,9 @@ operations:
self.assertIn('healthState.message || "GSCU1B 尚未连接"', extension_script)
self.assertIn('window.addEventListener("pageshow"', extension_script)
self.assertIn('[data-module="self-extension"]', extension_styles)
self.assertIn('[data-module="self-extension"] .v2-role-select', extension_styles)
self.assertNotIn('[data-module="self-extension"] .v2-role-select', extension_styles)
self.assertIn('.v2-extension-composer .primary-action {', extension_styles)
self.assertIn('--v2-accent: #0e7773', extension_styles)
self.assertNotIn('--v2-accent:', extension_styles)
self.assertIn('.v2-sidebar-collapsed[data-module="self-extension"] .v2-extension-sidebar', extension_styles)
self.assertIn('.v2-extension-capability span {', extension_styles)
self.assertIn('.v2-extension-parameter-list > span {', extension_styles)
@@ -482,14 +481,17 @@ operations:
)
self.assertIn("const stageHasLayout = () =>", app_script)
self.assertIn("window.requestAnimationFrame(initSeatTwin)", app_script)
self.assertIn('client_type: "web_console"', app_script)
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
self.assertIn('"cognitive-agent:module-change"', app_script)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v120"', service_worker)
self.assertIn('"/self-evolution.js?v=seat-weighing-2"', service_worker)
self.assertIn('"/self-evolution.css?v=seat-weighing-5"', service_worker)
self.assertIn(
'"/assets/models/n7_seat.glb?v=e7f50f03"', service_worker
)
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v163"', service_worker)
self.assertIn('"/assets/pets/self-extension-cat.png?v=3"', service_worker)
self.assertIn(".then(() => self.skipWaiting())", service_worker)
self.assertNotIn("const cached = await caches.match(request)", service_worker)
self.assertNotIn("self-evolution", service_worker)
self.assertNotIn("self-calibration", service_worker)
self.assertNotIn("self-repair", service_worker)
self.assertNotIn("n7_seat.glb", service_worker)
self.assertIn("hardware_access_required", app_script)
self.assertIn('button.setAttribute("aria-label", "关闭摄像头")', gesture_script)
self.assertIn("function refreshCameraAvailability", gesture_script)
@@ -499,9 +501,8 @@ operations:
self.assertIn('case "NotAllowedError":', gesture_script)
self.assertIn('case "NotReadableError":', gesture_script)
self.assertIn('case "OverconstrainedError":', gesture_script)
self.assertIn("function sendSimulatedGesture()", gesture_script)
self.assertIn('document.body.dataset.demoRole !== "admin"', gesture_script)
self.assertIn("function syncGestureDebugRole", gesture_script)
self.assertNotIn("sendSimulatedGesture", gesture_script)
self.assertNotIn("gestureDebug", gesture_script)
self.assertIn("HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000", app_script)
self.assertIn(
"requestTimeoutForExecutionMode(state.executionMode)", app_script
@@ -695,11 +696,11 @@ operations:
def test_self_extension_vehicle_success_contract_is_strict(self) -> None:
with self.opener.open(
f"{self.base_url}/self-extension.js?v=extension-21", timeout=3
f"{self.base_url}/self-extension.js?v=extension-44", timeout=3
) as response:
extension_script = response.read().decode("utf-8")
with self.opener.open(
f"{self.base_url}/app.js?v=dual-seat-82", timeout=3
f"{self.base_url}/app.js?v=dual-seat-83", timeout=3
) as response:
app_script = response.read().decode("utf-8")
+12 -67
View File
@@ -130,73 +130,18 @@ class TestSelfCalibrationApi:
assert reset["transport"]["state"] == "disconnected"
assert [axis["permille"] for axis in reset["axes"]] == [0, 0, 0, 0]
def test_frontend_and_original_model_are_served_by_existing_service(self) -> None:
def test_frontend_and_original_model_are_removed(self) -> None:
with urllib.request.urlopen(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-calibration.js?v=calibration-8",
timeout=3,
) as response:
script = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-calibration.css?v=calibration-12",
timeout=3,
) as response:
styles = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/console-v2.js?v=desktop-refactor-31",
timeout=3,
) as response:
console_script = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/assets/models/n7_seat.glb",
timeout=3,
) as response:
model = response.read()
content_type = response.headers["Content-Type"]
content_security_policy = response.headers["Content-Security-Policy"]
unversioned_cache_control = response.headers["Cache-Control"]
with urllib.request.urlopen(
f"{self.base_url}/assets/models/n7_seat.glb?v=e7f50f03",
timeout=3,
) as response:
versioned_model_prefix = response.read(4)
versioned_cache_control = response.headers["Cache-Control"]
with pytest.raises(urllib.error.HTTPError) as missing_model:
urllib.request.urlopen(
f"{self.base_url}/assets/models/missing-seat.glb?v=missing",
timeout=3,
)
assert "self-calibration" not in html
assert "vendor/mediamtx" not in html
assert 'href="/self-calibration.css?v=calibration-12"' in html
assert 'src="/self-calibration.js?v=calibration-8"' in html
assert 'data-calibration-page="live"' in script
assert 'data-calibration-view="live"' in script
assert 'data-calibration-page="hall"' not in script
assert 'data-calibration-page="stereo"' not in script
assert "座椅与双目联合标定" in script
assert 'class="v2-module-heading"' in script
assert 'SELF-CALIBRATION' in script
assert 'padding: var(--v2-module-title-block) 0 0' in styles
assert 'margin-inline: var(--v2-module-title-inline)' in styles
assert "实时座椅姿态" in script
assert "四轴实时控制" in script
assert "ZED 实时画面" in script
assert "双目位姿数据" not in script
assert 'class="v2-calibration-panel v2-calibration-mqtt-panel"' not in script
assert "grid-template-columns: minmax(420px, 1.06fr) minmax(380px, 0.94fr)" in styles
assert "标定仿真台架" in script
assert "X-Cognitive-Agent-Role" in script
assert "--v2-sidebar: #dae3f5" in styles
assert "--v2-sidebar-deep: #b6c7ea" in styles
assert "--v2-accent-strong: #1e386b" in styles
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'setRole("admin", { allowModuleTransition: true })' in console_script
assert len(model) == 845_992
assert versioned_model_prefix == model[:4]
assert content_type.startswith("model/gltf-binary")
assert unversioned_cache_control == "no-cache"
assert versioned_cache_control == "public, max-age=31536000, immutable"
assert missing_model.value.code == 404
assert "img-src 'self' data: blob:;" in content_security_policy
assert "connect-src 'self' blob:;" in content_security_policy
for path in (
"/self-calibration.js",
"/self-calibration.css",
"/vendor/mediamtx/reader.js",
"/assets/models/n7_seat.glb",
):
with pytest.raises(urllib.error.HTTPError) as missing_asset:
urllib.request.urlopen(f"{self.base_url}{path}", timeout=3)
assert missing_asset.value.code == 404
+11 -61
View File
@@ -142,74 +142,24 @@ class TestSelfRepairApi:
assert result["ok"] is True
assert result["self_test"]["result"] == "PASS"
def test_frontend_assets_and_role_transition_are_served(self) -> None:
def test_frontend_assets_and_role_transition_are_removed(self) -> None:
with urllib.request.urlopen(f"{self.base_url}/", timeout=3) as response:
html = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-repair.js?v=repair-21", timeout=3
) as response:
script = response.read().decode("utf-8").replace("\r\n", "\n")
with urllib.request.urlopen(
f"{self.base_url}/self-repair.css?v=repair-21", timeout=3
) as response:
styles = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/self-repair-seat3d.js?v=repair-21", timeout=3
) as response:
seat3d = response.read().decode("utf-8")
with urllib.request.urlopen(
f"{self.base_url}/console-v2.js?v=desktop-refactor-31", timeout=3
) as response:
console_script = response.read().decode("utf-8")
assert 'href="/self-repair.css?v=repair-21"' in html
assert 'src="/self-repair.js?v=repair-21"' in html
assert 'class="v2-module-heading"' in script
assert 'SELF-REPAIR' in script
assert 'padding: var(--v2-module-title-block) 0 0' in styles
assert 'margin-inline: var(--v2-module-title-inline)' in styles
assert 'src="/self-repair-seat3d.js?v=repair-21"' in html
assert "量产故障注入" in script
assert "运行与自修复日志" in script
assert "启动座椅运行" in script
assert "开始正常任务" not in script
assert 'id="repairSeatCanvas"' in script
assert 'id="repairSeatViewer"' in script
assert "self-repair-seat-reset" in script
assert "seat-healing-state" in script
assert 'id="repairConfirmDialog"' in script
assert "confirmAction(" in script
assert "window.confirm" not in script
assert 'hardware.link?.adapter === "OK"' in script
assert 'PASSED: ["21针通过", "online"]' in script
assert "connectorRows" in script
assert "Polling refreshes data only" in script
assert "renderRecords();\n showView(activeView);" not in script
assert 'let latest = fallbackState();' in script
assert 'renderAll();\n window.lucide?.createIcons' in script
assert '"WDT复位后失能"' in script
assert "--repair-deep-blue" in styles
assert '.console-v2[data-module="self-repair"] .section-kicker' in styles
assert 'grid-template-areas: "twin cases logs"' in styles
assert ".v2-repair-event-stream" in styles
assert "background: rgba(255, 255, 255, 0.94)" in styles
assert "border-color: color-mix(in srgb, var(--v2-accent) 18%, white)" in styles
assert ".v2-repair-toolbar button.primary:hover:not(:disabled)" in styles
assert "color: #fff;" in styles
assert "seat-healing-state" in seat3d
assert "function backrestShape" in seat3d
assert "new THREE.CapsuleGeometry" in seat3d
assert 'cssThemeColor("--v2-accent", "#315eaa")' in seat3d
assert 'viewer.addEventListener("pointerdown"' in seat3d
assert 'window.addEventListener("pointermove"' in seat3d
assert "window.addEventListener(resetEvent" in seat3d
assert "function viewerHasLayout(viewer)" in seat3d
assert "function deferSeat3dUntilVisible(viewer, options)" in seat3d
assert "if (!viewerHasLayout(viewer))" in seat3d
assert 'window.addEventListener("cognitive-agent:module-change"' in seat3d
assert 'canvas.addEventListener("webglcontextlost"' in seat3d
assert '["self-calibration", "self-repair"].includes(module)' in console_script
assert 'roleSelect.disabled = ui.activeModule === "self-repair"' in console_script
assert "self-repair" not in html
assert '"admin"' not in console_script
for path in (
"/self-repair.js",
"/self-repair.css",
"/self-repair-seat3d.js",
):
with pytest.raises(urllib.error.HTTPError) as missing_asset:
urllib.request.urlopen(f"{self.base_url}{path}", timeout=3)
assert missing_asset.value.code == 404
def test_self_repair_bridge_uses_the_backend_runtime_directory() -> None:
@@ -47,27 +47,16 @@ class FrontendVehicleStateSyncContractTests(unittest.TestCase):
self.extension_source,
)
def test_shared_twin_theme_tracks_demo_role_and_extension_is_always_user(self) -> None:
def test_shared_twin_theme_is_fixed_to_user(self) -> None:
self.assertIn("function applyTwinSceneTheme(", self.app_source)
self.assertIn('canvas.dataset.themeRole = themeRole', self.app_source)
self.assertIn('canvas.dataset.themeRole = "user"', self.app_source)
self.assertIn(
'canvas.dataset.themeAccent = `#${palette.accent.toString(16).padStart(6, "0")}`',
self.app_source,
)
self.assertIn("accent: 0x0e7773", self.app_source)
self.assertIn("accent: 0x315eaa", self.app_source)
self.assertIn(
'if (document.body.dataset.module === "self-extension") return "user"',
self.app_source,
)
self.assertIn(
'const themeRole = document.body.dataset.module === "self-extension"',
self.app_source,
)
self.assertIn(
'window.addEventListener("cognitive-agent:demo-role-change"',
self.app_source,
)
self.assertNotIn("accent: 0x315eaa", self.app_source)
self.assertNotIn("cognitive-agent:demo-role-change", self.app_source)
def test_shared_twin_ground_uses_theme_radial_alpha_texture(self) -> None:
self.assertIn("function createTwinGroundTexture(", self.app_source)
@@ -10,27 +10,29 @@ def source(name: str) -> str:
return (ROOT / "apps" / "web-console" / name).read_text(encoding="utf-8")
def test_calibration_twin_tracks_labels_and_commits_direct_axis_drag() -> None:
calibration = source("self-calibration.js")
seat3d = source("self-repair-seat3d.js")
styles = source("self-calibration.css")
def test_frontend_only_exposes_user_orchestration_and_extension() -> None:
web_root = ROOT / "apps" / "web-console"
html = source("index.html")
console = source("console-v2.js")
assert 'data-seat-axis-label="backrest"' in calibration
assert 'data-seat-label-line="legrest"' in calibration
assert 'window.addEventListener("calibration-seat-axis-preview"' in calibration
assert 'window.addEventListener("calibration-seat-axis-commit"' in calibration
assert "function layoutSeatLabels()" in seat3d
assert "makePanel(legrestPivot, cushionShape(0.08)" in seat3d
assert "function legrestShape(" not in seat3d
assert 'enableAxisDrag: true' in seat3d
assert 'function startAxisDrag(axis, event, source = "model")' in seat3d
assert 'startAxisDrag(item.axis, event, "label")' in seat3d
assert "calculateAxisDragVector(axis, value)" in seat3d
assert "Math.max(0, Math.min(100," in seat3d
assert 'new CustomEvent("calibration-seat-axis-commit"' in seat3d
assert ".v2-calibration-label-lines line" in styles
assert '.v2-axis-tag[data-control-enabled="true"]' in styles
assert "pointer-events: auto;" in styles
for removed in (
"self-calibration.js",
"self-calibration.css",
"self-repair.js",
"self-repair.css",
"self-repair-seat3d.js",
"self-evolution.js",
"self-evolution.css",
):
assert not (web_root / removed).exists()
assert removed not in html
assert "511工程-智能座舱" in console
assert '["self-orchestration", "self-extension"]' in console
assert 'id="vehicleExtensionSync"' in console
assert 'getRole: () => "user"' in console
assert '"admin"' not in console
assert "v2RoleSelect" not in console
def test_extension_pet_and_transient_vehicle_state_restart_contract() -> None:
@@ -41,17 +43,63 @@ def test_extension_pet_and_transient_vehicle_state_restart_contract() -> None:
assert "function initializeDesktopPet()" in extension
assert "saved.vehicleExecutions = {};" in extension
assert "saved.lastProtocol = null;" in extension
assert 'src="/assets/pets/self-extension-cat.png?v=3"' in extension
assert 'class="v2-extension-pet-entrance-canvas"' in extension
assert '"/assets/pets/self-extension-cat-entrance.png?v=3"' in extension
assert "const ENTRANCE_KEYFRAME_COUNT = 12" in extension
assert "const ENTRANCE_DURATION_MS = 2300" in extension
assert "const ENTRANCE_WALK_STEPS = 5" in extension
assert "const ENTRANCE_SETTLE_START_FRAME = 5" in extension
assert "0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4" in extension
assert "const easedStepProgress = 0.5 - (Math.cos(Math.PI * stepProgress) / 2)" in extension
assert "function playEntrance()" in extension
assert 'pet.dataset.entering = "true"' in extension
assert "const drawEntrancePose =" in extension
assert "window.requestAnimationFrame(renderEntrance)" in extension
assert "function createThemedPetDataUrl(" in extension
assert "blueDominance < 24" in extension
assert 'window.addEventListener("cognitive-agent:demo-role-change"' not in extension
assert 'class="v2-extension-pet-blink"' in extension
assert "const scheduleBlink =" in extension
assert 'pet.dataset.blinking = "true"' in extension
assert 'class="v2-extension-pet-mouth-closed"' in extension
assert "const animateMouth = () =>" in extension
assert "mouth.animate?.(" in extension
assert "{ opacity: 0, offset: 0.09 }" in extension
assert "{ opacity: 0, offset: 0.41 }" in extension
assert "{ opacity: 0, offset: 0.73 }" in extension
assert "{ duration: 1260" in extension
assert 'class="v2-extension-pet-tail-canvas"' in extension
assert "const runTailWave =" in extension
assert "const requestTailWave =" in extension
assert 'prefers-reduced-motion: reduce' not in extension
assert "tailWaveQueued = true" in extension
assert "window.setTimeout(() => runTailWave(true), 180)" in extension
assert "scheduleTailWave" not in extension
assert 'pet.dataset.tailWaving = "true"' in extension
assert "const tailMotionFrames = [" in extension
assert "const sampleTailOffset =" in extension
assert "sampleTailOffset(tailPose, distanceFromBase)" in extension
assert "clampToSegment = false" in extension
assert "Math.max(Math.min(start, end)" in extension
assert "const sampleTravelingTailOffset =" not in extension
assert 'document.visibilityState !== "visible"' in extension
assert ".v2-extension-pet-body" in styles
assert "@keyframes extension-pet-blink" in styles
assert ".v2-extension-pet-entrance-canvas" in styles
assert '.v2-extension-pet[data-entering="true"] .v2-extension-pet-image' in styles
assert '.v2-extension-pet[data-blinking="true"] .v2-extension-pet-blink' in styles
assert ".v2-extension-pet-mouth-closed" in styles
assert "width: 176px;" in styles
assert "font-size: 13px;" in styles
assert '.v2-extension-pet[data-tail-waving="true"] .v2-extension-pet-tail-canvas' in styles
assert '.v2-extension-pet[data-tail-waving="true"] .v2-extension-pet-image' in styles
assert "position: fixed;" in styles
assert "extension-pet-float" not in styles
assert '[data-module="self-extension"][data-demo-role="admin"] .v2-sidebar' not in styles
assert '[data-module="self-extension"][data-demo-role="admin"] .v2-avatar' not in styles
def test_virtual_modules_reset_but_physical_sessions_remain_authoritative() -> None:
def test_orchestration_session_reset_remains_available() -> None:
orchestration = source("app.js")
calibration = source("self-calibration.js")
repair = source("self-repair.js")
assert 'sessionStorage.removeItem("cognitive-agent.conversation-id")' in orchestration
assert 'api("/session/reset")' in calibration
assert 'if (state.transport?.mode === "simulation")' in calibration
assert 'if (backendMode === "bench")' in repair
assert 'await apiPost("/runtime/reset-statistics")' in repair
+36
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
import json
import tempfile
import time
from pathlib import Path
@@ -10,6 +11,41 @@ from cognitive_agent.application.calibration import protocol
from cognitive_agent.application.calibration.service import ApiError, CalibrationService
def test_video_settings_use_webrtc_and_migrate_legacy_rtsp() -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
data_dir = root / "data"
data_dir.mkdir()
(data_dir / "settings.json").write_text(
json.dumps(
{
"mqtt": {"host": "172.20.10.12", "port": 1883, "topic": "zed/seatpose"},
"video": {
"scheme": "http",
"host": "172.20.10.12",
"port": 8554,
"path": "zed",
"transport": "webrtc",
},
}
),
encoding="utf-8",
)
service = CalibrationService(root)
settings = service.snapshot()["settings"]
assert settings["mqtt"]["host"] == "172.20.10.6"
assert settings["video"] == {
"scheme": "http",
"host": "172.20.10.6",
"port": 8889,
"path": "zed",
"transport": "webrtc",
}
assert service.start_video()["video"]["state"] == "ready"
def test_protocol_matches_android_golden_vectors() -> None:
assert protocol.crc8(b"123456789") == 0x4B
vectors = (