test_server_tools.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. from __future__ import annotations
  2. import asyncio
  3. import inspect
  4. import unittest
  5. from unittest.mock import patch
  6. from data_collector_mcp import app, bacnet_server, common_server, modbus_server, s7_server
  7. class ServerToolTests(unittest.TestCase):
  8. def test_app_imports_tool_modules_for_registration(self) -> None:
  9. self.assertIs(app.common_server, common_server)
  10. self.assertIs(app.modbus_server, modbus_server)
  11. self.assertIs(app.s7_server, s7_server)
  12. self.assertIs(app.bacnet_server, bacnet_server)
  13. def test_app_registers_expected_mcp_tools(self) -> None:
  14. tools = asyncio.run(app.mcp.list_tools())
  15. tool_names = {tool.name for tool in tools}
  16. self.assertEqual(
  17. tool_names,
  18. {
  19. "project.list",
  20. "collector.device_list",
  21. "collector.device_connect",
  22. "collector.device_disconnect",
  23. "collector.device_points",
  24. "modbus.raw_read",
  25. "modbus.point_collect_test",
  26. "bacnet.point_collect_test",
  27. "bacnet.point_search",
  28. "bacnet.bbmd_whois",
  29. "s7.raw_read",
  30. "s7.point_collect_test",
  31. "s7.connect_scan",
  32. "collector.modbus_device_create",
  33. "collector.modbus_device_edit",
  34. "collector.modbus_point_create",
  35. "collector.modbus_point_edit",
  36. "collector.s7_device_create",
  37. "collector.s7_device_edit",
  38. "collector.s7_point_create",
  39. "collector.s7_point_edit",
  40. "collector.bacnet_device_create",
  41. "collector.bacnet_device_edit",
  42. "collector.bacnet_point_create",
  43. "collector.bacnet_point_edit",
  44. },
  45. )
  46. def test_common_tools_include_field_descriptions_and_output_schema(self) -> None:
  47. tools = {tool.name: tool for tool in asyncio.run(app.mcp.list_tools())}
  48. project_list = tools["project.list"]
  49. project_props = project_list.output_schema["properties"]
  50. self.assertIn("projects", project_props)
  51. project_item_props = project_props["projects"]["items"]["properties"]
  52. self.assertEqual(project_item_props["project_key"]["description"], "项目标识,用于其他采集工具的 project_key 参数。")
  53. device_list = tools["collector.device_list"]
  54. self.assertEqual(
  55. device_list.parameters["properties"]["project_key"]["description"],
  56. "项目标识,来自 project.list 返回的 project_key。",
  57. )
  58. self.assertEqual(device_list.parameters["properties"]["num_points"]["default"], False)
  59. self.assertIn("devices", device_list.output_schema["properties"])
  60. connect = tools["collector.device_connect"]
  61. self.assertEqual(
  62. connect.parameters["properties"]["device_type"]["description"],
  63. "设备协议类型,可传 modbus、s7、bacnet、ethernet-ip、opc-ua、opc-da、snmp、iec104。",
  64. )
  65. connect_data_schema = connect.output_schema["properties"]["data"]["anyOf"][0]
  66. self.assertIn("running_status", connect_data_schema["properties"])
  67. disconnect = tools["collector.device_disconnect"]
  68. self.assertEqual(disconnect.parameters["properties"]["device_type"]["default"], "modbus")
  69. self.assertIn("data", disconnect.output_schema["properties"])
  70. device_points = tools["collector.device_points"]
  71. self.assertEqual(device_points.parameters["properties"]["group_id"]["default"], 0)
  72. points_data_schema = device_points.output_schema["properties"]["data"]["anyOf"][0]
  73. point_props = points_data_schema["properties"]["point"]["items"]["properties"]
  74. self.assertEqual(point_props["present_value"]["description"], "当前内存中的点位最新值。")
  75. def test_protocol_tools_include_field_descriptions_and_output_schema(self) -> None:
  76. tools = {tool.name: tool for tool in asyncio.run(app.mcp.list_tools())}
  77. point_search = tools["bacnet.point_search"]
  78. self.assertEqual(
  79. point_search.parameters["properties"]["bacnet_device_id"]["description"],
  80. "BACnet 设备对象实例号,范围 0..4194303。",
  81. )
  82. point_output_props = point_search.output_schema["properties"]
  83. self.assertIn("code", point_output_props)
  84. point_data_schema = point_output_props["data"]["anyOf"][0]
  85. point_props = point_data_schema["properties"]["points"]["items"]["properties"]
  86. self.assertEqual(point_props["present_value"]["description"], "当前值;读取 BACnet present-value 得到。")
  87. device_create = tools["collector.bacnet_device_create"]
  88. device_item_props = device_create.parameters["properties"]["devices"]["items"]["properties"]
  89. self.assertEqual(device_item_props["ip"]["description"], "BACnet/IP 设备地址。")
  90. create_output_props = device_create.output_schema["properties"]
  91. self.assertIn("summary", create_output_props)
  92. self.assertIn("results", create_output_props)
  93. device_edit_props = tools["collector.bacnet_device_edit"].parameters["properties"]
  94. self.assertIn("id", device_edit_props)
  95. self.assertNotIn("ori_id", device_edit_props)
  96. point_edit_props = tools["collector.bacnet_point_edit"].parameters["properties"]
  97. self.assertIn("id", point_edit_props)
  98. self.assertNotIn("ori_id", point_edit_props)
  99. self.assertNotIn("data", tools["collector.bacnet_point_edit"].output_schema["properties"])
  100. modbus_collect = tools["modbus.point_collect_test"]
  101. modbus_point_props = modbus_collect.parameters["properties"]["points"]["items"]["properties"]
  102. self.assertEqual(modbus_point_props["address"]["description"], "寄存器地址。")
  103. self.assertIn("data", modbus_collect.output_schema["properties"])
  104. modbus_output_point_props = (
  105. modbus_collect.output_schema["properties"]["data"]["anyOf"][0]["properties"]["points"]["items"]["properties"]
  106. )
  107. self.assertIn("value", modbus_output_point_props)
  108. self.assertNotIn("present_value", modbus_output_point_props)
  109. for tool_name in ("collector.modbus_device_edit", "collector.modbus_point_edit"):
  110. props = tools[tool_name].parameters["properties"]
  111. self.assertIn("id", props)
  112. self.assertNotIn("ori_id", props)
  113. self.assertNotIn("register_type", tools["collector.modbus_point_edit"].parameters["properties"])
  114. self.assertIn("1=Read Coils", tools["collector.modbus_point_edit"].parameters["properties"]["func_code"]["description"])
  115. self.assertNotIn("data", tools["collector.modbus_point_edit"].output_schema["properties"])
  116. s7_collect = tools["s7.point_collect_test"]
  117. s7_point_props = s7_collect.parameters["properties"]["points"]["items"]["properties"]
  118. self.assertEqual(s7_point_props["area"]["description"], "读取区域,可用 DB、M、I、Q、V。")
  119. self.assertIn("data", s7_collect.output_schema["properties"])
  120. s7_output_point_props = s7_collect.output_schema["properties"]["data"]["anyOf"][0]["properties"]["points"]["items"]["properties"]
  121. self.assertIn("value", s7_output_point_props)
  122. self.assertNotIn("present_value", s7_output_point_props)
  123. for tool_name in ("collector.s7_device_edit", "collector.s7_point_edit"):
  124. props = tools[tool_name].parameters["properties"]
  125. self.assertIn("id", props)
  126. self.assertNotIn("ori_id", props)
  127. self.assertNotIn("data", tools["collector.s7_point_edit"].output_schema["properties"])
  128. s7_create_point_props = tools["collector.s7_point_create"].parameters["properties"]["points"]["items"]["properties"]
  129. self.assertIn("register_type", s7_create_point_props)
  130. self.assertNotIn("register_area", s7_create_point_props)
  131. self.assertNotIn("register_area", tools["collector.s7_point_edit"].parameters["properties"])
  132. def test_project_list_filters_enabled_projects_and_sorts(self) -> None:
  133. with patch(
  134. "data_collector_mcp.common_server.load_projects_config",
  135. return_value=[
  136. {
  137. "project_key": "z-prod",
  138. "project_name": "Prod",
  139. "base_url": "http://gateway.prod",
  140. "data_collector_base_url": "http://collector.prod",
  141. "enabled": True,
  142. },
  143. {
  144. "project_key": "disabled",
  145. "project_name": "Disabled",
  146. "base_url": "http://gateway.disabled",
  147. "data_collector_base_url": "http://collector.disabled",
  148. "enabled": False,
  149. },
  150. {
  151. "project_key": "a-dev",
  152. "project_name": "Dev",
  153. "base_url": "http://gateway.dev",
  154. "data_collector_base_url": "http://collector.dev",
  155. "enabled": True,
  156. },
  157. ],
  158. ):
  159. result = common_server.project_list()
  160. self.assertEqual(
  161. result,
  162. {
  163. "projects": [
  164. {
  165. "project_key": "a-dev",
  166. "project_name": "Dev",
  167. },
  168. {
  169. "project_key": "z-prod",
  170. "project_name": "Prod",
  171. },
  172. ],
  173. "total": 2,
  174. },
  175. )
  176. for project in result["projects"]:
  177. self.assertNotIn("base_url", project)
  178. self.assertNotIn("data_collector_base_url", project)
  179. def test_common_device_tools_forward_to_api(self) -> None:
  180. with patch("data_collector_mcp.common_server.api_list_devices", return_value={"state": 0}) as list_devices:
  181. self.assertEqual(common_server.collector_device_list("dev-01", num_points=True), {"state": 0})
  182. list_devices.assert_called_once_with("dev-01", num_points=True)
  183. with patch("data_collector_mcp.common_server.api_connect_device", return_value={"state": 0}) as connect:
  184. self.assertEqual(
  185. common_server.collector_device_connect("dev-01", device_id=1, device_type="s7"),
  186. {"state": 0},
  187. )
  188. connect.assert_called_once_with("dev-01", device_id=1, device_type="s7")
  189. with patch("data_collector_mcp.common_server.api_disconnect_device", return_value={"state": 0}) as disconnect:
  190. self.assertEqual(
  191. common_server.collector_device_disconnect("dev-01", device_id=1, device_type="modbus"),
  192. {"state": 0},
  193. )
  194. disconnect.assert_called_once_with("dev-01", device_id=1, device_type="modbus")
  195. with patch("data_collector_mcp.common_server.api_list_device_points", return_value={"state": 0}) as points:
  196. self.assertEqual(
  197. common_server.collector_device_points("dev-01", device_id=1, device_type="s7", group_id=2),
  198. {"state": 0},
  199. )
  200. points.assert_called_once_with("dev-01", device_id=1, device_type="s7", group_id=2)
  201. def test_modbus_device_create_accepts_batch_devices(self) -> None:
  202. signature = inspect.signature(modbus_server.collector_modbus_device_create)
  203. self.assertIn("devices", signature.parameters)
  204. devices = [
  205. {
  206. "name": "modbus_tcp_1",
  207. "device_type": 1,
  208. "ip": "127.0.0.1",
  209. "port": 5502,
  210. "slave_id": 1,
  211. "byte_order": 1,
  212. "word_order": 1,
  213. "address_base": 0,
  214. "group_id": 10,
  215. }
  216. ]
  217. with patch("data_collector_mcp.modbus_server.api_create_modbus_devices", return_value={"state": 0}) as api_create:
  218. result = modbus_server.collector_modbus_device_create(
  219. project_key="dev-01",
  220. devices=devices,
  221. )
  222. self.assertEqual(result, {"state": 0})
  223. api_create.assert_called_once_with("dev-01", devices)
  224. def test_modbus_raw_read_forwards_to_api(self) -> None:
  225. read = {"function_code": 3, "address": 0, "quantity": 4}
  226. with patch("data_collector_mcp.modbus_server.api_modbus_raw_read", return_value={"code": 0}) as raw_read:
  227. result = modbus_server.modbus_raw_read(
  228. project_key="dev-01",
  229. ip="192.168.1.10",
  230. port=502,
  231. slave_id=1,
  232. read=read,
  233. word_byte_order="DCBA",
  234. address_base=1,
  235. )
  236. self.assertEqual(result, {"code": 0})
  237. raw_read.assert_called_once_with(
  238. "dev-01",
  239. ip="192.168.1.10",
  240. port=502,
  241. slave_id=1,
  242. read=read,
  243. device_type="ModbusTCP",
  244. word_byte_order="DCBA",
  245. address_base=1,
  246. )
  247. def test_modbus_point_create_accepts_batch_points(self) -> None:
  248. points = [
  249. {
  250. "device_id": 1,
  251. "name": "temperature",
  252. "address": 10,
  253. "type": "uint16",
  254. "func_code": 3,
  255. "scale_ratio": 2,
  256. "value_offset": 1,
  257. "group_id": 5,
  258. "invalid_values": "-9999",
  259. "valid_range_start": 0,
  260. "valid_range_end": 100,
  261. }
  262. ]
  263. with patch("data_collector_mcp.modbus_server.api_create_modbus_points", return_value={"state": 0}) as api_create:
  264. result = modbus_server.collector_modbus_point_create(project_key="dev-01", points=points)
  265. self.assertEqual(result, {"state": 0})
  266. api_create.assert_called_once_with("dev-01", points)
  267. def test_modbus_device_edit_uses_explicit_parameters(self) -> None:
  268. signature = inspect.signature(modbus_server.collector_modbus_device_edit)
  269. self.assertNotIn("payload", signature.parameters)
  270. with patch("data_collector_mcp.modbus_server.api_edit_modbus_device", return_value={"state": 0}) as api_edit:
  271. result = modbus_server.collector_modbus_device_edit(
  272. project_key="dev-01",
  273. id=1,
  274. name="modbus_tcp_edited",
  275. device_type=1,
  276. ip="127.0.0.1",
  277. port=5502,
  278. slave_id=1,
  279. byte_order=2,
  280. word_order=2,
  281. address_offset=1,
  282. device_group_id=10,
  283. )
  284. self.assertEqual(result, {"state": 0})
  285. api_edit.assert_called_once_with(
  286. "dev-01",
  287. {
  288. "ori_id": 1,
  289. "name": "modbus_tcp_edited",
  290. "device_type": 1,
  291. "ip": "127.0.0.1",
  292. "port": 5502,
  293. "slave_id": 1,
  294. "byte_order": 2,
  295. "word_order": 2,
  296. "serial_port": "",
  297. "timeout": 3,
  298. "is_persistent": False,
  299. "baud_rate": 0,
  300. "data_bit": 0,
  301. "parity": 0,
  302. "stop_bit": 0,
  303. "mode": 0,
  304. "address_offset": 1,
  305. "retry_times": 0,
  306. "device_group_id": 10,
  307. "alarm_interval": 90,
  308. "collect_interval": 5,
  309. },
  310. )
  311. def test_modbus_point_edit_uses_explicit_parameters_with_func_code(self) -> None:
  312. signature = inspect.signature(modbus_server.collector_modbus_point_edit)
  313. self.assertNotIn("payload", signature.parameters)
  314. with patch("data_collector_mcp.modbus_server.api_edit_modbus_point", return_value={"state": 0}) as api_edit:
  315. result = modbus_server.collector_modbus_point_edit(
  316. project_key="dev-01",
  317. id=101,
  318. name="holding_register_uint16_edited",
  319. address=10,
  320. data_type="uint16",
  321. func_code=3,
  322. point_id="HR_UINT16_EDITED",
  323. scale_ratio=2,
  324. value_offset=1,
  325. group_id=5,
  326. invalid_values="-9999",
  327. valid_range_start=0,
  328. valid_range_end=100,
  329. )
  330. self.assertEqual(result, {"state": 0})
  331. api_edit.assert_called_once_with(
  332. "dev-01",
  333. {
  334. "ori_id": 101,
  335. "name": "holding_register_uint16_edited",
  336. "address": 10,
  337. "type": "uint16",
  338. "point_id": "HR_UINT16_EDITED",
  339. "scale_ratio": 2,
  340. "value_offset": 1,
  341. "group_id": 5,
  342. "invalid_values": "-9999",
  343. "valid_range_start": 0,
  344. "valid_range_end": 100,
  345. "bit": 0,
  346. "describe": "",
  347. "func_code": 3,
  348. },
  349. )
  350. def test_s7_device_create_accepts_batch_devices(self) -> None:
  351. signature = inspect.signature(s7_server.collector_s7_device_create)
  352. self.assertIn("devices", signature.parameters)
  353. devices = [{"name": "s7_1200_1", "ip": "127.0.0.1", "rock": 0, "slot": 1, "tsap_conn_type": "OP"}]
  354. with patch("data_collector_mcp.s7_server.api_create_s7_devices", return_value={"state": 0}) as api_create:
  355. result = s7_server.collector_s7_device_create(
  356. project_key="dev-01",
  357. devices=devices,
  358. )
  359. self.assertEqual(result, {"state": 0})
  360. api_create.assert_called_once_with(
  361. "dev-01",
  362. [{"name": "s7_1200_1", "ip": "127.0.0.1", "rock": 0, "slot": 1, "tsap_conn_type": "OP"}],
  363. )
  364. def test_s7_point_create_accepts_batch_points(self) -> None:
  365. points = [{"device_id": 3, "name": "db_real", "address": "1.10", "type": "REAL", "register_type": 4}]
  366. with patch("data_collector_mcp.s7_server.api_create_s7_points", return_value={"state": 0}) as api_create:
  367. result = s7_server.collector_s7_point_create(project_key="dev-01", points=points)
  368. self.assertEqual(result, {"state": 0})
  369. api_create.assert_called_once_with("dev-01", points)
  370. def test_s7_device_edit_uses_explicit_parameters(self) -> None:
  371. signature = inspect.signature(s7_server.collector_s7_device_edit)
  372. self.assertNotIn("payload", signature.parameters)
  373. with patch("data_collector_mcp.s7_server.api_edit_s7_device", return_value={"state": 0}) as api_edit:
  374. result = s7_server.collector_s7_device_edit(
  375. project_key="dev-01",
  376. id=3,
  377. name="s7_edited",
  378. ip="127.0.0.1",
  379. rock=0,
  380. slot=1,
  381. tsap_conn_type="OP",
  382. )
  383. self.assertEqual(result, {"state": 0})
  384. api_edit.assert_called_once_with(
  385. "dev-01",
  386. {
  387. "id": 3,
  388. "name": "s7_edited",
  389. "ip": "127.0.0.1",
  390. "rock": 0,
  391. "slot": 1,
  392. "port": 102,
  393. "device_type": 1,
  394. "tsap_conn_type": "OP",
  395. "is_persistent": False,
  396. "device_group_id": 0,
  397. "timeout": 3,
  398. "alarm_interval": 90,
  399. "collect_interval": 5,
  400. },
  401. )
  402. def test_s7_point_edit_uses_register_type(self) -> None:
  403. signature = inspect.signature(s7_server.collector_s7_point_edit)
  404. self.assertNotIn("payload", signature.parameters)
  405. with patch("data_collector_mcp.s7_server.api_edit_s7_point", return_value={"state": 0}) as api_edit:
  406. s7_server.collector_s7_point_edit(
  407. project_key="dev-01",
  408. id=101,
  409. device_id=3,
  410. name="db_real",
  411. address="1.10",
  412. data_type="float32",
  413. register_type=4,
  414. point_id="DB_REAL",
  415. )
  416. payload = api_edit.call_args.args[1]
  417. self.assertEqual(payload["register_type"], 4)
  418. self.assertNotIn("register_area", payload)
  419. def test_s7_gateway_tools_forward_to_api(self) -> None:
  420. with patch("data_collector_mcp.s7_server.api_s7_raw_read", return_value={"code": 0}) as raw_read:
  421. self.assertEqual(
  422. s7_server.s7_raw_read(
  423. project_key="dev-01",
  424. ip="192.168.1.10",
  425. rock=0,
  426. slot=1,
  427. read={"area": "DB", "db": 1, "start": 0, "size": 4},
  428. ),
  429. {"code": 0},
  430. )
  431. raw_read.assert_called_once_with(
  432. "dev-01",
  433. ip="192.168.1.10",
  434. rock=0,
  435. slot=1,
  436. read={"area": "DB", "db": 1, "start": 0, "size": 4},
  437. device_type="S7-1200",
  438. port=102,
  439. tsap_conn_type=None,
  440. )
  441. with patch("data_collector_mcp.s7_server.api_s7_point_collect_test", return_value={"code": 0}) as point_test:
  442. self.assertEqual(
  443. s7_server.s7_point_collect_test(
  444. project_key="dev-01",
  445. ip="192.168.1.10",
  446. port=1102,
  447. rock=0,
  448. slot=1,
  449. tsap_conn_type="BASIC",
  450. points=[{"area": "M", "start": 0, "type": "bool"}],
  451. ),
  452. {"code": 0},
  453. )
  454. point_test.assert_called_once_with(
  455. "dev-01",
  456. ip="192.168.1.10",
  457. rock=0,
  458. slot=1,
  459. points=[{"area": "M", "start": 0, "type": "bool"}],
  460. device_type="S7-1200",
  461. port=1102,
  462. tsap_conn_type="BASIC",
  463. )
  464. with patch("data_collector_mcp.s7_server.api_s7_connect_scan", return_value={"code": 0}) as connect_scan:
  465. self.assertEqual(s7_server.s7_connect_scan("dev-01", ip="192.168.1.10"), {"code": 0})
  466. connect_scan.assert_called_once_with("dev-01", ip="192.168.1.10")
  467. def test_bacnet_gateway_tools_forward_to_api(self) -> None:
  468. with patch(
  469. "data_collector_mcp.bacnet_server.api_bacnet_point_collect_test",
  470. return_value={"code": 0},
  471. ) as point_test:
  472. self.assertEqual(
  473. bacnet_server.bacnet_point_collect_test(
  474. project_key="dev-01",
  475. ip="192.168.1.20",
  476. bacnet_device_id=12345,
  477. port=47809,
  478. points=[{"object_type": "AnalogInput", "object_id": 1}],
  479. ),
  480. {"code": 0},
  481. )
  482. point_test.assert_called_once_with(
  483. "dev-01",
  484. ip="192.168.1.20",
  485. bacnet_device_id=12345,
  486. port=47809,
  487. points=[{"object_type": "AnalogInput", "object_id": 1}],
  488. )
  489. with patch(
  490. "data_collector_mcp.bacnet_server.api_bacnet_point_search",
  491. return_value={"code": 0},
  492. ) as point_search:
  493. self.assertEqual(
  494. bacnet_server.bacnet_point_search(
  495. project_key="dev-01",
  496. ip="192.168.1.20",
  497. bacnet_device_id=12345,
  498. ),
  499. {"code": 0},
  500. )
  501. point_search.assert_called_once_with(
  502. "dev-01",
  503. ip="192.168.1.20",
  504. bacnet_device_id=12345,
  505. port=47808,
  506. )
  507. with patch(
  508. "data_collector_mcp.bacnet_server.api_bacnet_bbmd_whois",
  509. return_value={"code": 0},
  510. ) as bbmd_whois:
  511. self.assertEqual(bacnet_server.bacnet_bbmd_whois("dev-01"), {"code": 0})
  512. bbmd_whois.assert_called_once_with("dev-01")
  513. def test_bacnet_collector_tools_forward_to_api(self) -> None:
  514. devices = [{"name": "bacnet_1", "ip": "192.168.1.20", "bacnet_device_id": 12345}]
  515. with patch(
  516. "data_collector_mcp.bacnet_server.api_create_bacnet_devices",
  517. return_value={"state": 0},
  518. ) as api_create:
  519. result = bacnet_server.collector_bacnet_device_create(project_key="dev-01", devices=devices)
  520. self.assertEqual(result, {"state": 0})
  521. api_create.assert_called_once_with("dev-01", devices)
  522. with patch(
  523. "data_collector_mcp.bacnet_server.api_edit_bacnet_device",
  524. return_value={"state": 0},
  525. ) as api_edit:
  526. result = bacnet_server.collector_bacnet_device_edit(
  527. project_key="dev-01",
  528. id=9,
  529. name="bacnet_edited",
  530. ip="192.168.1.21",
  531. bacnet_device_id=54321,
  532. device_group_id=10,
  533. )
  534. self.assertEqual(result, {"state": 0})
  535. api_edit.assert_called_once_with(
  536. "dev-01",
  537. {
  538. "ori_id": 9,
  539. "name": "bacnet_edited",
  540. "ip": "192.168.1.21",
  541. "bacnet_device_id": 54321,
  542. "port": 47808,
  543. "bacnet_net": 0,
  544. "asp_ip": "",
  545. "is_persistent": False,
  546. "device_group_id": 10,
  547. "timeout": 3,
  548. "alarm_interval": 90,
  549. "collect_interval": 5,
  550. },
  551. )
  552. points = [
  553. {
  554. "device_id": 9,
  555. "name": "zone_temperature",
  556. "object_type": "AnalogInput",
  557. "object_id": 1,
  558. "scale_ratio": 2,
  559. "value_offset": 1,
  560. "group_id": 5,
  561. "invalid_values": "-9999",
  562. "valid_range_start": 0,
  563. "valid_range_end": 100,
  564. }
  565. ]
  566. with patch(
  567. "data_collector_mcp.bacnet_server.api_create_bacnet_points",
  568. return_value={"state": 0},
  569. ) as api_point_create:
  570. result = bacnet_server.collector_bacnet_point_create(project_key="dev-01", points=points)
  571. self.assertEqual(result, {"state": 0})
  572. api_point_create.assert_called_once_with("dev-01", points)
  573. with patch(
  574. "data_collector_mcp.bacnet_server.api_edit_bacnet_point",
  575. return_value={"state": 0},
  576. ) as api_point_edit:
  577. result = bacnet_server.collector_bacnet_point_edit(
  578. project_key="dev-01",
  579. id=101,
  580. name="zone_temperature_edited",
  581. object_type="AnalogInput",
  582. object_id=1,
  583. point_id="AI_TEMP_EDITED",
  584. )
  585. self.assertEqual(result, {"state": 0})
  586. payload = api_point_edit.call_args.args[1]
  587. self.assertEqual(api_point_edit.call_args.args[0], "dev-01")
  588. self.assertEqual(payload["id"], 101)
  589. self.assertEqual(payload["name"], "zone_temperature_edited")
  590. self.assertEqual(payload["object_type"], "AnalogInput")
  591. self.assertEqual(payload["object_id"], 1)
  592. self.assertEqual(payload["point_id"], "AI_TEMP_EDITED")
  593. with patch(
  594. "data_collector_mcp.bacnet_server.api_edit_bacnet_point",
  595. return_value={"state": 0},
  596. ) as api_point_edit:
  597. bacnet_server.collector_bacnet_point_edit(
  598. project_key="dev-01",
  599. id=101,
  600. name="zone_temperature_edited",
  601. object_type="AnalogInput",
  602. object_id=1,
  603. scale_ratio=2,
  604. value_offset=1,
  605. group_id=5,
  606. invalid_values="-9999",
  607. valid_range_start=0,
  608. valid_range_end=100,
  609. )
  610. payload = api_point_edit.call_args.args[1]
  611. self.assertEqual(payload["scale_ratio"], 2)
  612. self.assertEqual(payload["value_offset"], 1)
  613. self.assertEqual(payload["group_id"], 5)
  614. self.assertEqual(payload["invalid_values"], "-9999")
  615. self.assertEqual(payload["valid_range_start"], 0)
  616. self.assertEqual(payload["valid_range_end"], 100)
  617. if __name__ == "__main__":
  618. unittest.main()