test_collector_api.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from __future__ import annotations
  2. import unittest
  3. from unittest.mock import patch
  4. from data_collector_mcp import collector_api
  5. class CollectorApiTests(unittest.TestCase):
  6. def _patch_project(self):
  7. patches = [
  8. patch(
  9. "data_collector_mcp.collector_api.find_project_config",
  10. return_value={
  11. "project_key": "dev-01",
  12. "data_collector_base_url": "http://collector.test",
  13. },
  14. ),
  15. patch(
  16. "data_collector_mcp.collector_api.resolve_project_token",
  17. return_value="token",
  18. ),
  19. ]
  20. for item in patches:
  21. item.start()
  22. self.addCleanup(item.stop)
  23. def test_create_modbus_device_merges_defaults_and_posts_to_collector(self) -> None:
  24. self._patch_project()
  25. response = {"state": 0, "state_info": "成功"}
  26. with patch(
  27. "data_collector_mcp.collector_api.request_json",
  28. return_value=response,
  29. ) as request_json:
  30. result = collector_api.create_modbus_device(
  31. "dev-01",
  32. {
  33. "name": "modbus_tcp_1",
  34. "device_type": 1,
  35. "ip": "127.0.0.1",
  36. "port": 5502,
  37. "slave_id": 1,
  38. "byte_order": 2,
  39. "word_order": 2,
  40. "address_base": 1,
  41. },
  42. )
  43. self.assertEqual(result, response)
  44. request_json.assert_called_once()
  45. self.assertEqual(request_json.call_args.args[:3], ("POST", "http://collector.test/api/collector/device", "token"))
  46. payload = request_json.call_args.kwargs["json_payload"]
  47. self.assertEqual(payload["type"], "modbus")
  48. self.assertEqual(payload["device_type"], 1)
  49. self.assertEqual(payload["timeout"], 3)
  50. self.assertEqual(payload["alarm_interval"], 90)
  51. self.assertEqual(payload["collect_interval"], 5)
  52. self.assertEqual(payload["byte_order"], 2)
  53. self.assertEqual(payload["word_order"], 2)
  54. self.assertEqual(payload["address_offset"], 1)
  55. self.assertNotIn("address_base", payload)
  56. self.assertEqual(payload["name"], "modbus_tcp_1")
  57. def test_create_modbus_device_requires_required_fields(self) -> None:
  58. self._patch_project()
  59. required_fields = [
  60. "name",
  61. "device_type",
  62. "ip",
  63. "port",
  64. "slave_id",
  65. "word_order",
  66. "byte_order",
  67. "address_base",
  68. ]
  69. base_payload = {
  70. "name": "modbus_tcp_1",
  71. "device_type": 1,
  72. "ip": "127.0.0.1",
  73. "port": 5502,
  74. "slave_id": 1,
  75. "byte_order": 1,
  76. "word_order": 1,
  77. "address_base": 0,
  78. }
  79. for field_name in required_fields:
  80. with self.subTest(field_name=field_name):
  81. payload = dict(base_payload)
  82. payload.pop(field_name)
  83. with self.assertRaisesRegex(ValueError, f"payload.{field_name} is required"):
  84. collector_api.create_modbus_device("dev-01", payload)
  85. def test_create_modbus_point_merges_defaults_and_posts_to_collector(self) -> None:
  86. self._patch_project()
  87. response = {"state": 0, "state_info": "成功", "data": None}
  88. with patch(
  89. "data_collector_mcp.collector_api.request_json",
  90. return_value=response,
  91. ) as request_json:
  92. result = collector_api.create_modbus_point(
  93. "dev-01",
  94. {
  95. "device_id": 1,
  96. "name": "holding_register_uint16",
  97. "point_id": "HR_UINT16",
  98. "func_code": 3,
  99. "address": 10,
  100. "type": "uint16",
  101. },
  102. )
  103. self.assertEqual(result, response)
  104. self.assertEqual(
  105. request_json.call_args.args[:3],
  106. (
  107. "POST",
  108. "http://collector.test/api/collector/modbus/point/add_collect_point",
  109. "token",
  110. ),
  111. )
  112. payload = request_json.call_args.kwargs["json_payload"]
  113. self.assertEqual(payload["scale_ratio"], 1)
  114. self.assertEqual(payload["value_offset"], 0)
  115. self.assertEqual(payload["group_id"], 0)
  116. self.assertEqual(payload["invalid_values"], "")
  117. self.assertIsNone(payload["valid_range_start"])
  118. self.assertIsNone(payload["valid_range_end"])
  119. self.assertEqual(payload["bit"], 0)
  120. self.assertEqual(payload["func_code"], 3)
  121. def test_create_modbus_point_normalizes_type_alias_and_register_type(self) -> None:
  122. self._patch_project()
  123. with patch(
  124. "data_collector_mcp.collector_api.request_json",
  125. return_value={"state": 0},
  126. ) as request_json:
  127. collector_api.create_modbus_point(
  128. "dev-01",
  129. {
  130. "device_id": 1,
  131. "name": "temperature",
  132. "point_id": "TEMP",
  133. "register_type": "holding_register",
  134. "address": 10,
  135. "type": "SHORT",
  136. },
  137. )
  138. payload = request_json.call_args.kwargs["json_payload"]
  139. self.assertEqual(payload["func_code"], 3)
  140. self.assertEqual(payload["type"], "int16")
  141. self.assertNotIn("register_type", payload)
  142. def test_create_modbus_point_requires_name(self) -> None:
  143. self._patch_project()
  144. with self.assertRaisesRegex(ValueError, "payload.name is required"):
  145. collector_api.create_modbus_point(
  146. "dev-01",
  147. {
  148. "device_id": 1,
  149. "func_code": 3,
  150. "address": 10,
  151. "type": "int16",
  152. },
  153. )
  154. def test_create_modbus_point_requires_register_type_or_func_code(self) -> None:
  155. self._patch_project()
  156. with self.assertRaisesRegex(ValueError, "payload.register_type is required"):
  157. collector_api.create_modbus_point(
  158. "dev-01",
  159. {
  160. "device_id": 1,
  161. "name": "temperature",
  162. "address": 10,
  163. "type": "int16",
  164. },
  165. )
  166. def test_create_modbus_point_requires_address(self) -> None:
  167. self._patch_project()
  168. with self.assertRaisesRegex(ValueError, "payload.address is required"):
  169. collector_api.create_modbus_point(
  170. "dev-01",
  171. {
  172. "device_id": 1,
  173. "name": "temperature",
  174. "func_code": 3,
  175. "type": "int16",
  176. },
  177. )
  178. def test_create_modbus_point_rejects_unknown_type(self) -> None:
  179. self._patch_project()
  180. with self.assertRaisesRegex(ValueError, "payload.type is invalid"):
  181. collector_api.create_modbus_point(
  182. "dev-01",
  183. {
  184. "device_id": 1,
  185. "name": "temperature",
  186. "func_code": 3,
  187. "address": 10,
  188. "type": "SHORT_REAL",
  189. },
  190. )
  191. def test_list_devices_defaults_num_points_false(self) -> None:
  192. self._patch_project()
  193. response = {"state": 0, "devices": []}
  194. with patch(
  195. "data_collector_mcp.collector_api.request_json",
  196. return_value=response,
  197. ) as request_json:
  198. result = collector_api.list_devices("dev-01")
  199. self.assertEqual(result, response)
  200. request_json.assert_called_once_with(
  201. "GET",
  202. "http://collector.test/api/collector/device?num_points=false",
  203. "token",
  204. json_payload=None,
  205. )
  206. def test_list_devices_can_enable_num_points(self) -> None:
  207. self._patch_project()
  208. with patch(
  209. "data_collector_mcp.collector_api.request_json",
  210. return_value={"state": 0},
  211. ) as request_json:
  212. collector_api.list_devices("dev-01", num_points=True)
  213. self.assertEqual(
  214. request_json.call_args.args[1],
  215. "http://collector.test/api/collector/device?num_points=true",
  216. )
  217. if __name__ == "__main__":
  218. unittest.main()