feat: add autonomous fiscal control panel
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
from central.tests.test_control_plane import setup_db
|
||||
from central.scripts.generate_control_panel_fixture import generate_fixture
|
||||
from central.app.models import AgentEvent, AgentSnapshot, Installation, Job
|
||||
|
||||
|
||||
def test_fixture_creates_one_hundred_varied_installations_and_events():
|
||||
session_factory = setup_db()
|
||||
with session_factory() as session:
|
||||
result = generate_fixture(session, agent_count=100, seed=11)
|
||||
assert result == {"agents": 100, "events": 500, "blockers": 50, "jobs": 100}
|
||||
assert session.query(Installation).count() == 100
|
||||
assert session.query(AgentEvent).count() == 500
|
||||
assert session.query(Job).count() == 100
|
||||
assert {row.agent_health for row in session.query(AgentSnapshot).all()} == {"ONLINE", "DEGRADED", "OFFLINE", "FAILED"}
|
||||
@@ -0,0 +1,73 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from central.app.human_auth import create_admin, create_session, hash_password, verify_password
|
||||
from central.app.main import create_app
|
||||
from central.app.models import Agent, AgentEvent, AgentSnapshot, Base, Installation, Tenant
|
||||
from central.app.events import purge_expired_events, record_event, sanitize_event
|
||||
from central.app.installation_identity import compute_agent_health
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
def setup_db():
|
||||
engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool)
|
||||
Base.metadata.create_all(engine)
|
||||
return sessionmaker(bind=engine, expire_on_commit=False)
|
||||
|
||||
|
||||
def seed(session_factory):
|
||||
with session_factory() as session:
|
||||
tenant = Tenant(name="Acme"); session.add(tenant); session.flush()
|
||||
agent = Agent(tenant_id=tenant.id, name="agent", token_hash="x" * 64); session.add(agent); session.flush()
|
||||
installation = Installation(agent_id=agent.id, tenant_id=tenant.id, installation_id="11111111-1111-4111-8111-111111111111", display_name="Acme ERP", hostname="srv-01", last_seen_at=datetime.now(timezone.utc)); session.add(installation); session.flush()
|
||||
session.add(AgentSnapshot(installation_id=installation.id, total_products=10, fiscally_complete=8, safe_blocked=2, blockers={"CFOP": 2}))
|
||||
admin = create_admin(session, "operator", "a-secure-password")
|
||||
return agent.id, admin.id
|
||||
|
||||
|
||||
def test_password_and_event_redaction_and_retention():
|
||||
assert verify_password("a-secure-password", hash_password("a-secure-password"))
|
||||
message, context = sanitize_event("password=bad token=secret", {"password": "bad", "nested": {"api_key": "key"}})
|
||||
assert "bad" not in message and context["password"] == "[REDACTED]" and context["nested"]["api_key"] == "[REDACTED]"
|
||||
session_factory = setup_db()
|
||||
with session_factory() as session:
|
||||
agent_id, _ = seed(session_factory)
|
||||
installation = session.query(Installation).first()
|
||||
old = record_event(session, tenant_id=installation.tenant_id, installation_id=installation.id, agent_id=agent_id, severity="INFO", event_type="HEARTBEAT", message="old", occurred_at=datetime.now(timezone.utc) - timedelta(days=100))
|
||||
keep = record_event(session, tenant_id=installation.tenant_id, installation_id=installation.id, agent_id=agent_id, severity="CRITICAL", event_type="CONNECTION_ERROR", message="keep", occurred_at=datetime.now(timezone.utc) - timedelta(days=100), is_audit=True)
|
||||
session.commit()
|
||||
assert purge_expired_events(session, retention_days=90) == 1
|
||||
assert session.get(AgentEvent, old.id) is None and session.get(AgentEvent, keep.id) is not None
|
||||
|
||||
|
||||
def test_health_model_separates_safe_blocked_from_technical_failure():
|
||||
now = datetime.now(timezone.utc)
|
||||
recent = SimpleNamespace(last_seen_at=now, failed_products=0, last_job_status="COMPLETED")
|
||||
blocked = SimpleNamespace(last_seen_at=now, failed_products=0, last_job_status="COMPLETED")
|
||||
failed = SimpleNamespace(last_seen_at=now, failed_products=1, last_job_status="FAILED", agent_health="FAILED")
|
||||
stale = SimpleNamespace(last_seen_at=now - timedelta(hours=2), failed_products=0, last_job_status="COMPLETED")
|
||||
assert compute_agent_health(recent)[0] == "ONLINE"
|
||||
assert compute_agent_health(blocked, supplemental=blocked)[0] == "ONLINE"
|
||||
assert compute_agent_health(failed)[0] == "FAILED"
|
||||
assert compute_agent_health(stale)[0] == "OFFLINE"
|
||||
assert compute_agent_health(SimpleNamespace(last_seen_at=None))[0] == "UNKNOWN"
|
||||
|
||||
|
||||
def test_human_login_and_dashboard_are_separate_from_agent_bearer():
|
||||
session_factory = setup_db(); seed(session_factory)
|
||||
client = TestClient(create_app(database_probe=lambda: True, session_factory=session_factory, bootstrap_token=None))
|
||||
assert client.get("/", follow_redirects=False).status_code == 303
|
||||
response = client.post("/login", data={"username": "operator", "password": "a-secure-password"}, follow_redirects=False)
|
||||
assert response.status_code == 303
|
||||
assert client.get("/v1/dashboard/summary").status_code == 200
|
||||
assert TestClient(create_app(database_probe=lambda: True, session_factory=session_factory, bootstrap_token=None)).get("/v1/dashboard/summary", headers={"Authorization": "Bearer x"}).status_code == 401
|
||||
assert client.get("/v1/agents").json()["items"][0]["installation_id"]
|
||||
csrf = client.cookies.get("gti_csrf")
|
||||
action = client.post("/v1/agents/1/actions/reprocess", json={"confirm": True, "csrf_token": csrf, "kind": "REPROCESS_ALL"})
|
||||
assert action.status_code == 200 and action.json()["status"] == "PENDING"
|
||||
denied = client.post("/v1/agents/1/actions/reprocess", json={"confirm": True, "csrf_token": "wrong", "kind": "REPROCESS_ALL"})
|
||||
assert denied.status_code == 403
|
||||
Reference in New Issue
Block a user