168 lines
5.6 KiB
Python
168 lines
5.6 KiB
Python
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
|
|
)
|