feat(n7web): enhance control workflows and status feedback
This commit is contained in:
@@ -10,7 +10,11 @@ ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def source(name: str) -> str:
|
||||
return (ROOT / "apps" / "web-console" / name).read_text(encoding="utf-8")
|
||||
web_root = ROOT / "apps" / "web-console"
|
||||
content = (web_root / name).read_text(encoding="utf-8")
|
||||
if name == "index.html":
|
||||
content += "\n" + (web_root / "src" / "App.vue").read_text(encoding="utf-8")
|
||||
return content
|
||||
|
||||
|
||||
def test_frontend_only_exposes_user_orchestration_and_extension() -> None:
|
||||
@@ -827,8 +831,8 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
|
||||
assert [commands[0]["deliveryLabel"] for commands in parsed] == [
|
||||
"未下发",
|
||||
"未下发",
|
||||
"下发",
|
||||
"下发",
|
||||
"已下发 · 待状态确认",
|
||||
"已下发 · 待状态确认",
|
||||
"安全阻止",
|
||||
]
|
||||
assert [commands[0]["deliveryState"] for commands in parsed] == [
|
||||
@@ -848,6 +852,137 @@ def test_parsed_command_delivery_is_independent_of_transport_and_target() -> Non
|
||||
]
|
||||
|
||||
|
||||
def test_delivery_status_distinguishes_blocked_failed_aborted_and_verified() -> None:
|
||||
node = shutil.which("node")
|
||||
if node is None:
|
||||
return
|
||||
console = source("console-v2.js")
|
||||
assert 'class="v2-can-command-delivery-detail"' in console
|
||||
assert "<b>状态说明</b>" in console
|
||||
start = console.index(" const normalizedCanTarget")
|
||||
end = console.index("\n\n function renderParsedCanCommands", start)
|
||||
helpers = console[start:end]
|
||||
|
||||
def sample(
|
||||
operation: dict,
|
||||
*,
|
||||
status: str,
|
||||
message: str = "",
|
||||
telemetry: dict | None = None,
|
||||
) -> dict:
|
||||
return {
|
||||
"execution_mode": "hardware",
|
||||
"control_transport": "wireless_mqtt",
|
||||
"function_configs": {
|
||||
"sample": {"spec": {"operations": [operation]}}
|
||||
},
|
||||
"execution_report": {
|
||||
"spec": {
|
||||
"results": [
|
||||
{
|
||||
"ref": operation["id"],
|
||||
"status": status,
|
||||
"message": message,
|
||||
"telemetry": telemetry or {},
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
samples = [
|
||||
sample(
|
||||
{
|
||||
"id": "window-blocked",
|
||||
"capability": "window.set_position",
|
||||
"target": {"zone": "front-right"},
|
||||
"parameters": {"position_pct": 20},
|
||||
},
|
||||
status="failed",
|
||||
message="未收到有效且新鲜的 0x388 车窗位置,拒绝启动机构",
|
||||
telemetry={"verification_status": "failed_or_blocked"},
|
||||
),
|
||||
sample(
|
||||
{
|
||||
"id": "infrared-dispatch-failed",
|
||||
"capability": "infrared.emit",
|
||||
"target": {"device": "reading-light", "name": "阅读灯"},
|
||||
"parameters": {"action": "开启", "slot": 2},
|
||||
},
|
||||
status="failed",
|
||||
message="GSCU1B 尚未连接到本地 TCP 桥接",
|
||||
telemetry={"delivery_status": "failed"},
|
||||
),
|
||||
sample(
|
||||
{
|
||||
"id": "infrared-aborted",
|
||||
"capability": "infrared.emit",
|
||||
"target": {"device": "reading-light", "name": "阅读灯"},
|
||||
"parameters": {"action": "切换为暖光", "slot": 5},
|
||||
},
|
||||
status="aborted",
|
||||
message="前序红外指令失败,未执行",
|
||||
telemetry={"delivery_status": "not_submitted"},
|
||||
),
|
||||
sample(
|
||||
{
|
||||
"id": "climate-verified",
|
||||
"capability": "climate.set_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "on"},
|
||||
},
|
||||
status="succeeded",
|
||||
telemetry={
|
||||
"delivery_status": "delivered",
|
||||
"physical_effect_confirmed": True,
|
||||
},
|
||||
),
|
||||
sample(
|
||||
{
|
||||
"id": "climate-post-submit-failed",
|
||||
"capability": "climate.set_power",
|
||||
"target": {"zone": "vehicle"},
|
||||
"parameters": {"state": "on"},
|
||||
},
|
||||
status="failed",
|
||||
message="网关提交后执行失败",
|
||||
telemetry={"delivery_status": "submitted"},
|
||||
),
|
||||
]
|
||||
script = (
|
||||
f"{helpers}\n"
|
||||
f"const samples = {json.dumps(samples, ensure_ascii=False)};\n"
|
||||
"process.stdout.write(JSON.stringify(samples.map((sample) => "
|
||||
"parsedCanCommands(sample)[0])));"
|
||||
)
|
||||
completed = subprocess.run(
|
||||
[node, "-"],
|
||||
input=script,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
commands = json.loads(completed.stdout)
|
||||
assert [item["deliveryLabel"] for item in commands] == [
|
||||
"安全阻止",
|
||||
"下发失败",
|
||||
"已中止",
|
||||
"执行已确认",
|
||||
"执行失败",
|
||||
]
|
||||
assert [item["deliveryState"] for item in commands] == [
|
||||
"blocked",
|
||||
"failed",
|
||||
"aborted",
|
||||
"submitted",
|
||||
"failed",
|
||||
]
|
||||
assert "0x388 实时位置" in commands[0]["deliveryDetail"]
|
||||
assert commands[1]["deliveryDetail"] == "GSCU1B 尚未连接到本地 TCP 桥接"
|
||||
|
||||
|
||||
def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences() -> None:
|
||||
app = source("app.js")
|
||||
console = source("console-v2.js")
|
||||
@@ -870,6 +1005,12 @@ def test_mode_editor_renders_readable_commands_with_expandable_mqtt_sequences()
|
||||
assert 'executionMode: "dry_run"' in app
|
||||
assert 'clientType: "mode_editor_compile"' in app
|
||||
assert "previewOnly: true" in app
|
||||
compile_flow = app.split(
|
||||
"async function executeModeEditorInstruction", 1
|
||||
)[1].split("async function completeModeEditor", 1)[0]
|
||||
assert "renderResult(" not in compile_flow
|
||||
assert "cognitive-agent:validated-can-commands" not in compile_flow
|
||||
assert "cognitive-agent:result" not in compile_flow
|
||||
assert 'const nonDispatchTaskSources = new Set(["mode_editor_compile"])' in console
|
||||
assert "if (isDispatchTask(task))" in console
|
||||
assert "下发控制指令" in console
|
||||
|
||||
Reference in New Issue
Block a user