feat(web-console): rebuild intelligent cockpit sync interface
This commit is contained in:
+104
-103
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user