"""Seat-aware built-in defaults must remain safe and preserve saved user intent.""" from __future__ import annotations import hashlib from collections.abc import Iterator from pathlib import Path from unittest.mock import patch import pytest from cognitive_agent.application.mode_profile import ModeProfileRegistry from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService COMFORT_TEMPLATE_IDS = ( "reading", "cinema", "camping", "quiet-night", "reception", "romantic", "winter-warmth", "summer-cooling", "long-trip-comfort", "relaxation", "seat-comfort", ) DRIVER_COMFORT_CAPABILITIES = { "seat.set_driver_ventilation_native", "seat.set_driver_heating_native", "seat.set_driver_massage_native", } @pytest.fixture def service(tmp_path: Path) -> Iterator[SeatDemoService]: with patch.dict( "os.environ", { "COGNITIVE_AGENT_HARDWARE_EXECUTION_ENABLED": "false", "COGNITIVE_AGENT_VEHICLE_CAN_ENABLED": "false", "COGNITIVE_AGENT_WIRED_PASSENGER_CAN_ENABLED": "false", "COGNITIVE_AGENT_VEHICLE_MQTT_ENABLED": "false", }, ): instance = SeatDemoService(REPOSITORY_ROOT) instance.artifact_root = tmp_path / "runs" instance.mode_profiles = ModeProfileRegistry( REPOSITORY_ROOT / "config" / "modes", user_root=tmp_path / "user-modes", ) try: with patch.object( instance.hardware_execution, "reserve", side_effect=AssertionError("creating or previewing a mode reached real hardware"), ): yield instance finally: instance.close() def _preview(service: SeatDemoService, instruction: str, *, plan=None) -> dict: result = service.preview_native_can( instruction, provider="local", conversation_id="conv-registered-seat-coverage", execution_mode="hardware", vehicle_id="denza_n7", assistant_plan=plan, ) assert result["status"] == "succeeded", result.get("validation") assert result["execution_mode"] == "dry_run" assert result["hardware_execution_allowed"] is False return result def _action_map(result: dict) -> dict: return { ( action["capability"], action["target"].get("instance") or action["target"].get("zone"), ): action["parameters"] for action in result["intent_ir"]["spec"]["actions"] } @pytest.mark.parametrize("template_id", COMFORT_TEMPLATE_IDS) @pytest.mark.parametrize(("seat_prefix", "targets"), ( ("", {"passenger"}), ("主驾", {"driver"}), ("副驾", {"passenger"}), ("主驾和副驾", {"driver", "passenger"}), )) def test_comfort_templates_select_only_requested_seat_without_mutating_seat_seeds( service: SeatDemoService, template_id: str, seat_prefix: str, targets: set[str], ) -> None: template = service.scene_templates.get_template(template_id) original_revision = template.revision result = _preview(service, f"构建一个{seat_prefix}{template.name}", plan=template.plan()) actions = _action_map(result) seat_keys = {key for key in actions if key[0].startswith("seat.")} assert {target for _, target in seat_keys} == targets assert { operation.target_scope for operation in template.operations if operation.domain == "seat" } == {"driver", "passenger"} assert all( operation.capability in DRIVER_COMFORT_CAPABILITIES for operation in template.operations if operation.domain == "seat" and operation.target_scope == "driver" ), "driver positional or timed movement must not enter static comfort defaults" assert not any( "massage" in capability for capability, target in seat_keys if target == "passenger" ) assert actions == { (operation.capability, operation.target_scope): operation.parameters for operation in template.operations if operation.domain != "seat" or operation.target_scope in targets } assert result["mode_profile_draft"]["generation_source"] == "scene_template_copy" assert service.scene_templates.get_template(template_id).revision == original_revision @pytest.mark.parametrize( ("mode_id", "mode_name"), ( ("office", "办公模式"), ("nap", "小憩模式"), ("meditation_space", "冥想空间模式"), ("leisure_lounger", "休闲躺椅模式"), ), ) @pytest.mark.parametrize(("seat_prefix", "targets"), ( ("", {"passenger"}), ("主驾", {"driver"}), ("副驾", {"passenger"}), ("主驾和副驾", {"driver", "passenger"}), )) def test_registered_cabin_modes_select_only_requested_seat_on_creation( service: SeatDemoService, mode_id: str, mode_name: str, seat_prefix: str, targets: set[str], ) -> None: result = _preview(service, f"构建一个{seat_prefix}{mode_name}") actions = _action_map(result) assert {target for capability, target in actions if capability.startswith("seat.")} == targets if "passenger" in targets: assert ("seat.set_backrest_recline", "passenger") in actions draft = result["mode_profile_draft"] if seat_prefix: assert "existing_mode_id" not in draft, "a selected-seat copy must not overwrite the original preset" else: assert draft["existing_mode_id"] == mode_id assert draft["generation_source"] == "registered_mode_profile" profile = service.mode_profiles.get_profile(mode_id, "denza_n7") assert actions == { (operation.capability, operation.target_scope): operation.parameters for operation in profile.operations if operation.domain != "seat" or operation.target_scope in targets } @pytest.mark.parametrize("template_id", ("fresh-air", "rain-defrost")) def test_air_quality_and_visibility_templates_do_not_invent_seat_actions( service: SeatDemoService, template_id: str, ) -> None: template = service.scene_templates.get_template(template_id) result = _preview(service, f"构建一个{template.name}", plan=template.plan()) assert not any(capability.startswith("seat.") for capability, _ in _action_map(result)) @pytest.mark.parametrize( "template_id", ( "long-trip-comfort", "summer-cooling", "winter-warmth", "reception", "quiet-night", "romantic", ), ) def test_non_posture_templates_keep_both_seat_positions_unchanged( service: SeatDemoService, template_id: str, ) -> None: template = service.scene_templates.get_template(template_id) assert all( "position_pct" not in operation.parameters and "duration_ms" not in operation.parameters for operation in template.operations if operation.domain == "seat" ) def test_native_driver_and_passenger_thermal_levels_are_not_confused( service: SeatDemoService, ) -> None: for template_id in ( "reading", "camping", "quiet-night", "reception", "romantic", "long-trip-comfort", "seat-comfort", ): template = service.scene_templates.get_template(template_id) params = {(op.capability, op.target_scope): op.parameters for op in template.operations} assert params[("seat.set_driver_ventilation_native", "driver")] == { "state": "on", "level": 2, } assert params[("seat.set_ventilation", "passenger")] == {"state": "on", "level": 1} def test_upgraded_template_does_not_silently_add_seats_to_a_saved_custom_mode( service: SeatDemoService, tmp_path: Path, ) -> None: # Simulate a previously saved user preference with the same name as a template. original = _preview(service, "构建一个观影模式,空调温度调整到25度") draft = original["mode_profile_draft"] assert len(_action_map(original)) == 1 saved = service.create_mode_profile_card( draft["name"], draft["instruction"], draft["source_request_id"], draft["vehicle_id"], ) assert saved["status"] == "succeeded" paths = list((tmp_path / "user-modes").rglob("*.yaml")) hashes = {path: hashlib.sha256(path.read_bytes()).hexdigest() for path in paths} repeated = _preview(service, "构建一个观影模式") assert repeated["mode_profile_draft"]["generation_source"] == "registered_mode_profile" assert _action_map(repeated) == _action_map(original) assert {path: hashlib.sha256(path.read_bytes()).hexdigest() for path in paths} == hashes assert len(service.scene_templates.get_template("cinema").operations) == 11 def test_explicit_seat_only_requirement_is_not_expanded_to_other_seat_defaults( service: SeatDemoService, ) -> None: result = _preview(service, "构建一个阅读模式,副驾座椅通风关闭") assert _action_map(result) == {("seat.set_ventilation", "passenger"): {"state": "off"}} assert result["mode_profile_draft"]["generation_source"] == "explicit_scene_requirements" @pytest.mark.parametrize("mode_id", ("office", "nap", "meditation_space", "leisure_lounger")) @pytest.mark.parametrize(("seat", "spoken_seat"), (("driver", "主驾"), ("passenger", "副驾驶"))) def test_explicit_single_seat_invocation_selects_without_mutating_cabin_mode( service: SeatDemoService, mode_id: str, seat: str, spoken_seat: str, ) -> None: profile = service.mode_profiles.get_profile(mode_id, "denza_n7") original_revision = service.mode_profiles.profile_revision(profile) expected = { (operation.capability, operation.target_scope): operation.parameters for operation in profile.operations if operation.domain != "seat" or operation.target_scope == seat } result = service.preview_native_can( f"请让{spoken_seat}进入{profile.name}", provider="local", conversation_id="conv-explicit-seat-mode-selection", execution_mode="dry_run", vehicle_id="denza_n7", ) assert result["status"] == "succeeded", result.get("validation") assert _action_map(result) == expected assert ( service.mode_profiles.profile_revision( service.mode_profiles.get_profile(mode_id, "denza_n7"), ) == original_revision ) assert any( operation.target_scope != seat for operation in service.mode_profiles.get_profile(mode_id, "denza_n7").operations if operation.domain == "seat" ), "single-seat execution must not edit the persisted dual-seat profile" @pytest.mark.parametrize( ("mode_id", "spoken_seat"), [ (mode_id, seat) for mode_id in ("zero_gravity", "easy_entry_exit", "rocking_horse", "office") for seat in ("主驾", "后排", "所有座椅") if (mode_id, seat) != ("office", "主驾") ], ) def test_mode_invocation_still_rejects_genuinely_unsupported_seat_scopes( service: SeatDemoService, mode_id: str, spoken_seat: str, ) -> None: profile = service.mode_profiles.get_profile(mode_id, "denza_n7") result = service.preview_native_can( f"请让{spoken_seat}进入{profile.name}", provider="local", conversation_id="conv-explicit-seat-mode-rejection", execution_mode="dry_run", vehicle_id="denza_n7", ) assert result["status"] in {"no_action", "needs_confirmation"} assert _action_map(result) == {} assert any( issue.get("reason") == "mode_target_scope_mismatch" for issue in result["intent_ir"]["spec"].get("ambiguities", []) ) @pytest.mark.parametrize( "message", ("创建一个主驾办公模式", "修改办公模式,只要主驾", "主驾办公模式"), ) def test_mode_seat_selection_requires_an_explicit_invocation_not_configuration( service: SeatDemoService, message: str, ) -> None: profile = service.mode_profiles.get_profile("office", "denza_n7") assert service._explicit_mode_seat_selection(profile, message) is None @pytest.mark.parametrize("mode_id", ("office", "nap", "meditation_space", "leisure_lounger")) def test_unscoped_mode_invocation_keeps_registered_native_driver_actions( service: SeatDemoService, mode_id: str, ) -> None: profile = service.mode_profiles.get_profile(mode_id, "denza_n7") result = service.preview_native_can( f"进入{profile.name}", provider="local", conversation_id="conv-mode-native-seat-preserved", execution_mode="dry_run", vehicle_id="denza_n7", ) assert result["status"] == "succeeded" assert _action_map(result) == { (operation.capability, operation.target_scope): operation.parameters for operation in profile.operations }