Files
GTI_GERENCIADOR_FISCAL/agent/tests/test_preflight_and_dry_run.py
T

133 lines
8.1 KiB
Python

import importlib.util
import sys
from pathlib import Path
import pytest
from gti_fiscal_agent.company_context import resolve
from gti_fiscal_agent.preflight import major_minor, run_preflight
ROOT = Path(__file__).parents[2]
spec = importlib.util.spec_from_file_location("run_agent_service", ROOT / "agent" / "scripts" / "run_agent_service.py")
service = importlib.util.module_from_spec(spec); sys.modules[spec.name] = service; spec.loader.exec_module(service)
CN = "11111111000111"
def env(tmp_path, **over):
base = {"GTI_HUB_URL": "http://hub.test", "AGENT_TOKEN": "SECRETTOKENVALUE9", "FIREBIRD_DATABASE_PATH": "x.fdb", "FB_USER": "u", "FB_PASSWORD": "SECRETPASSVALUE9", "FISCAL_MODE": "DRY_RUN", "FIREBIRD_WRITES": "0",
"AGENT_STATE_DB": str(tmp_path / "state.sqlite"), "AGENT_LOG_PATH": str(tmp_path / "logs" / "a.log"), "AGENT_VERSION": "1.0.0-rc1"}
base.update(over)
return base
def http_ok(url, token=None, timeout=10):
if url.endswith("/health"):
return 200, {"status": "ok"}
if url.endswith("/version"):
return 200, {"version": "1.0.0-rc1"}
return (200 if token == "SECRETTOKENVALUE9" else 401), {}
def fake_probe(company_over=None, caps_missing=()):
def probe(env):
cols = {"CLASSIFICACAO_FISCAL": (37, 20), "CFOP_ENTRADA": (14, 4), "CST_ENTRADA": (14, 4), "CFOP": (37, 4), "CST": (37, 10), "ALIQUOTA": (16, 8), "CEST": (37, 7), "SITUACAO_TRIBUTARIA": (37, 2),
"COD_ENTRADA_COFINS": (37, 3), "COD_SAIDA_COFINS": (37, 3), "ALIQ_COFINS": (37, 8), "COD_ENTRADA_PIS": (37, 3), "COD_SAIDA_PIS": (37, 3), "ALIQ_PIS": (37, 8), "CSOSN": (37, 60), "IPI": (27, 8),
"CST_IPI": (14, 3), "IAT": (37, 1), "INCIDENCIA_PISCOFINS": (37, 30), "PISCOFINS": (37, 1), "IPPT": (37, 1), "EX_TIPI": (37, 3), "COD_ORIGEM_MERCADORIA": (8, 4)}
for c in caps_missing:
cols.pop(c)
ev = {"c000004": [{"code": "000001", "cnpj": CN, "uf": "BA", "crt": "1", "optante": "S"}], "emissor": [{"cnpj": CN, "uf": "BA", "crt": "1"}], "nfce_keys": [{"cuf": "29", "cnpj": CN, "n": 5}], "nf_by_filial": {}}
ev.update(company_over or {})
return {"server_version": "WI-V6.3.9 Firebird 2.5", "ods": "11.2", "tables": {"C000004": {}, "C000025": cols}, "products": 3712, "company": resolve(ev, {})}
return probe
def test_preflight_passes_for_a_clean_dry_run_installation(tmp_path):
r = run_preflight(env(tmp_path), http_get=http_ok, probe=fake_probe())
assert r["PREFLIGHT"] == "PASS" and r["blockers"] == [] and r["effective_firebird_writes"] is False
names = {c["name"] for c in r["checks"]}
assert {"HUB_REACHABLE", "AUTH", "HUB_VERSION_COMPATIBLE", "FIREBIRD_REACHABLE", "C000004", "C000025", "CAPABILITIES", "COMPANY_CONTEXT", "UF", "CRT", "SCHEMA_TYPE_GUARDS", "SQLITE", "LOG_DIRECTORY", "FISCAL_MODE", "FIREBIRD_WRITES"} <= names
assert "SECRETTOKENVALUE9" not in str(r) and "SECRETPASSVALUE9" not in str(r)
@pytest.mark.parametrize("over,blocker", [
({"FISCAL_MODE": "DRY_RUN", "FIREBIRD_WRITES": "1"}, "FIREBIRD_WRITES"),
({"FISCAL_MODE": ""}, "FISCAL_MODE"),
({"AGENT_TOKEN": ""}, "CONFIG"),
])
def test_preflight_blocks_bad_config(tmp_path, over, blocker):
r = run_preflight(env(tmp_path, **over), http_get=http_ok, probe=fake_probe())
assert r["PREFLIGHT"] == "FAIL" and any(b.startswith(blocker) for b in r["blockers"])
def test_preflight_fails_on_hub_auth_version_and_missing_capability(tmp_path):
def bad_auth(url, token=None, timeout=10):
return http_ok(url, "wrong" if token else None)
assert any(b.startswith("AUTH") for b in run_preflight(env(tmp_path), http_get=bad_auth, probe=fake_probe())["blockers"])
def old_hub(url, token=None, timeout=10):
return (200, {"version": "0.9.0"}) if url.endswith("/version") else http_ok(url, token)
assert any(b.startswith("HUB_VERSION_COMPATIBLE") for b in run_preflight(env(tmp_path), http_get=old_hub, probe=fake_probe())["blockers"])
assert any(b.startswith("CAPABILITIES") for b in run_preflight(env(tmp_path), http_get=http_ok, probe=fake_probe(caps_missing=("CEST",)))["blockers"])
def test_preflight_fails_when_company_context_is_ambiguous_or_crt_conflicts(tmp_path):
two = {"c000004": [{"code": "1", "cnpj": CN, "uf": "BA", "crt": "1"}, {"code": "2", "cnpj": "22222222000122", "uf": "SP", "crt": "1"}], "emissor": []}
r = run_preflight(env(tmp_path), http_get=http_ok, probe=fake_probe(two))
assert r["PREFLIGHT"] == "FAIL" and any(b.startswith("COMPANY_CONTEXT") for b in r["blockers"])
conflict = {"emissor": [{"cnpj": CN, "uf": "BA", "crt": "3"}]}
assert any("CRT_SOURCE_CONFLICT" in b for b in run_preflight(env(tmp_path), http_get=http_ok, probe=fake_probe(conflict))["blockers"])
def test_hub_unreachable_is_a_clear_blocker(tmp_path):
def down(url, token=None, timeout=10):
raise ConnectionError("nope")
r = run_preflight(env(tmp_path), http_get=down, probe=fake_probe())
assert r["PREFLIGHT"] == "FAIL" and any(b.startswith("HUB_REACHABLE") for b in r["blockers"])
def test_major_minor():
assert major_minor("1.0.0-rc1") == "1.0" and major_minor("1.0.3") == "1.0" and major_minor("0.9.0") != major_minor("1.0.0-rc1")
# ---------------- zero-write guarantee ----------------
def test_dry_run_makes_writes_technically_impossible_whatever_firebird_writes_says():
for extra in ({"FIREBIRD_WRITES": "1", "FISCAL_MODE": "DRY_RUN"}, {"FIREBIRD_WRITES": "0", "FISCAL_MODE": "AUTO_APPLY"}, {"FIREBIRD_WRITES": "1", "FISCAL_MODE": "dry_run"}):
cfg = service.Config({"FIREBIRD_DATABASE_PATH": "x.fdb", **extra})
assert cfg.firebird_writes is False
assert service.Config({"FIREBIRD_DATABASE_PATH": "x.fdb", "FIREBIRD_WRITES": "1", "FISCAL_MODE": "AUTO_APPLY"}).firebird_writes is True
assert service.Config({"FIREBIRD_DATABASE_PATH": "x.fdb", "FIREBIRD_WRITES": "1"}).firebird_writes is True # legacy: unset FISCAL_MODE follows the flag
assert service.Config({"FIREBIRD_DATABASE_PATH": "x.fdb"}).fiscal_mode == "DRY_RUN"
def test_write_connection_cannot_be_opened_when_writes_are_disabled(monkeypatch):
opened = []
monkeypatch.setitem(sys.modules, "fdb", type("F", (), {"connect": staticmethod(lambda **kw: opened.append(kw))}))
with pytest.raises(RuntimeError):
service.open_write_connection({"FB_USER": "u", "FB_PASSWORD": "SECRETPASSVALUE9"}, "x.fdb", firebird_writes=False)
assert opened == []
def test_dry_run_job_never_opens_a_write_connection_nor_calls_the_executor(monkeypatch):
class Boom:
def __getattr__(self, name):
if name in ("execute_guarded_update", "execute_guarded_restore"):
raise AssertionError("executor write called in DRY_RUN")
return getattr(__import__("scripts.firebird_write_executor", fromlist=["x"]) if False else object(), name, None)
cfg = service.Config({"FIREBIRD_DATABASE_PATH": "x.fdb", "FIREBIRD_WRITES": "1", "FISCAL_MODE": "DRY_RUN"})
monkeypatch.setattr(service, "read_products", lambda env, codigos=None: [{"CODIGO": "000001", "PRODUTO": "X", "CLASSIFICACAO_FISCAL": "22030000", "IPI": None}])
monkeypatch.setattr(service, "HubClient", lambda url, token: type("H", (), {"classify_batch": lambda self, p: [{"status": "EXACT_NCM", "ipi_status": "IPI_RESOLVED", "classification": {"ipi": {"rate_kind": "NUMERIC", "rate_numeric": 3.9}},
"dataset_versions": {}}]})())
monkeypatch.setattr(service, "build_job_coverage", lambda *a, **k: {"TOTAL_PRODUCTS": 1})
monkeypatch.setattr(service, "open_write_connection", lambda *a, **k: (_ for _ in ()).throw(AssertionError("write connection opened in DRY_RUN")))
class State:
def log_ipi_write(self, *a): pass
class Ex:
WriteRefused = Exception
def execute_guarded_update(self, *a, **k): raise AssertionError("executor write called in DRY_RUN")
result, error = service.execute_job({"kind": "REPROCESS_ALL", "agent_id": 1, "payload": {}, "job_id": 1}, cfg, Ex(), State(), {})
assert error is None and result["writes_attempted"] == 0 and result["firebird_writes_enabled"] is False