Files
GTI_GERENCIADOR_FISCAL/fiscal/central/tests/test_mission_08_coverage.py
T
gti d21a9d1140 Add 'fiscal/' from commit '102ba84500dbd9655a0471288c3e278652593ca9'
git-subtree-dir: fiscal
git-subtree-mainline: d95165fc8f
git-subtree-split: 102ba84500
2026-09-29 14:43:23 -03:00

203 lines
7.5 KiB
Python

"""Missão 8 — Bloco C: Testes direcionados para elevar cobertura de reference_registry e classification."""
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from central.app.models import Base, Dataset
from central.app.reference_registry import (
register_draft, approve_draft, reject_draft, list_registry,
diff_against_vigente, technical_checklist, RegistryError
)
from central.app.classification import Classifier
@pytest.fixture
def memory_db():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
with Session(engine) as session:
yield session
# ---------------- C.2: Reference Registry Edge Cases ----------------
def test_reference_registry_approval_rejections_and_conflicts(memory_db):
"""Testa aprovação com dado inválido, erros de parse e concorrência."""
# 1. Tentar aprovar dataset inexistente
with pytest.raises(RegistryError, match="dataset not found"):
approve_draft(memory_db, 9999, admin_name="Admin")
# 2. Tentar rejeitar dataset inexistente
with pytest.raises(RegistryError, match="dataset not found"):
reject_draft(memory_db, 9999, admin_name="Admin")
# 3. Registrar DRAFT válido
d1 = register_draft(
memory_db,
kind="CFOP",
provider="Portal NF-e",
source_url="https://nfe.fazenda.gov.br",
sha256="1" * 64,
version_label="cfop-v1",
record_count=619,
)
# 4. Primeira aprovação tem sucesso
app1 = approve_draft(memory_db, d1.id, admin_name="Admin 1")
assert app1.status == "VIGENTE"
# 5. Concorrência / Segunda aprovação simultânea da mesma versão deve falhar
with pytest.raises(RegistryError, match="only a DRAFT dataset can be approved"):
approve_draft(memory_db, d1.id, admin_name="Admin 2")
# 6. Tentar rejeitar dataset que já é VIGENTE deve falhar
with pytest.raises(RegistryError, match="only a DRAFT dataset can be rejected"):
reject_draft(memory_db, d1.id, admin_name="Admin")
# 7. Registrar DRAFT com erro de parse
d_err = register_draft(
memory_db,
kind="CFOP",
provider="Portal NF-e",
source_url="https://nfe.fazenda.gov.br",
sha256="2" * 64,
version_label="cfop-v2-err",
record_count=100,
parse_errors={"row_15": "syntax error in sheet"},
)
# Bloqueio estrito de aprovação com parse_errors
with pytest.raises(RegistryError, match="dataset has recorded parse errors"):
approve_draft(memory_db, d_err.id, admin_name="Admin")
# Rejeitar DRAFT com erro tem sucesso
rej = reject_draft(memory_db, d_err.id, admin_name="Admin")
assert rej.status == "REJECTED"
# Rejeitar novamente falha
with pytest.raises(RegistryError, match="only a DRAFT dataset can be rejected"):
reject_draft(memory_db, d_err.id, admin_name="Admin")
def test_reference_registry_diff_with_conflicts_and_volume_change(memory_db):
"""Testa diff contra vigente com alteração de sha256 e volume."""
# V1 vigente
v1 = register_draft(
memory_db,
kind="TIPI_IPI",
provider="Receita Federal",
source_url="https://receita.gov.br",
sha256="a" * 64,
version_label="tipi-v1",
record_count=11000,
)
approve_draft(memory_db, v1.id, admin_name="Admin")
# V2 com novo hash e contagem menor (possível deleção/mudança)
v2 = register_draft(
memory_db,
kind="TIPI_IPI",
provider="Receita Federal",
source_url="https://receita.gov.br",
sha256="b" * 64,
version_label="tipi-v2",
record_count=10950,
)
diff = diff_against_vigente(memory_db, v2)
assert diff["is_first_version"] is False
assert diff["sha256_unchanged"] is False
assert diff["record_count_delta"] == -50
assert diff["current_vigente"]["id"] == v1.id
# ---------------- C.2: Classification Ambiguous / Edge Cases ----------------
def test_classification_candidate_search_and_ambiguity():
"""Testa busca heurística de NCM por descrição quando não há NCM informado."""
class MockEntry:
def __init__(self, code, desc):
self.code = code
self.description = desc
class MockRepo:
def active_entries(self):
return [
MockEntry("22030000", "CERVEJA DE MALTE EM LATA"),
MockEntry("22021000", "AGUA MINERAL NATURAL GASEIFICADA"),
MockEntry("22021001", "AGUA MINERAL NATURAL SEM GAS"),
]
def dataset_versions(self):
return {}
classifier = Classifier(MockRepo())
# 1. Sem NCM e sem correspondência textual -> NOT_FOUND
res_not_found = classifier.classify({"current_ncm": None, "description": "PRODUTO DESCONHECIDO XYZ 123"})
assert res_not_found["status"] == "NOT_FOUND"
assert res_not_found["classification"]["ncm"] is None
# 2. Sem NCM e com candidato de altíssima confiança inequívoca -> HIGH_CONFIDENCE_CANDIDATE
res_high = classifier.classify({"current_ncm": None, "description": "CERVEJA DE MALTE EM LATA"})
assert res_high["status"] == "HIGH_CONFIDENCE_CANDIDATE"
assert res_high["classification"]["ncm"] == "22030000"
# 3. Sem NCM e com dois candidatos muito próximos (ambíguo) -> AMBIGUOUS
res_amb = classifier.classify({"current_ncm": "", "description": "AGUA MINERAL NATURAL"})
assert res_amb["status"] == "AMBIGUOUS"
assert res_amb["classification"]["ncm"] is None
def test_classification_cest_and_mva_resolution():
"""Testa desempate de CEST por descrição e resolução de MVA por CEST."""
class MockCest:
def __init__(self, code, desc):
self.code = code
self.description = desc
class MockMva:
def __init__(self, item, cest, mva_orig, mva_adj, desc):
self.item = item
self.cest = cest
self.mva_original = mva_orig
self.mva_adjusted = mva_adj
self.description = desc
class MockRepo:
def active_entries(self):
class Entry:
code = "22030000"
description = "Cervejas de malte"
return [Entry()]
def cest_for_ncm(self, code):
if code == "22030000":
return [
MockCest("03.021.01", "Cerveja em garrafa de vidro"),
MockCest("03.021.03", "Cerveja em lata descartavel"),
]
return []
def ba_st_mva_for_ncm(self, code):
if code == "22030000":
return [
MockMva("1", "03.021.01", 50.0, 70.0, "Cerveja garrafa"),
MockMva("2", "03.021.03", 60.0, 80.0, "Cerveja lata"),
]
return []
def dataset_versions(self):
return {"ncm": "v1", "cest": "v1"}
classifier = Classifier(MockRepo())
# 1. Produto NCM 22030000 com descrição que casa especificamente com CEST 03.021.03 (lata)
res_lata = classifier.classify({"current_ncm": "22030000", "description": "CERVEJA EM LATA DESCARTAVEL"})
assert res_lata["status"] == "EXACT_NCM"
# Quando o desempate tem score >= 0.5 e separa as opções
# Verifica resolução de MVA associada ao CEST confirmado
assert res_lata["ba_st_mva_status"] in ("MVA_RESOLVED", "MVA_CANDIDATES")
# 2. Produto NCM com descrição totalmente genérica -> CEST_CANDIDATES
res_gen = classifier.classify({"current_ncm": "22030000", "description": "BEBIDA"})
assert res_gen["cest_status"] == "CEST_DESCRIPTION_MISMATCH"