import importlib import io import json import os import unittest from app import GatewayApp from public_gateway import PublicGatewayApp from services.output_presenter import OutputPresenter class RecordingApiClient: def __init__(self, enabled=True): self.enabled = enabled self.last_call = None def list_enabled_tools(self): return { 'code': 'MCP_0000', 'data': {'tool_codes': ( ['query_customs_declaration_files'] if self.enabled else [] )}, } def call_tool(self, tool_code, route_path, payload, request_id): self.last_call = { 'tool_code': tool_code, 'route_path': route_path, 'payload': payload, 'request_id': request_id, } return {'code': 'MCP_0000', 'data': {'columns': [], 'records': []}} class PublicSessionStore: def get(self, gateway_session_id): if gateway_session_id == 'GWS_test': return {'mcp_token': 'MT_test', 'company_id': 7, 'admin_id': 9} return None class PublicApiClient: def __init__(self, enabled=True): self.enabled = enabled self.calls = [] def list_enabled_tools(self, token): return { 'code': 'MCP_0000', 'data': {'tool_codes': ( ['query_customs_declaration_files'] if self.enabled else [] )}, } def call_tool(self, **kwargs): self.calls.append(kwargs) return {'code': 'MCP_0000'} class QueryCustomsDeclarationFilesToolTest(unittest.TestCase): def tool_class(self): path = os.path.join( os.path.dirname(os.path.dirname(__file__)), 'tools', 'query_customs_declaration_files.py', ) self.assertTrue(os.path.exists(path), path) module = importlib.import_module( 'tools.query_customs_declaration_files' ) return module.QueryCustomsDeclarationFilesTool def test_metadata_teaches_ai_exact_order_number_semantics(self): metadata = self.tool_class()().metadata() schema = metadata['input_schema'] properties = schema['properties'] self.assertFalse(schema['additionalProperties']) self.assertEqual([ {'required': ['outbound_numbers']}, {'required': ['order_numbers']}, ], schema['oneOf']) for field in ('outbound_numbers', 'order_numbers'): prop = properties[field] self.assertEqual('array', prop['type']) self.assertEqual('string', prop['items']['type']) self.assertEqual(1, prop['minItems']) self.assertEqual(100, prop['maxItems']) self.assertNotIn('uniqueItems', prop) order_description = properties['order_numbers']['description'] for phrase in ( '订单号', '后台订单列表', '排舱详情', '不是系统单号', 'order_id/id', '不是客户参考号', '快递单号', '排舱单号', ): self.assertIn(phrase, order_description) self.assertNotIn('系统订单号', order_description) description = metadata['description'] for phrase in ( '用户明确说“订单号”', '用户明确说“排舱单号”', '没有明确说明是订单号还是排舱单号', '必须先提问,让用户选择“订单号”或“排舱单号”', '只说“单号”时必须先追问', '不是系统单号', '用户说“系统单号”时也不得当作订单号', '不得根据号码格式猜测', '不得跨字段重试', '不得展示内部参数名', ): self.assertIn(phrase, description) self.assertNotIn('系统订单号', description) def test_outbound_batch_normalizes_and_forwards_supported_fields(self): client = RecordingApiClient() tool = self.tool_class()(api_client=client) result = tool.call( outbound_numbers=[' PC001 ', 'PC002', 'PC001'], page=2, limit=50, request_id='rq_customs', ) self.assertEqual('MCP_0000', result['code']) self.assertEqual( 'query_customs_declaration_files', client.last_call['tool_code'], ) self.assertEqual( '/mcp/tools/queryCustomsDeclarationFiles', client.last_call['route_path'], ) self.assertEqual({ 'outbound_numbers': ['PC001', 'PC002'], 'page': 2, 'limit': 50, }, client.last_call['payload']) def test_order_batch_uses_order_numbers_business_field(self): client = RecordingApiClient() tool = self.tool_class()(api_client=client) tool.call(order_numbers=[' ORD001 ', 'ORD002']) self.assertEqual({ 'order_numbers': ['ORD001', 'ORD002'], 'page': 1, 'limit': 20, }, client.last_call['payload']) def test_call_rejects_mixed_missing_and_invalid_batches(self): tool = self.tool_class()(api_client=RecordingApiClient()) invalid = ( {}, {'outbound_numbers': ['PC001'], 'order_numbers': ['ORD001']}, {'order_numbers': []}, {'order_numbers': 'ORD001'}, {'order_numbers': ['']}, {'order_numbers': ['ORD001', 2]}, {'order_numbers': ['X' * 101]}, {'order_numbers': ['ORD{0}'.format(i) for i in range(101)]}, {'order_numbers': ['ORD001'], 'page': 0}, {'order_numbers': ['ORD001'], 'page': 101}, {'order_numbers': ['ORD001'], 'limit': 101}, {'order_numbers': ['ORD001'], 'page': True}, {'order_numbers': ['ORD001'], 'page': 'not-a-number'}, ) for arguments in invalid: with self.subTest(arguments=arguments): with self.assertRaises(ValueError): tool.call(**arguments) with self.assertRaisesRegex(RuntimeError, 'api client is required'): self.tool_class()().call(order_numbers=['ORD001']) def test_local_and_public_gateways_follow_dynamic_registry(self): local_enabled = { item['name'] for item in GatewayApp(api_client=RecordingApiClient()).list_tools() } local_disabled = { item['name'] for item in GatewayApp( api_client=RecordingApiClient(enabled=False) ).list_tools() } public_enabled = { item['name'] for item in PublicGatewayApp( PublicSessionStore(), PublicApiClient(), ).list_tools('GWS_test') } self.assertIn('query_customs_declaration_files', local_enabled) self.assertNotIn('query_customs_declaration_files', local_disabled) self.assertIn('query_customs_declaration_files', public_enabled) def test_public_gateway_forwards_request_scoped_token_without_company_input(self): api_client = PublicApiClient() gateway = PublicGatewayApp(PublicSessionStore(), api_client) self.assertIn( 'query_customs_declaration_files', gateway.registered_tool_names(), ) gateway.call_tool( 'GWS_test', 'query_customs_declaration_files', {'order_numbers': ['ORD001']}, request_id='rq_public_customs', ) call = api_client.calls[0] self.assertEqual('MT_test', call['token']) self.assertEqual( '/mcp/tools/queryCustomsDeclarationFiles', call['route_path'], ) self.assertEqual({'order_numbers': ['ORD001']}, call['payload']) self.assertNotIn('company_id', call['payload']) def test_output_presenter_hides_internal_fields_and_keeps_links(self): result = OutputPresenter().present( 'query_customs_declaration_files', { 'code': 'MCP_0000', 'data': { 'summary': '当前页返回 1 个订单的报关资料', 'columns': [ {'key': 'outbound_number', 'name': '排舱单号'}, {'key': 'order_number', 'name': '订单号'}, {'key': 'file_name', 'name': '文件名'}, {'key': 'file_url', 'name': '文件链接'}, ], 'records': [{ 'outbound_number': 'PC001', 'order_number': 'ORD001', 'file_name': '报关单.pdf', 'file_url': 'https://files.test/a.pdf', 'order_id': 99, }], }, 'meta': { 'page': 1, 'limit': 20, 'has_more': False, 'request_id': 'rq_present', }, }, ) self.assertFalse(result['is_error']) self.assertEqual( ['排舱单号', '订单号', '文件名', '文件链接'], [header['label'] for header in result['structured_content']['headers']], ) self.assertIn('https://files.test/a.pdf', result['text']) serialized = json.dumps(result, ensure_ascii=False) for internal in ( 'outbound_number', 'order_number', 'file_name', 'file_url', 'order_id', ): self.assertNotIn(internal, serialized) def test_cli_forwards_batch_arguments(self): client = RecordingApiClient() stdout = io.StringIO() app = GatewayApp(api_client=client) self.assertIn( 'query_customs_declaration_files', app.registered_tool_names(), ) result = app.run_cli([ 'call', '--tool', 'query_customs_declaration_files', '--order-numbers', 'ORD001,ORD002', '--page', '2', '--limit', '10', ], stdout=stdout) self.assertEqual(0, result) self.assertEqual({ 'order_numbers': ['ORD001', 'ORD002'], 'page': 2, 'limit': 10, }, client.last_call['payload']) app.run_cli([ 'call', '--tool', 'query_customs_declaration_files', '--outbound-numbers', 'PC001,PC002', ], stdout=io.StringIO()) self.assertEqual({ 'outbound_numbers': ['PC001', 'PC002'], 'page': 1, 'limit': 20, }, client.last_call['payload']) if __name__ == '__main__': unittest.main()