| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- """Integration tests against a live room-cal API server.
- Reads config from <project_root>/api.env (two levels above code/).
- Skipped automatically when the URL is not configured.
- Run with:
- uv run --only-group integration pytest tests_integration/
- """
- from __future__ import annotations
- from datetime import date, timedelta
- from pathlib import Path
- import requests
- import pytest
- from custom_components.room_cal_ha.backends.api import _parse_reservation
- _ENV_FILE = Path(__file__).parents[2] / "api.env"
- def _read_env() -> dict[str, str]:
- result: dict[str, str] = {}
- if _ENV_FILE.exists():
- for line in _ENV_FILE.read_text().splitlines():
- line = line.strip()
- if "=" in line and not line.startswith("#"):
- k, _, v = line.partition("=")
- result[k.strip()] = v.strip()
- return result
- @pytest.fixture
- def api_cfg():
- env = _read_env()
- url = env.get("url", "").rstrip("/")
- if not url:
- pytest.skip("api.env: url not set")
- return {"url": url, "token": env.get("token", "")}
- @pytest.fixture
- def headers(api_cfg):
- return {"Authorization": f"Bearer {api_cfg['token']}"}
- def _reservation_url(base: str, *, days_back: int = 0, days_forward: int = 90) -> str:
- today = date.today()
- start = today - timedelta(days=days_back)
- end = today + timedelta(days=days_forward)
- return f"{base}/reservation/{start}/{end}"
- # ── tests ─────────────────────────────────────────────────────────────────────
- def test_connection(api_cfg, headers):
- """API responds with a non-error HTTP status."""
- resp = requests.get(_reservation_url(api_cfg["url"]), headers=headers, timeout=10)
- assert resp.status_code not in (401, 403), f"Auth rejected: HTTP {resp.status_code}"
- assert resp.status_code < 500, f"Server error: HTTP {resp.status_code}"
- def test_reservation_response_is_json(api_cfg, headers):
- """Reservation endpoint returns a valid JSON object."""
- resp = requests.get(_reservation_url(api_cfg["url"]), headers=headers, timeout=10)
- resp.raise_for_status()
- data = resp.json()
- assert isinstance(data, dict), "Expected a JSON object"
- def test_reservations_exist_in_window(api_cfg, headers):
- """At least one reservation exists within ±90 days of today."""
- resp = requests.get(
- _reservation_url(api_cfg["url"], days_back=90, days_forward=90),
- headers=headers,
- timeout=10,
- )
- resp.raise_for_status()
- data = resp.json()
- non_null = [v for v in data.values() if v is not None]
- assert len(non_null) > 0, "Expected at least one reservation in the ±90-day window"
- def test_parse_reservation_fields(api_cfg, headers):
- """_parse_reservation correctly maps raw API dicts to ReservationData."""
- resp = requests.get(
- _reservation_url(api_cfg["url"], days_back=90, days_forward=90),
- headers=headers,
- timeout=10,
- )
- resp.raise_for_status()
- data = resp.json()
- parsed = [_parse_reservation(v) for v in data.values() if v is not None]
- assert len(parsed) > 0, "No reservations to parse"
- for res in parsed:
- assert res is not None
- assert res.check_in_date is not None
- assert res.check_out_date is not None
- assert res.check_in_date <= res.check_out_date
- def test_invalid_token_returns_401_or_403(api_cfg):
- """A bad token is rejected with a 401 or 403."""
- bad_headers = {"Authorization": "Bearer invalid-token-xyz"}
- resp = requests.get(_reservation_url(api_cfg["url"]), headers=bad_headers, timeout=10)
- assert resp.status_code in (401, 403), f"Expected auth error, got HTTP {resp.status_code}"
|