agent: implement api
This commit is contained in:
parent
b63d69c81d
commit
33ba248c49
4 changed files with 662 additions and 34 deletions
|
|
@ -1 +1,152 @@
|
|||
"""Reservations API unit tests — Plan 04."""
|
||||
"""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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue