@@ -0,0 +1 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
@@ -0,0 +1,36 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from devplacepy.database import get_setting, set_setting
|
||||
from devplacepy.database.nodes import create_device
|
||||
from devplacepy.main import app
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
JSON = {"Accept": "application/json"}
|
||||
|
||||
|
||||
def admin_headers(who: dict) -> dict:
|
||||
return {**who["headers"], **JSON}
|
||||
|
||||
|
||||
def make_admin() -> dict:
|
||||
return make_member(consent=False, enabled=False, role="Admin")
|
||||
|
||||
|
||||
def make_device(owner_uid: str, label: str = "Home laptop") -> dict:
|
||||
return create_device(owner_uid, label=label, kind="native")[0]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def client():
|
||||
previous = get_setting("nodes_enabled", "0")
|
||||
with TestClient(app) as test_client:
|
||||
yield test_client
|
||||
set_setting("nodes_enabled", previous)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def admin():
|
||||
return make_admin()
|
||||
@@ -0,0 +1,34 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.database.nodes import list_flags, raise_flag
|
||||
from tests.api.admin.nodes.conftest import admin_headers, make_admin, make_device
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
|
||||
def test_member_cannot_resolve(client):
|
||||
member = make_member()
|
||||
assert client.post("/admin/nodes/flags/x/resolve", headers=admin_headers(member)).status_code == 403
|
||||
|
||||
|
||||
def test_resolve_closes_the_flag_once(client, admin):
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
flag = raise_flag("refusal_mismatch", device_uid=device["uid"], user_uid=owner["uid"], detail="test")
|
||||
listed = client.get("/admin/nodes", headers=admin_headers(admin)).json()["flags"]
|
||||
assert flag["uid"] in {item["uid"] for item in listed}
|
||||
assert client.post(f"/admin/nodes/flags/{flag['uid']}/resolve", headers=admin_headers(admin)).status_code == 200
|
||||
assert flag["uid"] not in {item["uid"] for item in list_flags()}
|
||||
assert client.post(f"/admin/nodes/flags/{flag['uid']}/resolve", headers=admin_headers(admin)).status_code == 409
|
||||
|
||||
|
||||
def test_unknown_flag_is_404(client, admin):
|
||||
assert client.post("/admin/nodes/flags/nope/resolve", headers=admin_headers(admin)).status_code == 404
|
||||
|
||||
|
||||
def test_junior_admin_cannot_resolve_a_senior_admins_flag(client):
|
||||
senior = make_admin()
|
||||
junior = make_admin()
|
||||
device = make_device(senior["uid"])
|
||||
flag = raise_flag("integrity_failure", device_uid=device["uid"], user_uid=senior["uid"], detail="test")
|
||||
client.post(f"/admin/nodes/flags/{flag['uid']}/resolve", headers=admin_headers(junior))
|
||||
assert flag["uid"] in {item["uid"] for item in list_flags()}
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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",
|
||||
}
|
||||
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
|
||||
@@ -0,0 +1,24 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.database import get_setting, set_setting
|
||||
from tests.api.admin.nodes.conftest import admin_headers
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
|
||||
def test_member_cannot_flip_the_switch(client):
|
||||
member = make_member()
|
||||
assert client.post("/admin/nodes/killswitch", json={"enabled": True}, headers=admin_headers(member)).status_code == 403
|
||||
|
||||
|
||||
def test_admin_sets_and_flips_the_switch(client, admin):
|
||||
previous = get_setting("nodes_enabled", "0")
|
||||
try:
|
||||
set_setting("nodes_enabled", "0")
|
||||
on = client.post("/admin/nodes/killswitch", json={"enabled": True}, headers=admin_headers(admin))
|
||||
assert on.json()["data"] == {"enabled": True}
|
||||
assert get_setting("nodes_enabled", "0") == "1"
|
||||
flipped = client.post("/admin/nodes/killswitch", json={}, headers=admin_headers(admin))
|
||||
assert flipped.json()["data"] == {"enabled": False}
|
||||
assert get_setting("nodes_enabled", "1") == "0"
|
||||
finally:
|
||||
set_setting("nodes_enabled", previous)
|
||||
@@ -0,0 +1,36 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.database import get_setting, set_setting
|
||||
from devplacepy.database.nodes import LIMITS
|
||||
from tests.api.admin.nodes.conftest import admin_headers
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
KEYS = ("nodes_max_total", "nodes_daily_quota_user", "nodes_social_via_nodes")
|
||||
|
||||
|
||||
def test_member_cannot_change_settings(client):
|
||||
member = make_member()
|
||||
assert client.post("/admin/nodes/settings", json={"nodes_max_total": 1}, headers=admin_headers(member)).status_code == 403
|
||||
|
||||
|
||||
def test_only_submitted_fields_change_and_ceilings_clamp(client, admin):
|
||||
previous = {key: get_setting(key, "") for key in KEYS}
|
||||
try:
|
||||
response = client.post(
|
||||
"/admin/nodes/settings",
|
||||
json={"nodes_max_total": LIMITS.max_connected_nodes + 500, "nodes_social_via_nodes": "1"},
|
||||
headers=admin_headers(admin),
|
||||
)
|
||||
data = response.json()["data"]
|
||||
assert "nodes_max_total" in data["clamped"]
|
||||
assert get_setting("nodes_max_total", "") == str(LIMITS.max_connected_nodes)
|
||||
assert get_setting("nodes_social_via_nodes", "") == "1"
|
||||
assert get_setting("nodes_daily_quota_user", "") == previous["nodes_daily_quota_user"]
|
||||
finally:
|
||||
for key, value in previous.items():
|
||||
set_setting(key, value)
|
||||
|
||||
|
||||
def test_negative_and_unknown_values_are_rejected(client, admin):
|
||||
assert client.post("/admin/nodes/settings", json={"nodes_max_total": -1}, headers=admin_headers(admin)).status_code in (400, 422)
|
||||
assert client.post("/admin/nodes/settings", json={"nodes_social_via_nodes": "2"}, headers=admin_headers(admin)).status_code in (400, 422)
|
||||
@@ -0,0 +1,47 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from devplacepy.database.nodes import get_device
|
||||
from tests.api.admin.nodes.conftest import admin_headers, make_admin, make_device
|
||||
from tests.api.nodes.conftest import make_member
|
||||
|
||||
|
||||
def test_member_cannot_suspend(client):
|
||||
member = make_member()
|
||||
device = make_device(member["uid"])
|
||||
response = client.post(f"/admin/nodes/devices/{device['uid']}/suspend", json={"minutes": 5}, headers=admin_headers(member))
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_suspend_and_unsuspend_round_trip(client, admin):
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
url = f"/admin/nodes/devices/{device['uid']}"
|
||||
response = client.post(f"{url}/suspend", json={"minutes": 30, "reason": "noisy"}, headers=admin_headers(admin))
|
||||
assert response.json()["data"] == {"uid": device["uid"], "minutes": 30}
|
||||
row = get_device(device["uid"])
|
||||
assert row["status"] == "suspended"
|
||||
assert row["suspend_reason"] == "noisy"
|
||||
assert client.post(f"{url}/unsuspend", headers=admin_headers(admin)).status_code == 200
|
||||
assert get_device(device["uid"])["status"] == "active"
|
||||
assert client.post(f"{url}/unsuspend", headers=admin_headers(admin)).status_code == 409
|
||||
|
||||
|
||||
def test_minutes_are_bounded(client, admin):
|
||||
owner = make_member()
|
||||
device = make_device(owner["uid"])
|
||||
url = f"/admin/nodes/devices/{device['uid']}/suspend"
|
||||
for minutes in (0, 10081):
|
||||
assert client.post(url, json={"minutes": minutes}, headers=admin_headers(admin)).status_code in (400, 422)
|
||||
|
||||
|
||||
def test_unknown_device_is_404(client, admin):
|
||||
response = client.post("/admin/nodes/devices/nope/suspend", json={"minutes": 5}, headers=admin_headers(admin))
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_junior_admin_cannot_suspend_a_senior_admins_device(client):
|
||||
senior = make_admin()
|
||||
junior = make_admin()
|
||||
device = make_device(senior["uid"])
|
||||
client.post(f"/admin/nodes/devices/{device['uid']}/suspend", json={"minutes": 5}, headers=admin_headers(junior))
|
||||
assert get_device(device["uid"])["status"] == "active"
|
||||
Reference in New Issue
Block a user