import re
from datetime import datetime, timedelta, timezone
from fastapi.testclient import TestClient
from sqlalchemy import select
from central.app.blocked_products import upsert_blocker
from central.app.events import record_event
from central.app.main import create_app
from central.app.models import Agent, AgentEvent, AgentSnapshot, Installation, Job, Tenant
from central.tests.test_control_plane import seed, setup_db
def _panel_client():
session_factory = setup_db()
agent_id, _ = seed(session_factory)
now = datetime(2026, 9, 22, 19, 11, tzinfo=timezone.utc)
with session_factory() as session:
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
agent = session.get(Agent, agent_id)
agent.agent_version = "0.9.0"
snapshot = session.scalar(select(AgentSnapshot).where(AgentSnapshot.installation_id == installation.id))
snapshot.total_products = 50
snapshot.fiscally_complete = 49
snapshot.coverage_percent = 98
job = Job(
agent_id=agent_id,
kind="REPROCESS_PRODUCTS",
status="RUNNING",
created_at=now - timedelta(minutes=3),
started_at=now - timedelta(minutes=2),
payload={"codigos": ["P-001"]},
)
session.add(job)
session.flush()
blocker = upsert_blocker(
session,
installation.tenant_id,
{
"company": "Acme",
"product_code": "P-001",
"description": "Produto de teste",
"blocker_type": "BLOCKED_MISSING_EVIDENCE",
"field": "CFOP",
"reason_code": "MISSING_CONTEXT",
"input_fingerprint": "page-test",
"evidence_summary": {"source": "snapshot"},
},
installation_id=installation.id,
)
record_event(
session,
tenant_id=installation.tenant_id,
installation_id=installation.id,
agent_id=agent_id,
severity="WARNING",
event_type="PRODUCT_BLOCKED",
message="Contexto fiscal ausente",
job_id=job.id,
product_code="P-001",
blocker_id=blocker.id,
occurred_at=now,
)
session.commit()
client = TestClient(create_app(database_probe=lambda: True, session_factory=session_factory, bootstrap_token=None))
client._session_factory = session_factory
login = client.post("/login", data={"username": "operator", "password": "a-secure-password"}, follow_redirects=False)
assert login.status_code == 303
return client, agent_id, blocker.id
def test_dashboard_renders_operational_queue_labels_and_human_dates():
client, _, _ = _panel_client()
response = client.get("/")
assert response.status_code == 200
for label in ("Agents total", "Online", "Degraded", "Offline", "Failed", "Unknown"):
assert label in response.text
assert "Produtos bloqueados" in response.text
assert "Blockers abertos" in response.text
assert "ATENÇÃO NECESSÁRIA" in response.text
assert "Ver todos" in response.text
assert "Jobs com falha" in response.text
assert "Agents desatualizados" in response.text
assert not re.search(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", response.text)
def test_dashboard_lists_bounded_failed_job_rows_with_identity_error_age_and_empty_state():
client, agent_id, _ = _panel_client()
factory = client._session_factory
with factory() as session:
for index in range(12):
session.add(Job(agent_id=agent_id, kind="REPROCESS_PRODUCTS", status="FAILED", error=f"erro curto {index}", created_at=datetime(2026, 9, 22, 18, 0, tzinfo=timezone.utc)))
session.commit()
response = client.get("/")
assert response.status_code == 200
assert "Acme" in response.text and "Acme ERP" in response.text and "srv-01" in response.text
assert "erro curto" in response.text and "Job ID" in response.text and "Criado" in response.text
assert 'href="/jobs?job_id=' in response.text
assert response.text.count("erro curto") == 10
with factory() as session:
session.query(Job).filter(Job.status == "FAILED").delete()
session.commit()
assert "Nenhum job com falha." in client.get("/").text
def test_blocker_summary_uses_complete_filtered_relation_and_tenant_product_identity():
client, agent_id, _ = _panel_client()
factory = client._session_factory
with factory() as session:
original = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
other_tenant = Tenant(name="Outra empresa")
session.add(other_tenant); session.flush()
other_agent = Agent(tenant_id=other_tenant.id, name="agent-2", token_hash="y" * 64)
session.add(other_agent); session.flush()
other_installation = Installation(agent_id=other_agent.id, tenant_id=other_tenant.id, installation_id="22222222-2222-4222-8222-222222222222", display_name="Outra ERP", hostname="srv-02")
session.add(other_installation); session.flush()
for index in range(55):
upsert_blocker(session, original.tenant_id, {"company": "Acme", "product_code": f"P-{index:03}", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "CFOP", "reason_code": "MISSING", "input_fingerprint": f"many-{index}"}, installation_id=original.id)
upsert_blocker(session, other_tenant.id, {"company": "Outra", "product_code": "P-000", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "NCM", "reason_code": "MISSING", "input_fingerprint": "other-tenant"}, installation_id=other_installation.id)
resolved = upsert_blocker(session, original.tenant_id, {"company": "Acme", "product_code": "P-RES", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "ST", "reason_code": "MISSING", "input_fingerprint": "resolved"}, installation_id=original.id)
resolved.resolved_at = datetime.now(timezone.utc)
session.commit()
open_page = client.get("/blockers?status=OPEN&field=CFOP")
assert open_page.status_code == 200
assert 'Blockers abertos56' in open_page.text
assert 'Produtos afetados55' in open_page.text
assert 'Clientes afetados1' in open_page.text
resolved_page = client.get("/blockers?status=RESOLVED")
assert 'Blockers abertos0' in resolved_page.text
assert 'Produtos afetados1' in resolved_page.text
all_page = client.get("/blockers?status=ALL")
assert 'Blockers abertos57' in all_page.text
assert 'Produtos afetados57' in all_page.text
assert "Produtos afetados" in open_page.text and "Clientes afetados" in open_page.text
def test_agents_render_separate_health_and_fiscal_status_with_server_sort():
client, _, _ = _panel_client()
response = client.get("/agents?sort=worst_coverage&health=ONLINE&search=Acme")
assert response.status_code == 200
for label in ("Cliente", "Nome de exibição", "Hostname", "Saúde técnica", "Status fiscal", "Cobertura fiscal", "Versão"):
assert label in response.text
assert "Acme ERP" in response.text
assert "srv-01" in response.text
assert "98,0%" in response.text
assert 'name="health"' in response.text
assert 'value="Acme"' in response.text
assert 'value="worst_coverage"' in response.text
def test_jobs_render_client_identity_duration_and_preserve_filters():
client, _, _ = _panel_client()
response = client.get("/jobs?status=RUNNING&search=Acme")
assert response.status_code == 200
for label in ("Cliente", "Nome de exibição", "Hostname", "Job ID", "Tipo", "Status", "Criado", "Finalizado", "Duração", "Erro"):
assert label in response.text
assert "Acme" in response.text
assert "srv-01" in response.text
assert "em andamento" in response.text
assert 'value="Acme"' in response.text
assert '