test_customer_unverified_bill_details_tool.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import json
  2. import unittest
  3. from io import StringIO
  4. from app import GatewayApp
  5. from public_gateway import PublicGatewayApp
  6. from services.output_presenter import OutputPresenter
  7. from tools.query_customer_unverified_bill_details import (
  8. QueryCustomerUnverifiedBillDetailsTool,
  9. )
  10. class RecordingApiClient:
  11. def __init__(self):
  12. self.calls = []
  13. def call_tool(self, tool_code, route_path, payload, request_id):
  14. self.calls.append((tool_code, route_path, payload, request_id))
  15. return {'code': 'MCP_0000', 'data': {}}
  16. def list_enabled_tools(self, request_id=''):
  17. return {
  18. 'code': 'MCP_0000',
  19. 'data': {'tool_codes': ['query_customer_unverified_bill_details']},
  20. }
  21. class CustomerUnverifiedBillDetailsToolTest(unittest.TestCase):
  22. def test_schema_is_closed_requires_customer_and_forwards_call(self):
  23. client = RecordingApiClient()
  24. tool = QueryCustomerUnverifiedBillDetailsTool(client)
  25. schema = tool.metadata()['input_schema']
  26. self.assertEqual(['customer_id'], schema['required'])
  27. self.assertFalse(schema['additionalProperties'])
  28. self.assertEqual({'customer_id', 'page', 'limit'}, set(schema['properties']))
  29. result = tool.call(customer_id=7, page=2, limit=30, request_id='rq_detail')
  30. self.assertEqual('MCP_0000', result['code'])
  31. self.assertEqual((
  32. 'query_customer_unverified_bill_details',
  33. '/mcp/tools/queryCustomerUnverifiedBillDetails',
  34. {'customer_id': 7, 'page': 2, 'limit': 30},
  35. 'rq_detail',
  36. ), client.calls[0])
  37. def test_invalid_arguments_and_missing_client_fail(self):
  38. tool = QueryCustomerUnverifiedBillDetailsTool(RecordingApiClient())
  39. for arguments in (
  40. {'customer_id': True}, {'customer_id': 0}, {'customer_id': '7'},
  41. {'customer_id': 7, 'page': 0}, {'customer_id': 7, 'page': 101},
  42. {'customer_id': 7, 'limit': False}, {'customer_id': 7, 'limit': 101},
  43. ):
  44. with self.subTest(arguments=arguments):
  45. with self.assertRaises(ValueError):
  46. tool.call(**arguments)
  47. with self.assertRaises(RuntimeError):
  48. QueryCustomerUnverifiedBillDetailsTool().call(customer_id=7)
  49. def test_registries_and_cli_include_detail_tool(self):
  50. client = RecordingApiClient()
  51. local = GatewayApp(api_client=client)
  52. public = PublicGatewayApp(None, None)
  53. self.assertEqual(local.registered_tool_names(), public.registered_tool_names())
  54. self.assertEqual(17, len(local.registered_tool_names()))
  55. self.assertIn('query_customer_unverified_bill_details', local.registered_tool_names())
  56. output = StringIO()
  57. local.run_cli([
  58. 'call', '--tool', 'query_customer_unverified_bill_details',
  59. '--customer-id', '7', '--page', '2', '--limit', '30',
  60. ], stdout=output)
  61. self.assertEqual('MCP_0000', json.loads(output.getvalue())['code'])
  62. self.assertEqual({'customer_id': 7, 'page': 2, 'limit': 30}, client.calls[-1][2])
  63. with self.assertRaises(ValueError):
  64. local.run_cli([
  65. 'call', '--tool', 'query_customer_unverified_bill_details',
  66. ], stdout=StringIO())
  67. class CustomerUnverifiedBillDetailsPresenterTest(unittest.TestCase):
  68. KEYS = [
  69. 'bill_month', 'bill_no', 'business_type', 'settlement_mode',
  70. 'unverified_amount', 'customer_receivable_date',
  71. ]
  72. def payload(self):
  73. return {
  74. 'code': 'MCP_0000',
  75. 'data': {
  76. 'columns': [{'key': key, 'name': key} for key in self.KEYS],
  77. 'records': [{
  78. 'bill_month': '2026-01',
  79. 'bill_no': 'DFC2601010001',
  80. 'business_type': '头程业务',
  81. 'settlement_mode': '月结30天',
  82. 'unverified_amount': 90.0,
  83. 'customer_receivable_date': '2026-01-31',
  84. }],
  85. },
  86. 'meta': {'page': 1, 'limit': 20, 'has_more': False},
  87. }
  88. def test_translates_all_six_fields(self):
  89. result = OutputPresenter().present(
  90. 'query_customer_unverified_bill_details', self.payload()
  91. )
  92. self.assertFalse(result['is_error'])
  93. self.assertEqual(6, len(result['structured_content']['headers']))
  94. self.assertEqual(6, len(result['structured_content']['rows'][0]))
  95. def test_missing_extra_malformed_or_bad_pagination_fails_closed(self):
  96. cases = []
  97. missing = self.payload()
  98. del missing['data']['records'][0]['bill_no']
  99. cases.append(missing)
  100. extra = self.payload()
  101. extra['data']['records'][0]['secret'] = 1
  102. cases.append(extra)
  103. bad_amount = self.payload()
  104. bad_amount['data']['records'][0]['unverified_amount'] = True
  105. cases.append(bad_amount)
  106. bad_text = self.payload()
  107. bad_text['data']['records'][0]['bill_month'] = []
  108. cases.append(bad_text)
  109. bad_page = self.payload()
  110. bad_page['meta']['page'] = 101
  111. cases.append(bad_page)
  112. extra_meta = self.payload()
  113. extra_meta['meta']['total'] = 1
  114. cases.append(extra_meta)
  115. extra_data = self.payload()
  116. extra_data['data']['internal'] = True
  117. cases.append(extra_data)
  118. bad_records = self.payload()
  119. bad_records['data']['records'] = 'bad'
  120. cases.append(bad_records)
  121. bad_columns = self.payload()
  122. bad_columns['data']['columns'] = 'bad'
  123. cases.append(bad_columns)
  124. bad_column = self.payload()
  125. bad_column['data']['columns'][0]['internal'] = True
  126. cases.append(bad_column)
  127. presenter = OutputPresenter()
  128. for payload in cases:
  129. with self.subTest(payload=payload):
  130. self.assertTrue(presenter.present(
  131. 'query_customer_unverified_bill_details', payload
  132. )['is_error'])
  133. if __name__ == '__main__':
  134. unittest.main()