| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- import atexit
- import json
- import os
- import socket
- import threading
- import time
- import urllib.error
- import urllib.request
- from contextlib import closing
- import uvicorn
- from app.main import app
- CONFIGURED_BASE_URL = os.getenv("DATA_COLLECTOR_BASE_URL", "").rstrip("/")
- REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "30"))
- BACNET_DEVICE_IP = os.getenv("BACNET_DEVICE_IP", "192.168.75.240")
- BACNET_DEVICE_ID = int(os.getenv("BACNET_DEVICE_ID", "12345"))
- BACNET_PORT = int(os.getenv("BACNET_PORT", "47808"))
- _LOCAL_API_SERVER = None
- def find_free_port() -> int:
- with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
- sock.bind(("127.0.0.1", 0))
- return int(sock.getsockname()[1])
- class LocalApiServer:
- def __init__(self) -> None:
- self.port = find_free_port()
- self.base_url = f"http://127.0.0.1:{self.port}"
- config = uvicorn.Config(app, host="127.0.0.1", port=self.port, log_level="critical", access_log=False)
- self.server = uvicorn.Server(config)
- self.thread = threading.Thread(target=self.server.run, daemon=True)
- def start(self) -> None:
- self.thread.start()
- deadline = time.time() + 10
- while time.time() < deadline:
- try:
- with urllib.request.urlopen(f"{self.base_url}/api/dc-debugtool/health", timeout=1) as response:
- if response.status == 200:
- return
- except OSError:
- time.sleep(0.05)
- raise RuntimeError("local API test server did not start")
- def stop(self) -> None:
- self.server.should_exit = True
- self.thread.join(timeout=5)
- def base_url() -> str:
- global _LOCAL_API_SERVER
- if CONFIGURED_BASE_URL:
- return CONFIGURED_BASE_URL
- if _LOCAL_API_SERVER is None:
- _LOCAL_API_SERVER = LocalApiServer()
- _LOCAL_API_SERVER.start()
- return _LOCAL_API_SERVER.base_url
- def stop_local_api_server() -> None:
- if _LOCAL_API_SERVER is not None:
- _LOCAL_API_SERVER.stop()
- atexit.register(stop_local_api_server)
- def post_json(path: str, payload: dict) -> tuple[int, dict]:
- data = json.dumps(payload).encode("utf-8")
- request = urllib.request.Request(
- f"{base_url()}{path}",
- data=data,
- headers={"Content-Type": "application/json"},
- method="POST",
- )
- try:
- with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT) as response:
- body = response.read().decode("utf-8")
- return response.status, json.loads(body)
- except urllib.error.HTTPError as exc:
- body = exc.read().decode("utf-8")
- return exc.code, json.loads(body)
- def device_payload() -> dict:
- return {
- "ip": BACNET_DEVICE_IP,
- "bacnet_device_id": BACNET_DEVICE_ID,
- "port": BACNET_PORT,
- }
- def assert_response_contract(testcase, status: int, data: dict) -> None:
- testcase.assertEqual(200, status, data)
- testcase.assertIn("code", data)
- testcase.assertIn("msg", data)
- testcase.assertIn("data", data)
- testcase.assertIsInstance(data["data"], dict)
- def assert_bacnet_device(testcase, payload: dict, data: dict) -> None:
- testcase.assertEqual(
- {
- "device_type": "BACnet/IP",
- "ip": payload["ip"],
- "port": payload["port"],
- "bacnet_device_id": payload["bacnet_device_id"],
- },
- data["data"]["device"],
- )
|