Files
Cognitive_Agent/domains/seat/tests/integration/test_seat_service.py
T

786 lines
32 KiB
Python
Executable File

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from cognitive_agent.application.seat_service import REPOSITORY_ROOT, SeatDemoService
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError
class SeatServiceIntegrationTests(unittest.TestCase):
def setUp(self) -> None:
self.service = SeatDemoService(REPOSITORY_ROOT)
self.temporary_directory = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary_directory.cleanup)
self.service.artifact_root = Path(self.temporary_directory.name)
def test_supported_request_completes_full_dry_run(self) -> None:
result = self.service.process(
"打开副驾座椅通风,同时关闭副驾座椅加热", provider="local"
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 2)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["command_frame"]["data_hex"], "00 90")
self.assertTrue(Path(result["artifact_path"]).is_absolute())
self.assertTrue(
(self.service.artifact_root / result["request_id"] / "result.json").is_file()
)
for event in result["trace_events"]:
self.assertEqual(self.service.contracts.validate(event), [])
def test_gear_request_is_preserved_and_simulated(self) -> None:
result = self.service.process("副驾座椅加热开到三档", provider="local")
self.assertEqual(result["status"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["parameters"]["level"], 3)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["effective_level"], 3)
self.assertTrue(telemetry["level_command_simulated"])
self.assertIn(
"LEVEL_COMMAND_SIMULATED", {issue["code"] for issue in result["validation"]}
)
def test_consecutive_feedback_uses_previous_successful_state(self) -> None:
conversation_id = "conv-test-context"
first = self.service.process(
"副驾有点热,有点闷",
provider="local",
conversation_id=conversation_id,
)
second = self.service.process(
"副驾还有点闷",
provider="local",
conversation_id=conversation_id,
)
self.assertEqual(first["status"], "succeeded")
self.assertEqual(first["actuator_state"]["passenger"]["ventilation"]["level"], 1)
self.assertEqual(second["status"], "succeeded")
self.assertEqual(second["conversation_turn"], 2)
self.assertEqual(second["actuator_state"]["passenger"]["ventilation"]["level"], 2)
operation = second["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["parameters"], {"state": "on", "level": 2})
self.assertIn("提升到2档", "".join(second["decision_summary"]))
def test_four_turn_causal_feedback_sequence(self) -> None:
conversation_id = "conv-causal-sequence"
utterances = [
"我有点热",
"我有点冷",
"我还有点冷",
"副驾吹的我有点冷",
]
first, second, third, fourth = [
self.service.process(text, "local", conversation_id) for text in utterances
]
self.assertEqual(
first["function_config"]["spec"]["operations"][0]["parameters"],
{"state": "on", "level": 1},
)
second_operation = second["function_config"]["spec"]["operations"][0]
self.assertEqual(second_operation["capability"], "seat.set_ventilation")
self.assertEqual(second_operation["parameters"], {"state": "off"})
third_operation = third["function_config"]["spec"]["operations"][0]
self.assertEqual(third_operation["capability"], "seat.set_heating")
self.assertEqual(third_operation["parameters"], {"state": "on", "level": 1})
fourth_operation = fourth["function_config"]["spec"]["operations"][0]
self.assertEqual(fourth_operation["target"]["instance"], "passenger")
self.assertEqual(fourth_operation["capability"], "seat.set_ventilation")
self.assertEqual(fourth_operation["parameters"], {"state": "off"})
self.assertEqual(
fourth["actuator_state"]["passenger"]["heating"],
{"state": "on", "level": 1},
)
self.assertEqual(fourth["conversation_turn"], 4)
def test_compound_cold_and_stuffy_feedback_generates_balanced_dry_run(self) -> None:
result = self.service.process(
"副驾有点冷、有点闷",
provider="local",
conversation_id="conv-compound-comfort",
)
self.assertEqual(result["status"], "succeeded")
operations = result["function_config"]["spec"]["operations"]
self.assertEqual(
{operation["capability"] for operation in operations},
{"seat.set_heating", "seat.set_ventilation"},
)
command = result["command_plan"]["spec"]["commands"][0]
self.assertEqual(
command["signals"],
{
"Front_Passenger_Seat_Heating_S": 1,
"Front_Passenger_Seat_Ventilation_S": 1,
},
)
self.assertIn(
"COMBINED_THERMAL_SIMULATED",
{issue["code"] for issue in result["validation"]},
)
def test_vehicle_scope_is_routed_away_from_seat_domain(self) -> None:
result = self.service.process(
"车上太闷了",
provider="local",
conversation_id="conv-vehicle-scope",
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertEqual(result["intent_ir"]["spec"]["detected_domains"], ["climate"])
self.assertIsNone(result["function_config"])
self.assertIsNone(result["command_plan"])
self.assertEqual(result["actuator_state"]["passenger"]["ventilation"]["state"], "off")
route = next(event for event in result["trace_events"] if event["stage"] == "domain_route")
self.assertEqual(route["details"]["domains"], ["climate"])
def test_openai_output_is_not_rewritten_by_local_semantic_planner(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_ventilation",
"target_scope": "passenger",
"operation": "deactivate",
"parameters": [{"name": "state", "value": "off"}],
"reason": "model-owned-plan",
}
],
"ambiguities": [],
"decision_summary": ["model-owned-plan"],
"confidence": 0.91,
}
context = self.service.conversations.snapshot("conv-model-owned-plan")
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret:
parsed, provider, notice = self.service._interpret(
"车上太闷了", "openai", context
)
self.assertEqual(parsed["decision_summary"], ["model-owned-plan"])
self.assertEqual(parsed["model_requests"], model_output["requests"])
self.assertEqual(
parsed["actions"],
[
{
"target": "passenger",
"function": "ventilation",
"state": "off",
"requested_level": None,
}
],
)
self.assertEqual(provider, f"openai:{self.service.openai.model}")
self.assertIsNone(notice)
interpret.assert_called_once_with(
"车上太闷了", context, self.service.capability_context
)
def test_explicit_gpt_failure_falls_back_to_local_rules(self) -> None:
self.service.openai.api_key = "test-key"
with patch.object(
self.service.openai,
"interpret",
side_effect=OpenAIIntentError("invalid_api_key"),
):
result = self.service.process(
"打开副驾座椅通风",
"openai",
"conv-explicit-gpt-fallback",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["provider_used"], "local_rules_fallback")
self.assertEqual(
result["function_config"]["spec"]["operations"][0]["target"]["instance"],
"passenger",
)
self.assertTrue(
any("已降级到本地规则解析" in item for item in result["decision_summary"])
)
def test_model_climate_request_completes_domain_loop(self) -> None:
model_output = {
"detected_domains": ["climate"],
"requests": [
{
"domain": "climate",
"capability": "climate.set_fan_level",
"target_scope": "vehicle",
"operation": "increase",
"parameters": [{"name": "level", "value": 3}],
"reason": "Increase cabin airflow.",
}
],
"ambiguities": [],
"decision_summary": ["整车舱风量增加需求。"],
"confidence": 0.94,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"车上太闷了",
"openai",
"conv-model-deferred",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["model_requests"], model_output["requests"])
self.assertEqual(result["deferred_requests"], [])
self.assertIn("climate", result["function_configs"])
request = result["intent_ir"]["spec"]["requests"][0]
self.assertEqual(request["domain"], "climate")
self.assertEqual(request["parameters"]["capability"], "climate.set_fan_level")
def test_climate_and_seat_requests_generate_scene(self) -> None:
model_output = {
"detected_domains": ["seat", "climate"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_heating",
"target_scope": "passenger",
"operation": "activate",
"parameters": [
{"name": "state", "value": "on"},
{"name": "level", "value": 1},
],
"reason": "Warm the passenger seat.",
},
{
"domain": "climate",
"capability": "climate.set_fan_level",
"target_scope": "vehicle",
"operation": "set",
"parameters": [{"name": "level", "value": 2}],
"reason": "Improve cabin airflow.",
},
],
"ambiguities": [],
"decision_summary": ["座椅加热与整车空调风量并行执行。"],
"confidence": 0.9,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"副驾有点冷、有点闷",
"openai",
"conv-model-partial",
)
self.assertEqual(result["status"], "succeeded")
self.assertIsNotNone(result["execution_report"])
self.assertEqual(
result["actuator_state"]["passenger"]["heating"],
{"state": "on", "level": 1},
)
self.assertEqual(result["domain_state"]["climate"]["fan_level"], 2)
self.assertEqual(set(result["function_configs"]), {"seat", "climate"})
self.assertEqual(result["scene_config"]["kind"], "SceneConfig")
def test_model_motion_requests_complete_control_and_feedback_loop(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 35}],
"reason": "Move the passenger seat backward from its current midpoint.",
},
{
"domain": "seat",
"capability": "seat.set_backrest_recline",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 60}],
"reason": "Recline the passenger backrest.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_height",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 65}],
"reason": "Raise the passenger leg rest.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_extension",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "position_pct", "value": 70}],
"reason": "Extend the passenger leg rest.",
},
],
"ambiguities": [],
"decision_summary": ["四个电机轴使用绝对位置目标。"],
"confidence": 0.93,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"副驾往后一点并把靠背躺下,腿托升起并伸长",
"openai",
"conv-model-motion",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(len(result["function_config"]["spec"]["operations"]), 4)
self.assertEqual(len(result["command_plan"]["spec"]["commands"]), 4)
self.assertEqual(len(result["execution_report"]["spec"]["results"]), 4)
passenger = result["actuator_state"]["passenger"]["position"]
self.assertEqual(passenger["longitudinal"]["position_pct"], 35)
self.assertEqual(passenger["backrest_recline"]["position_pct"], 60)
self.assertEqual(passenger["legrest_height"]["position_pct"], 65)
self.assertEqual(passenger["legrest_extension"]["position_pct"], 70)
telemetry = result["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(telemetry["start_position_pct"], 50)
self.assertEqual(telemetry["effective_position_pct"], 35)
self.assertEqual(telemetry["feedback_frame"]["frame_id"], "0x559")
self.assertEqual(telemetry["fault_mask"], 0)
codes = {issue["code"] for issue in result["validation"]}
self.assertIn("SYNTHETIC_MOTION_CAN", codes)
def test_motion_follow_up_state_is_supplied_to_model(self) -> None:
def output(position: int) -> dict[str, object]:
return {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
"operation": "increase",
"parameters": [{"name": "position_pct", "value": position}],
"reason": "Model resolved the relative request.",
}
],
"ambiguities": [],
"decision_summary": [f"Move to {position}%."],
"confidence": 0.9,
}
self.service.openai.api_key = "test-key"
with patch.object(
self.service.openai,
"interpret",
side_effect=[output(60), output(70)],
) as interpret:
first = self.service.process(
"副驾往后一点", "openai", "conv-motion-follow-up"
)
second = self.service.process(
"还是靠前,再往后一点", "openai", "conv-motion-follow-up"
)
self.assertEqual(
first["actuator_state"]["passenger"]["position"]["longitudinal"][
"position_pct"
],
60,
)
self.assertEqual(
second["actuator_state"]["passenger"]["position"]["longitudinal"][
"position_pct"
],
70,
)
second_context = interpret.call_args_list[1].args[1]
self.assertEqual(
second_context["devices"]["passenger"]["position"]["longitudinal"][
"position_pct"
],
60,
)
def test_model_relative_physical_motion_uses_vehicle_profile(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
"operation": "decrease",
"parameters": [{"name": "distance_mm", "value": 50}],
"reason": "Move forward by a physical distance.",
},
{
"domain": "seat",
"capability": "seat.set_backrest_recline",
"target_scope": "passenger",
"operation": "increase",
"parameters": [{"name": "angle_delta_deg", "value": 10}],
"reason": "Recline by a relative physical angle.",
},
],
"ambiguities": [],
"decision_summary": ["使用车型量程把相对物理量换算为绝对百分比。"],
"confidence": 0.94,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output) as interpret:
result = self.service.process(
"副驾水平向前移动50毫米,靠背往后10度",
"openai",
"conv-vehicle-profile-relative",
vehicle_id="denza_n7",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(result["vehicle"]["id"], "denza_n7")
operations = result["function_config"]["spec"]["operations"]
self.assertEqual(operations[0]["parameters"], {"position_pct": 27})
self.assertEqual(
operations[1]["parameters"],
{"position_pct": 35, "angle_deg": 118.6},
)
backrest_motion = operations[1]["motion"]
self.assertEqual(backrest_motion["mode"], "relative")
self.assertEqual(backrest_motion["direction"], "increase")
self.assertEqual(backrest_motion["start_position_pct"], 20)
self.assertEqual(backrest_motion["delta_position_pct"], 15)
self.assertEqual(backrest_motion["start_angle_deg"], 108.6)
self.assertEqual(backrest_motion["delta_angle_deg"], 10)
context_sent_to_model = interpret.call_args.args[1]
self.assertEqual(context_sent_to_model["vehicle"]["id"], "denza_n7")
self.assertEqual(
context_sent_to_model["devices"]["passenger"]["position"][
"backrest_recline"
]["angle_deg"],
108.6,
)
def test_vehicle_profile_angle_limits_are_accepted(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_backrest_recline",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "angle_deg", "value": 163}],
"reason": "Use the vehicle-specific maximum backrest angle.",
},
{
"domain": "seat",
"capability": "seat.set_legrest_height",
"target_scope": "passenger",
"operation": "set",
"parameters": [{"name": "angle_deg", "value": 167}],
"reason": "Use the vehicle-specific maximum legrest angle.",
},
],
"ambiguities": [],
"decision_summary": ["车型角度边界按腾势 N7 配置执行。"],
"confidence": 0.95,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"副驾靠背到163度,腿托到167度",
"openai",
"conv-vehicle-angle-limits",
vehicle_id="denza_n7",
)
self.assertEqual(result["status"], "succeeded")
operations = result["function_config"]["spec"]["operations"]
self.assertEqual(operations[0]["parameters"], {"position_pct": 100, "angle_deg": 163})
self.assertEqual(operations[1]["parameters"], {"position_pct": 100, "angle_deg": 167})
def test_motion_adapter_does_not_resolve_missing_absolute_target(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_longitudinal_position",
"target_scope": "passenger",
"operation": "decrease",
"parameters": [],
"reason": "No absolute target was produced.",
}
],
"ambiguities": [],
"decision_summary": ["Unresolved relative request."],
"confidence": 0.6,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"副驾往后一点", "openai", "conv-motion-missing-target"
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
self.assertIn(
"AMBIGUOUS_INTENT", {issue["code"] for issue in result["validation"]}
)
self.assertNotIn(
"NO_EXECUTABLE_OPERATION",
{issue["code"] for issue in result["validation"]},
)
self.assertNotIn(
"PUBLIC_SCHEMA", {issue["code"] for issue in result["validation"]}
)
def test_exact_angles_and_named_preset_complete_conversation_loop(self) -> None:
def plan(requests: list[dict[str, object]], summary: str) -> dict[str, object]:
return {
"detected_domains": ["seat"],
"requests": requests,
"ambiguities": [],
"decision_summary": [summary],
"confidence": 0.96,
}
def request(
capability: str,
operation: str,
parameters: list[dict[str, object]],
) -> dict[str, object]:
return {
"domain": "seat",
"capability": capability,
"target_scope": "passenger",
"operation": operation,
"parameters": parameters,
"reason": "Model resolved the user's posture goal against runtime context.",
}
first_plan = plan(
[
request(
"seat.set_backrest_recline",
"set",
[{"name": "angle_deg", "value": 128}],
),
request(
"seat.set_legrest_height",
"set",
[{"name": "angle_deg", "value": 133}],
),
],
"副驾靠背/坐垫夹角设为128度,坐垫/腿托夹角设为133度。",
)
recline_plan = plan(
[
request(
"seat.set_backrest_recline",
"increase",
[{"name": "angle_deg", "value": 133}],
)
],
"根据当前128度姿态,将副驾靠背继续后仰至133度。",
)
save_plan = plan(
[
request(
"seat.save_posture_preset",
"save",
[{"name": "preset_name", "value": "零重力模式"}],
)
],
"将副驾当前五轴姿态保存为零重力模式。",
)
leave_preset_plan = plan(
[
request(
"seat.set_backrest_recline",
"set",
[{"name": "angle_deg", "value": 105}],
)
],
"将副驾靠背恢复到105度用于验证预设回调。",
)
apply_plan = plan(
[
request(
"seat.apply_posture_preset",
"apply",
[{"name": "preset_name", "value": "零重力模式"}],
)
],
"调用已保存的副驾零重力模式。",
)
self.service.openai.api_key = "test-key"
conversation_id = "conv-angle-preset-loop"
with patch.object(
self.service.openai,
"interpret",
side_effect=[
first_plan,
recline_plan,
save_plan,
leave_preset_plan,
apply_plan,
],
) as interpret:
first = self.service.process(
"副驾调整为零重力模式,要求靠背座椅夹角128度,坐垫脚托夹角133度",
"openai",
conversation_id,
)
second = self.service.process(
"靠背再向后一点", "openai", conversation_id
)
saved = self.service.process(
"将当前状态保存为零重力模式", "openai", conversation_id
)
changed = self.service.process(
"副驾靠背恢复直立一些", "openai", conversation_id
)
applied = self.service.process(
"副驾恢复到零重力模式", "openai", conversation_id
)
first_operations = first["function_config"]["spec"]["operations"]
self.assertEqual(
first_operations[0]["parameters"],
{"position_pct": 49, "angle_deg": 128},
)
self.assertEqual(
first_operations[1]["parameters"],
{"position_pct": 53, "angle_deg": 133},
)
self.assertEqual(
first["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
128,
)
first_telemetry = first["execution_report"]["spec"]["results"][0]["telemetry"]
self.assertEqual(first_telemetry["requested_angle_deg"], 128)
self.assertEqual(first_telemetry["effective_position_pct"], 49)
self.assertEqual(
second["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
133,
)
relative_motion = second["function_config"]["spec"]["operations"][0][
"motion"
]
self.assertEqual(relative_motion["mode"], "relative")
self.assertEqual(relative_motion["direction"], "increase")
self.assertEqual(relative_motion["start_position_pct"], 49)
self.assertEqual(relative_motion["delta_position_pct"], 7)
self.assertEqual(relative_motion["delta_angle_deg"], 5)
second_context = interpret.call_args_list[1].args[1]
self.assertEqual(
second_context["devices"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
128,
)
self.assertEqual(saved["status"], "succeeded")
self.assertIsNone(saved["function_config"])
self.assertEqual(saved["preset_config"]["kind"], "FunctionPreset")
self.assertEqual(saved["preset_config"]["metadata"]["name"], "零重力模式")
self.assertFalse(
saved["execution_report"]["spec"]["results"][0]["telemetry"][
"can_command_emitted"
]
)
self.assertEqual(
saved["saved_presets"],
[{"name": "零重力模式", "target": "passenger"}],
)
preset_path = (
self.service.artifact_root
/ "user-presets"
/ f"{conversation_id}.json"
)
self.assertTrue(preset_path.is_file())
self.assertEqual(
changed["actuator_state"]["passenger"]["position"]["backrest_recline"][
"angle_deg"
],
105,
)
self.assertEqual(applied["status"], "succeeded")
restored = applied["actuator_state"]["passenger"]["position"]
self.assertEqual(restored["backrest_recline"]["angle_deg"], 133)
self.assertEqual(restored["legrest_height"]["angle_deg"], 133)
self.assertEqual(len(applied["function_config"]["spec"]["operations"]), 5)
apply_context = interpret.call_args_list[4].args[1]
self.assertEqual(apply_context["presets"][0]["name"], "零重力模式")
def test_missing_target_defaults_to_passenger(self) -> None:
result = self.service.process("打开座椅通风", provider="local")
self.assertEqual(result["status"], "succeeded")
operation = result["function_config"]["spec"]["operations"][0]
self.assertEqual(operation["target"]["instance"], "passenger")
def test_explicit_driver_target_is_rejected(self) -> None:
result = self.service.process("打开主驾座椅通风", provider="local")
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
questions = [
item["question"] for item in result["intent_ir"]["spec"]["ambiguities"]
]
self.assertTrue(any("仅控制副驾" in question for question in questions))
def test_model_driver_target_is_rejected(self) -> None:
model_output = {
"detected_domains": ["seat"],
"requests": [
{
"domain": "seat",
"capability": "seat.set_ventilation",
"target_scope": "driver",
"operation": "activate",
"parameters": [{"name": "state", "value": "on"}],
"reason": "Ventilate the explicitly requested driver seat.",
}
],
"ambiguities": [],
"decision_summary": ["Explicit driver-seat request."],
"confidence": 0.95,
}
self.service.openai.api_key = "test-key"
with patch.object(self.service.openai, "interpret", return_value=model_output):
result = self.service.process(
"打开主驾座椅通风", "openai", "conv-driver-rejected"
)
self.assertEqual(result["status"], "needs_confirmation")
self.assertIsNone(result["function_config"])
questions = [
item["question"] for item in result["intent_ir"]["spec"]["ambiguities"]
]
self.assertTrue(any("仅控制副驾" in question for question in questions))
if __name__ == "__main__":
unittest.main()