test_trial_goods_tax_tool.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import io
  2. import math
  3. import unittest
  4. from unittest.mock import Mock, patch
  5. from app import GatewayApp
  6. from public_gateway import PublicGatewayApp
  7. from services.output_presenter import OutputPresenter
  8. from tools.trial_goods_tax import TrialGoodsTaxTool
  9. class TrialGoodsTaxToolTest(unittest.TestCase):
  10. def test_call_forwards_required_fields(self):
  11. client = Mock()
  12. client.call_tool.return_value = {'code': 'MCP_0000'}
  13. TrialGoodsTaxTool(client).call(
  14. sku=' SKU-1 ',
  15. departure_country=' CN ',
  16. destination_country='us',
  17. price=10,
  18. qty=2,
  19. gross_weight=1.5,
  20. box=1,
  21. )
  22. client.call_tool.assert_called_once_with(
  23. 'trial_goods_tax',
  24. '/mcp/tools/trialGoodsTax',
  25. {
  26. 'sku': 'SKU-1',
  27. 'departure_country': 'CN',
  28. 'destination_country': 'US',
  29. 'price': 10,
  30. 'qty': 2,
  31. 'gross_weight': 1.5,
  32. 'box': 1,
  33. },
  34. 'rq_trial_goods_tax',
  35. )
  36. def test_call_rejects_country_name(self):
  37. tool = TrialGoodsTaxTool(api_client=Mock())
  38. with self.assertRaises(ValueError):
  39. tool.call(
  40. sku='SKU-1', departure_country='中国', destination_country='US',
  41. price=10, qty=1, gross_weight=1, box=1,
  42. )
  43. def test_call_rejects_zero_price(self):
  44. tool = TrialGoodsTaxTool(api_client=Mock())
  45. with self.assertRaises(ValueError):
  46. tool.call(
  47. sku='SKU-1', departure_country='CN', destination_country='US',
  48. price=0, qty=1, gross_weight=1, box=1,
  49. )
  50. def test_call_requires_api_client(self):
  51. with self.assertRaises(RuntimeError):
  52. TrialGoodsTaxTool().call(
  53. sku='SKU-1', departure_country='CN', destination_country='US',
  54. price=1, qty=1, gross_weight=1, box=1,
  55. )
  56. def test_call_rejects_invalid_fields(self):
  57. tool = TrialGoodsTaxTool(api_client=Mock())
  58. cases = [
  59. {'sku': 1},
  60. {'sku': ''},
  61. {'sku': 'S' * 101},
  62. {'departure_country': None},
  63. {'departure_country': 'A'},
  64. {'departure_country': 'ABCD'},
  65. {'departure_country': 'C1'},
  66. {'price': True},
  67. {'qty': '1'},
  68. {'gross_weight': math.inf},
  69. {'box': 1000000001},
  70. ]
  71. base = {
  72. 'sku': 'SKU-1', 'departure_country': 'CN', 'destination_country': 'USA',
  73. 'price': 1, 'qty': 1, 'gross_weight': 1, 'box': 1,
  74. }
  75. for override in cases:
  76. with self.subTest(override=override):
  77. payload = dict(base)
  78. payload.update(override)
  79. with self.assertRaises(ValueError):
  80. tool.call(**payload)
  81. def test_cli_forwards_trial_fields(self):
  82. app = GatewayApp()
  83. with patch.object(app, 'call_tool', return_value={'code': 'MCP_0000'}) as call_tool:
  84. code = app.run_cli([
  85. 'call', '--tool', 'trial_goods_tax',
  86. '--sku', 'SKU-1',
  87. '--departure-country', 'CN',
  88. '--destination-country', 'US',
  89. '--price', '10',
  90. '--qty', '2',
  91. '--gross-weight', '1.5',
  92. '--box', '1',
  93. ], stdout=io.StringIO())
  94. self.assertEqual(0, code)
  95. self.assertEqual(
  96. {
  97. 'page': 1,
  98. 'limit': 20,
  99. 'sku': 'SKU-1',
  100. 'departure_country': 'CN',
  101. 'destination_country': 'US',
  102. 'price': 10.0,
  103. 'qty': 2.0,
  104. 'gross_weight': 1.5,
  105. 'box': 1.0,
  106. },
  107. call_tool.call_args[0][1],
  108. )
  109. def test_registries_match(self):
  110. local = GatewayApp().registered_tool_names()
  111. public = PublicGatewayApp(None, None).registered_tool_names()
  112. self.assertEqual(local, public)
  113. self.assertIn('trial_goods_tax', local)
  114. self.assertEqual(51, len(local))
  115. self.assertIn('trial_goods_tax', OutputPresenter.SAFE_TOOLS)
  116. def test_presenter_renders_amount(self):
  117. result = OutputPresenter().present('trial_goods_tax', {
  118. 'code': 'MCP_0000',
  119. 'data': {
  120. 'estimated_tax': 12.5,
  121. 'sku': 'SKU-1',
  122. 'departure_country': 'CN',
  123. 'destination_country': 'US',
  124. },
  125. 'meta': {'request_id': 'rq1'},
  126. })
  127. self.assertFalse(result['is_error'])
  128. self.assertEqual(12.5, result['structured_content']['estimated_tax'])
  129. self.assertIn('预计关税金额', result['text'])
  130. def test_presenter_keeps_safe_failure_message(self):
  131. result = OutputPresenter().present('trial_goods_tax', {
  132. 'code': 'MCP_1501',
  133. 'msg': '未找到已审核商品',
  134. 'data': [],
  135. 'meta': {'request_id': 'rq1'},
  136. })
  137. self.assertTrue(result['is_error'])
  138. self.assertEqual('未找到已审核商品', result['structured_content']['message'])
  139. def test_presenter_rejects_invalid_tax_payload(self):
  140. presenter = OutputPresenter()
  141. valid = {
  142. 'estimated_tax': 12,
  143. 'sku': 'SKU-1',
  144. 'departure_country': 'CN',
  145. 'destination_country': 'USA',
  146. }
  147. ok = presenter.present('trial_goods_tax', {
  148. 'code': 'MCP_0000',
  149. 'data': valid,
  150. })
  151. self.assertFalse(ok['is_error'])
  152. self.assertEqual(12, ok['structured_content']['estimated_tax'])
  153. cases = [
  154. {'estimated_tax': 1},
  155. dict(valid, estimated_tax=True),
  156. dict(valid, estimated_tax='12'),
  157. dict(valid, estimated_tax=math.nan),
  158. dict(valid, sku=1),
  159. dict(valid, sku=' '),
  160. dict(valid, departure_country=1),
  161. dict(valid, departure_country='C1'),
  162. dict(valid, departure_country='cn'),
  163. dict(valid, departure_country='A'),
  164. dict(valid, departure_country='ABCD'),
  165. dict(valid, destination_country='us'),
  166. ]
  167. for data in cases:
  168. with self.subTest(data=data):
  169. result = presenter.present('trial_goods_tax', {
  170. 'code': 'MCP_0000',
  171. 'data': data,
  172. })
  173. self.assertTrue(result['is_error'])
  174. self.assertNotIn('SKU-1', result['text'])