feat: deterministic cClassTrib resolver + gated CCLASSTRIB auto-apply (default OFF), RTC panel summary; 1.1.9
- resolver (rules RTC-R0..R4/A1..A3/G1..G2) over the official cClassTrib table 2026-06-22 and LC 214/2025 annexes; local vs official catalog comparison - ONE root-cause RTC blocker per product (missing/unresolved/ambiguous/conflict/official mismatch/catalog outdated/evidence missing) - auto-apply: C000025.CCLASSTRIB only, three switches (FIREBIRD_WRITES + RTC_AUTO_APPLY_ENABLED + Hub authorization), per-product re-read and re-decision, never overwrites, rowcount 1, audit table rtc_write_audits (migration 0027) - copy-only write E2E (280/280 applied, only CCLASSTRIB changed, 2nd run 0 writes, stale decision 0 writes); real databases untouched - docs: cclasstrib_resolution, rtc_auto_apply, unified_fiscal_completeness Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -587,3 +587,45 @@ def test_rtc_status_shows_where_and_when_the_agent_observed_it():
|
||||
assert "linhas de PARAMETROS divergentes" in conflict and ">Desativada<" in conflict
|
||||
for legacy in ("Escopo anterior", "Escopo ampliado", "Legacy coverage", "Expanded coverage"):
|
||||
assert legacy not in conflict
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- RTC auto-apply authorization (central, default OFF)
|
||||
def _installation(client, agent_id):
|
||||
from central.app.models import Installation as Inst
|
||||
with client._session_factory() as session:
|
||||
row = session.scalar(select(Inst).where(Inst.agent_id == agent_id))
|
||||
return bool(row.rtc_auto_apply_authorized)
|
||||
|
||||
|
||||
def test_rtc_auto_apply_authorization_is_off_by_default_and_needs_csrf_and_a_typed_confirmation():
|
||||
client, agent_id, _ = _panel_client()
|
||||
assert _installation(client, agent_id) is False
|
||||
csrf = client.cookies.get("gti_csrf")
|
||||
url = f"/agents/{agent_id}/rtc-auto-apply"
|
||||
assert client.post(url, data={"csrf_token": "wrong", "decision": "authorize", "confirm": "PREENCHER"}).status_code == 403
|
||||
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize"}).status_code == 422
|
||||
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize", "confirm": "sim"}).status_code == 422
|
||||
assert _installation(client, agent_id) is False
|
||||
assert client.post(url, data={"csrf_token": csrf, "decision": "authorize", "confirm": "preencher"}, follow_redirects=False).status_code == 303
|
||||
assert _installation(client, agent_id) is True
|
||||
assert client.post(url, data={"csrf_token": csrf, "decision": "revoke"}, follow_redirects=False).status_code == 303
|
||||
assert _installation(client, agent_id) is False
|
||||
assert client.post(url, data={"csrf_token": csrf, "decision": "other"}).status_code == 422
|
||||
from central.app.models import AuditEvent
|
||||
with client._session_factory() as session:
|
||||
actions = [a.action for a in session.scalars(select(AuditEvent).order_by(AuditEvent.id)).all() if a.action.startswith("RTC_AUTO_APPLY")]
|
||||
assert actions == ["RTC_AUTO_APPLY_AUTHORIZE", "RTC_AUTO_APPLY_REVOKE"]
|
||||
|
||||
|
||||
def test_panel_shows_rtc_products_summary_and_the_pending_message_only_when_rtc_is_on():
|
||||
client, agent_id, _ = _panel_client()
|
||||
_complete_sync(client, agent_id, {"rtc_enabled": False, "rtc_supported": True, "rtc_resolution": None})
|
||||
off = client.get(f"/agents/{agent_id}").text
|
||||
assert "Produtos RTC" not in off and "cClassTrib pendente" not in off and "Preenchimento automático" not in off
|
||||
resolution = {"total": 521, "valid_existing": 0, "ready_to_apply": 280, "ambiguous": 215, "insufficient": 26, "catalog_compatibility": {"compatibility": "PARTIAL_LOCAL_OUTDATED"}}
|
||||
_complete_sync(client, agent_id, {"rtc_enabled": True, "rtc_supported": True, "rtc_catalog_valid": True, "rtc_resolution": resolution})
|
||||
on = client.get(f"/agents/{agent_id}").text
|
||||
assert "Reforma Tributária ativa — cClassTrib pendente em 521 produtos" in on and "prontos para preenchimento 280" in on and "ambíguos/bloqueados 241" in on
|
||||
assert "Preenchimento automático" in on and "Desativado" in on and "digite PREENCHER" in on
|
||||
for legacy in ("Escopo anterior", "Escopo ampliado"):
|
||||
assert legacy not in on
|
||||
|
||||
Reference in New Issue
Block a user