test_cli_and_file_store.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import io
  2. import json
  3. import os
  4. import tempfile
  5. import unittest
  6. from app import GatewayApp
  7. from services.token_store import FileTokenStore
  8. class DummyAuthClient:
  9. def __init__(self, token_store):
  10. self.token_store = token_store
  11. self.exchange_calls = []
  12. def exchange(self, auth_code):
  13. self.exchange_calls.append(auth_code)
  14. self.token_store.save('MT_bound', '2099-01-01T00:00:00')
  15. return {
  16. 'code': 'MCP_0000',
  17. 'msg': 'success',
  18. 'data': {
  19. 'mcp_token': 'MT_bound',
  20. 'expire_time': '2099-01-01T00:00:00',
  21. },
  22. }
  23. class DummyApiClient:
  24. def __init__(self):
  25. self.calls = []
  26. def call_tool(self, tool_code, route_path, payload, request_id):
  27. self.calls.append(
  28. {
  29. 'tool_code': tool_code,
  30. 'route_path': route_path,
  31. 'payload': payload,
  32. 'request_id': request_id,
  33. }
  34. )
  35. return {
  36. 'code': 'MCP_0000',
  37. 'msg': 'success',
  38. 'data': {
  39. 'summary': 'ok',
  40. 'records': [],
  41. 'tips': [],
  42. },
  43. 'meta': {
  44. 'request_id': request_id,
  45. },
  46. }
  47. class CliAndFileStoreTest(unittest.TestCase):
  48. def test_file_token_store_persists_session_across_instances(self):
  49. with tempfile.TemporaryDirectory() as tmp_dir:
  50. path = os.path.join(tmp_dir, 'token.json')
  51. first = FileTokenStore(path, refresh_skew_seconds=60)
  52. first.save('MT_file', '2099-01-01T00:00:00')
  53. second = FileTokenStore(path, refresh_skew_seconds=60)
  54. self.assertEqual('MT_file', second.get()['token'])
  55. second.clear()
  56. self.assertIsNone(second.get())
  57. self.assertFalse(os.path.exists(path))
  58. def test_run_cli_bind_and_call_emit_json(self):
  59. with tempfile.TemporaryDirectory() as tmp_dir:
  60. path = os.path.join(tmp_dir, 'token.json')
  61. token_store = FileTokenStore(path, refresh_skew_seconds=60)
  62. auth_client = DummyAuthClient(token_store)
  63. api_client = DummyApiClient()
  64. app = GatewayApp(
  65. auth_client=auth_client,
  66. api_client=api_client,
  67. token_store=token_store,
  68. )
  69. bind_stdout = io.StringIO()
  70. bind_code = app.run_cli(['bind', '--auth-code', 'AUTH123'], stdout=bind_stdout)
  71. bind_payload = json.loads(bind_stdout.getvalue())
  72. call_stdout = io.StringIO()
  73. call_code = app.run_cli([
  74. 'call',
  75. '--tool', 'query_order',
  76. '--keyword', 'SO20260706001',
  77. '--page', '2',
  78. '--limit', '15',
  79. ], stdout=call_stdout)
  80. call_payload = json.loads(call_stdout.getvalue())
  81. self.assertEqual(0, bind_code)
  82. self.assertEqual('MCP_0000', bind_payload['code'])
  83. self.assertEqual(0, call_code)
  84. self.assertEqual('MCP_0000', call_payload['code'])
  85. self.assertEqual(['AUTH123'], auth_client.exchange_calls)
  86. self.assertEqual('query_order', api_client.calls[0]['tool_code'])
  87. self.assertEqual({'keyword': 'SO20260706001', 'page': 2, 'limit': 15}, api_client.calls[0]['payload'])
  88. def test_run_cli_query_track_accepts_tracking_number(self):
  89. with tempfile.TemporaryDirectory() as tmp_dir:
  90. path = os.path.join(tmp_dir, 'token.json')
  91. token_store = FileTokenStore(path, refresh_skew_seconds=60)
  92. token_store.save('MT_bound', '2099-01-01T00:00:00')
  93. api_client = DummyApiClient()
  94. app = GatewayApp(
  95. auth_client=DummyAuthClient(token_store),
  96. api_client=api_client,
  97. token_store=token_store,
  98. )
  99. stdout = io.StringIO()
  100. exit_code = app.run_cli([
  101. 'call',
  102. '--tool', 'query_track',
  103. '--tracking-number', '1471904540000000301',
  104. '--page', '1',
  105. '--limit', '20',
  106. ], stdout=stdout)
  107. self.assertEqual(0, exit_code)
  108. self.assertEqual('query_track', api_client.calls[0]['tool_code'])
  109. self.assertEqual('/mcp/tools/queryTrack', api_client.calls[0]['route_path'])
  110. self.assertEqual('1471904540000000301', api_client.calls[0]['payload']['tracking_number'])
  111. self.assertNotIn('order_id', api_client.calls[0]['payload'])
  112. self.assertNotIn('order_number', api_client.calls[0]['payload'])
  113. def test_run_cli_serve_stdio_handles_initialize_request(self):
  114. with tempfile.TemporaryDirectory() as tmp_dir:
  115. path = os.path.join(tmp_dir, 'token.json')
  116. token_store = FileTokenStore(path, refresh_skew_seconds=60)
  117. token_store.save('MT_bound', '2099-01-01T00:00:00')
  118. app = GatewayApp(
  119. auth_client=DummyAuthClient(token_store),
  120. api_client=DummyApiClient(),
  121. token_store=token_store,
  122. )
  123. stdin = io.StringIO(
  124. json.dumps(
  125. {
  126. 'jsonrpc': '2.0',
  127. 'id': 1,
  128. 'method': 'initialize',
  129. 'params': {
  130. 'protocolVersion': '2025-06-18',
  131. 'capabilities': {},
  132. 'clientInfo': {'name': 'workbuddy', 'version': '1.0.0'},
  133. },
  134. }
  135. )
  136. + '\n'
  137. )
  138. stdout = io.StringIO()
  139. exit_code = app.run_cli(['serve-stdio'], stdin=stdin, stdout=stdout)
  140. response = json.loads(stdout.getvalue().strip())
  141. self.assertEqual(0, exit_code)
  142. self.assertEqual(1, response['id'])
  143. self.assertEqual('2025-06-18', response['result']['protocolVersion'])
  144. def test_serve_public_command_is_registered(self):
  145. app = GatewayApp(auth_client=None, api_client=None, token_store=None)
  146. with self.assertRaises(SystemExit) as error:
  147. app.run_cli(['serve-public', '--help'])
  148. self.assertEqual(0, error.exception.code)
  149. if __name__ == '__main__':
  150. unittest.main()