common.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import atexit
  2. import json
  3. import os
  4. import socket
  5. import threading
  6. import time
  7. import urllib.error
  8. import urllib.request
  9. from contextlib import closing
  10. import uvicorn
  11. from app.main import app
  12. CONFIGURED_BASE_URL = os.getenv("DATA_COLLECTOR_BASE_URL", "").rstrip("/")
  13. REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "30"))
  14. BACNET_DEVICE_IP = os.getenv("BACNET_DEVICE_IP", "192.168.75.240")
  15. BACNET_DEVICE_ID = int(os.getenv("BACNET_DEVICE_ID", "12345"))
  16. BACNET_PORT = int(os.getenv("BACNET_PORT", "47808"))
  17. _LOCAL_API_SERVER = None
  18. def find_free_port() -> int:
  19. with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
  20. sock.bind(("127.0.0.1", 0))
  21. return int(sock.getsockname()[1])
  22. class LocalApiServer:
  23. def __init__(self) -> None:
  24. self.port = find_free_port()
  25. self.base_url = f"http://127.0.0.1:{self.port}"
  26. config = uvicorn.Config(app, host="127.0.0.1", port=self.port, log_level="critical", access_log=False)
  27. self.server = uvicorn.Server(config)
  28. self.thread = threading.Thread(target=self.server.run, daemon=True)
  29. def start(self) -> None:
  30. self.thread.start()
  31. deadline = time.time() + 10
  32. while time.time() < deadline:
  33. try:
  34. with urllib.request.urlopen(f"{self.base_url}/api/dc-debugtool/health", timeout=1) as response:
  35. if response.status == 200:
  36. return
  37. except OSError:
  38. time.sleep(0.05)
  39. raise RuntimeError("local API test server did not start")
  40. def stop(self) -> None:
  41. self.server.should_exit = True
  42. self.thread.join(timeout=5)
  43. def base_url() -> str:
  44. global _LOCAL_API_SERVER
  45. if CONFIGURED_BASE_URL:
  46. return CONFIGURED_BASE_URL
  47. if _LOCAL_API_SERVER is None:
  48. _LOCAL_API_SERVER = LocalApiServer()
  49. _LOCAL_API_SERVER.start()
  50. return _LOCAL_API_SERVER.base_url
  51. def stop_local_api_server() -> None:
  52. if _LOCAL_API_SERVER is not None:
  53. _LOCAL_API_SERVER.stop()
  54. atexit.register(stop_local_api_server)
  55. def post_json(path: str, payload: dict) -> tuple[int, dict]:
  56. data = json.dumps(payload).encode("utf-8")
  57. request = urllib.request.Request(
  58. f"{base_url()}{path}",
  59. data=data,
  60. headers={"Content-Type": "application/json"},
  61. method="POST",
  62. )
  63. try:
  64. with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT) as response:
  65. body = response.read().decode("utf-8")
  66. return response.status, json.loads(body)
  67. except urllib.error.HTTPError as exc:
  68. body = exc.read().decode("utf-8")
  69. return exc.code, json.loads(body)
  70. def device_payload() -> dict:
  71. return {
  72. "ip": BACNET_DEVICE_IP,
  73. "bacnet_device_id": BACNET_DEVICE_ID,
  74. "port": BACNET_PORT,
  75. }
  76. def assert_response_contract(testcase, status: int, data: dict) -> None:
  77. testcase.assertEqual(200, status, data)
  78. testcase.assertIn("code", data)
  79. testcase.assertIn("msg", data)
  80. testcase.assertIn("data", data)
  81. testcase.assertIsInstance(data["data"], dict)
  82. def assert_bacnet_device(testcase, payload: dict, data: dict) -> None:
  83. testcase.assertEqual(
  84. {
  85. "device_type": "BACnet/IP",
  86. "ip": payload["ip"],
  87. "port": payload["port"],
  88. "bacnet_device_id": payload["bacnet_device_id"],
  89. },
  90. data["data"]["device"],
  91. )