kalanos-mcp / tests /test_adapter.py
kalanosdev's picture
Expose combined dataset discovery through HF MCP
ba73fa6
Raw History Blame Contribute Delete
5 kB
import asyncio
import unittest
from unittest.mock import AsyncMock, patch
import app
from gradio.mcp import GradioMCPServer
class AdapterTests(unittest.IsolatedAsyncioTestCase):
async def test_only_six_read_tools_exposed(self):
server = GradioMCPServer(app.demo)
self.assertEqual(set(server.tool_to_endpoint), app.ALLOWED)
self.assertEqual(len(app.demo.fns), 6)
for fn in app.demo.fns.values():
self.assertEqual(fn.concurrency_limit, 4)
self.assertEqual(fn.concurrency_id, 'public_reads')
async def test_unsafe_tool_rejected_before_network(self):
with patch.object(app.httpx, 'AsyncClient') as client:
with self.assertRaisesRegex(ValueError, 'not allowed'):
await app.forward('run', {'dataset': 'lerobot/pusht'})
client.assert_not_called()
async def test_bounds_and_unknown_parameters_rejected_before_network(self):
for name,args in [('list_datasets',{'limit':1000}),('get_report',{'url':'http://localhost'}),
('get_findings',{'dataset':'lerobot/pusht'}),
('search_datasets',{'q':'a'*129}),('search_episodes',{'limit':31}),('search_combined_datasets',{'per_page':1}),('search_combined_datasets',{'direction':'bad'})]:
with self.subTest(name=name),patch.object(app.httpx, 'AsyncClient') as client:
with self.assertRaisesRegex(ValueError, 'Invalid tool arguments'):
await app.forward(name,args)
client.assert_not_called()
async def test_wrappers_preserve_revision_and_pagination(self):
result={'content':[], 'structuredContent':{'report_revision':'r1'},'isError':False}
with patch.object(app,'forward',new=AsyncMock(return_value=result)) as forward:
self.assertEqual(await app.get_findings('r1',dataset='lerobot/pusht',after=5,limit=3),result)
name,args=forward.call_args.args
self.assertEqual(name,'get_findings')
self.assertEqual(args['report_revision'],'r1')
self.assertEqual(args['after'],5)
self.assertEqual(args['limit'],3)
async def test_fixed_anonymous_origin_and_no_environment_auth(self):
with patch.object(app.httpx,'AsyncClient',side_effect=TimeoutError) as client:
with self.assertRaisesRegex(app.gr.Error,'No audit was started'):
await app.get_report(dataset='lerobot/pusht')
self.assertFalse(client.call_args.kwargs['trust_env'])
self.assertFalse(client.call_args.kwargs['follow_redirects'])
self.assertNotIn('headers',client.call_args.kwargs)
self.assertEqual(app.UPSTREAM,'https://mcp.kalanos.ai/mcp')
async def test_transport_preserves_json_and_upstream_errors(self):
import json
from contextlib import asynccontextmanager
from unittest.mock import Mock
@asynccontextmanager
async def transport(*args, **kwargs):
yield None, None, None
@asynccontextmanager
async def session_context(*args):
yield session
session=Mock(initialize=AsyncMock(),call_tool=AsyncMock())
for failed in (False,True):
envelope={'content':[], 'structuredContent':{'report_revision':'r1'},'isError':failed}
result=Mock();result.model_dump.return_value=envelope
session.call_tool.return_value=result
with patch.object(app,'streamable_http_client',transport),patch.object(app,'ClientSession',session_context):
if failed:
with self.assertRaisesRegex(app.gr.Error,'Upstream read-only error'):
await app.get_report(report_id='report1')
else:
self.assertEqual(json.loads(await app.get_report(report_id='report1')),envelope)
session.call_tool.assert_awaited_with('get_report',{'report_id':'report1'})
if __name__ == '__main__':
unittest.main()
class CombinedAdapterTests(unittest.IsolatedAsyncioTestCase):
async def test_combined_wrapper_forwards_filter_and_paging(self):
with patch.object(app, 'forward', new=AsyncMock(return_value='ok')) as f:
self.assertEqual(await app.search_combined_datasets(task=['pick'],robot=['aloha'],page=2,per_page=40,direction='asc',facets=True),'ok')
name,args=f.call_args.args
self.assertEqual(name,'search_combined_datasets')
self.assertEqual([args[k] for k in ['task','robot','page','per_page','direction','facets']],[['pick'],['aloha'],2,40,'asc',True])
async def test_schema_matches_checked_upstream_contract(self):
import json
from pathlib import Path
from jsonschema import Draft7Validator
schema=app.SCHEMAS['search_combined_datasets'];Draft7Validator.check_schema(schema)
self.assertFalse(list(Draft7Validator(schema).iter_errors({'task':['pick'],'page':2,'per_page':40,'direction':'asc'})))