Skip to content

Commit 9b23590

Browse files
committed
added some preliminary unit testing for ListToolHandler, tool property, and get_tool() functions
1 parent 24116cd commit 9b23590

File tree

6 files changed

+154
-0
lines changed

6 files changed

+154
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""A mock extension exposing a structured tool."""
2+
3+
def jupyter_server_extension_tools():
4+
return {
5+
"mock_tool": {
6+
"metadata": {
7+
"name": "mock_tool",
8+
"description": "A mock tool for testing.",
9+
"inputSchema": {
10+
"type": "object",
11+
"properties": {
12+
"input": {"type": "string"}
13+
},
14+
"required": ["input"]
15+
}
16+
},
17+
"callable": lambda input: f"Echo: {input}"
18+
}
19+
}
20+
21+
def _load_jupyter_server_extension(serverapp):
22+
serverapp.log.info("Loaded mock tool extension.")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""A mock extension that defines a duplicate tool name to test conflict handling."""
2+
3+
def jupyter_server_extension_tools():
4+
return {
5+
"mock_tool": { # <-- duplicate on purpose
6+
"metadata": {
7+
"name": "mock_tool",
8+
"description": "Conflicting tool name.",
9+
"inputSchema": {
10+
"type": "object",
11+
"properties": {
12+
"input": {"type": "string"}
13+
}
14+
}
15+
},
16+
"callable": lambda input: f"Echo again: {input}"
17+
}
18+
}
19+
20+
def _load_jupyter_server_extension(serverapp):
21+
serverapp.log.info("Loaded dupe tool extension.")
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""A mock extension that provides a custom validation schema."""
2+
3+
OPENAI_TOOL_SCHEMA = {
4+
"type": "object",
5+
"properties": {
6+
"name": {"type": "string"},
7+
"description": {"type": "string"},
8+
"parameters": {
9+
"type": "object",
10+
"properties": {
11+
"input": {"type": "string"}
12+
},
13+
"required": ["input"]
14+
}
15+
},
16+
"required": ["name", "parameters"]
17+
}
18+
19+
def jupyter_server_extension_tools():
20+
tools = {
21+
"openai_style_tool": {
22+
"metadata": {
23+
"name": "openai_style_tool",
24+
"description": "Tool using OpenAI-style parameters",
25+
"parameters": {
26+
"type": "object",
27+
"properties": {
28+
"input": {"type": "string"}
29+
},
30+
"required": ["input"]
31+
}
32+
},
33+
"callable": lambda input: f"Got {input}"
34+
}
35+
}
36+
return (tools, OPENAI_TOOL_SCHEMA)
37+
38+
def _load_jupyter_server_extension(serverapp):
39+
serverapp.log.info("Loaded mock custom-schema extension.")

tests/extension/test_manager.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,28 @@ def test_disable_no_import(jp_serverapp, has_app):
167167
assert ext_pkg.extension_points == {}
168168
assert ext_pkg.version == ""
169169
assert ext_pkg.metadata == []
170+
171+
172+
def test_extension_point_tools_default_schema():
173+
ep = ExtensionPoint(metadata={"module": "tests.extension.mockextensions.mockext_tool"})
174+
assert "mock_tool" in ep.tools
175+
176+
177+
def test_extension_point_tools_custom_schema():
178+
ep = ExtensionPoint(metadata={"module": "tests.extension.mockextensions.mockext_customschema"})
179+
assert "openai_style_tool" in ep.tools
180+
metadata = ep.tools["openai_style_tool"]["metadata"]
181+
assert "parameters" in metadata
182+
183+
184+
def test_extension_manager_duplicate_tool_name_raises(jp_serverapp):
185+
from jupyter_server.extension.manager import ExtensionManager
186+
187+
manager = ExtensionManager(serverapp=jp_serverapp)
188+
manager.add_extension("tests.extension.mockextensions.mockext_tool", enabled=True)
189+
manager.add_extension("tests.extension.mockextensions.mockext_dupes", enabled=True)
190+
manager.link_all_extensions()
191+
192+
with pytest.raises(ValueError, match="Duplicate tool name detected: 'mock_tool'"):
193+
manager.get_tools()
194+

tests/services/tools/test_api.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import json
2+
import pytest
3+
4+
@pytest.fixture
5+
def jp_server_config():
6+
return {
7+
"ServerApp": {
8+
"jpserver_extensions": {
9+
"tests.extension.mockextensions.mockext_tool": True,
10+
"tests.extension.mockextensions.mockext_customschema": True,
11+
}
12+
}
13+
}
14+
15+
@pytest.mark.asyncio
16+
async def test_multiple_tools_present(jp_fetch):
17+
response = await jp_fetch("api", "tools", method="GET")
18+
assert response.code == 200
19+
20+
body = json.loads(response.body.decode())
21+
tools = body["discovered_tools"]
22+
23+
# Check default schema tool
24+
assert "mock_tool" in tools
25+
assert "inputSchema" in tools["mock_tool"]
26+
27+
# Check custom schema tool
28+
assert "openai_style_tool" in tools
29+
assert "parameters" in tools["openai_style_tool"]

tests/test_serverapp.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,24 @@ def test_immutable_cache_trait():
644644
assert serverapp.web_app.settings["static_immutable_cache"] == ["/test/immutable"]
645645

646646

647+
# testing get_tools
648+
def test_serverapp_get_tools_empty(jp_serverapp):
649+
# testing the default empty state
650+
tools = jp_serverapp.get_tools()
651+
assert tools == {}
652+
653+
def test_serverapp_get_tools(jp_serverapp):
654+
jp_serverapp.extension_manager.add_extension(
655+
"tests.extension.mockextensions.mockext_tool", enabled=True
656+
)
657+
jp_serverapp.extension_manager.link_all_extensions()
658+
659+
tools = jp_serverapp.get_tools()
660+
assert "mock_tool" in tools
661+
metadata = tools["mock_tool"]["metadata"]
662+
assert metadata["name"] == "mock_tool"
663+
664+
647665
def test():
648666
pass
649667

0 commit comments

Comments
 (0)