test_gateway_query_order.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import unittest
  2. from unittest.mock import patch
  3. from app import GatewayApp
  4. from services.api_client import ApiClient, JsonTransport
  5. from services.token_store import InMemoryTokenStore
  6. from tools.query_order import QueryOrderTool
  7. class DummyTransport:
  8. def __init__(self):
  9. self.calls = []
  10. def post_json(self, url, payload, headers, timeout):
  11. self.calls.append(
  12. {
  13. 'url': url,
  14. 'payload': payload,
  15. 'headers': headers,
  16. 'timeout': timeout,
  17. }
  18. )
  19. if url.endswith('/mcp/tools/listEnabledTools'):
  20. return {
  21. 'code': 'MCP_0000',
  22. 'msg': 'success',
  23. 'data': {
  24. 'tool_codes': [
  25. 'query_order',
  26. 'query_track',
  27. 'query_order_exact',
  28. 'list_order_filter_options',
  29. ],
  30. },
  31. }
  32. return {
  33. 'code': 'MCP_0000',
  34. 'msg': 'success',
  35. 'data': {
  36. 'summary': 'ok',
  37. 'records': [],
  38. 'tips': [],
  39. },
  40. 'meta': {
  41. 'request_id': headers.get('X-Request-Id', ''),
  42. },
  43. }
  44. class BomHttpResponse:
  45. def __init__(self, body):
  46. self.body = body
  47. def __enter__(self):
  48. return self
  49. def __exit__(self, exc_type, exc_value, traceback):
  50. return False
  51. def read(self):
  52. return self.body
  53. class GatewayQueryOrderTest(unittest.TestCase):
  54. def test_gateway_lists_query_order_tool(self):
  55. store = InMemoryTokenStore(refresh_skew_seconds=60)
  56. store.save('MT_demo', '2099-01-01T00:00:00')
  57. app = GatewayApp(api_client=ApiClient(
  58. base_url='http://tools.example.test',
  59. token_store=store,
  60. transport=DummyTransport(),
  61. ))
  62. tools = app.list_tools()
  63. by_name = {tool['name']: tool for tool in tools}
  64. self.assertIn('query_order', by_name)
  65. self.assertIn('query_order_exact', by_name)
  66. self.assertIn('list_order_filter_options', by_name)
  67. self.assertIn('keyword', by_name['query_order']['input_schema']['required'])
  68. def test_json_transport_accepts_utf8_bom_response(self):
  69. transport = JsonTransport()
  70. body = b'\xef\xbb\xbf{"code":"MCP_0000","data":{"summary":"ok"}}'
  71. with patch('urllib.request.urlopen', return_value=BomHttpResponse(body)):
  72. response = transport.post_json(
  73. 'http://tools.example.test/mcp/tools/queryOrder',
  74. {'keyword': 'SO20260706001'},
  75. {},
  76. 8,
  77. )
  78. self.assertEqual('MCP_0000', response['code'])
  79. self.assertEqual('ok', response['data']['summary'])
  80. def test_api_client_adds_mcp_headers_when_calling_tool(self):
  81. transport = DummyTransport()
  82. store = InMemoryTokenStore(refresh_skew_seconds=60)
  83. store.save('MT_demo', '2099-01-01T00:00:00')
  84. client = ApiClient(
  85. base_url='http://tools.example.test',
  86. token_store=store,
  87. transport=transport,
  88. timeout=8,
  89. )
  90. response = client.call_tool(
  91. tool_code='query_order',
  92. route_path='/mcp/tools/queryOrder',
  93. payload={'keyword': 'SO20260706001', 'page': 1, 'limit': 20},
  94. request_id='rq_demo',
  95. )
  96. self.assertEqual('MCP_0000', response['code'])
  97. self.assertEqual(1, len(transport.calls))
  98. self.assertEqual('Bearer MT_demo', transport.calls[0]['headers']['Authorization'])
  99. self.assertEqual('query_order', transport.calls[0]['headers']['X-MCP-Tool-Code'])
  100. self.assertEqual('rq_demo', transport.calls[0]['headers']['X-Request-Id'])
  101. self.assertEqual('http://tools.example.test/mcp/tools/queryOrder', transport.calls[0]['url'])
  102. def test_api_client_lists_enabled_tools_without_tool_headers(self):
  103. transport = DummyTransport()
  104. store = InMemoryTokenStore(refresh_skew_seconds=60)
  105. store.save('MT_demo', '2099-01-01T00:00:00')
  106. client = ApiClient(
  107. base_url='http://tools.example.test',
  108. token_store=store,
  109. transport=transport,
  110. timeout=8,
  111. )
  112. response = client.list_enabled_tools('rq_enabled_tools')
  113. self.assertEqual('MCP_0000', response['code'])
  114. self.assertEqual(
  115. 'http://tools.example.test/mcp/tools/listEnabledTools',
  116. transport.calls[0]['url'],
  117. )
  118. self.assertEqual({}, transport.calls[0]['payload'])
  119. self.assertEqual({
  120. 'Authorization': 'Bearer MT_demo',
  121. 'X-Request-Id': 'rq_enabled_tools',
  122. }, transport.calls[0]['headers'])
  123. def test_query_order_tool_normalizes_input_before_forwarding(self):
  124. transport = DummyTransport()
  125. store = InMemoryTokenStore(refresh_skew_seconds=60)
  126. store.save('MT_demo', '2099-01-01T00:00:00')
  127. client = ApiClient(
  128. base_url='http://tools.example.test',
  129. token_store=store,
  130. transport=transport,
  131. timeout=8,
  132. )
  133. tool = QueryOrderTool(api_client=client)
  134. tool.call(keyword=' SO20260706001 ', page=2, limit=15, request_id='rq_tool')
  135. self.assertEqual(
  136. {
  137. 'keyword': 'SO20260706001',
  138. 'page': 2,
  139. 'limit': 15,
  140. },
  141. transport.calls[0]['payload'],
  142. )
  143. self.assertEqual('http://tools.example.test/mcp/tools/queryOrder', transport.calls[0]['url'])
  144. if __name__ == '__main__':
  145. unittest.main()