Skip to content

Commit 25a3eee

Browse files
committed
added basic tests
1 parent 5ba8a44 commit 25a3eee

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

packages/toolbox-core/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ test = [
4444
"isort==6.0.1",
4545
"mypy==1.15.0",
4646
"pytest==8.3.5",
47-
"pytest-aioresponses==0.3.0"
47+
"pytest-aioresponses==0.3.0",
48+
"pytest-asyncio==0.26.0"
4849
]
4950
[build-system]
5051
requires = ["setuptools"]
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
from unittest.mock import AsyncMock, MagicMock
17+
from inspect import Parameter, Signature
18+
from typing import Any, Optional, Callable
19+
20+
from toolbox_core.tool import ToolboxTool
21+
22+
class TestToolboxTool:
23+
@pytest.fixture
24+
def mock_session(self) -> MagicMock: # Added self
25+
session = MagicMock()
26+
session.post = MagicMock()
27+
return session
28+
29+
@pytest.fixture
30+
def tool_details(self) -> dict:
31+
base_url = "http://fake-toolbox.com"
32+
tool_name = "test_tool"
33+
params = [
34+
Parameter("arg1", Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
35+
Parameter("opt_arg", Parameter.POSITIONAL_OR_KEYWORD, default=123, annotation=Optional[int]),
36+
]
37+
return {
38+
"base_url": base_url,
39+
"name": tool_name,
40+
"desc": "A tool for testing.",
41+
"params": params,
42+
"signature": Signature(parameters=params, return_annotation=str),
43+
"expected_url": f"{base_url}/api/tool/{tool_name}/invoke",
44+
"annotations": {"arg1": str, "opt_arg": Optional[int]},
45+
}
46+
47+
@pytest.fixture
48+
def tool(self, mock_session: MagicMock, tool_details: dict) -> ToolboxTool:
49+
return ToolboxTool(
50+
session=mock_session,
51+
base_url=tool_details["base_url"],
52+
name=tool_details["name"],
53+
desc=tool_details["desc"],
54+
params=tool_details["params"],
55+
)
56+
57+
@pytest.fixture
58+
def configure_mock_response(self, mock_session: MagicMock) -> Callable:
59+
def _configure(json_data: Any, status: int = 200):
60+
mock_resp = MagicMock()
61+
mock_resp.status = status
62+
mock_resp.json = AsyncMock(return_value=json_data)
63+
mock_resp.__aenter__.return_value = mock_resp
64+
mock_resp.__aexit__.return_value = None
65+
mock_session.post.return_value = mock_resp
66+
return _configure
67+
68+
@pytest.mark.asyncio
69+
async def test_initialization_and_introspection(self, tool: ToolboxTool, tool_details: dict):
70+
"""Verify attributes are set correctly during initialization."""
71+
assert tool.__name__ == tool_details["name"]
72+
assert tool.__doc__ == tool_details["desc"]
73+
assert tool._ToolboxTool__url == tool_details["expected_url"]
74+
assert tool._ToolboxTool__session is tool._ToolboxTool__session
75+
assert tool.__signature__ == tool_details["signature"]
76+
assert tool.__annotations__ == tool_details["annotations"]
77+
# assert hasattr(tool, "__qualname__")
78+
79+
@pytest.mark.asyncio
80+
async def test_call_success(
81+
self,
82+
tool: ToolboxTool,
83+
mock_session: MagicMock,
84+
tool_details: dict,
85+
configure_mock_response: Callable
86+
):
87+
expected_result = "Operation successful!"
88+
configure_mock_response({"result": expected_result})
89+
90+
arg1_val = "test_value"
91+
opt_arg_val = 456
92+
result = await tool(arg1_val, opt_arg=opt_arg_val)
93+
94+
assert result == expected_result
95+
mock_session.post.assert_called_once_with(
96+
tool_details["expected_url"],
97+
json={"arg1": arg1_val, "opt_arg": opt_arg_val},
98+
)
99+
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
100+
101+
@pytest.mark.asyncio
102+
async def test_call_success_with_defaults(
103+
self,
104+
tool: ToolboxTool,
105+
mock_session: MagicMock,
106+
tool_details: dict,
107+
configure_mock_response: Callable
108+
):
109+
expected_result = "Default success!"
110+
configure_mock_response({"result": expected_result})
111+
112+
arg1_val = "another_test"
113+
default_opt_val = tool_details["params"][1].default
114+
result = await tool(arg1_val)
115+
116+
assert result == expected_result
117+
mock_session.post.assert_called_once_with(
118+
tool_details["expected_url"],
119+
json={"arg1": arg1_val, "opt_arg": default_opt_val},
120+
)
121+
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
122+
123+
@pytest.mark.asyncio
124+
async def test_call_api_error(
125+
self,
126+
tool: ToolboxTool,
127+
mock_session: MagicMock,
128+
tool_details: dict,
129+
configure_mock_response: Callable
130+
):
131+
error_message = "Tool execution failed on server"
132+
configure_mock_response({"error": error_message})
133+
default_opt_val = tool_details["params"][1].default
134+
135+
with pytest.raises(Exception) as exc_info:
136+
await tool("some_arg")
137+
138+
assert str(exc_info.value) == error_message
139+
mock_session.post.assert_called_once_with(
140+
tool_details["expected_url"],
141+
json={"arg1": "some_arg", "opt_arg": default_opt_val},
142+
)
143+
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
144+
145+
@pytest.mark.asyncio
146+
async def test_call_missing_result_key(
147+
self,
148+
tool: ToolboxTool,
149+
mock_session: MagicMock,
150+
tool_details: dict,
151+
configure_mock_response: Callable
152+
):
153+
fallback_response = {"status": "completed", "details": "some info"}
154+
configure_mock_response(fallback_response)
155+
default_opt_val = tool_details["params"][1].default
156+
157+
result = await tool("value_for_arg1")
158+
159+
assert result == fallback_response
160+
mock_session.post.assert_called_once_with(
161+
tool_details["expected_url"],
162+
json={"arg1": "value_for_arg1", "opt_arg": default_opt_val},
163+
)
164+
mock_session.post.return_value.__aenter__.return_value.json.assert_awaited_once()
165+
166+
@pytest.mark.asyncio
167+
async def test_call_invalid_arguments_type_error(
168+
self,
169+
tool: ToolboxTool,
170+
mock_session: MagicMock
171+
):
172+
with pytest.raises(TypeError):
173+
await tool("val1", 2, 3)
174+
175+
with pytest.raises(TypeError):
176+
await tool("val1", non_existent_arg="bad")
177+
178+
with pytest.raises(TypeError):
179+
await tool(opt_arg=500)
180+
181+
mock_session.post.assert_not_called()

0 commit comments

Comments
 (0)