"""Reservations API unit tests.""" from __future__ import annotations from datetime import UTC, datetime from fastapi.testclient import TestClient from nix_builder_autoscaler.api import create_app from nix_builder_autoscaler.config import AppConfig, CapacityConfig from nix_builder_autoscaler.metrics import MetricsRegistry from nix_builder_autoscaler.providers.clock import FakeClock from nix_builder_autoscaler.state_db import StateDB def _make_client() -> tuple[TestClient, StateDB, FakeClock, MetricsRegistry]: clock = FakeClock() db = StateDB(":memory:", clock=clock) db.init_schema() db.init_slots("slot", 3, "x86_64-linux", "all") config = AppConfig(capacity=CapacityConfig(reservation_ttl_seconds=1200)) metrics = MetricsRegistry() app = create_app(db, config, clock, metrics) return TestClient(app), db, clock, metrics def test_create_reservation_returns_200() -> None: client, _, _, _ = _make_client() response = client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "test"}) assert response.status_code == 200 body = response.json() assert body["reservation_id"].startswith("resv_") assert body["phase"] == "pending" assert body["system"] == "x86_64-linux" assert "created_at" in body assert "expires_at" in body def test_get_reservation_returns_current_phase() -> None: client, _, _, _ = _make_client() created = client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "test"}) reservation_id = created.json()["reservation_id"] response = client.get(f"/v1/reservations/{reservation_id}") assert response.status_code == 200 assert response.json()["phase"] == "pending" def test_release_reservation_moves_to_released() -> None: client, _, _, _ = _make_client() created = client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "test"}) reservation_id = created.json()["reservation_id"] response = client.post(f"/v1/reservations/{reservation_id}/release") assert response.status_code == 200 assert response.json()["phase"] == "released" def test_unknown_reservation_returns_404() -> None: client, _, _, _ = _make_client() response = client.get("/v1/reservations/resv_nonexistent") assert response.status_code == 404 body = response.json() assert body["error"]["code"] == "not_found" assert "request_id" in body def test_malformed_body_returns_422() -> None: client, _, _, _ = _make_client() response = client.post("/v1/reservations", json={"invalid": "data"}) assert response.status_code == 422 def test_list_reservations_returns_all() -> None: client, _, _, _ = _make_client() client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "a"}) client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "b"}) response = client.get("/v1/reservations") assert response.status_code == 200 assert len(response.json()) == 2 def test_list_reservations_filters_by_phase() -> None: client, _, _, _ = _make_client() created = client.post("/v1/reservations", json={"system": "x86_64-linux", "reason": "test"}) reservation_id = created.json()["reservation_id"] client.post(f"/v1/reservations/{reservation_id}/release") response = client.get("/v1/reservations?phase=released") assert response.status_code == 200 body = response.json() assert len(body) == 1 assert body[0]["phase"] == "released" def test_list_slots_returns_all_slots() -> None: client, _, _, _ = _make_client() response = client.get("/v1/slots") assert response.status_code == 200 assert len(response.json()) == 3 def test_state_summary_returns_counts() -> None: client, _, _, _ = _make_client() response = client.get("/v1/state/summary") assert response.status_code == 200 body = response.json() assert body["slots"]["total"] == 3 assert body["slots"]["empty"] == 3 def test_health_live_returns_ok() -> None: client, _, _, _ = _make_client() response = client.get("/health/live") assert response.status_code == 200 assert response.json()["status"] == "ok" def test_health_ready_returns_ok_when_no_checks() -> None: client, _, _, _ = _make_client() response = client.get("/health/ready") assert response.status_code == 200 assert response.json()["status"] == "ok" def test_metrics_returns_prometheus_text() -> None: client, _, _, metrics = _make_client() metrics.counter("autoscaler_test_counter", {}, 1.0) response = client.get("/metrics") assert response.status_code == 200 assert "text/plain" in response.headers["content-type"] assert "autoscaler_test_counter" in response.text def test_capacity_hint_accepted() -> None: client, _, _, _ = _make_client() response = client.post( "/v1/hints/capacity", json={ "builder": "buildbot", "queued": 2, "running": 4, "system": "x86_64-linux", "timestamp": datetime(2026, 1, 1, tzinfo=UTC).isoformat(), }, ) assert response.status_code == 200 assert response.json()["status"] == "accepted" def test_release_nonexistent_returns_404() -> None: client, _, _, _ = _make_client() response = client.post("/v1/reservations/resv_nonexistent/release") assert response.status_code == 404 assert response.json()["error"]["code"] == "not_found"