POST /nodes/request-update-all (admin) does what Update Node does for each approved node: flags update_requested_at for the HTTPS updater and nudges it over MQTT. Pending and rejected nodes are skipped. Admin-only button on the Nodes page reports which nodes weren't reachable over MQTT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
"""
|
|
POST /nodes/request-update-all — the fleet-wide "Update Node": flags every
|
|
approved node (HTTPS) and nudges it over MQTT; pending/rejected nodes are skipped.
|
|
"""
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import app
|
|
from app.internal.auth import require_admin_token, require_service_or_firebase_token
|
|
from app.routers import nodes
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def setup_function():
|
|
app.dependency_overrides[require_admin_token] = lambda: {"admin": True}
|
|
app.dependency_overrides[require_service_or_firebase_token] = lambda: {"admin": True}
|
|
|
|
|
|
def teardown_function():
|
|
app.dependency_overrides.pop(require_admin_token, None)
|
|
app.dependency_overrides.pop(require_service_or_firebase_token, None)
|
|
|
|
|
|
def test_update_all_flags_only_approved_nodes():
|
|
fleet = [
|
|
{"node_id": "a", "approval_status": "approved"},
|
|
{"node_id": "b", "approval_status": "approved"},
|
|
{"node_id": "p", "approval_status": "pending"},
|
|
{"node_id": "r", "approval_status": "rejected"},
|
|
]
|
|
doc_update = AsyncMock()
|
|
send = MagicMock(side_effect=lambda nid, cmd: nid == "a")
|
|
with patch.object(nodes.fstore, "collection_list", AsyncMock(return_value=fleet)), \
|
|
patch.object(nodes.fstore, "doc_update", doc_update), \
|
|
patch.object(nodes.mqtt_handler, "send_command", send):
|
|
r = client.post("/nodes/request-update-all")
|
|
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["requested"] == ["a", "b"]
|
|
assert body["mqtt_sent"] == ["a"]
|
|
assert sorted(body["skipped"]) == ["p", "r"]
|
|
flagged = {c.args[1] for c in doc_update.await_args_list}
|
|
assert flagged == {"a", "b"}
|
|
for c in doc_update.await_args_list:
|
|
assert c.args[2] == {"update_requested_at": body["update_requested_at"]}
|
|
assert {c.args[0] for c in send.call_args_list} == {"a", "b"}
|
|
assert all(c.args[1] == {"action": "node_update"} for c in send.call_args_list)
|
|
|
|
|
|
def test_update_all_requires_admin():
|
|
app.dependency_overrides.pop(require_admin_token, None)
|
|
r = client.post("/nodes/request-update-all")
|
|
assert r.status_code in (401, 403)
|