fix(scenes): restore cinema seat controls and ordered romantic lighting
This commit is contained in:
@@ -125,8 +125,10 @@ def _received_cinema_scene() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("full_scene", [False, True])
|
||||
def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
tmp_path: Path,
|
||||
full_scene: bool,
|
||||
) -> None:
|
||||
service = SeatDemoService(REPOSITORY_ROOT)
|
||||
service.artifact_root = tmp_path / "runs"
|
||||
@@ -138,10 +140,41 @@ def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
SimulationGateway(),
|
||||
catalog_db_path=tmp_path / "infrared.sqlite3",
|
||||
)
|
||||
extension.sync_peripheral_catalog([_learned_light_power_on()])
|
||||
received_cinema = _received_cinema_scene()
|
||||
lights = [_learned_light_power_on()]
|
||||
if full_scene:
|
||||
extra = [
|
||||
*(('window', 'window.set_position', target, {"position_pct": 0})
|
||||
for target in ('front_right', 'rear_left', 'rear_right')),
|
||||
('roof', 'roof.set_shade_position', 'vehicle', {"position_pct": 0}),
|
||||
('seat', 'seat.set_ventilation', 'passenger', {"state": "off"}),
|
||||
('climate', 'climate.set_power', 'vehicle', {"state": "on"}),
|
||||
('climate', 'climate.set_fan_level', 'vehicle', {"level": 2}),
|
||||
('climate', 'climate.set_outlet_mode', 'vehicle', {"mode": "face"}),
|
||||
('climate', 'climate.set_cycle_mode', 'vehicle', {"mode": "internal"}),
|
||||
('climate', 'climate.set_control_mode', 'vehicle', {"mode": "manual"}),
|
||||
('interior_light', 'interior_light.set_reading_power', 'all', {"state": "off"}),
|
||||
('ambient_light', 'ambient_light.set_power', 'vehicle', {"state": "on"}),
|
||||
('ambient_light', 'ambient_light.set_color_index', 'vehicle', {"color_index": 36}),
|
||||
('ambient_light', 'ambient_light.set_brightness_level', 'vehicle', {"brightness_level": 4}),
|
||||
]
|
||||
received_cinema["actions"].extend({
|
||||
"type": "control", "at_ms": 0, "domain": domain, "capability": capability,
|
||||
"target_scope": target, "operation": "set", "parameters": parameters,
|
||||
} for domain, capability, target, parameters in extra)
|
||||
received_cinema["operation_count"] = len(received_cinema["actions"])
|
||||
lights = [{
|
||||
**_learned_light_power_on(), "id": f"ir-slot:{device}:{slot}",
|
||||
"device_id": device, "device_name": name, "object_names": [name],
|
||||
"action": action, "action_names": [action], "slot": slot,
|
||||
} for device, name, action, slot in [
|
||||
("lantern", "灯笼", "打开", 2), ("lantern", "灯笼", "红色", 3),
|
||||
("strip", "灯带", "打开", 22), ("strip", "灯带", "红色", 23),
|
||||
("candle", "蜡烛", "打开", 41),
|
||||
]]
|
||||
extension.sync_peripheral_catalog(lights)
|
||||
service.import_n7_mode_scene(received_cinema)
|
||||
stale_profile = service.mode_profiles.save_mode(
|
||||
stale_profile = None if full_scene else service.mode_profiles.save_mode(
|
||||
"浪漫影院模式",
|
||||
[
|
||||
{
|
||||
@@ -203,7 +236,7 @@ def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
side_effect=AssertionError("mode creation must not emit infrared"),
|
||||
):
|
||||
response = assistant.stream(
|
||||
"创建浪漫影院模式",
|
||||
"创建一个浪漫影院模式",
|
||||
conversation_id="conv-romantic-cinema-template",
|
||||
principal="romantic-cinema-template-test",
|
||||
history=[],
|
||||
@@ -221,8 +254,11 @@ def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
client.start_turn.assert_not_called()
|
||||
assert result["status"] == "succeeded"
|
||||
assert result["mode_profile_draft"]["generation_source"] == "scene_template_copy"
|
||||
assert result["mode_profile_draft"]["existing_mode_id"] == stale_profile.id
|
||||
assert result["mode_profile_draft"]["operation_count"] == 3
|
||||
if stale_profile is not None:
|
||||
assert result["mode_profile_draft"]["existing_mode_id"] == stale_profile.id
|
||||
assert result["mode_profile_draft"]["operation_count"] == (25 if full_scene else 7)
|
||||
safe_result = CodexAssistantService._safe_vehicle_result(result)
|
||||
assert len(safe_result["actions"]) == (25 if full_scene else 7)
|
||||
|
||||
native_actions = {
|
||||
(
|
||||
@@ -233,21 +269,35 @@ def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
if action["domain"] != "self_extension"
|
||||
}
|
||||
assert native_actions == {
|
||||
(action["capability"], action["target_scope"]): action["parameters"]
|
||||
for action in received_cinema["actions"]
|
||||
if action["domain"] in {"window", "climate"}
|
||||
**{
|
||||
(action["capability"], action["target_scope"]): action["parameters"]
|
||||
for action in received_cinema["actions"]
|
||||
if action["domain"] not in {"local_vehicle", "android"}
|
||||
},
|
||||
("seat.set_longitudinal_position", "passenger"): {"position_pct": 51},
|
||||
("seat.set_cushion_tilt", "passenger"): {"position_pct": 30},
|
||||
("seat.set_backrest_recline", "passenger"): {"position_pct": 44},
|
||||
("seat.set_legrest_height", "passenger"): {"position_pct": 53},
|
||||
}
|
||||
assert result["n7_transfer_candidates"] == received_cinema["actions"][2:]
|
||||
assert [action["slot"] for action in result["infrared_actions"]] == [1]
|
||||
video = [action for action in received_cinema["actions"] if action["domain"] == "android"]
|
||||
assert result["n7_transfer_candidates"] == video
|
||||
slots = [action["slot"] for action in result["infrared_actions"]]
|
||||
assert sorted(slots) == ([2, 3, 22, 23, 41] if full_scene else [1])
|
||||
if full_scene:
|
||||
assert slots.index(2) < slots.index(3)
|
||||
assert slots.index(22) < slots.index(23)
|
||||
|
||||
draft = result["mode_profile_draft"]
|
||||
saved = service.save_mode_profile_card(
|
||||
stale_profile.id,
|
||||
arguments = (
|
||||
draft["name"],
|
||||
draft["instruction"],
|
||||
draft["source_request_id"],
|
||||
draft["vehicle_id"],
|
||||
)
|
||||
saved = (
|
||||
service.save_mode_profile_card(stale_profile.id, *arguments)
|
||||
if stale_profile is not None else service.create_mode_profile_card(*arguments)
|
||||
)
|
||||
profile = service.get_mode_profile_card(saved["mode_profile"]["id"], "denza_n7")
|
||||
saved_selectors = {
|
||||
(operation.capability, operation.target_scope)
|
||||
@@ -256,13 +306,45 @@ def test_romantic_cinema_template_preserves_cinema_and_adds_authorized_light(
|
||||
assert saved_selectors == {
|
||||
(action["capability"], action["target_scope"])
|
||||
for action in received_cinema["actions"]
|
||||
} | {("infrared.emit", "romance-light")}
|
||||
if action["capability"] != "local_scene.seat_preset"
|
||||
} | {
|
||||
("seat.set_longitudinal_position", "passenger"),
|
||||
("seat.set_cushion_tilt", "passenger"),
|
||||
("seat.set_backrest_recline", "passenger"),
|
||||
("seat.set_legrest_height", "passenger"),
|
||||
} | {("infrared.emit", item["device_id"]) for item in lights}
|
||||
_local_requests, transfer_actions = service._mode_requests_and_n7_transfers(profile)
|
||||
assert transfer_actions == received_cinema["actions"][2:]
|
||||
assert transfer_actions == video
|
||||
finally:
|
||||
service.close()
|
||||
|
||||
|
||||
def test_expanded_mode_overflow_cannot_leave_a_saveable_partial_draft() -> None:
|
||||
handler = object.__new__(SeatDemoHandler)
|
||||
handler.service = SimpleNamespace(
|
||||
_scrub_failed_scene_preview=SeatDemoService._scrub_failed_scene_preview,
|
||||
persist_composed_result=MagicMock(),
|
||||
)
|
||||
native = {
|
||||
"request_id": "req-expanded-overflow", "status": "succeeded",
|
||||
"execution_mode": "dry_run", "mode_profile_draft": {"operation_count": 256},
|
||||
"intent_ir": {"spec": {"actions": [
|
||||
{"domain": "seat", "capability": "seat.set_ventilation"}
|
||||
] * 256}},
|
||||
}
|
||||
infrared = {"function_configs": {"self_extension": {"spec": {"operations": [{
|
||||
"target": {"device": "lamp"}, "parameters": {"action": "打开", "slot": 1},
|
||||
}]}}}}
|
||||
with patch.object(handler, "_merge_can_and_peripheral_results", return_value=native):
|
||||
with pytest.raises(ExtensionIntegrationError, match="257.*256"):
|
||||
handler._merge_scene_peripheral_preview("创建浪漫模式", native, infrared, [])
|
||||
failed = handler.service.persist_composed_result.call_args.args[0]
|
||||
assert failed["status"] == "validation_failed"
|
||||
assert failed["mode_profile_draft"] is None
|
||||
assert failed["command_plans"] == {}
|
||||
assert failed["intent_ir"]["spec"]["actions"] == []
|
||||
|
||||
|
||||
def test_scene_composer_mixed_can_ir_preview_save_and_revalidate(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user