Merge branch 'feature/N7Web' of https://git.kendeji.fun/vee/Cognitive_Agent into feature/N7Web
# Conflicts: # apps/web-console/app.js # apps/web-console/index.html # apps/web-console/service-worker.js # src/cognitive_agent/server.py # tests/e2e/test_seat_web_api.py
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB = ROOT / "apps" / "web-console"
|
||||
|
||||
|
||||
def source(name: str) -> str:
|
||||
return (WEB / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_unified_console_exposes_nfc_discovery_and_learning_flow() -> None:
|
||||
script = source("self-extension.js")
|
||||
styles = source("self-extension.css")
|
||||
html = source("index.html")
|
||||
|
||||
assert 'data-open-nfc-learning' in script
|
||||
assert '靠近读卡器后' in script
|
||||
assert '感应玩具 NFC' in script
|
||||
assert 'api("nfc/status")' in script
|
||||
assert 'function createToyFromNfc()' in script
|
||||
assert 'function bindCardToDevice(device, cardUid)' in script
|
||||
assert 'function importLegacyProfiles(value)' in script
|
||||
assert 'N7WEB_REQUEST_IR_TOY_PROFILES' in script
|
||||
assert 'IR_TOY_LEGACY_PROFILES' in script
|
||||
assert 'value.legacy_profiles' in script
|
||||
assert 'value.legacy_bridge_addresses' in script
|
||||
assert 'cardUid: normalizeCardUid(cardUid)' in script
|
||||
assert 'data-nfc-run-capability' in script
|
||||
assert 'data-nfc-learn-capability' in script
|
||||
assert 'data-nfc-relearn-capability' in script
|
||||
assert '重新学习' in script
|
||||
assert 'const targetSlot = relearning ? Number(capability.slot) : allocateSlot()' in script
|
||||
assert 'sendGatewayCommand("read", { slot: learning.draft.slot })' in script
|
||||
assert 'function infraredSignalFromResponse(value)' in script
|
||||
assert 'function infraredFingerprint(signal)' in script
|
||||
assert '学习回执成功,但物理槽位回读为空' in script
|
||||
assert 'signalSource: "n7web"' in script
|
||||
assert 'if (capability.signalSource === "n7web") return' in script
|
||||
assert '建立档案并开始学习' in script
|
||||
assert 'const activeCatalog = sensedCardUid' in script
|
||||
assert 'normalizeCardUid(item.card_uid) === sensedCardUid' in script
|
||||
assert '&& item.status === "learned"' in script
|
||||
assert 'slot: capability.slot' in script
|
||||
assert '当前未感应到外设' in script
|
||||
assert '将已绑定且已学习的 NFC 外设靠近读卡器后显示能力' in script
|
||||
|
||||
assert '.v2-nfc-inline' in styles
|
||||
assert '.v2-nfc-dialog' in styles
|
||||
assert '.v2-nfc-capability-grid' in styles
|
||||
assert 'self-extension.js?v=nfc-toy-33' in html
|
||||
assert 'self-extension.css?v=nfc-toy-33' in html
|
||||
|
||||
|
||||
def test_server_exposes_nfc_snapshot_through_same_origin_extension_api() -> None:
|
||||
server = (ROOT / "src" / "cognitive_agent" / "server.py").read_text(encoding="utf-8")
|
||||
|
||||
assert 'nfc = NfcReaderService()' in server
|
||||
assert 'path == "/api/extension/nfc/status"' in server
|
||||
assert '"nfc": self.nfc.snapshot()' in server
|
||||
assert 'SeatDemoHandler.nfc.start()' in server
|
||||
assert 'SeatDemoHandler.nfc.close()' in server
|
||||
assert "frame-src 'self' http://*:3001" in server
|
||||
@@ -0,0 +1,141 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cognitive_agent.infrastructure.nfc.reader import NfcReaderService
|
||||
|
||||
|
||||
@dataclass
|
||||
class FakePort:
|
||||
device: str
|
||||
description: str = ""
|
||||
hwid: str = ""
|
||||
|
||||
|
||||
class FakeReader:
|
||||
def __init__(self, port: str) -> None:
|
||||
self.port = port
|
||||
self.closed = False
|
||||
self.sam_configured = False
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
def get_firmware_version(self) -> tuple[int, int]:
|
||||
return (1, 6)
|
||||
|
||||
def sam_configuration(self) -> bool:
|
||||
self.sam_configured = True
|
||||
return True
|
||||
|
||||
def read_uid(self) -> bytes:
|
||||
return bytes.fromhex("04A1B2C3")
|
||||
|
||||
|
||||
def wait_until(predicate, timeout: float = 1.0) -> bool:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if predicate():
|
||||
return True
|
||||
time.sleep(0.01)
|
||||
return predicate()
|
||||
|
||||
|
||||
def test_resolve_port_prefers_a_single_ch340_reader() -> None:
|
||||
ports = [
|
||||
FakePort("COM3", "Bluetooth serial"),
|
||||
FakePort("COM5", "USB-SERIAL CH340", "USB VID:PID=1A86:7523"),
|
||||
]
|
||||
|
||||
assert NfcReaderService.resolve_port("auto", ports) == "COM5"
|
||||
assert NfcReaderService.resolve_port("COM8", ports) == "COM8"
|
||||
|
||||
|
||||
def test_reader_service_publishes_reader_and_card_state() -> None:
|
||||
created: list[FakeReader] = []
|
||||
|
||||
def create_reader(port: str) -> FakeReader:
|
||||
reader = FakeReader(port)
|
||||
created.append(reader)
|
||||
return reader
|
||||
|
||||
service = NfcReaderService(
|
||||
reader_factory=create_reader,
|
||||
ports_provider=lambda: [FakePort("COM5", "PCR532 CH340")],
|
||||
retry_seconds=0.01,
|
||||
poll_seconds=0.01,
|
||||
mqtt_enabled=False,
|
||||
)
|
||||
service.start()
|
||||
try:
|
||||
assert wait_until(lambda: service.snapshot()["card"] is not None)
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"] == {
|
||||
"online": True,
|
||||
"state": "ready",
|
||||
"port": "COM5",
|
||||
"firmware": "1.6",
|
||||
"error": None,
|
||||
"source": "serial",
|
||||
}
|
||||
assert snapshot["card"]["uid"] == "04A1B2C3"
|
||||
assert snapshot["card"]["action"] == "present"
|
||||
assert created[0].sam_configured is True
|
||||
finally:
|
||||
service.close()
|
||||
assert created[0].closed is True
|
||||
|
||||
|
||||
def test_disabled_reader_is_non_blocking_and_reported() -> None:
|
||||
service = NfcReaderService(enabled=False, mqtt_enabled=False)
|
||||
service.start()
|
||||
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"]["online"] is False
|
||||
assert snapshot["reader"]["state"] == "disabled"
|
||||
assert snapshot["card"] is None
|
||||
|
||||
|
||||
def test_existing_mqtt_bridge_is_reused_when_it_owns_the_reader() -> None:
|
||||
service = NfcReaderService(enabled=True, mqtt_enabled=False)
|
||||
|
||||
service._handle_bridge_status(
|
||||
{"online": True, "state": "ready", "port": "COM5", "firmware": "1.6"}
|
||||
)
|
||||
service._handle_bridge_card(
|
||||
{"action": "present", "uid": "1a53dd95"}
|
||||
)
|
||||
service._handle_bridge_profiles(
|
||||
{
|
||||
"profiles": [
|
||||
{
|
||||
"id": "ambient-light",
|
||||
"name": "第三方氛围灯",
|
||||
"cardUid": "1a53dd95",
|
||||
"actions": [
|
||||
{"id": "red", "slot": 7, "name": "红色", "learned": True}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
service._handle_local_bridge_status({"addresses": ["172.20.10.8"]})
|
||||
|
||||
snapshot = service.snapshot()
|
||||
assert snapshot["reader"] == {
|
||||
"online": True,
|
||||
"state": "ready",
|
||||
"port": "COM5",
|
||||
"firmware": "1.6",
|
||||
"error": None,
|
||||
"source": "mqtt_bridge",
|
||||
}
|
||||
assert snapshot["card"]["uid"] == "1A53DD95"
|
||||
assert snapshot["legacy_profiles"][0]["name"] == "第三方氛围灯"
|
||||
assert snapshot["legacy_profiles"][0]["cardUid"] == "1A53DD95"
|
||||
assert snapshot["legacy_profiles"][0]["actions"][0]["slot"] == 7
|
||||
assert snapshot["legacy_bridge_addresses"] == ["172.20.10.8"]
|
||||
|
||||
service._handle_bridge_card({"action": "removed", "uid": "1A53DD95"})
|
||||
assert service.snapshot()["card"] is None
|
||||
@@ -16,7 +16,9 @@ from cognitive_agent.application.self_extension import (
|
||||
SelfExtensionService,
|
||||
SimulationGateway,
|
||||
TcpGateway,
|
||||
active_peripheral_plan_catalog,
|
||||
build_gateway_command,
|
||||
resolve_active_peripheral_command,
|
||||
validate_slot,
|
||||
)
|
||||
from cognitive_agent.application.zcan_seat_executor import ZcanBridgeSeatExecutor
|
||||
@@ -54,6 +56,250 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
self.assertFalse(result["physical_state_confirmed"])
|
||||
self.assertEqual(len(result["device_response"]), 2)
|
||||
|
||||
def test_main_orchestration_resolves_only_live_bound_nfc_peripheral(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": "cap-third-party-light-on",
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A:53:DD:95",
|
||||
"action": "开启",
|
||||
"aliases": ["开灯"],
|
||||
"slot": 9,
|
||||
},
|
||||
{
|
||||
"id": "cap-third-party-light-off",
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "关闭",
|
||||
"slot": 10,
|
||||
},
|
||||
]
|
||||
present = {"card": {"action": "present", "uid": "1a53dd95"}}
|
||||
|
||||
resolved = resolve_active_peripheral_command(
|
||||
"打开第三方氛围灯", catalog, present
|
||||
)
|
||||
|
||||
self.assertIsNotNone(resolved)
|
||||
self.assertEqual(resolved["id"], "cap-third-party-light-on")
|
||||
self.assertEqual(resolved["slot"], 9)
|
||||
self.assertEqual(resolved["card_uid"], "1A53DD95")
|
||||
self.assertIsNone(
|
||||
resolve_active_peripheral_command("打开氛围灯", catalog, present)
|
||||
)
|
||||
self.assertIsNone(
|
||||
resolve_active_peripheral_command(
|
||||
"打开第三方氛围灯", catalog, {"card": None}
|
||||
)
|
||||
)
|
||||
self.assertIsNone(
|
||||
resolve_active_peripheral_command(
|
||||
"打开第三方氛围灯",
|
||||
catalog,
|
||||
{"card": {"uid": "FFFFFFFF"}},
|
||||
)
|
||||
)
|
||||
|
||||
def test_timed_phrase_selects_live_nfc_catalog_for_gpt_plan(self) -> None:
|
||||
catalog = [
|
||||
{
|
||||
"id": "on",
|
||||
"status": "learned",
|
||||
"device_name": "#001 · 第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "开启灯光",
|
||||
"slot": 1,
|
||||
},
|
||||
{
|
||||
"id": "red",
|
||||
"status": "learned",
|
||||
"device_name": "#001 · 第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "红色灯光",
|
||||
"slot": 2,
|
||||
},
|
||||
]
|
||||
|
||||
selected = active_peripheral_plan_catalog(
|
||||
"打开第三方氛围灯,红灯闪烁5s后关闭",
|
||||
catalog,
|
||||
{"card": {"uid": "1A53DD95"}},
|
||||
)
|
||||
|
||||
self.assertEqual([item["id"] for item in selected], ["on", "red"])
|
||||
self.assertEqual(
|
||||
active_peripheral_plan_catalog(
|
||||
"打开第三方氛围灯",
|
||||
catalog,
|
||||
{"card": {"uid": "1A53DD95"}},
|
||||
),
|
||||
[],
|
||||
)
|
||||
|
||||
def test_main_peripheral_result_uses_gscu_without_vehicle_can(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
|
||||
result = service.execute_peripheral_request(
|
||||
"打开第三方氛围灯",
|
||||
{
|
||||
"id": "cap-third-party-light-on",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "开启",
|
||||
"slot": 9,
|
||||
},
|
||||
conversation_id="conv-main-extension",
|
||||
conversation_turn=2,
|
||||
execution_mode="hardware",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["orchestration_route"], "nfc_peripheral")
|
||||
self.assertEqual(result["peripheral"]["slot"], 9)
|
||||
self.assertEqual(result["execution_report"]["spec"]["mode"], "hil")
|
||||
operation = result["execution_report"]["spec"]["results"][0]
|
||||
self.assertEqual(operation["domain"], "self_extension")
|
||||
self.assertTrue(operation["telemetry"]["gateway_acknowledged"])
|
||||
|
||||
def test_main_peripheral_sequence_executes_validated_actions_and_delays(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
{
|
||||
"id": "red",
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "红色灯光",
|
||||
"slot": 3,
|
||||
},
|
||||
{
|
||||
"id": "blue",
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "蓝色灯光",
|
||||
"slot": 4,
|
||||
},
|
||||
{
|
||||
"id": "off",
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": "关闭灯光",
|
||||
"slot": 5,
|
||||
},
|
||||
]
|
||||
plan_result = {
|
||||
"model": "gpt-5.6-sol",
|
||||
"summary": "红灯保持五秒后切换蓝灯",
|
||||
"steps": [
|
||||
{"kind": "peripheral", "capability_id": "red"},
|
||||
{"kind": "delay", "delay_ms": 5000},
|
||||
{"kind": "peripheral", "capability_id": "blue"},
|
||||
],
|
||||
}
|
||||
|
||||
with patch("cognitive_agent.application.self_extension.time.sleep") as sleep:
|
||||
result = service.execute_peripheral_plan_request(
|
||||
"第三方氛围灯红灯闪烁5秒后切换蓝灯",
|
||||
plan_result,
|
||||
catalog,
|
||||
conversation_id="conv-main-extension-sequence",
|
||||
execution_mode="hardware",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(
|
||||
result["orchestration_route"], "nfc_peripheral_sequence"
|
||||
)
|
||||
self.assertEqual(result["sequence"]["step_count"], 3)
|
||||
self.assertEqual(result["sequence"]["action_count"], 2)
|
||||
self.assertEqual(result["sequence"]["emit_count"], 2)
|
||||
self.assertEqual(result["sequence"]["total_delay_ms"], 5000)
|
||||
self.assertEqual(result["sequence"]["steady_blink_fallbacks"], 1)
|
||||
self.assertEqual(
|
||||
[item["status"] for item in result["operation_results"]],
|
||||
["succeeded", "succeeded", "succeeded"],
|
||||
)
|
||||
sleep.assert_any_call(5.0)
|
||||
self.assertNotIn(0.5, [call.args[0] for call in sleep.call_args_list])
|
||||
|
||||
def test_color_sequence_never_cycles_the_power_off_capability(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
catalog = [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"card_uid": "1A53DD95",
|
||||
"action": action,
|
||||
"slot": slot,
|
||||
}
|
||||
for capability_id, action, slot in (
|
||||
("on", "开启灯光", 1),
|
||||
("off", "关闭灯光", 2),
|
||||
("red", "红色灯光", 3),
|
||||
("green", "绿色灯光", 4),
|
||||
("blue", "蓝色灯光", 5),
|
||||
("white", "白色灯光", 6),
|
||||
)
|
||||
]
|
||||
plan_result = {
|
||||
"model": "gpt-5.6-sol",
|
||||
"summary": "按顺序显示四种颜色",
|
||||
"steps": [
|
||||
{"kind": "peripheral", "capability_id": "on"},
|
||||
{"kind": "peripheral", "capability_id": "red"},
|
||||
{"kind": "delay", "delay_ms": 5000},
|
||||
{"kind": "peripheral", "capability_id": "blue"},
|
||||
{"kind": "delay", "delay_ms": 5000},
|
||||
{"kind": "peripheral", "capability_id": "green"},
|
||||
{"kind": "delay", "delay_ms": 5000},
|
||||
{"kind": "peripheral", "capability_id": "white"},
|
||||
{"kind": "delay", "delay_ms": 5000},
|
||||
{"kind": "peripheral", "capability_id": "off"},
|
||||
],
|
||||
}
|
||||
|
||||
with patch("cognitive_agent.application.self_extension.time.sleep") as sleep:
|
||||
result = service.execute_peripheral_plan_request(
|
||||
(
|
||||
"打开第三方氛围灯,红灯闪烁5s后,蓝光闪烁5s后,"
|
||||
"绿灯闪烁5s后,白色闪烁5s,最后关闭第三方氛围灯"
|
||||
),
|
||||
plan_result,
|
||||
catalog,
|
||||
conversation_id="conv-color-sequence-no-power-cycle",
|
||||
execution_mode="hardware",
|
||||
)
|
||||
|
||||
self.assertEqual(result["status"], "succeeded")
|
||||
self.assertEqual(result["sequence"]["emit_count"], 6)
|
||||
self.assertEqual(result["sequence"]["steady_blink_fallbacks"], 4)
|
||||
self.assertEqual(result["sequence"]["total_delay_ms"], 20_000)
|
||||
self.assertEqual(
|
||||
[
|
||||
operation["telemetry"]["slot"]
|
||||
for operation in result["operation_results"]
|
||||
if operation["telemetry"].get("slot")
|
||||
],
|
||||
[1, 3, 5, 4, 6, 2],
|
||||
)
|
||||
self.assertEqual(
|
||||
[call.args[0] for call in sleep.call_args_list].count(5.0),
|
||||
4,
|
||||
)
|
||||
self.assertNotIn(0.5, [call.args[0] for call in sleep.call_args_list])
|
||||
|
||||
def test_plan_only_accepts_learned_catalog_capabilities(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
proposed = {
|
||||
@@ -117,6 +363,122 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
self.assertEqual(result["steps"][0]["capability_id"], "cap-light-on")
|
||||
self.assertEqual(result["steps"][1]["command"], "light reading all on")
|
||||
|
||||
def test_plan_maps_short_planner_ids_back_to_opaque_capability_ids(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
opaque_id = "cap-b0ab02a2-3640-4468-8cdc-3bb7164c7078"
|
||||
|
||||
def planned_response(
|
||||
_utterance: str,
|
||||
peripherals: list[dict[str, object]],
|
||||
_vehicle_devices: list[dict[str, object]],
|
||||
_api_key: str,
|
||||
) -> str:
|
||||
self.assertEqual(peripherals[0]["id"], "peripheral_1")
|
||||
self.assertNotIn(opaque_id, [item["id"] for item in peripherals])
|
||||
return json.dumps(
|
||||
{
|
||||
"summary": "切换红灯",
|
||||
"needs_confirmation": False,
|
||||
"question": "",
|
||||
"steps": [
|
||||
{
|
||||
"kind": "peripheral",
|
||||
"capability_id": "peripheral_1",
|
||||
"command": "红色灯光",
|
||||
"label": "第三方氛围灯 · 红色灯光",
|
||||
"delay_ms": 0,
|
||||
}
|
||||
],
|
||||
},
|
||||
ensure_ascii=False,
|
||||
)
|
||||
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
|
||||
with patch.object(
|
||||
service,
|
||||
"_request_gpt_plan",
|
||||
side_effect=planned_response,
|
||||
):
|
||||
result = service.plan(
|
||||
{
|
||||
"utterance": "第三方氛围灯切换红灯",
|
||||
"peripherals": [
|
||||
{
|
||||
"id": opaque_id,
|
||||
"status": "learned",
|
||||
"device_name": "第三方氛围灯",
|
||||
"action": "红色灯光",
|
||||
"risk": "low",
|
||||
}
|
||||
],
|
||||
"vehicle_devices": [],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(result["steps"][0]["capability_id"], opaque_id)
|
||||
|
||||
def test_plan_recovers_full_color_sequence_when_gpt_returns_no_steps(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
actions = (
|
||||
("on", "开启灯光", 1),
|
||||
("off", "关闭灯光", 2),
|
||||
("red", "红色灯光", 3),
|
||||
("green", "绿色灯光", 4),
|
||||
("blue", "蓝色灯光", 5),
|
||||
("white", "白色灯光", 6),
|
||||
)
|
||||
proposed = {
|
||||
"summary": "模型未能生成动作",
|
||||
"needs_confirmation": False,
|
||||
"question": "",
|
||||
"steps": [],
|
||||
}
|
||||
with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}, clear=False):
|
||||
with patch.object(
|
||||
service,
|
||||
"_request_gpt_plan",
|
||||
return_value=json.dumps(proposed, ensure_ascii=False),
|
||||
):
|
||||
result = service.plan(
|
||||
{
|
||||
"utterance": (
|
||||
"打开第三方氛围灯,红灯闪烁5s后,蓝光闪烁5s后,"
|
||||
"绿灯闪烁5s后,白色闪烁5s,最后关闭第三方氛围灯"
|
||||
),
|
||||
"peripherals": [
|
||||
{
|
||||
"id": capability_id,
|
||||
"status": "learned",
|
||||
"device_id": "third-party-light",
|
||||
"device_name": "第三方氛围灯",
|
||||
"action": action,
|
||||
"aliases": [action],
|
||||
"slot": slot,
|
||||
"risk": "low",
|
||||
}
|
||||
for capability_id, action, slot in actions
|
||||
],
|
||||
"vehicle_devices": [],
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
[
|
||||
step.get("capability_id")
|
||||
for step in result["steps"]
|
||||
if step["kind"] == "peripheral"
|
||||
],
|
||||
["on", "red", "blue", "green", "white", "off"],
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
step.get("delay_ms")
|
||||
for step in result["steps"]
|
||||
if step["kind"] == "delay"
|
||||
],
|
||||
[5000, 5000, 5000, 5000],
|
||||
)
|
||||
|
||||
def test_plan_rejects_vehicle_command_outside_current_catalog(self) -> None:
|
||||
service = SelfExtensionService(SimulationGateway())
|
||||
proposed = {
|
||||
@@ -235,6 +597,10 @@ class SelfExtensionTests(unittest.TestCase):
|
||||
]["items"]["properties"]["command"]
|
||||
self.assertEqual(command_schema["type"], "string")
|
||||
self.assertEqual(command_schema["maxLength"], 300)
|
||||
capability_schema = request_payload["text"]["format"]["schema"]["properties"][
|
||||
"steps"
|
||||
]["items"]["properties"]["capability_id"]
|
||||
self.assertEqual(capability_schema["enum"], [""])
|
||||
|
||||
def test_plan_accepts_bounded_passenger_position_for_declared_ch0_executor(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user