ForcePilot/backend/test/unit/channels/contract/dtos/test_tools.py
Kris 3ae9c0bd21 test: 批量修复与新增单元测试用例,清理废弃测试目录
1.  删除了 test/unit/external_systems/framework/ 下的废弃空测试目录
2.  修复多处测试断言逻辑、参数传递与测试数据构造
3.  新增日志级别、缓存令牌、流事件等DTO单元测试
4.  补充路由绑定、会话仓储、outbox仓储的测试覆盖
5.  更新测试用例中的异常类型、参数校验与业务逻辑断言
2026-07-11 21:43:16 +08:00

236 lines
7.4 KiB
Python

"""tools.py DTO 单元测试。
覆盖 ``ToolParameter`` / ``ChannelTool`` / ``ToolDefinition`` /
``ToolResult`` 的字段赋值、默认值、不可变语义与 ``__post_init__`` 校验逻辑。
"""
from __future__ import annotations
import dataclasses
from typing import Any
import pytest
from yuxi.channels.contract.dtos.tools import (
ChannelTool,
ToolDefinition,
ToolParameter,
ToolResult,
)
from yuxi.channels.contract.errors import ValidationError
pytestmark = pytest.mark.unit
@pytest.mark.unit
class TestToolParameter:
"""工具参数 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
param = ToolParameter(name="count", type="int", description="数量")
# Assert
assert param.name == "count"
assert param.type == "int"
assert param.description == "数量"
assert param.required is True
assert param.default is None
def test_with_optional_fields(self):
# Arrange
# Act
param = ToolParameter(
name="verbose",
type="bool",
description="详细模式",
required=False,
default=False,
)
# Assert
assert param.required is False
assert param.default is False
@pytest.mark.parametrize("ptype", ["str", "int", "bool", "json"])
def test_valid_types(self, ptype):
# Arrange
# Act
param = ToolParameter(name="p", type=ptype, description="d") # type: ignore[arg-type]
# Assert
assert param.type == ptype
def test_is_frozen(self):
# Arrange
param = ToolParameter(name="p", type="str", description="d")
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
param.name = "other" # type: ignore[misc]
def test_empty_name_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ToolParameter(name="", type="str", description="d")
assert exc_info.value.field == "name"
def test_invalid_type_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ToolParameter(name="p", type="float", description="d") # type: ignore[arg-type]
assert exc_info.value.field == "type"
@pytest.mark.unit
class TestChannelTool:
"""渠道工具 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
tool = ChannelTool(name="search", description="搜索", handler_id="handler-1")
# Assert
assert tool.name == "search"
assert tool.description == "搜索"
assert tool.handler_id == "handler-1"
assert tool.parameters == ()
assert tool.scope == "all"
def test_with_parameters_and_scope(self):
# Arrange
params = (ToolParameter(name="q", type="str", description="query"),)
# Act
tool = ChannelTool(
name="search",
description="搜索",
handler_id="handler-1",
parameters=params,
scope="private",
)
# Assert
assert tool.parameters == params
assert tool.scope == "private"
def test_is_frozen(self):
# Arrange
tool = ChannelTool(name="search", description="d", handler_id="h")
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
tool.name = "other" # type: ignore[misc]
def test_empty_name_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ChannelTool(name="", description="d", handler_id="h")
assert exc_info.value.field == "name"
def test_empty_description_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ChannelTool(name="n", description="", handler_id="h")
assert exc_info.value.field == "description"
def test_empty_handler_id_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ChannelTool(name="n", description="d", handler_id="")
assert exc_info.value.field == "handler_id"
def test_invalid_scope_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ChannelTool(name="n", description="d", handler_id="h", scope="invalid") # type: ignore[arg-type]
assert exc_info.value.field == "scope"
@pytest.mark.unit
class TestToolDefinition:
"""工具定义 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
schema: dict[str, Any] = {"type": "object"}
# Act
defn = ToolDefinition(name="search", description="搜索", parameters_schema=schema)
# Assert
assert defn.name == "search"
assert defn.description == "搜索"
assert defn.parameters_schema is schema
assert defn.scope == "all"
def test_with_scope(self):
# Arrange
# Act
defn = ToolDefinition(
name="search", description="d", parameters_schema={}, scope="group"
)
# Assert
assert defn.scope == "group"
def test_is_frozen(self):
# Arrange
defn = ToolDefinition(name="n", description="d", parameters_schema={})
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
defn.name = "other" # type: ignore[misc]
def test_empty_name_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ToolDefinition(name="", description="d", parameters_schema={})
assert exc_info.value.field == "name"
def test_empty_description_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ToolDefinition(name="n", description="", parameters_schema={})
assert exc_info.value.field == "description"
def test_invalid_scope_raises_validation_error(self):
# Arrange
# Act / Assert
with pytest.raises(ValidationError) as exc_info:
ToolDefinition(name="n", description="d", parameters_schema={}, scope="invalid") # type: ignore[arg-type]
assert exc_info.value.field == "scope"
@pytest.mark.unit
class TestToolResult:
"""工具结果 DTO 测试。"""
def test_required_fields_are_assigned(self):
# Arrange
# Act
result = ToolResult(success=True)
# Assert
assert result.success is True
assert result.result is None
assert result.error is None
def test_success_with_result(self):
# Arrange
# Act
result = ToolResult(success=True, result={"data": "ok"})
# Assert
assert result.result == {"data": "ok"}
def test_failure_with_error(self):
# Arrange
# Act
result = ToolResult(success=False, error="not found")
# Assert
assert result.success is False
assert result.error == "not found"
def test_is_frozen(self):
# Arrange
result = ToolResult(success=True)
# Act / Assert
with pytest.raises(dataclasses.FrozenInstanceError):
result.success = False # type: ignore[misc]