test_gateway_runtime.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import unittest
  2. from datetime import datetime, timedelta
  3. from app import GatewayApp
  4. from services.token_store import InMemoryTokenStore
  5. class DummyAuthClient:
  6. def __init__(self, token_store):
  7. self.token_store = token_store
  8. self.refresh_calls = []
  9. def refresh(self, mcp_token):
  10. self.refresh_calls.append(mcp_token)
  11. self.token_store.save('MT_refresh', '2099-01-02T00:00:00')
  12. return {
  13. 'code': 'MCP_0000',
  14. 'msg': 'success',
  15. 'data': {
  16. 'mcp_token': 'MT_refresh',
  17. 'expire_time': '2099-01-02T00:00:00',
  18. },
  19. }
  20. class DummyApiClient:
  21. def __init__(self):
  22. self.calls = []
  23. self.enabled_calls = 0
  24. self.enabled_response = {
  25. 'code': 'MCP_0000',
  26. 'data': {
  27. 'tool_codes': [
  28. 'query_order',
  29. 'query_track',
  30. 'query_order_exact',
  31. 'list_order_filter_options',
  32. ],
  33. },
  34. }
  35. def list_enabled_tools(self):
  36. self.enabled_calls += 1
  37. return self.enabled_response
  38. def call_tool(self, tool_code, route_path, payload, request_id):
  39. self.calls.append(
  40. {
  41. 'tool_code': tool_code,
  42. 'route_path': route_path,
  43. 'payload': payload,
  44. 'request_id': request_id,
  45. }
  46. )
  47. return {
  48. 'code': 'MCP_0000',
  49. 'msg': 'success',
  50. 'data': {
  51. 'summary': 'ok',
  52. 'records': [],
  53. 'tips': [],
  54. },
  55. 'meta': {
  56. 'request_id': request_id,
  57. },
  58. }
  59. class GatewayRuntimeTest(unittest.TestCase):
  60. def test_list_tools_does_not_include_bind_auth_code(self):
  61. store = InMemoryTokenStore(refresh_skew_seconds=60)
  62. app = GatewayApp(
  63. auth_client=DummyAuthClient(store),
  64. api_client=DummyApiClient(),
  65. token_store=store,
  66. )
  67. names = [tool['name'] for tool in app.list_tools()]
  68. self.assertIn('query_order', names)
  69. self.assertIn('query_track', names)
  70. self.assertIn('query_order_exact', names)
  71. self.assertIn('list_order_filter_options', names)
  72. self.assertNotIn('bind_auth_code', names)
  73. self.assertFalse(hasattr(app, 'bind'))
  74. def test_list_tools_intersects_registry_codes_with_local_tools(self):
  75. api_client = DummyApiClient()
  76. api_client.enabled_response = {
  77. 'code': 'MCP_0000',
  78. 'data': {
  79. 'tool_codes': ['query_order_exact', 'unknown_tool'],
  80. },
  81. }
  82. app = GatewayApp(api_client=api_client)
  83. names = [tool['name'] for tool in app.list_tools()]
  84. self.assertEqual(['query_order_exact'], names)
  85. self.assertEqual(1, api_client.enabled_calls)
  86. def test_list_tools_fails_closed_on_registry_error(self):
  87. api_client = DummyApiClient()
  88. api_client.enabled_response = {
  89. 'code': 'MCP_9001',
  90. 'msg': 'registry unavailable',
  91. 'data': {},
  92. }
  93. app = GatewayApp(api_client=api_client)
  94. with self.assertRaisesRegex(RuntimeError, 'registry unavailable'):
  95. app.list_tools()
  96. def test_call_tool_rejects_dynamically_disabled_tool_before_forwarding(self):
  97. store = InMemoryTokenStore(refresh_skew_seconds=60)
  98. store.save('MT_valid', '2099-01-01T00:00:00')
  99. api_client = DummyApiClient()
  100. api_client.enabled_response = {
  101. 'code': 'MCP_0000',
  102. 'data': {'tool_codes': ['query_track']},
  103. }
  104. app = GatewayApp(api_client=api_client, token_store=store)
  105. with self.assertRaisesRegex(RuntimeError, 'tool disabled: query_order'):
  106. app.call_tool('query_order', {'keyword': 'ORDER-1'})
  107. self.assertEqual([], api_client.calls)
  108. def test_call_tool_refreshes_expiring_token_and_generates_request_id(self):
  109. store = InMemoryTokenStore(refresh_skew_seconds=60)
  110. expiring_time = (datetime.now() + timedelta(seconds=10)).isoformat(timespec='seconds')
  111. store.save('MT_old', expiring_time)
  112. auth_client = DummyAuthClient(store)
  113. api_client = DummyApiClient()
  114. app = GatewayApp(
  115. auth_client=auth_client,
  116. api_client=api_client,
  117. token_store=store,
  118. )
  119. response = app.call_tool('query_order', {'keyword': 'SO20260706001'})
  120. self.assertEqual(['MT_old'], auth_client.refresh_calls)
  121. self.assertEqual('MT_refresh', store.get()['token'])
  122. self.assertEqual('query_order', api_client.calls[0]['tool_code'])
  123. self.assertTrue(api_client.calls[0]['request_id'].startswith('rq_'))
  124. self.assertEqual(api_client.calls[0]['request_id'], response['meta']['request_id'])
  125. if __name__ == '__main__':
  126. unittest.main()