From 75ca1a951f48515bb351bbafb3a1488efb97ab24 Mon Sep 17 00:00:00 2001 From: Tyler Woods Date: Tue, 26 May 2026 23:12:03 +0000 Subject: [PATCH] test: add optional live controller smoke tests --- .env.example | 8 +++- README.md | 21 ++++++++++ pyproject.toml | 5 +++ tests/test_live_controller.py | 73 +++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 tests/test_live_controller.py diff --git a/.env.example b/.env.example index ac64b72..bfee467 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,9 @@ -# Connection details for the UniFi Controller (for development purposes) +# Connection details for the UniFi Controller (for optional live smoke tests) UNIFI_CONTROLLER_URL=https://192.168.1.1 UNIFI_USERNAME=admin UNIFI_PASSWORD=password -UNIFI_IS_UDM_PRO=false \ No newline at end of file +UNIFI_IS_UDM_PRO=true +UNIFI_VERIFY_SSL=false +UNIFI_REQUEST_TIMEOUT=15 +# Optional: enables the read-only device-list smoke test +UNIFI_SITE_NAME=default diff --git a/README.md b/README.md index ce91a99..5840a3a 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,27 @@ except Exception as e: --- +## Optional Live Controller Smoke Tests + +The normal test suite uses local fakes and does not contact a controller. To run read-only smoke tests against a real UniFi controller, provide environment variables and run the live-marked tests: + +```bash +export UNIFI_CONTROLLER_URL="https://192.168.1.1" +export UNIFI_USERNAME="" +export UNIFI_PASSWORD="" +export UNIFI_IS_UDM_PRO=true +export UNIFI_VERIFY_SSL=false +export UNIFI_REQUEST_TIMEOUT=15 +# Optional: also exercise read-only device listing for one site +export UNIFI_SITE_NAME=default + +python -m pytest -m live -q +``` + +The live tests authenticate, list sites, and optionally list devices for `UNIFI_SITE_NAME`. They are skipped automatically when the required environment variables are absent. + +--- + ## Data Models The library automatically maps JSON API responses to Python data classes located in `unifi_controller_api.models`. Key models include: diff --git a/pyproject.toml b/pyproject.toml index 8cabb20..7f86e20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,11 @@ include = ["unifi_controller_api*"] [tool.setuptools.package-data] "unifi_controller_api" = ["device-models.json"] +[tool.pytest.ini_options] +markers = [ + "live: read-only tests that authenticate to a real UniFi controller when env vars are set", +] + [project.optional-dependencies] dev = [ "pytest>=6.0", diff --git a/tests/test_live_controller.py b/tests/test_live_controller.py new file mode 100644 index 0000000..6345243 --- /dev/null +++ b/tests/test_live_controller.py @@ -0,0 +1,73 @@ +import os +from typing import TypedDict + +import pytest + +from unifi_controller_api import UnifiController + + +class LiveControllerConfig(TypedDict): + controller_url: str + username: str + password: str + is_udm_pro: bool + verify_ssl: bool + request_timeout: float + + +REQUIRED_ENV_VARS = ( + "UNIFI_CONTROLLER_URL", + "UNIFI_USERNAME", + "UNIFI_PASSWORD", +) + + +def _env_bool(name: str, default: bool) -> bool: + value = os.environ.get(name) + if value is None: + return default + return value.strip().lower() in {"1", "true", "yes", "on"} + + +def _require_live_controller_config() -> LiveControllerConfig: + missing = [name for name in REQUIRED_ENV_VARS if not os.environ.get(name)] + if missing: + pytest.skip( + "live UniFi controller config not provided; missing " + + ", ".join(missing) + ) + + return { + "controller_url": os.environ["UNIFI_CONTROLLER_URL"], + "username": os.environ["UNIFI_USERNAME"], + "password": os.environ["UNIFI_PASSWORD"], + "is_udm_pro": _env_bool("UNIFI_IS_UDM_PRO", True), + "verify_ssl": _env_bool("UNIFI_VERIFY_SSL", True), + "request_timeout": float(os.environ.get("UNIFI_REQUEST_TIMEOUT", "15")), + } + + +@pytest.mark.live +def test_live_controller_authenticates_and_lists_sites(): + """Read-only smoke test against a real UniFi controller when env vars are set.""" + controller = UnifiController(**_require_live_controller_config()) + + sites = controller.get_unifi_site(include_health=False, raw=True) + + assert isinstance(sites, list) + assert all(isinstance(site, dict) for site in sites) + + +@pytest.mark.live +def test_live_controller_can_fetch_devices_for_configured_site(): + """Optional read-only device smoke test for a configured site name.""" + site_name = os.environ.get("UNIFI_SITE_NAME") + if not site_name: + pytest.skip("UNIFI_SITE_NAME not provided") + + controller = UnifiController(**_require_live_controller_config()) + + devices = controller.get_unifi_site_device(site_name=site_name, raw=True) + + assert isinstance(devices, list) + assert all(isinstance(device, dict) for device in devices)