fix: close operational panel page review findings
This commit is contained in:
@@ -7,7 +7,7 @@ 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, AgentSnapshot, Installation, Job
|
||||
from central.app.models import Agent, AgentEvent, AgentSnapshot, Installation, Job, Tenant
|
||||
from central.tests.test_control_plane import seed, setup_db
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ def _panel_client():
|
||||
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
|
||||
@@ -86,6 +87,56 @@ def test_dashboard_renders_operational_queue_labels_and_human_dates():
|
||||
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 '<span>Blockers abertos</span><strong>56</strong>' in open_page.text
|
||||
assert '<span>Produtos afetados</span><strong>55</strong>' in open_page.text
|
||||
assert '<span>Clientes afetados</span><strong>1</strong>' in open_page.text
|
||||
resolved_page = client.get("/blockers?status=RESOLVED")
|
||||
assert '<span>Blockers abertos</span><strong>0</strong>' in resolved_page.text
|
||||
assert '<span>Produtos afetados</span><strong>1</strong>' in resolved_page.text
|
||||
all_page = client.get("/blockers?status=ALL")
|
||||
assert '<span>Blockers abertos</span><strong>57</strong>' in all_page.text
|
||||
assert '<span>Produtos afetados</span><strong>57</strong>' 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()
|
||||
|
||||
@@ -135,6 +186,10 @@ def test_blockers_render_visible_filters_summary_priority_and_detail_link():
|
||||
|
||||
def test_agent_and_blocker_details_keep_safe_forms_and_human_event_rows():
|
||||
client, agent_id, blocker_id = _panel_client()
|
||||
with client._session_factory() as session:
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
|
||||
upsert_blocker(session, installation.tenant_id, {"company": "Acme", "product_code": "LEGACY", "blocker_type": "BLOCKED_MISSING_EVIDENCE", "field": "ST", "reason_code": "LEGACY", "input_fingerprint": "tenant-only"})
|
||||
session.commit()
|
||||
|
||||
agent_response = client.get(f"/agents/{agent_id}?tab=events&severity=WARNING&event_type=PRODUCT_BLOCKED&page=2")
|
||||
blocker_response = client.get(f"/blockers/{blocker_id}")
|
||||
@@ -143,9 +198,14 @@ def test_agent_and_blocker_details_keep_safe_forms_and_human_event_rows():
|
||||
for label in ("Cliente", "Nome de exibição", "Hostname", "Saúde técnica", "Status fiscal", "Último heartbeat", "Versão", "Empresa", "UF", "CRT", "Eventos recentes", "Severidade", "Tipo de evento"):
|
||||
assert label in agent_response.text
|
||||
assert "Contexto fiscal ausente" in agent_response.text
|
||||
assert f"/blockers?agent_id={agent_id}" in agent_response.text
|
||||
assert ">CFOP</span><b>1</b>" in agent_response.text
|
||||
assert ">ST</span><b>0</b>" in agent_response.text
|
||||
agent_blockers = client.get(f"/blockers?agent_id={agent_id}")
|
||||
assert "Produto de teste" in agent_blockers.text and "LEGACY" not in agent_blockers.text
|
||||
assert not re.search(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", agent_response.text)
|
||||
assert "severity=WARNING" in agent_response.text
|
||||
assert "event_type=PRODUCT_BLOCKED" in agent_response.text
|
||||
assert 'value="WARNING" selected' in agent_response.text
|
||||
assert 'value="PRODUCT_BLOCKED"' in agent_response.text
|
||||
|
||||
assert blocker_response.status_code == 200
|
||||
for label in ("Detalhes do blocker", "Evidência", "Contexto atual", "Salvar contexto e reprocessar", "Chave de contexto", "Valor", "Escopo", "Nota"):
|
||||
@@ -153,3 +213,48 @@ def test_agent_and_blocker_details_keep_safe_forms_and_human_event_rows():
|
||||
assert 'name="context"' not in blocker_response.text
|
||||
assert "data-confirm" not in blocker_response.text
|
||||
assert not re.search(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}", blocker_response.text)
|
||||
|
||||
|
||||
def test_agent_detail_resolves_agent_after_first_hundred_installations():
|
||||
client, _, _ = _panel_client()
|
||||
factory = client._session_factory
|
||||
with factory() as session:
|
||||
tenant = session.scalar(select(Tenant).where(Tenant.name == "Acme"))
|
||||
target_id = 0
|
||||
for index in range(105):
|
||||
agent = Agent(tenant_id=tenant.id, name=f"late-{index}", token_hash=f"{index:064d}")
|
||||
session.add(agent); session.flush()
|
||||
session.add(Installation(agent_id=agent.id, tenant_id=tenant.id, installation_id=f"{index + 10:08d}-1111-4111-8111-111111111111", display_name=f"Late ERP {index}", hostname=f"late-{index}"))
|
||||
target_id = agent.id
|
||||
session.commit()
|
||||
response = client.get(f"/agents/{target_id}")
|
||||
assert response.status_code == 200
|
||||
assert "Late ERP 104" in response.text
|
||||
|
||||
|
||||
def test_agent_event_filters_lookahead_and_safe_event_reference_links_and_confirmation():
|
||||
client, agent_id, _ = _panel_client()
|
||||
factory = client._session_factory
|
||||
with factory() as session:
|
||||
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
|
||||
job = session.scalar(select(Job).where(Job.agent_id == agent_id))
|
||||
for index in range(27):
|
||||
session.add(AgentEvent(tenant_id=installation.tenant_id, installation_id=installation.id, agent_id=agent_id, severity="INFO", event_type="HEARTBEAT", message=f"evento {index}", structured_context={}, job_id=job.id, product_code="P-001", occurred_at=datetime(2026, 9, 21 + index % 2, tzinfo=timezone.utc)))
|
||||
session.add(AgentEvent(tenant_id=installation.tenant_id, installation_id=installation.id, agent_id=agent_id, severity="INFO", event_type="HEARTBEAT", message="evento anterior ao filtro", structured_context={}, occurred_at=datetime(2026, 9, 20, tzinfo=timezone.utc)))
|
||||
session.commit()
|
||||
first = client.get(f"/agents/{agent_id}?tab=events&since=2026-09-21&until=2026-09-22")
|
||||
assert first.status_code == 200
|
||||
assert 'name="since"' in first.text and 'name="until"' in first.text
|
||||
assert "evento anterior ao filtro" not in first.text
|
||||
assert "Próxima" in first.text and "since=2026-09-21" in first.text and "until=2026-09-22" in first.text
|
||||
last = client.get(f"/agents/{agent_id}?tab=events&event_page=2&since=2026-09-21&until=2026-09-22")
|
||||
assert "Anterior" in last.text and "Próxima" not in last.text
|
||||
assert "/jobs?job_id=" in first.text and "/blockers?agent_id=" in first.text
|
||||
csrf = client.cookies.get("gti_csrf")
|
||||
blocked = client.post(f"/agents/{agent_id}/reprocess", data={"csrf_token": csrf})
|
||||
assert blocked.status_code == 400 and "confirmar" in blocked.text.lower() and 'name="confirm"' in blocked.text
|
||||
confirmation = client.get(f"/agents/{agent_id}/reprocess-confirmation")
|
||||
assert confirmation.status_code == 200 and 'name="confirm"' in confirmation.text and 'required' in confirmation.text
|
||||
assert f'href="/agents/{agent_id}"' in confirmation.text
|
||||
assert client.post(f"/agents/{agent_id}/reprocess", data={"csrf_token": "wrong", "confirm": "yes"}).status_code == 403
|
||||
assert client.post(f"/agents/{agent_id}/reprocess", data={"csrf_token": csrf, "confirm": "yes"}, follow_redirects=False).status_code == 303
|
||||
|
||||
Reference in New Issue
Block a user