feat: add agent management

This commit is contained in:
lussierj committed 2026-08-27 17:00:56 +00:00
1 parent d9f0da694b
commit b9b5e5e36f
17 files changed
+1644 -1

No files matched your search

@@ -0,0 +1,141 @@
"""Frontend contract tests for Agent Management."""
import unittest
from pathlib import Path
from fastapi import FastAPI
from fastapi.testclient import TestClient
from app.frontend.router import router
from app.identity import get_current_user, required_page_permission
STATIC_ROOT = Path(__file__).parents[1] / "app" / "frontend" / "static"
class AgentManagementFrontendTests(unittest.TestCase):
def setUp(self):
self.app = FastAPI()
self.app.include_router(router)
self.client = TestClient(self.app)
def authenticate(self, permissions):
self.app.dependency_overrides[get_current_user] = lambda: {
"id": "00000000-0000-4000-8000-000000009001",
"username": "agent-test-user",
"display_name": "Agent Test User",
"role": "Test Role",
"permissions": tuple(permissions),
}
def test_page_requires_agents_view(self):
self.authenticate(("unrelated.permission",))
self.assertEqual(
self.client.get("/agent/management").status_code,
403,
)
self.authenticate(("agents.view",))
response = self.client.get("/agent/management")
self.assertEqual(response.status_code, 200)
self.assertIn("Agent Management", response.text)
self.assertIn('href="/agent/management"', response.text)
def test_manage_controls_follow_agents_manage_permission(self):
self.authenticate(("agents.view",))
restricted = self.client.get("/agent/management").text
self.assertNotIn('id="agentCreateForm"', restricted)
restricted_text = " ".join(restricted.split())
self.assertIn(
"cannot create Agent enrollments.",
restricted_text,
)
self.authenticate(("agents.view", "agents.manage"))
managed = self.client.get("/agent/management").text
self.assertIn('id="agentCreateForm"', managed)
self.assertIn('id="agentSite"', managed)
self.assertIn('id="agentZone"', managed)
self.assertIn('id="agentDisplayName"', managed)
self.assertIn("Linux x64 — coming later", managed)
self.assertIn("Linux ARM64 / Raspberry Pi — coming later", managed)
def test_route_and_asset_contract(self):
self.assertEqual(
required_page_permission("/agent/management"),
"agents.view",
)
self.authenticate(("agents.view",))
page = self.client.get("/agent/management")
self.assertIn(
"/assets/minerops-agent-management.js",
page.text,
)
asset = self.client.get(
"/assets/minerops-agent-management.js"
)
self.assertEqual(asset.status_code, 200)
self.assertEqual(
asset.headers.get("cache-control"),
"no-store",
)
def test_javascript_is_secret_conscious_and_safe_dom_only(self):
source = (
STATIC_ROOT / "agent-management.js"
).read_text(encoding="utf-8")
self.assertIn('api("/api/agents")', source)
self.assertIn('api("/api/agent-enrollments")', source)
self.assertIn(
'api("/api/agent-enrollments", {',
source,
)
for unsafe in (
"innerHTML",
"outerHTML",
"insertAdjacentHTML",
"localStorage",
"sessionStorage",
"console.log",
):
self.assertNotIn(unsafe, source)
self.assertIn(
"el.agentEnrollmentToken.textContent",
source,
)
self.assertIn(
"el.agentEnrollmentToken.textContent = \"\"",
source,
)
def test_agent_management_quiet_actions_have_visible_text(self):
css = (STATIC_ROOT / "app.css").read_text(encoding="utf-8")
self.assertIn(
".agent-management-columns .quiet-button,",
css,
)
self.assertIn(
".agent-enrollment-panel .quiet-button {",
css,
)
self.assertIn(
"color: var(--ink);",
css,
)
if __name__ == "__main__":
unittest.main()
+120
View File
@@ -0,0 +1,120 @@
"""Focused tests for the Agent Management read model."""
import unittest
from uuid import UUID
from app.agent_registry.service import AgentRegistryService
from app.identity import required_api_permission
SITE_ID = UUID("00000000-0000-4000-8000-000000001800")
ZONE_ID = UUID("00000000-0000-4000-8000-000000001801")
class FakeAgentRegistryRepository:
def list_registry(self):
return {
"sites": [
{
"id": SITE_ID,
"name": "Test Site",
}
],
"zones": [
{
"id": ZONE_ID,
"site_id": SITE_ID,
"site_name": "Test Site",
"name": "Network A",
"description": None,
"is_enabled": True,
"assigned_agent_id": "agent-test",
"networks": ["10.50.0.0/24"],
}
],
"agents": [
{
"agent_id": "agent-test",
"display_name": "Test Agent",
"site_id": SITE_ID,
"site_name": "Test Site",
"agent_zone_id": ZONE_ID,
"agent_zone_name": "Network A",
"agent_zone_enabled": True,
"hostname": "test-agent",
"version": "1.0.0-alpha",
"ip_address": "10.50.0.10",
"last_checkin_at": "2026-08-26T20:00:00+00:00",
"credential_state": "active",
"credential_created_at": "2026-08-26T19:00:00+00:00",
"credential_revoked_at": None,
}
],
}
class AgentRegistryServiceTests(unittest.TestCase):
def test_projection_is_factual_and_zone_aware(self):
data = AgentRegistryService(
FakeAgentRegistryRepository()
).list_registry()
self.assertEqual(len(data.agents), 1)
self.assertEqual(len(data.sites), 1)
self.assertEqual(len(data.zones), 1)
agent = data.agents[0]
self.assertEqual(agent.agent_id, "agent-test")
self.assertEqual(agent.credential_state, "active")
self.assertEqual(agent.routing_state, "assigned")
self.assertEqual(
data.zones[0].networks,
["10.50.0.0/24"],
)
def test_unassigned_and_disabled_routing_are_not_fabricated_online_states(self):
repository = FakeAgentRegistryRepository()
repository.list_registry = lambda: {
"sites": [],
"zones": [],
"agents": [
{
"agent_id": "legacy",
"display_name": None,
"site_id": None,
"site_name": "Legacy Site",
"agent_zone_id": None,
"agent_zone_name": None,
"agent_zone_enabled": None,
"hostname": None,
"version": None,
"ip_address": None,
"last_checkin_at": "2026-08-26T20:00:00+00:00",
"credential_state": "legacy",
"credential_created_at": None,
"credential_revoked_at": None,
}
],
}
data = AgentRegistryService(repository).list_registry()
self.assertEqual(
data.agents[0].routing_state,
"unassigned",
)
self.assertEqual(
data.agents[0].credential_state,
"legacy",
)
def test_api_permission_mapping_is_explicit(self):
self.assertEqual(
required_api_permission("GET", "/api/agents"),
"agents.view",
)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,194 @@
"""PostgreSQL integration test for the Agent registry projection."""
import unittest
from uuid import uuid4
from app.agent_registry.repository import (
AgentRegistryRepository,
default_connection_factory,
)
from app.agent_registry.service import AgentRegistryService
class AgentRegistryPostgresTests(unittest.TestCase):
def setUp(self):
self.site_id = uuid4()
self.zone_id = uuid4()
self.agent_id = f"agent-registry-{uuid4().hex}"
connection = default_connection_factory()
try:
with connection.cursor() as cur:
cur.execute(
"""
INSERT INTO sites (id, name)
VALUES (%s, %s)
""",
(
self.site_id,
"Registry Integration Site",
),
)
cur.execute(
"""
INSERT INTO agent_zones (
id,
site_id,
name,
description,
is_enabled
)
VALUES (%s, %s, %s, %s, true)
""",
(
self.zone_id,
self.site_id,
"Registry Network",
"Integration-test network",
),
)
cur.execute(
"""
INSERT INTO agent_zone_network_ranges (
agent_zone_id,
network
)
VALUES (%s, '10.251.0.0/24'::cidr)
""",
(self.zone_id,),
)
cur.execute(
"""
INSERT INTO minerops_site_agents (
agent_id,
site_name,
site_id,
agent_zone_id,
display_name,
hostname,
version,
ip_address,
agent_secret_hash,
credential_created_at
)
VALUES (
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s,
%s,
now()
)
""",
(
self.agent_id,
"Registry Integration Site",
self.site_id,
self.zone_id,
"Registry Test Agent",
"registry-agent",
"1.0.0-alpha",
"10.251.0.10",
"a" * 64,
),
)
connection.commit()
finally:
connection.close()
def tearDown(self):
connection = default_connection_factory()
try:
with connection.cursor() as cur:
cur.execute(
"""
DELETE FROM minerops_site_agents
WHERE agent_id = %s
""",
(self.agent_id,),
)
cur.execute(
"""
DELETE FROM agent_zone_network_ranges
WHERE agent_zone_id = %s
""",
(self.zone_id,),
)
cur.execute(
"""
DELETE FROM agent_zones
WHERE id = %s
""",
(self.zone_id,),
)
cur.execute(
"""
DELETE FROM sites
WHERE id = %s
""",
(self.site_id,),
)
connection.commit()
finally:
connection.close()
def test_registry_joins_site_zone_network_and_redacts_secret_hash(self):
data = AgentRegistryService(
AgentRegistryRepository()
).list_registry()
agents = [
item
for item in data.agents
if item.agent_id == self.agent_id
]
self.assertEqual(len(agents), 1)
agent = agents[0]
self.assertEqual(
agent.site_name,
"Registry Integration Site",
)
self.assertEqual(
agent.agent_zone_name,
"Registry Network",
)
self.assertEqual(agent.credential_state, "active")
self.assertEqual(agent.routing_state, "assigned")
zones = [
zone
for zone in data.zones
if zone.id == self.zone_id
]
self.assertEqual(len(zones), 1)
self.assertEqual(
zones[0].networks,
["10.251.0.0/24"],
)
serialized = data.model_dump_json()
self.assertNotIn("agent_secret_hash", serialized)
self.assertNotIn("a" * 64, serialized)
if __name__ == "__main__":
unittest.main()