2026-10-16 07:20:44 +02:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
from devplacepy.database.nodes import LIMITS
|
|
|
|
|
from tests.api.admin.nodes.conftest import admin_headers, make_device
|
|
|
|
|
from tests.api.nodes.conftest import make_member
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_guest_is_refused(client):
|
|
|
|
|
assert client.get("/admin/nodes", headers={"Accept": "application/json"}).status_code == 401
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_member_is_refused(client):
|
|
|
|
|
member = make_member()
|
|
|
|
|
assert client.get("/admin/nodes", headers=admin_headers(member)).status_code == 403
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_admin_overview_shape(client, admin):
|
|
|
|
|
owner = make_member()
|
|
|
|
|
device = make_device(owner["uid"], "Overview laptop")
|
|
|
|
|
body = client.get("/admin/nodes", headers=admin_headers(admin)).json()
|
|
|
|
|
assert set(body["pool"]) >= {"online", "connected", "capacity", "open_circuits"}
|
|
|
|
|
assert body["queue"]["max_depth"] == LIMITS.max_queue_depth
|
|
|
|
|
assert body["limits"]["max_connected_nodes"] == LIMITS.max_connected_nodes
|
|
|
|
|
assert {item["key"] for item in body["settings"]} == {
|
|
|
|
|
"nodes_max_total",
|
|
|
|
|
"nodes_daily_quota_user",
|
|
|
|
|
"nodes_daily_quota_guest",
|
|
|
|
|
"nodes_cache_ttl_seconds",
|
|
|
|
|
"nodes_daily_social_user",
|
|
|
|
|
"nodes_social_via_nodes",
|
2026-10-17 02:23:43 +02:00
|
|
|
"nodes_allow_unknown_class",
|
|
|
|
|
"nodes_redundancy_percent",
|
2026-10-16 07:20:44 +02:00
|
|
|
}
|
|
|
|
|
listed = {row["uid"]: row for row in body["devices"]}
|
|
|
|
|
assert listed[device["uid"]]["owner_username"] == owner["username"]
|
|
|
|
|
assert len(body["devices"]) <= 100
|
|
|
|
|
assert "secret_hash" not in listed[device["uid"]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_html_page_renders(client, admin):
|
|
|
|
|
response = client.get("/admin/nodes", headers=admin["headers"])
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
assert "Community nodes" in response.text
|
|
|
|
|
assert "data-live=\"summary\"" in response.text
|
2026-10-17 02:23:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_overview_carries_circuits_outcomes_lifetime_geo_and_cache(client, admin):
|
|
|
|
|
from devplacepy.database.nodes import flush_counters, save_circuits
|
|
|
|
|
|
|
|
|
|
flush_counters({"outcome:reddit:residential:ok": 3, "outcome:reddit:residential:blocked": 1, "kicked": 2})
|
|
|
|
|
save_circuits([{"key": "destination:reddit.com", "failures": 9, "state": "open", "opened_at": 4_000_000_000.0}])
|
|
|
|
|
try:
|
|
|
|
|
body = client.get("/admin/nodes", headers=admin_headers(admin)).json()
|
|
|
|
|
row = next(item for item in body["outcomes"] if item["adapter"] == "reddit" and item["asn_class"] == "residential")
|
|
|
|
|
assert row["ok"] >= 3 and row["blocked"] >= 1 and row["total"] == row["ok"] + row["blocked"] + row["failed"]
|
|
|
|
|
assert body["lifetime"]["kicked"] >= 2
|
|
|
|
|
assert not any(name.startswith("outcome:") for name in body["lifetime"])
|
|
|
|
|
assert body["circuits"][0]["key"] == "destination:reddit.com" and body["circuits"][0]["state"] == "open"
|
|
|
|
|
assert set(body["geo"]) >= {"available", "path"}
|
|
|
|
|
assert set(body["cache"]) == {"entries", "bytes"}
|
|
|
|
|
finally:
|
|
|
|
|
save_circuits([])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_html_shows_the_new_sections_and_the_empty_outcome_state(client, admin):
|
|
|
|
|
text = client.get("/admin/nodes", headers=admin["headers"]).text
|
|
|
|
|
assert "Circuit breakers" in text and "Success by address class" in text and "Address database" in text
|