"""工具 schema 注册表:合法性、read_only 标注、有理秒参数形状。""" import json import pytest from quercus_core.providers.base import ToolSchema from quercus_core.tools.schemas import ( RATIONAL_SCHEMA, TIME_RANGE_SCHEMA, TOOL_LIST, TOOLS, get_tool, tool_read_only, ) # 期望的 read_only 标注(独立于代码本身,防止误改)。 EXPECTED_READ_ONLY = { "open_project": False, "save_project": False, "get_project_overview": True, "probe_media": True, "import_footage": False, "list_footage": True, "add_track": False, "place_clip": False, "split_clip": False, "trim_clip": False, "move_clip": False, "ripple_delete": False, "add_transition": False, "add_marker": False, "add_effect": False, "set_param": False, "set_keyframe": False, "list_effects": True, "get_frame": True, "scan_timeline": True, "get_audio_levels": True, "play": True, "pause": True, "seek": True, "export_render": False, "undo_last_action": False, } def test_tool_count(): assert len(TOOLS) == 26 assert len(TOOL_LIST) == 26 assert len({t.name for t in TOOL_LIST}) == 26 def test_schema_shape(): for name, tool in TOOLS.items(): assert isinstance(tool, ToolSchema) assert tool.name == name assert isinstance(tool.description, str) and tool.description p = tool.parameters assert p["type"] == "object" assert isinstance(p["properties"], dict) required = p.get("required", []) assert isinstance(required, list) for req in required: assert req in p["properties"], f"{name}: required {req} 不在 properties" json.dumps(p) # 可 JSON 序列化 => 合法 JSON Schema 对象 def test_read_only_flags(): assert set(TOOLS) == set(EXPECTED_READ_ONLY) for name, expected in EXPECTED_READ_ONLY.items(): assert TOOLS[name].read_only is expected, f"{name} read_only 应为 {expected}" assert tool_read_only(name) is expected, f"{name} tool_read_only 不一致" def test_unknown_tool_treated_as_mutation(): assert tool_read_only("no_such_tool") is False @pytest.mark.parametrize( "tool_name,param_name", [ ("place_clip", "time"), ("split_clip", "time"), ("trim_clip", "in_point"), ("move_clip", "time"), ("add_marker", "time"), ("set_keyframe", "time"), ("get_frame", "time"), ("seek", "time"), ], ) def test_rational_params_are_num_den(tool_name, param_name): props = TOOLS[tool_name].parameters["properties"] assert props[param_name] == RATIONAL_SCHEMA, f"{tool_name}.{param_name}" def test_time_range_param_shape(): props = TOOLS["scan_timeline"].parameters["properties"] assert props["range"] == TIME_RANGE_SCHEMA assert props["count"]["maximum"] == 64 def test_get_frame_max_size(): props = TOOLS["get_frame"].parameters["properties"] max_size = props["max_size"] assert max_size["type"] == "object" assert max_size["required"] == ["width", "height"] def test_get_tool_lookup(): assert get_tool("play") is TOOLS["play"] assert get_tool("nope") is None