test_remote_address_tools.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. import unittest
  2. from tools.maintain_remote_address import MaintainRemoteAddressTool
  3. from tools.query_remote_address_list import QueryRemoteAddressListTool
  4. from tools.save_remote_address import SaveRemoteAddressTool
  5. from app import GatewayApp
  6. from public_gateway import PublicGatewayApp
  7. from services.output_presenter import OutputPresenter
  8. class RemoteAddressToolTest(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_remote_address_list', local)
  14. self.assertIn('maintain_remote_address', local)
  15. self.assertIn('save_remote_address', local)
  16. self.assertEqual('base', QueryRemoteAddressListTool.backend)
  17. self.assertEqual('base', MaintainRemoteAddressTool.backend)
  18. self.assertEqual('base', SaveRemoteAddressTool.backend)
  19. def test_query_posts_filters_to_base_route(self):
  20. client = FakeClient()
  21. result = QueryRemoteAddressListTool(api_client=client).call(
  22. address_no=' PY12 ',
  23. remote_name='美国',
  24. status=1,
  25. postcode='10001',
  26. page=2,
  27. limit=20,
  28. request_id='rq_remote_list',
  29. )
  30. self.assertEqual({'ok': True}, result)
  31. self.assertEqual('query_remote_address_list', client.calls[0][0])
  32. self.assertEqual('/mcp/tools/queryRemoteAddressList', client.calls[0][1])
  33. self.assertEqual({
  34. 'page': 2,
  35. 'limit': 20,
  36. 'address_no': 'PY12',
  37. 'remote_name': '美国',
  38. 'postcode': '10001',
  39. 'status': 1,
  40. }, client.calls[0][2])
  41. def test_maintain_without_number_prepares_create(self):
  42. client = FakeClient()
  43. MaintainRemoteAddressTool(api_client=client).call(request_id='rq_maintain')
  44. self.assertEqual('/mcp/tools/maintainRemoteAddress', client.calls[0][1])
  45. self.assertEqual({'page': 1, 'limit': 20}, client.calls[0][2])
  46. def test_presenter_hides_temp_key(self):
  47. columns = [
  48. {'key': key, 'name': name}
  49. for key, name in OutputPresenter.REMOTE_ADDRESS_DETAIL_COLUMNS
  50. ]
  51. record = {key: '' for key, _name in OutputPresenter.REMOTE_ADDRESS_DETAIL_COLUMNS}
  52. result = OutputPresenter().present('maintain_remote_address', {
  53. 'code': 'MCP_0000',
  54. 'msg': 'success',
  55. 'data': {
  56. 'summary': '准备新增地址库',
  57. 'mode_text': '新增',
  58. 'address_no': '',
  59. 'remote_name': '',
  60. 'status_text': '',
  61. 'temp_key': 'secret-temp-key',
  62. 'columns': columns,
  63. 'records': [record],
  64. 'tips': [],
  65. },
  66. 'meta': {'request_id': 'rq_maintain', 'page': 1, 'limit': 20, 'total': 0},
  67. })
  68. rendered = str(result)
  69. self.assertNotIn('secret-temp-key', rendered)
  70. self.assertNotIn('temp_key', rendered)
  71. def test_save_create_posts_details(self):
  72. client = FakeClient()
  73. SaveRemoteAddressTool(api_client=client).call(
  74. remote_name=' 美西偏远 ',
  75. status=1,
  76. details=[{
  77. 'country': 'US',
  78. 'province': 'CA',
  79. 'start_postcode': '90001',
  80. 'end_postcode': '90099',
  81. }],
  82. request_id='rq_save_remote',
  83. )
  84. self.assertEqual('save_remote_address', client.calls[0][0])
  85. self.assertEqual('/mcp/tools/saveRemoteAddress', client.calls[0][1])
  86. self.assertEqual({
  87. 'remote_name': '美西偏远',
  88. 'status': 1,
  89. 'details': [{
  90. 'country': 'US',
  91. 'province': 'CA',
  92. 'start_postcode': '90001',
  93. 'end_postcode': '90099',
  94. }],
  95. }, client.calls[0][2])
  96. def test_save_rejects_end_postcode_without_start(self):
  97. tool = SaveRemoteAddressTool(api_client=FakeClient())
  98. with self.assertRaises(ValueError):
  99. tool.call(
  100. remote_name='美西偏远',
  101. status=1,
  102. details=[{'country': 'US', 'end_postcode': '90099'}],
  103. )
  104. def test_save_rejects_non_ascii_postcode(self):
  105. tool = SaveRemoteAddressTool(api_client=FakeClient())
  106. with self.assertRaises(ValueError):
  107. tool.call(
  108. remote_name='美西偏远',
  109. status=1,
  110. details=[{'country': 'US', 'start_postcode': '九万'}],
  111. )
  112. def test_query_rejects_overlong_text(self):
  113. tool = QueryRemoteAddressListTool(api_client=FakeClient())
  114. with self.assertRaises(ValueError):
  115. tool.call(address_no='P' * 21)
  116. with self.assertRaises(ValueError):
  117. tool.call(country='USUSUSUSUSU')
  118. def test_save_edit_rejects_details(self):
  119. tool = SaveRemoteAddressTool(api_client=FakeClient())
  120. with self.assertRaises(ValueError):
  121. tool.call(
  122. address_no='PY12',
  123. remote_name='美西偏远',
  124. status=0,
  125. details=[{'country': 'US'}],
  126. )
  127. def test_presenter_shows_saved_address(self):
  128. result = OutputPresenter().present('save_remote_address', {
  129. 'code': 'MCP_0000',
  130. 'msg': 'success',
  131. 'data': {
  132. 'summary': '已新增地址库 PY12 美西偏远,状态启用',
  133. 'mode_text': '新增',
  134. 'address_no': 'PY12',
  135. 'remote_name': '美西偏远',
  136. 'status_text': '启用',
  137. },
  138. 'meta': {'request_id': 'rq_save_remote'},
  139. })
  140. rendered = str(result)
  141. self.assertIn('PY12', rendered)
  142. self.assertIn('美西偏远', rendered)
  143. self.assertNotIn('temp_key', rendered)
  144. def test_presenter_keeps_missing_detail_message(self):
  145. result = OutputPresenter().present('save_remote_address', {
  146. 'code': 'MCP_1401',
  147. 'msg': '地址库没有详情,请导入',
  148. 'data': {},
  149. 'meta': {'request_id': 'rq_save_remote'},
  150. })
  151. self.assertIn('地址库没有详情,请导入', str(result))
  152. def test_tools_expose_metadata_and_reject_bad_input(self):
  153. self.assertIn('address_no', QueryRemoteAddressListTool().metadata()['input_schema']['properties'])
  154. self.assertIn('address_no', MaintainRemoteAddressTool().metadata()['input_schema']['properties'])
  155. self.assertIn('details', SaveRemoteAddressTool().metadata()['input_schema']['properties'])
  156. with self.assertRaises(RuntimeError):
  157. QueryRemoteAddressListTool().call()
  158. with self.assertRaises(RuntimeError):
  159. MaintainRemoteAddressTool().call()
  160. with self.assertRaises(RuntimeError):
  161. SaveRemoteAddressTool().call(remote_name='美西', status=1, details=[{'country': 'US'}])
  162. query = QueryRemoteAddressListTool(api_client=FakeClient())
  163. for kwargs in (
  164. {'country': 1},
  165. {'status': True},
  166. {'status': 2},
  167. {'page': True},
  168. {'page': 0},
  169. {'limit': 101},
  170. ):
  171. with self.assertRaises(ValueError):
  172. query.call(**kwargs)
  173. query.call(province='CA', city='LA', remote_name=' ')
  174. maintain = MaintainRemoteAddressTool(api_client=FakeClient())
  175. with self.assertRaises(ValueError):
  176. maintain.call(address_no=12)
  177. with self.assertRaises(ValueError):
  178. maintain.call(page=True)
  179. with self.assertRaises(ValueError):
  180. maintain.call(limit=0)
  181. maintain.call(address_no=' ')
  182. maintain.call(address_no=' PY12 ')
  183. save = SaveRemoteAddressTool(api_client=FakeClient())
  184. detail = {'country': 'US', 'province': '', 'start_postcode': 'A 1-b'}
  185. for kwargs in (
  186. {'remote_name': 1, 'status': 1, 'details': [detail]},
  187. {'remote_name': ' ', 'status': 1, 'details': [detail]},
  188. {'remote_name': '名' * 101, 'status': 1, 'details': [detail]},
  189. {'remote_name': '美西', 'status': True, 'details': [detail]},
  190. {'remote_name': '美西', 'status': 2, 'details': [detail]},
  191. {'remote_name': '美西', 'status': 1, 'address_no': 1, 'details': [detail]},
  192. {'remote_name': '美西', 'status': 1, 'address_no': 'P' * 21, 'details': [detail]},
  193. {'remote_name': '美西', 'status': 1},
  194. {'remote_name': '美西', 'status': 1, 'details': 'US'},
  195. {'remote_name': '美西', 'status': 1, 'details': []},
  196. {'remote_name': '美西', 'status': 1, 'details': [{'country': 'US'}] * 201},
  197. {'remote_name': '美西', 'status': 1, 'details': ['US']},
  198. {'remote_name': '美西', 'status': 1, 'details': [{'country': 'US', 'extra': '1'}]},
  199. {'remote_name': '美西', 'status': 1, 'details': [{'country': 1}]},
  200. {'remote_name': '美西', 'status': 1, 'details': [{'country': 'U' * 51}]},
  201. {'remote_name': '美西', 'status': 1, 'details': [{'province': 'CA'}]},
  202. {'remote_name': '美西', 'status': 1, 'details': [{'shipper_country': 'CN', 'shipper_end_postcode': '518099'}]},
  203. ):
  204. with self.assertRaises(ValueError):
  205. save.call(**kwargs)
  206. client = FakeClient()
  207. SaveRemoteAddressTool(api_client=client).call(
  208. remote_name='美西',
  209. status=1,
  210. address_no=' ',
  211. details=[detail],
  212. )
  213. self.assertNotIn('address_no', client.calls[0][2])
  214. SaveRemoteAddressTool(api_client=client).call(
  215. address_no='PY12',
  216. remote_name='美西',
  217. status=0,
  218. )
  219. def test_presenter_rejects_bad_maintain_and_save_payloads(self):
  220. presenter = OutputPresenter()
  221. columns = [
  222. {'key': key, 'name': name}
  223. for key, name in OutputPresenter.REMOTE_ADDRESS_DETAIL_COLUMNS
  224. ]
  225. record = {key: '' for key, _name in OutputPresenter.REMOTE_ADDRESS_DETAIL_COLUMNS}
  226. base = {
  227. 'summary': '准备新增地址库',
  228. 'mode_text': '新增',
  229. 'address_no': '',
  230. 'remote_name': '',
  231. 'status_text': '',
  232. 'temp_key': 'temp',
  233. 'columns': columns,
  234. 'records': [record],
  235. }
  236. bad_payloads = [None, dict(base, extra=1), dict(base, mode_text='查看')]
  237. missing = dict(base)
  238. missing.pop('summary')
  239. bad_payloads.append(missing)
  240. bad_payloads.append(dict(base, summary=1))
  241. bad_payloads.append(dict(base, columns=[]))
  242. bad_payloads.append(dict(base, records={}))
  243. bad_columns = [dict(column) for column in columns]
  244. bad_columns[0] = {'key': 'other', 'name': columns[0]['name']}
  245. bad_payloads.append(dict(base, columns=bad_columns))
  246. bad_payloads.append(dict(base, records=[{}]))
  247. for data in bad_payloads:
  248. result = presenter.present('maintain_remote_address', {
  249. 'code': 'MCP_0000',
  250. 'msg': 'success',
  251. 'data': data,
  252. 'meta': {'request_id': 'rq_maintain'},
  253. })
  254. self.assertIn('格式异常', str(result))
  255. tipped = dict(base, tips=['该地址库还没有地址明细'])
  256. tipped_result = presenter.present('maintain_remote_address', {
  257. 'code': 'MCP_0000',
  258. 'msg': 'success',
  259. 'data': tipped,
  260. 'meta': {'request_id': 'rq_maintain'},
  261. })
  262. self.assertIn('该地址库还没有地址明细', str(tipped_result))
  263. saved = {
  264. 'summary': '已新增地址库 PY12 美西,状态启用',
  265. 'mode_text': '新增',
  266. 'address_no': 'PY12',
  267. 'remote_name': '美西',
  268. 'status_text': '启用',
  269. }
  270. for data in (
  271. dict(saved, extra=1),
  272. dict(saved, mode_text='查看'),
  273. dict(saved, status_text='草稿'),
  274. dict(saved, address_no='12'),
  275. dict(saved, remote_name=' '),
  276. dict(saved, summary=1),
  277. ):
  278. result = presenter.present('save_remote_address', {
  279. 'code': 'MCP_0000',
  280. 'msg': 'success',
  281. 'data': data,
  282. 'meta': {'request_id': 'rq_save_remote'},
  283. })
  284. self.assertIn('格式异常', str(result))
  285. for message in (
  286. '一次最多保存200条地址明细',
  287. '第1条收件人邮编不正确',
  288. '不能保存',
  289. ):
  290. result = presenter.present('save_remote_address', {
  291. 'code': 'MCP_1401',
  292. 'msg': message,
  293. 'data': {},
  294. 'meta': {'request_id': 'rq_save_remote'},
  295. })
  296. rendered = str(result)
  297. if message == '不能保存':
  298. self.assertIn('参数不正确', rendered)
  299. else:
  300. self.assertIn(message, rendered)
  301. for tool_name in ('maintain_remote_address', 'save_remote_address'):
  302. result = presenter.present(tool_name, {
  303. 'code': 'MCP_1501',
  304. 'msg': '地址库不存在',
  305. 'data': {},
  306. 'meta': {'request_id': 'rq_missing'},
  307. })
  308. self.assertIn('地址库不存在', str(result))
  309. class FakeClient:
  310. def __init__(self):
  311. self.calls = []
  312. def call_tool(self, name, route, payload, request_id):
  313. self.calls.append((name, route, payload, request_id))
  314. return {'ok': True}
  315. if __name__ == '__main__':
  316. unittest.main()