Files
Cognitive_Agent/tests/integration/test_registered_scene_seat_coverage.py
T

348 lines
12 KiB
Python

"""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
}