test_fuel_rate_tools.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import unittest
  2. from tools.query_fuel_rate_list import QueryFuelRateListTool
  3. from tools.save_fuel_rate import SaveFuelRateTool
  4. from tools.enable_fuel_rate import EnableFuelRateTool
  5. from app import GatewayApp
  6. from public_gateway import PublicGatewayApp
  7. from services.output_presenter import OutputPresenter
  8. class FuelRateToolTest(unittest.TestCase):
  9. def test_tools_are_registered_on_base_backend(self):
  10. local = GatewayApp().registered_tool_names()
  11. public = PublicGatewayApp(None, None).registered_tool_names()
  12. self.assertEqual(local, public)
  13. self.assertIn('query_fuel_rate_list', local)
  14. self.assertIn('save_fuel_rate', local)
  15. self.assertIn('enable_fuel_rate', local)
  16. self.assertEqual('base', QueryFuelRateListTool.backend)
  17. self.assertEqual('base', SaveFuelRateTool.backend)
  18. self.assertEqual('base', EnableFuelRateTool.backend)
  19. def test_query_posts_exact_filters(self):
  20. client = FakeClient()
  21. QueryFuelRateListTool(api_client=client).call(
  22. fuel_no=' RY12 ',
  23. product='美国专线',
  24. price_type='销售燃油',
  25. status=3,
  26. start_date='2026-10-21',
  27. page=2,
  28. limit=20,
  29. request_id='rq_fuel_list',
  30. )
  31. self.assertEqual('query_fuel_rate_list', client.calls[0][0])
  32. self.assertEqual('/mcp/tools/queryFuelRateList', client.calls[0][1])
  33. self.assertEqual({
  34. 'page': 2,
  35. 'limit': 20,
  36. 'fuel_no': 'RY12',
  37. 'product': '美国专线',
  38. 'price_type': '销售燃油',
  39. 'status': '3',
  40. 'start_date': '2026-10-21',
  41. }, client.calls[0][2])
  42. def test_save_posts_condition_item(self):
  43. client = FakeClient()
  44. SaveFuelRateTool(api_client=client).call(
  45. price_type=1,
  46. start_date='2026-10-21',
  47. items=[{
  48. 'fuel_ratio': 0.15,
  49. 'conditions': [{
  50. 'condition': '物流产品',
  51. 'group': '1',
  52. 'condition_value': '美国专线',
  53. }],
  54. }],
  55. request_id='rq_fuel_save',
  56. )
  57. self.assertEqual('/mcp/tools/saveFuelRate', client.calls[0][1])
  58. self.assertEqual({
  59. 'price_type': '1',
  60. 'start_date': '2026-10-21',
  61. 'items': [{
  62. 'fuel_ratio': '0.1500',
  63. 'conditions': [{
  64. 'condition': '物流产品',
  65. 'group': '1',
  66. 'condition_value': '美国专线',
  67. }],
  68. }],
  69. }, client.calls[0][2])
  70. def test_save_edit_posts_fuel_no(self):
  71. client = FakeClient()
  72. SaveFuelRateTool(api_client=client).call(
  73. fuel_no=' RY12 ',
  74. price_type='销售燃油',
  75. start_date='2026-10-22',
  76. items=[{
  77. 'fuel_ratio': '0.2000',
  78. 'conditions': [{
  79. 'condition': '物流产品',
  80. 'group': '1',
  81. 'condition_value': '美国专线',
  82. }],
  83. }],
  84. )
  85. self.assertEqual('RY12', client.calls[0][2]['fuel_no'])
  86. self.assertEqual('销售燃油', client.calls[0][2]['price_type'])
  87. self.assertEqual(1, len(client.calls[0][2]['items']))
  88. def test_save_rejects_invalid_fuel_ratio(self):
  89. tool = SaveFuelRateTool(api_client=FakeClient())
  90. item = {
  91. 'conditions': [{
  92. 'condition': '物流产品',
  93. 'group': '1',
  94. 'condition_value': '美国专线',
  95. }],
  96. }
  97. for ratio in ('abc', '1.2', '0.12345', True):
  98. with self.assertRaises(ValueError):
  99. tool.call(
  100. price_type=1,
  101. start_date='2026-10-22',
  102. items=[dict(item, fuel_ratio=ratio)],
  103. )
  104. def test_save_edit_rejects_multiple_items(self):
  105. tool = SaveFuelRateTool(api_client=FakeClient())
  106. item = {
  107. 'fuel_ratio': '0.2000',
  108. 'conditions': [{
  109. 'condition': '物流产品',
  110. 'group': '1',
  111. 'condition_value': '美国专线',
  112. }],
  113. }
  114. with self.assertRaises(ValueError):
  115. tool.call(
  116. fuel_no='RY12',
  117. price_type=1,
  118. start_date='2026-10-22',
  119. items=[item, item],
  120. )
  121. def test_presenter_keeps_missing_condition_message(self):
  122. result = OutputPresenter().present('save_fuel_rate', {
  123. 'code': 'MCP_1401',
  124. 'msg': '第1条物流产品条件值[美国专线]不存在或未启用',
  125. 'data': {},
  126. 'meta': {'request_id': 'rq_fuel_save'},
  127. })
  128. self.assertIn('不存在或未启用', str(result))
  129. def test_presenter_shows_fuel_list(self):
  130. columns = [
  131. {'key': key, 'name': name}
  132. for key, name in (
  133. ('fuel_no', '燃油代码'),
  134. ('price_type_name', '燃油类型'),
  135. ('status_name', '状态'),
  136. ('source', '来源'),
  137. )
  138. ]
  139. result = OutputPresenter().present('query_fuel_rate_list', {
  140. 'code': 'MCP_0000',
  141. 'msg': 'success',
  142. 'data': {
  143. 'summary': '共查询到 1 条燃油系数',
  144. 'columns': columns,
  145. 'records': [{
  146. 'fuel_no': 'RY12',
  147. 'price_type_name': '销售燃油',
  148. 'status_name': '启用',
  149. 'source': 'MCP',
  150. }],
  151. 'tips': [],
  152. },
  153. 'meta': {'request_id': 'rq_fuel_list', 'page': 1, 'limit': 20, 'total': 1},
  154. })
  155. rendered = str(result)
  156. self.assertIn('RY12', rendered)
  157. self.assertIn('MCP', rendered)
  158. def test_enable_posts_fuel_numbers(self):
  159. client = FakeClient()
  160. EnableFuelRateTool(api_client=client).call(fuel_no=[' RY12 ', 'RY13', 'RY12'])
  161. self.assertEqual('enable_fuel_rate', client.calls[0][0])
  162. self.assertEqual('/mcp/tools/enableFuelRate', client.calls[0][1])
  163. self.assertEqual(['RY12', 'RY13'], client.calls[0][2]['fuel_no'])
  164. def test_enable_rejects_empty_list(self):
  165. with self.assertRaises(ValueError):
  166. EnableFuelRateTool(api_client=FakeClient()).call(fuel_no=[])
  167. def test_presenter_keeps_already_enabled_message(self):
  168. result = OutputPresenter().present('enable_fuel_rate', {
  169. 'code': 'MCP_1401',
  170. 'msg': '燃油系数RY12已启用',
  171. 'data': {},
  172. 'meta': {'request_id': 'rq_fuel_enable'},
  173. })
  174. self.assertIn('已启用', str(result))
  175. def test_presenter_shows_enabled_fuel(self):
  176. result = OutputPresenter().present('enable_fuel_rate', {
  177. 'code': 'MCP_0000',
  178. 'msg': 'success',
  179. 'data': {
  180. 'summary': '已启用 1 条燃油系数',
  181. 'columns': [
  182. {'key': 'fuel_no', 'name': '燃油代码'},
  183. {'key': 'status_text', 'name': '状态'},
  184. ],
  185. 'records': [{'fuel_no': 'RY12', 'status_text': '启用'}],
  186. 'tips': [],
  187. },
  188. 'meta': {'request_id': 'rq_fuel_enable'},
  189. })
  190. rendered = str(result)
  191. self.assertIn('RY12', rendered)
  192. self.assertIn('启用', rendered)
  193. def test_tools_expose_metadata_and_reject_bad_input(self):
  194. self.assertIn('fuel_no', QueryFuelRateListTool().metadata()['input_schema']['properties'])
  195. self.assertIn('items', SaveFuelRateTool().metadata()['input_schema']['properties'])
  196. self.assertIn('fuel_no', EnableFuelRateTool().metadata()['input_schema']['properties'])
  197. with self.assertRaises(RuntimeError):
  198. QueryFuelRateListTool().call()
  199. with self.assertRaises(RuntimeError):
  200. SaveFuelRateTool().call(price_type='1', start_date='2026-10-22', items=[])
  201. with self.assertRaises(RuntimeError):
  202. EnableFuelRateTool().call(fuel_no='RY12')
  203. query = QueryFuelRateListTool(api_client=FakeClient())
  204. for kwargs in (
  205. {'page': True},
  206. {'page': 0},
  207. {'limit': 101},
  208. {'product': 1},
  209. {'channel': 'C' * 101},
  210. {'price_type': True},
  211. {'status': '停用'},
  212. ):
  213. with self.assertRaises(ValueError):
  214. query.call(**kwargs)
  215. query.call(product=' ', price_type=2, status=1)
  216. save = SaveFuelRateTool(api_client=FakeClient())
  217. good = {
  218. 'fuel_ratio': '0',
  219. 'conditions': [{
  220. 'condition': '物流产品',
  221. 'group': '1',
  222. 'condition_value': '美国专线',
  223. 'condition_relation': 'and',
  224. }],
  225. }
  226. for kwargs in (
  227. {'price_type': True, 'start_date': '2026-10-22', 'items': [good]},
  228. {'price_type': '销售', 'start_date': '2026-10-22', 'items': [good]},
  229. {'price_type': '1', 'start_date': '', 'items': [good]},
  230. {'price_type': '1', 'start_date': '2026-10-22', 'end_date': 'x' * 31, 'items': [good]},
  231. {'price_type': '1', 'start_date': '2026-10-22', 'fuel_no': 1, 'items': [good]},
  232. {'price_type': '1', 'start_date': '2026-10-22', 'fuel_no': ' ', 'items': [good]},
  233. {'price_type': '1', 'start_date': '2026-10-22', 'fuel_no': 'R' * 21, 'items': [good]},
  234. {'price_type': '1', 'start_date': '2026-10-22', 'items': {}},
  235. {'price_type': '1', 'start_date': '2026-10-22', 'items': []},
  236. {'price_type': '1', 'start_date': '2026-10-22', 'items': [good] * 21},
  237. {'price_type': '1', 'start_date': '2026-10-22', 'items': ['0.1']},
  238. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'extra': 1}]},
  239. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': ' ', 'conditions': good['conditions']}]},
  240. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '1.2.3', 'conditions': good['conditions']}]},
  241. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '2', 'conditions': good['conditions']}]},
  242. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.', 'conditions': good['conditions']}]},
  243. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0./', 'conditions': good['conditions']}]},
  244. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.a', 'conditions': good['conditions']}]},
  245. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '1.001', 'conditions': good['conditions']}]},
  246. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': {}}]},
  247. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': []}]},
  248. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': [good['conditions'][0]] * 31}]},
  249. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': ['物流产品']}]},
  250. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': [{'condition': '物流产品', 'group': '1', 'condition_value': '美国专线', 'extra': 1}]}]},
  251. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': [{'condition': '', 'group': '1', 'condition_value': '美国专线'}]}]},
  252. {'price_type': '1', 'start_date': '2026-10-22', 'items': [{'fuel_ratio': '0.1', 'conditions': [{'condition': '物流产品', 'group': '1', 'condition_value': '美国专线', 'condition_relation': 'xor'}]}]},
  253. ):
  254. with self.assertRaises(ValueError):
  255. save.call(**kwargs)
  256. client = FakeClient()
  257. SaveFuelRateTool(api_client=client).call(
  258. price_type='1',
  259. start_date='2026-10-22',
  260. end_date='2026-12-31',
  261. items=[{
  262. 'fuel_ratio': 1,
  263. 'conditions': [{
  264. 'condition': '物流产品',
  265. 'group': '1',
  266. 'condition_value': '美国专线',
  267. 'condition_relation': 'or',
  268. }],
  269. }, {
  270. 'fuel_ratio': '1.0000',
  271. 'conditions': good['conditions'],
  272. }],
  273. )
  274. self.assertEqual('2026-12-31', client.calls[0][2]['end_date'])
  275. self.assertEqual('1.0000', client.calls[0][2]['items'][0]['fuel_ratio'])
  276. enable = EnableFuelRateTool(api_client=FakeClient())
  277. enable.call(fuel_no=' RY12 ')
  278. for value in (1, [1], [' '], ['R' * 21], ['RY1'] * 21):
  279. with self.assertRaises(ValueError):
  280. enable.call(fuel_no=value)
  281. presenter = OutputPresenter()
  282. for message in ('', 'x' * 401, 'has sql', 'Exception', 'a\\b', '没有标记'):
  283. result = presenter.present('save_fuel_rate', {
  284. 'code': 'MCP_1401',
  285. 'msg': message,
  286. 'data': {},
  287. 'meta': {'request_id': 'rq_fuel_save'},
  288. })
  289. self.assertIn('参数不正确', str(result))
  290. class FakeClient:
  291. def __init__(self):
  292. self.calls = []
  293. def call_tool(self, name, route, payload, request_id):
  294. self.calls.append((name, route, payload, request_id))
  295. return {'ok': True}
  296. if __name__ == '__main__':
  297. unittest.main()