test_public_gateway_unit.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import unittest
  2. from unittest.mock import MagicMock, patch
  3. from constants import BIND_HINT
  4. from public_gateway import PublicGatewayApp
  5. class TestPublicGatewayApp(unittest.TestCase):
  6. def setUp(self):
  7. self.mock_session_store = MagicMock()
  8. self.mock_api_client = MagicMock()
  9. self.mock_auth_client = MagicMock()
  10. self.app = PublicGatewayApp(
  11. session_store=self.mock_session_store,
  12. api_client=self.mock_api_client,
  13. auth_client=self.mock_auth_client
  14. )
  15. def test_list_tools_returns_metadata(self):
  16. tools = self.app.list_tools()
  17. self.assertIsInstance(tools, list)
  18. self.assertGreater(len(tools), 0)
  19. # Check that each tool has required fields
  20. for tool in tools:
  21. self.assertIn('name', tool)
  22. self.assertIn('description', tool)
  23. self.assertIn('input_schema', tool)
  24. def test_build_request_id_generates_id_when_empty(self):
  25. request_id = self.app.build_request_id('')
  26. self.assertTrue(request_id.startswith('rq_'))
  27. self.assertEqual(len(request_id), 3 + 16) # 'rq_' + 16 hex chars
  28. def test_build_request_id_uses_provided_id(self):
  29. provided_id = 'custom_request_123'
  30. request_id = self.app.build_request_id(provided_id)
  31. self.assertEqual(request_id, provided_id)
  32. def test_bind_auth_code_success(self):
  33. gateway_session_id = 'GWS_test123'
  34. auth_code = 'AC_xyz789'
  35. self.mock_auth_client.exchange.return_value = {
  36. 'code': 'MCP_0000',
  37. 'msg': 'success',
  38. 'data': {'mcp_token': 'MT_token'}
  39. }
  40. self.mock_session_store.get.return_value = {
  41. 'admin_id': 123,
  42. 'company_id': 100
  43. }
  44. result = self.app.bind_auth_code(gateway_session_id, auth_code)
  45. self.mock_auth_client.exchange.assert_called_once_with(gateway_session_id, auth_code)
  46. self.assertEqual(result['code'], 'MCP_0000')
  47. def test_bind_auth_code_log_does_not_include_auth_code_value(self):
  48. gateway_session_id = 'GWS_test123'
  49. auth_code = 'AC_secret_value_123'
  50. self.mock_auth_client.exchange.return_value = {'code': 'MCP_9999', 'msg': 'failed'}
  51. with self.assertLogs('public_gateway', level='INFO') as logs:
  52. self.app.bind_auth_code(gateway_session_id, auth_code)
  53. joined_logs = '\n'.join(logs.output)
  54. self.assertIn('bind_auth_code', joined_logs)
  55. self.assertNotIn(auth_code, joined_logs)
  56. self.assertNotIn('AC_secre', joined_logs)
  57. def test_bind_auth_code_log_does_not_include_auth_code_value(self):
  58. gateway_session_id = 'GWS_test123'
  59. auth_code = 'AC_secret_value_123'
  60. self.mock_auth_client.exchange.return_value = {'code': 'MCP_9999', 'msg': 'failed'}
  61. with self.assertLogs('public_gateway', level='INFO') as logs:
  62. self.app.bind_auth_code(gateway_session_id, auth_code)
  63. joined_logs = '\n'.join(logs.output)
  64. self.assertIn('bind_auth_code', joined_logs)
  65. self.assertNotIn(auth_code, joined_logs)
  66. self.assertNotIn('AC_secre', joined_logs)
  67. def test_bind_auth_code_raises_when_no_auth_client(self):
  68. app_no_auth = PublicGatewayApp(
  69. session_store=self.mock_session_store,
  70. api_client=self.mock_api_client,
  71. auth_client=None
  72. )
  73. with self.assertRaises(RuntimeError) as context:
  74. app_no_auth.bind_auth_code('GWS_test', 'AC_code')
  75. self.assertIn('auth client is required', str(context.exception))
  76. def test_call_tool_bind_auth_code(self):
  77. gateway_session_id = 'GWS_bind'
  78. arguments = {'auth_code': 'AC_test'}
  79. self.mock_auth_client.exchange.return_value = {
  80. 'code': 'MCP_0000',
  81. 'data': {}
  82. }
  83. self.mock_session_store.get.return_value = {'admin_id': 1}
  84. result = self.app.call_tool(gateway_session_id, 'bind_auth_code', arguments)
  85. self.mock_auth_client.exchange.assert_called_once_with(gateway_session_id, 'AC_test')
  86. def test_call_tool_raises_on_unregistered_tool(self):
  87. with self.assertRaises(KeyError) as context:
  88. self.app.call_tool('GWS_test', 'nonexistent_tool', {})
  89. self.assertIn('tool not registered', str(context.exception))
  90. def test_call_tool_raises_when_session_not_found(self):
  91. self.mock_session_store.get.return_value = None
  92. with self.assertRaises(RuntimeError) as context:
  93. self.app.call_tool('GWS_nosession', 'query_order', {})
  94. self.assertEqual(str(context.exception), BIND_HINT)
  95. def test_call_tool_raises_when_no_token_in_session(self):
  96. self.mock_session_store.get.return_value = {
  97. 'admin_id': 123,
  98. 'company_id': 100
  99. # Missing mcp_token
  100. }
  101. with self.assertRaises(RuntimeError) as context:
  102. self.app.call_tool('GWS_notoken', 'query_order', {})
  103. self.assertEqual(str(context.exception), BIND_HINT)
  104. def test_call_tool_success(self):
  105. gateway_session_id = 'GWS_valid'
  106. tool_name = 'query_order'
  107. arguments = {'order_no': 'ABC123'}
  108. self.mock_session_store.get.return_value = {
  109. 'mcp_token': 'MT_token123',
  110. 'admin_id': 456,
  111. 'company_id': 200
  112. }
  113. self.mock_api_client.call_tool.return_value = {
  114. 'code': '0',
  115. 'msg': 'success',
  116. 'data': {'order': 'details'}
  117. }
  118. result = self.app.call_tool(gateway_session_id, tool_name, arguments, 'rq_test')
  119. # Verify api_client.call_tool was called correctly
  120. self.mock_api_client.call_tool.assert_called_once()
  121. call_args = self.mock_api_client.call_tool.call_args[1]
  122. self.assertEqual(call_args['token'], 'MT_token123')
  123. self.assertEqual(call_args['tool_code'], 'query_order')
  124. self.assertEqual(call_args['payload'], arguments)
  125. self.assertTrue(call_args['request_id'].startswith('rq_'))
  126. # Verify result
  127. self.assertEqual(result['code'], '0')
  128. def test_call_tool_generates_request_id_when_not_provided(self):
  129. self.mock_session_store.get.return_value = {
  130. 'mcp_token': 'MT_token',
  131. 'admin_id': 1,
  132. 'company_id': 1
  133. }
  134. self.mock_api_client.call_tool.return_value = {'code': '0'}
  135. self.app.call_tool('GWS_test', 'query_order', {}, '')
  136. call_args = self.mock_api_client.call_tool.call_args[1]
  137. self.assertTrue(call_args['request_id'].startswith('rq_'))
  138. def test_call_tool_uses_provided_request_id(self):
  139. self.mock_session_store.get.return_value = {
  140. 'mcp_token': 'MT_token',
  141. 'admin_id': 1,
  142. 'company_id': 1
  143. }
  144. self.mock_api_client.call_tool.return_value = {'code': '0'}
  145. custom_request_id = 'rq_custom_123'
  146. self.app.call_tool('GWS_test', 'query_order', {}, custom_request_id)
  147. call_args = self.mock_api_client.call_tool.call_args[1]
  148. self.assertEqual(call_args['request_id'], custom_request_id)
  149. def test_call_tool_logs_error_on_exception(self):
  150. """Test that tool call exceptions are logged and re-raised"""
  151. gateway_session_id = 'GWS_error_test'
  152. tool_name = 'query_order'
  153. self.mock_session_store.get.return_value = {
  154. 'mcp_token': 'MT_token',
  155. 'admin_id': 999,
  156. 'company_id': 888
  157. }
  158. # Make API client raise an exception
  159. self.mock_api_client.call_tool.side_effect = RuntimeError("API connection failed")
  160. with self.assertRaises(RuntimeError) as context:
  161. self.app.call_tool(gateway_session_id, tool_name, {}, 'rq_err')
  162. self.assertIn("API connection failed", str(context.exception))
  163. if __name__ == '__main__':
  164. unittest.main()