from __future__ import annotations import sys from pathlib import Path from types import SimpleNamespace from unittest.mock import patch import pytest from cognitive_agent.application.assistant_capabilities import AssistantCapabilityCatalog from cognitive_agent.application.codex_assistant import CodexAssistantService from cognitive_agent.application.mode_profile import ModeProfileRegistry from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService from cognitive_agent.application.self_extension import ( SelfExtensionService, SimulationGateway, ) from cognitive_agent.infrastructure.codex.app_server import CodexAppServerClient from cognitive_agent.server import SeatDemoHandler, VehicleDisplaySync FAKE_SERVER = REPOSITORY_ROOT / "tests" / "fixtures" / "fake_codex_app_server.py" MCP_SERVER = ( REPOSITORY_ROOT / "src/cognitive_agent/infrastructure/codex/vehicle_mcp_server.py" ) CALLBACK_URL = "http://127.0.0.1:8765/api/internal/codex/vehicle-control" @pytest.mark.parametrize( ("message", "expected_name"), ( ("构建一个星夜模式", "星夜模式"), ("创建学习模式", "学习模式"), ("创建中秋欢快模式", "中秋欢快模式"), ), ) def test_structured_scene_turn_reaches_the_complete_local_dry_run_pipeline( tmp_path: Path, message: str, expected_name: str, ) -> None: service = SeatDemoService(REPOSITORY_ROOT) service.artifact_root = tmp_path / "runs" service.mode_profiles = ModeProfileRegistry( REPOSITORY_ROOT / "config" / "modes", user_root=tmp_path / "user-modes", ) client = CodexAppServerClient( cwd=tmp_path / "codex-workspace", codex_command=[sys.executable, "-u", str(FAKE_SERVER)], model="gpt-test-fast", reasoning_effort="low", request_timeout=2, startup_timeout=2, environment={"OPENAI_BASE_URL": "https://api.openai.com/v1"}, mcp_server_path=MCP_SERVER, ) assistant = CodexAssistantService(client) catalog = AssistantCapabilityCatalog(service) snapshot = catalog.snapshot() events: list[dict[str, object]] = [] def execute_control( instruction: str, progress: object, assistant_plan: object, ) -> dict[str, object]: assert isinstance(assistant_plan, dict) return service.preview_native_can( instruction, provider="local", conversation_id="conv-scene-e2e-pipeline", execution_mode="hardware", vehicle_id="denza_n7", on_commands_validated=progress if callable(progress) else None, assistant_plan=assistant_plan, ) try: assistant.start(CALLBACK_URL) with patch.object( service.hardware_execution, "reserve", side_effect=AssertionError("scene preview must never reserve hardware"), ): response = assistant.stream( message, conversation_id="conv-scene-e2e-pipeline", principal="scene-e2e-user", history=[], emit=lambda event: events.append(event) or True, execute_control=execute_control, capability_snapshot=snapshot, capability_context=catalog.compact_context(snapshot), scene_capability_context=catalog.scene_composition_context(snapshot), control_terms=catalog.control_terms(snapshot), ) finally: assistant.close() result = response["result"] draft = result["mode_profile_draft"] assert response["route"] == "scene_composer" assert response["success"] is True assert result["status"] == "succeeded" assert result["execution_mode"] == "dry_run" assert result["hardware_execution_allowed"] is False assert result["orchestration_route"] == "mode_profile_create_preview" assert draft["name"] == expected_name assert draft["generation_source"] == "codex_scene_composition" assert draft["operation_count"] == 2 capabilities = { action["capability"] for action in result["actions"] } assert capabilities == { "climate.set_temperature", "roof.set_shade_position", } assert not list((tmp_path / "user-modes").rglob("*.yaml")) assert any(event.get("type") == "action.completed" for event in events) assert all("request_token" not in str(event) for event in events) def test_mid_autumn_cloud_scene_requires_authorized_flashing_light( tmp_path: Path, ) -> None: service = SeatDemoService(REPOSITORY_ROOT) service.artifact_root = tmp_path / "runs" service.mode_profiles = ModeProfileRegistry( REPOSITORY_ROOT / "config" / "modes", user_root=tmp_path / "user-modes", ) extension = SelfExtensionService( SimulationGateway(), catalog_db_path=tmp_path / "infrared.sqlite3", ) extension.sync_peripheral_catalog( [ { "id": "ir-slot:flashing-light:2", "status": "learned", "enabled": True, "device_id": "flashing-light", "device_name": "闪烁灯", "object_names": ["闪烁灯", "闪光灯"], "card_uid": "E113B203", "action": "打开", "action_names": ["打开", "开启", "启动"], "risk": "low", "slot": 2, }, { "id": "ir-slot:flashing-light:3", "status": "learned", "enabled": True, "device_id": "flashing-light", "device_name": "闪烁灯", "object_names": ["闪烁灯", "闪光灯"], "card_uid": "E113B203", "action": "切换为红色", "action_names": ["切换为红色", "设置为红色"], "risk": "low", "slot": 3, }, ] ) handler = object.__new__(SeatDemoHandler) handler.service = service handler.extension = extension handler.nfc = SimpleNamespace(snapshot=lambda: {"card": None}) handler.vehicle_display_sync = VehicleDisplaySync() catalog = AssistantCapabilityCatalog( service, peripheral_provider=extension.list_peripheral_routing_capabilities, ) snapshot = catalog.snapshot() client = CodexAppServerClient( cwd=tmp_path / "codex-workspace", codex_command=[sys.executable, "-u", str(FAKE_SERVER)], model="gpt-test-fast", reasoning_effort="low", request_timeout=2, startup_timeout=2, environment={"OPENAI_BASE_URL": "https://api.openai.com/v1"}, mcp_server_path=MCP_SERVER, ) assistant = CodexAssistantService(client) def execute_control( instruction: str, progress: object, assistant_plan: object, ) -> dict[str, object]: assert isinstance(assistant_plan, dict) result = handler._execute_request_with_peripherals( "/api/requests/preview", { "utterance": instruction, "provider": "local", "conversation_id": "conv-mid-autumn-flashing-light", "execution_mode": "hardware", "vehicle_id": "denza_n7", "simulation_fault": "normal", "hardware_access_bypass": True, "control_transport": "wired_can", "assistant_plan": assistant_plan, }, on_commands_validated=progress, ) assert result is not None return result try: assistant.start(CALLBACK_URL) with patch.object( extension, "execute_gateway", side_effect=AssertionError("mode creation must not emit infrared"), ): response = assistant.stream( "创建中秋欢快模式", conversation_id="conv-mid-autumn-flashing-light", principal="mid-autumn-e2e-user", history=[], emit=lambda _event: True, execute_control=execute_control, capability_snapshot=snapshot, capability_context=catalog.compact_context(snapshot), scene_capability_context=catalog.scene_composition_context(snapshot), control_terms=catalog.control_terms(snapshot), ) finally: assistant.close() result = response["result"] assert response["success"] is True assert result["status"] == "succeeded" assert result["execution_mode"] == "dry_run" assert [item["slot"] for item in result["infrared_actions"]] == [2, 3] assert result["mode_profile_draft"]["domains"][-1] == "self_extension"