from __future__ import annotations from typing import Any import pytest from fastapi.testclient import TestClient from tests.conftest import csrf_headers def create( client: TestClient, path: str, payload: dict[str, object], ) -> dict[str, Any]: response = client.post(path, json=payload, headers=csrf_headers(client)) assert response.status_code == 201, response.text return response.json()["data"] def configure_credentials(client: TestClient, payload: dict[str, object]) -> None: response = client.put( "/api/v1/integrations", json=payload, headers=csrf_headers(client), ) assert response.status_code == 200, response.text def finish_failed(client: TestClient, run_id: str) -> None: repository = client.app.state.container.rotation_repository assert repository.acquire_lease(run_id, "api-test-worker") is True repository.transition_run(run_id, status="running", expected_owner="api-test-worker") item = repository.current_item(run_id) assert item is not None repository.fail_execution( run_id, item.id, error_code="TEST_FAILURE", error_message="test failure", outcome="failed", expected_owner="api-test-worker", ) def test_dashboard_aggregates_instances_groups_and_schedule_state( authenticated_client: TestClient, managed_instance_payload: dict[str, object], group_payload: dict[str, object], ) -> None: client = authenticated_client first = create(client, "/api/v1/instances", managed_instance_payload) second = create( client, "/api/v1/instances", { **managed_instance_payload, "id": "instance-two", "display_name": "Proxy Two", "lightsail_instance_name": "proxy-node-two", "cloudflare_record_name": "two.example.com", "enabled": False, }, ) group = create( client, "/api/v1/instance-groups", {**group_payload, "member_ids": [first["id"], second["id"]]}, ) response = client.get("/api/v1/status?refresh=true") assert response.status_code == 200 data = response.json()["data"] assert data["summary"] == { "instances": 2, "enabled_instances": 1, "groups": 1, "enabled_groups": 1, "active_runs": 0, "next_run_at": group["next_run_at"], } assert data["groups"][0]["member_count"] == 2 assert data["groups"][0]["enabled_member_count"] == 1 assert data["active_run"] is None assert data["recent_runs"] == [] assert data["server_time"].endswith("Z") def test_manual_rotation_routes_history_filters_csrf_and_removed_schedule( authenticated_client: TestClient, integration_payload: dict[str, object], managed_instance_payload: dict[str, object], group_payload: dict[str, object], monkeypatch: pytest.MonkeyPatch, ) -> None: client = authenticated_client configure_credentials(client, integration_payload) first = create(client, "/api/v1/instances", managed_instance_payload) second = create( client, "/api/v1/instances", { **managed_instance_payload, "id": "instance-two", "display_name": "Proxy Two", "lightsail_instance_name": "proxy-node-two", "cloudflare_record_name": "two.example.com", }, ) group = create( client, "/api/v1/instance-groups", {**group_payload, "member_ids": [second["id"]]}, ) rotation_service = client.app.state.container.rotation_service spawned: list[str] = [] monkeypatch.setattr(rotation_service, "_spawn", spawned.append) denied = client.post(f"/api/v1/instances/{first['id']}/rotations") assert denied.status_code == 403 instance_run = client.post( f"/api/v1/instances/{first['id']}/rotations", headers=csrf_headers(client), ) assert instance_run.status_code == 202, instance_run.text instance_run_id = instance_run.json()["data"]["id"] assert instance_run.headers["location"] == f"/api/v1/rotations/{instance_run_id}" assert spawned == [instance_run_id] finish_failed(client, instance_run_id) group_run = client.post( f"/api/v1/instance-groups/{group['id']}/rotations", headers=csrf_headers(client), ) assert group_run.status_code == 202, group_run.text group_run_id = group_run.json()["data"]["id"] assert spawned[-1] == group_run_id by_instance = client.get(f"/api/v1/rotations?instance_id={first['id']}").json() assert by_instance["pagination"]["total"] == 1 assert by_instance["data"][0]["id"] == instance_run_id by_group = client.get(f"/api/v1/rotations?group_id={group['id']}").json() assert by_group["pagination"]["total"] == 1 assert by_group["data"][0]["id"] == group_run_id failed = client.get("/api/v1/rotations?status=failed").json() assert failed["pagination"]["total"] == 1 assert failed["data"][0]["error_code"] == "TEST_FAILURE" assert failed["summary"] == { "total": 2, "completed": 1, "succeeded": 0, "latest_success_at": None, } detail = client.get(f"/api/v1/rotations/{group_run_id}") assert detail.status_code == 200 assert detail.json()["data"]["items"][0]["instance_id"] == second["id"] assert detail.json()["data"]["events"][0]["stage"] == "queued" assert client.get("/api/v1/schedule").status_code == 404 assert ( client.put( "/api/v1/schedule", json={"enabled": True, "interval_minutes": 15}, headers=csrf_headers(client), ).status_code == 404 )