from __future__ import annotations from runtime.tool_specs import ( ACTION_TOOL_SPECS, ALL_TOOL_SPECS, FINISH_TASK_SPEC, INPUT_TEXT_SPEC, LAUNCH_APP_SPEC, SWIPE_SPEC, TAP_SPEC, TERMINATE_APP_SPEC, ToolSpec, ) def test_action_tool_specs_has_five_entries_and_all_tool_specs_adds_finish_task() -> ( None ): assert len(ACTION_TOOL_SPECS) == 7 assert len(ALL_TOOL_SPECS) == 8 assert ALL_TOOL_SPECS == [*ACTION_TOOL_SPECS, FINISH_TASK_SPEC] assert FINISH_TASK_SPEC not in ACTION_TOOL_SPECS def test_all_tool_spec_names_are_unique() -> None: names = [spec.name for spec in ALL_TOOL_SPECS] assert len(names) == len(set(names)) def test_every_tool_spec_schema_forbids_additional_properties() -> None: for spec in ALL_TOOL_SPECS: assert isinstance(spec, ToolSpec) assert spec.parameters["type"] == "object" assert spec.parameters["additionalProperties"] is False def test_tap_spec_requires_x_and_y() -> None: assert TAP_SPEC.parameters["required"] == ["x", "y", "purpose", "expected_outcome"] assert set(TAP_SPEC.parameters["properties"]) == { "x", "y", "purpose", "expected_outcome", } def test_swipe_spec_requires_coordinates_and_makes_duration_optional() -> None: assert SWIPE_SPEC.parameters["required"] == [ "start_x", "start_y", "end_x", "end_y", "purpose", "expected_outcome", ] assert set(SWIPE_SPEC.parameters["properties"]) == { "start_x", "start_y", "end_x", "end_y", "duration_ms", "purpose", "expected_outcome", } assert "duration_ms" not in SWIPE_SPEC.parameters["required"] assert SWIPE_SPEC.parameters["properties"]["duration_ms"]["default"] == 500 def test_input_text_spec_requires_text() -> None: assert INPUT_TEXT_SPEC.parameters["required"] == [ "text", "purpose", "expected_outcome", ] assert set(INPUT_TEXT_SPEC.parameters["properties"]) == { "text", "purpose", "expected_outcome", } def test_launch_and_terminate_app_specs_require_app_id() -> None: for spec in (LAUNCH_APP_SPEC, TERMINATE_APP_SPEC): assert spec.parameters["required"] == [ "app_id", "purpose", "expected_outcome", ] assert set(spec.parameters["properties"]) == { "app_id", "purpose", "expected_outcome", } def test_action_metadata_is_required_nonempty_text() -> None: for spec in ACTION_TOOL_SPECS: for field_name in ("purpose", "expected_outcome"): field = spec.parameters["properties"][field_name] assert field["type"] == "string" assert field["minLength"] == 1 def test_finish_task_spec_requires_success_and_reason() -> None: assert FINISH_TASK_SPEC.parameters["required"] == ["success", "reason"] assert set(FINISH_TASK_SPEC.parameters["properties"]) == {"success", "reason"} assert FINISH_TASK_SPEC.parameters["properties"]["success"]["type"] == "boolean" def test_no_tool_spec_declares_device_id() -> None: for spec in ALL_TOOL_SPECS: assert "device_id" not in spec.parameters["properties"] def test_long_press_and_double_tap_are_action_tools(): from runtime.tool_specs import ACTION_TOOL_NAMES assert "long_press" in ACTION_TOOL_NAMES assert "double_tap" in ACTION_TOOL_NAMES def test_long_press_spec_has_duration_with_default(): from runtime.tool_specs import LONG_PRESS_SPEC props = LONG_PRESS_SPEC.parameters["properties"] assert props["x"]["type"] == "number" assert props["y"]["type"] == "number" assert props["duration_ms"]["default"] == 1200 # purpose/expected_outcome are required metadata assert "purpose" in LONG_PRESS_SPEC.parameters["required"] def test_double_tap_spec_has_interval_with_default(): from runtime.tool_specs import DOUBLE_TAP_SPEC props = DOUBLE_TAP_SPEC.parameters["properties"] assert props["interval_ms"]["default"] == 80