test_query_goods_info_tool.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import io
  2. import unittest
  3. from unittest.mock import Mock, patch
  4. from app import GatewayApp
  5. from services.output_presenter import OutputPresenter
  6. from tools.query_goods_info import QueryGoodsInfoTool
  7. def goods_columns():
  8. return [
  9. {'key': key, 'name': name}
  10. for key, name in OutputPresenter.GOODS_INFO_COLUMNS
  11. ]
  12. def goods_record():
  13. return {key: key + '-v' for key, _name in OutputPresenter.GOODS_INFO_COLUMNS}
  14. class QueryGoodsInfoToolTest(unittest.TestCase):
  15. def test_call_forwards_sku_name_and_page(self):
  16. client = Mock()
  17. client.call_tool.return_value = {'code': 'MCP_0000'}
  18. QueryGoodsInfoTool(client).call(
  19. sku=' SKU-1 ',
  20. cn_name=' 衬衫 ',
  21. page=2,
  22. limit=100,
  23. request_id='rq_goods',
  24. ignored=True,
  25. )
  26. client.call_tool.assert_called_once_with(
  27. 'query_goods_info',
  28. '/mcp/tools/queryGoodsInfo',
  29. {
  30. 'page': 2,
  31. 'limit': 100,
  32. 'sku': 'SKU-1',
  33. 'cn_name': '衬衫',
  34. },
  35. 'rq_goods',
  36. )
  37. def test_call_accepts_either_sku_or_name(self):
  38. client = Mock()
  39. client.call_tool.return_value = {'code': 'MCP_0000'}
  40. tool = QueryGoodsInfoTool(client)
  41. tool.call(sku='SKU-1', cn_name=' ')
  42. self.assertEqual({'page': 1, 'limit': 20, 'sku': 'SKU-1'}, client.call_tool.call_args[0][2])
  43. tool.call(cn_name='衬衫', sku=None)
  44. self.assertEqual(
  45. {'page': 1, 'limit': 20, 'cn_name': '衬衫'},
  46. client.call_tool.call_args[0][2],
  47. )
  48. def test_call_requires_api_client_and_a_lookup_field(self):
  49. with self.assertRaises(RuntimeError):
  50. QueryGoodsInfoTool().call(sku='SKU-1')
  51. tool = QueryGoodsInfoTool(api_client=Mock())
  52. with self.assertRaises(ValueError):
  53. tool.call()
  54. with self.assertRaises(ValueError):
  55. tool.call(sku='', cn_name='')
  56. def test_call_rejects_invalid_fields(self):
  57. tool = QueryGoodsInfoTool(api_client=Mock())
  58. with self.assertRaises(ValueError):
  59. tool.call(sku=1)
  60. with self.assertRaises(ValueError):
  61. tool.call(cn_name=1)
  62. with self.assertRaises(ValueError):
  63. tool.call(sku='S' * 2001)
  64. with self.assertRaises(ValueError):
  65. tool.call(cn_name='名' * 101)
  66. for page in (True, '1', 0, 101):
  67. with self.subTest(page=page):
  68. with self.assertRaises(ValueError):
  69. tool.call(sku='SKU-1', page=page)
  70. with self.assertRaises(ValueError):
  71. tool.call(sku='SKU-1', limit=0)
  72. def test_cli_forwards_sku_or_name(self):
  73. app = GatewayApp()
  74. stdout = io.StringIO()
  75. with self.assertRaises(ValueError):
  76. app.run_cli(['call', '--tool', 'query_goods_info'], stdout=stdout)
  77. with patch.object(app, 'call_tool', return_value={'code': 'MCP_0000'}) as call_tool:
  78. self.assertEqual(0, app.run_cli([
  79. 'call', '--tool', 'query_goods_info', '--sku', 'SKU-1',
  80. ], stdout=stdout))
  81. self.assertEqual('SKU-1', call_tool.call_args[0][1]['sku'])
  82. self.assertNotIn('cn_name', call_tool.call_args[0][1])
  83. self.assertEqual(0, app.run_cli([
  84. 'call', '--tool', 'query_goods_info', '--cn-name', '衬衫',
  85. ], stdout=stdout))
  86. self.assertEqual('衬衫', call_tool.call_args[0][1]['cn_name'])
  87. self.assertNotIn('sku', call_tool.call_args[0][1])
  88. self.assertEqual(0, app.run_cli([
  89. 'call', '--tool', 'query_goods_info',
  90. '--sku', 'SKU-1', '--cn-name', '衬衫',
  91. ], stdout=stdout))
  92. payload = call_tool.call_args[0][1]
  93. self.assertEqual('SKU-1', payload['sku'])
  94. self.assertEqual('衬衫', payload['cn_name'])
  95. def test_presenter_renders_goods_table(self):
  96. record = goods_record()
  97. result = OutputPresenter().present('query_goods_info', {
  98. 'code': 'MCP_0000',
  99. 'data': {'columns': goods_columns(), 'records': [record]},
  100. 'meta': {'page': 1, 'limit': 20, 'has_more': False, 'request_id': 'rq_g'},
  101. })
  102. self.assertFalse(result['is_error'])
  103. self.assertEqual(24, len(result['structured_content']['headers']))
  104. self.assertEqual('sku-v', result['structured_content']['rows'][0][0])
  105. self.assertIn('SKU', result['text'])
  106. empty = OutputPresenter().present('query_goods_info', {
  107. 'code': 'MCP_0000',
  108. 'data': {'columns': goods_columns(), 'records': []},
  109. 'meta': {'page': 1, 'limit': 20, 'has_more': False},
  110. })
  111. self.assertFalse(empty['is_error'])
  112. self.assertEqual([], empty['structured_content']['rows'])
  113. def test_presenter_rejects_invalid_goods_payload(self):
  114. presenter = OutputPresenter()
  115. columns = goods_columns()
  116. record = goods_record()
  117. meta = {'page': 1, 'limit': 20, 'has_more': False}
  118. bad_column = list(columns)
  119. bad_column[0] = {'key': 'sku', 'name': '错'}
  120. bad_record = dict(record)
  121. bad_record['sku'] = 1
  122. extra_record = dict(record)
  123. extra_record['secret'] = 'x'
  124. cases = [
  125. {'columns': columns},
  126. {'columns': 'bad', 'records': []},
  127. {'columns': columns, 'records': None},
  128. {'columns': columns[:1], 'records': []},
  129. {'columns': columns, 'records': []},
  130. {'columns': bad_column, 'records': []},
  131. {'columns': columns, 'records': ['x']},
  132. {'columns': columns, 'records': [extra_record]},
  133. {'columns': columns, 'records': [bad_record]},
  134. ]
  135. metas = [meta, meta, meta, meta, {'request_id': 'rq'}, meta, meta, meta, meta]
  136. for data, raw_meta in zip(cases, metas):
  137. with self.subTest(data=data):
  138. result = presenter.present('query_goods_info', {
  139. 'code': 'MCP_0000',
  140. 'data': data,
  141. 'meta': raw_meta,
  142. })
  143. self.assertTrue(result['is_error'])
  144. self.assertNotIn('secret', result['text'])
  145. self.assertNotIn('sku-v', result['text'])