nix-builder-autoscaler/agent/nix_builder_autoscaler/tests/test_reservations_api.py
2026-02-27 12:46:32 +01:00

152 lines
5.4 KiB
Python

"""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"