mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
fase 3
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
"""
|
||||
Test unitari per app.parsers.pec_parser.
|
||||
|
||||
Copertura:
|
||||
- Tutti i tipi di ricevuta PEC (accettazione, avvenuta-consegna, ecc.)
|
||||
- Varianti per provider: Aruba, Namirial, Legalmail/Poste, InfoCert
|
||||
- Precedenza X-TipoRicevuta su X-Ricevuta
|
||||
- Estrazione X-Riferimento-Message-ID
|
||||
- State machine outbound (apply_outbound_transition)
|
||||
- Funzione get_state_transition per tutti i tipi
|
||||
"""
|
||||
|
||||
import email
|
||||
|
||||
import pytest
|
||||
|
||||
from app.parsers.pec_parser import (
|
||||
VALID_OUTBOUND_TRANSITIONS,
|
||||
PecClassification,
|
||||
apply_outbound_transition,
|
||||
classify_pec_message,
|
||||
get_state_transition,
|
||||
)
|
||||
|
||||
|
||||
# ─── Helper per costruire messaggi di test ────────────────────────────────────
|
||||
|
||||
|
||||
def make_msg(headers: dict[str, str], body: str = "") -> email.message.Message:
|
||||
"""Costruisce un email.message.Message con gli header dati."""
|
||||
raw = ""
|
||||
for k, v in headers.items():
|
||||
raw += f"{k}: {v}\r\n"
|
||||
raw += f"\r\n{body}"
|
||||
return email.message_from_string(raw)
|
||||
|
||||
|
||||
# ─── Test classificazione tipi ricevuta (provider standard / Aruba) ───────────
|
||||
|
||||
|
||||
class TestArubaProvider:
|
||||
"""Header con trattini, stile Aruba PEC (provider più diffuso)."""
|
||||
|
||||
def test_accettazione(self):
|
||||
msg = make_msg({"X-Ricevuta": "accettazione"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "accettazione"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_avvenuta_consegna(self):
|
||||
msg = make_msg({"X-Ricevuta": "avvenuta-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_mancata_consegna(self):
|
||||
msg = make_msg({"X-Ricevuta": "mancata-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "mancata_consegna"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_non_accettazione(self):
|
||||
msg = make_msg({"X-Ricevuta": "non-accettazione"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "non_accettazione"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_presa_in_carico(self):
|
||||
msg = make_msg({"X-Ricevuta": "presa-in-carico"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "presa_in_carico"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_errore_consegna(self):
|
||||
msg = make_msg({"X-Ricevuta": "errore-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "errore_consegna"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_preavviso_mancata_consegna(self):
|
||||
msg = make_msg({"X-Ricevuta": "preavviso-mancata-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "preavviso_mancata_consegna"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_rilevazione_virus(self):
|
||||
msg = make_msg({"X-Ricevuta": "rilevazione-virus"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "rilevazione_virus"
|
||||
assert result.is_receipt is True
|
||||
|
||||
def test_posta_certificata_assenza_header(self):
|
||||
"""Messaggio PEC originale: nessun header X-Ricevuta."""
|
||||
msg = make_msg({
|
||||
"From": "mittente@pec.it",
|
||||
"To": "destinatario@pec.it",
|
||||
"Subject": "Test PEC",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "posta_certificata"
|
||||
assert result.is_receipt is False
|
||||
|
||||
def test_posta_certificata_esplicita(self):
|
||||
"""X-Ricevuta: posta-certificata (usato da Aruba per indicare il messaggio stesso)."""
|
||||
msg = make_msg({"X-Ricevuta": "posta-certificata"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "posta_certificata"
|
||||
assert result.is_receipt is False
|
||||
|
||||
|
||||
# ─── Test provider Namirial (usa X-TipoRicevuta) ─────────────────────────────
|
||||
|
||||
|
||||
class TestNamirialProvider:
|
||||
"""Namirial usa X-TipoRicevuta invece di X-Ricevuta."""
|
||||
|
||||
def test_x_tipo_ricevuta_avvenuta_consegna(self):
|
||||
msg = make_msg({"X-TipoRicevuta": "avvenuta-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
assert result.x_tipo_ricevuta == "avvenuta-consegna"
|
||||
|
||||
def test_x_tipo_ricevuta_accettazione(self):
|
||||
msg = make_msg({"X-TipoRicevuta": "accettazione"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "accettazione"
|
||||
|
||||
def test_x_tipo_ricevuta_ha_precedenza_su_x_ricevuta(self):
|
||||
"""X-TipoRicevuta deve avere precedenza su X-Ricevuta."""
|
||||
msg = make_msg({
|
||||
"X-Ricevuta": "accettazione",
|
||||
"X-TipoRicevuta": "avvenuta-consegna",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna" # X-TipoRicevuta vince
|
||||
|
||||
def test_mancata_consegna(self):
|
||||
msg = make_msg({"X-TipoRicevuta": "mancata-consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "mancata_consegna"
|
||||
|
||||
|
||||
# ─── Test provider Legalmail/Poste (varianti underscore e spazi) ──────────────
|
||||
|
||||
|
||||
class TestLegalmailProvider:
|
||||
"""Legalmail/Poste Italiane può usare underscore o spazi nei valori."""
|
||||
|
||||
def test_avvenuta_consegna_underscore(self):
|
||||
msg = make_msg({"X-Ricevuta": "avvenuta_consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
|
||||
def test_non_accettazione_underscore(self):
|
||||
msg = make_msg({"X-Ricevuta": "non_accettazione"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "non_accettazione"
|
||||
|
||||
def test_mancata_consegna_spazio(self):
|
||||
msg = make_msg({"X-Ricevuta": "mancata consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "mancata_consegna"
|
||||
|
||||
def test_avvenuta_consegna_spazio(self):
|
||||
msg = make_msg({"X-Ricevuta": "avvenuta consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
|
||||
def test_presa_in_carico_underscore(self):
|
||||
msg = make_msg({"X-Ricevuta": "presa_in_carico"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "presa_in_carico"
|
||||
|
||||
def test_abbreviazione_consegna(self):
|
||||
"""Legalmail usa talvolta solo 'consegna' come abbreviazione."""
|
||||
msg = make_msg({"X-Ricevuta": "consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
|
||||
def test_rilevazione_virus_underscore(self):
|
||||
msg = make_msg({"X-Ricevuta": "rilevazione_virus"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "rilevazione_virus"
|
||||
|
||||
|
||||
# ─── Test case insensitivity ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestCaseInsensitivity:
|
||||
"""I valori degli header PEC devono essere trattati case-insensitive."""
|
||||
|
||||
def test_uppercase_accettazione(self):
|
||||
msg = make_msg({"X-Ricevuta": "ACCETTAZIONE"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "accettazione"
|
||||
|
||||
def test_mixed_case_avvenuta_consegna(self):
|
||||
msg = make_msg({"X-Ricevuta": "Avvenuta-Consegna"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
|
||||
def test_uppercase_x_tipo(self):
|
||||
msg = make_msg({"X-TipoRicevuta": "MANCATA-CONSEGNA"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "mancata_consegna"
|
||||
|
||||
|
||||
# ─── Test X-Riferimento-Message-ID ───────────────────────────────────────────
|
||||
|
||||
|
||||
class TestRiferimentoMessageId:
|
||||
"""Il campo X-Riferimento-Message-ID collega la ricevuta al messaggio originale."""
|
||||
|
||||
def test_estrazione_riferimento(self):
|
||||
msg = make_msg({
|
||||
"X-Ricevuta": "avvenuta-consegna",
|
||||
"X-Riferimento-Message-ID": "<msg123@pec.it>",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.riferimento_message_id == "<msg123@pec.it>"
|
||||
|
||||
def test_riferimento_assente(self):
|
||||
msg = make_msg({"X-Ricevuta": "accettazione"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.riferimento_message_id is None
|
||||
|
||||
def test_posta_certificata_senza_riferimento(self):
|
||||
"""I messaggi originali non hanno X-Riferimento-Message-ID."""
|
||||
msg = make_msg({"From": "mittente@pec.it", "To": "dest@pec.it"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.riferimento_message_id is None
|
||||
|
||||
def test_riferimento_con_spazi_extra(self):
|
||||
"""Il valore deve essere pulito da spazi iniziali/finali."""
|
||||
msg = make_msg({
|
||||
"X-Ricevuta": "accettazione",
|
||||
"X-Riferimento-Message-ID": " <msg456@pec.it> ",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.riferimento_message_id == "<msg456@pec.it>"
|
||||
|
||||
def test_x_raw_headers_conservati(self):
|
||||
"""I valori raw degli header devono essere conservati nel risultato."""
|
||||
msg = make_msg({
|
||||
"X-Ricevuta": "accettazione",
|
||||
"X-TipoRicevuta": "avvenuta-consegna",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.x_ricevuta == "accettazione"
|
||||
assert result.x_tipo_ricevuta == "avvenuta-consegna"
|
||||
|
||||
def test_tipo_sconosciuto_mappa_posta_certificata(self):
|
||||
"""Valori sconosciuti devono mappare a posta_certificata."""
|
||||
msg = make_msg({"X-Ricevuta": "valore-sconosciuto"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "posta_certificata"
|
||||
assert result.is_receipt is False
|
||||
|
||||
|
||||
# ─── Test state machine outbound ─────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestStateMachine:
|
||||
"""Test della state machine per messaggi outbound."""
|
||||
|
||||
# ── get_state_transition ──────────────────────────────────────────────────
|
||||
|
||||
def test_accettazione_produce_accepted(self):
|
||||
assert get_state_transition("accettazione") == "accepted"
|
||||
|
||||
def test_presa_in_carico_produce_accepted(self):
|
||||
assert get_state_transition("presa_in_carico") == "accepted"
|
||||
|
||||
def test_avvenuta_consegna_produce_delivered(self):
|
||||
assert get_state_transition("avvenuta_consegna") == "delivered"
|
||||
|
||||
def test_non_accettazione_produce_anomaly(self):
|
||||
assert get_state_transition("non_accettazione") == "anomaly"
|
||||
|
||||
def test_mancata_consegna_produce_anomaly(self):
|
||||
assert get_state_transition("mancata_consegna") == "anomaly"
|
||||
|
||||
def test_errore_consegna_produce_anomaly(self):
|
||||
assert get_state_transition("errore_consegna") == "anomaly"
|
||||
|
||||
def test_preavviso_mancata_produce_anomaly(self):
|
||||
assert get_state_transition("preavviso_mancata_consegna") == "anomaly"
|
||||
|
||||
def test_rilevazione_virus_produce_anomaly(self):
|
||||
assert get_state_transition("rilevazione_virus") == "anomaly"
|
||||
|
||||
def test_posta_certificata_nessuna_transizione(self):
|
||||
"""posta_certificata non è una ricevuta: nessuna transizione."""
|
||||
assert get_state_transition("posta_certificata") is None
|
||||
|
||||
def test_tipo_sconosciuto_nessuna_transizione(self):
|
||||
assert get_state_transition("unknown") is None
|
||||
|
||||
# ── apply_outbound_transition ─────────────────────────────────────────────
|
||||
|
||||
def test_sent_plus_accettazione_da_accepted(self):
|
||||
assert apply_outbound_transition("sent", "accettazione") == "accepted"
|
||||
|
||||
def test_queued_plus_accettazione_da_accepted(self):
|
||||
assert apply_outbound_transition("queued", "accettazione") == "accepted"
|
||||
|
||||
def test_accepted_plus_avvenuta_consegna_da_delivered(self):
|
||||
assert apply_outbound_transition("accepted", "avvenuta_consegna") == "delivered"
|
||||
|
||||
def test_sent_plus_avvenuta_consegna_non_valido(self):
|
||||
"""Non si può saltare direttamente da sent a delivered."""
|
||||
assert apply_outbound_transition("sent", "avvenuta_consegna") is None
|
||||
|
||||
def test_delivered_terminal(self):
|
||||
"""delivered è uno stato terminale: nessuna transizione possibile."""
|
||||
assert apply_outbound_transition("delivered", "avvenuta_consegna") is None
|
||||
assert apply_outbound_transition("delivered", "mancata_consegna") is None
|
||||
|
||||
def test_anomaly_terminal(self):
|
||||
"""anomaly è uno stato terminale: nessuna transizione possibile."""
|
||||
assert apply_outbound_transition("anomaly", "avvenuta_consegna") is None
|
||||
|
||||
def test_sent_plus_mancata_consegna_da_anomaly(self):
|
||||
assert apply_outbound_transition("sent", "mancata_consegna") == "anomaly"
|
||||
|
||||
def test_accepted_plus_non_accettazione_da_anomaly(self):
|
||||
assert apply_outbound_transition("accepted", "non_accettazione") == "anomaly"
|
||||
|
||||
def test_posta_certificata_nessun_effetto(self):
|
||||
"""posta_certificata non è una ricevuta: nessuna transizione."""
|
||||
assert apply_outbound_transition("sent", "posta_certificata") is None
|
||||
|
||||
# ── VALID_OUTBOUND_TRANSITIONS struttura ──────────────────────────────────
|
||||
|
||||
def test_valid_transitions_queued(self):
|
||||
assert "accepted" in VALID_OUTBOUND_TRANSITIONS["queued"]
|
||||
assert "anomaly" in VALID_OUTBOUND_TRANSITIONS["queued"]
|
||||
assert "delivered" not in VALID_OUTBOUND_TRANSITIONS["queued"]
|
||||
|
||||
def test_valid_transitions_sent(self):
|
||||
assert "accepted" in VALID_OUTBOUND_TRANSITIONS["sent"]
|
||||
assert "anomaly" in VALID_OUTBOUND_TRANSITIONS["sent"]
|
||||
|
||||
def test_valid_transitions_accepted(self):
|
||||
assert "delivered" in VALID_OUTBOUND_TRANSITIONS["accepted"]
|
||||
assert "anomaly" in VALID_OUTBOUND_TRANSITIONS["accepted"]
|
||||
assert "accepted" not in VALID_OUTBOUND_TRANSITIONS["accepted"]
|
||||
|
||||
def test_delivered_non_in_valid_transitions(self):
|
||||
"""delivered è terminale: non deve comparire come chiave."""
|
||||
assert "delivered" not in VALID_OUTBOUND_TRANSITIONS
|
||||
|
||||
def test_anomaly_non_in_valid_transitions(self):
|
||||
"""anomaly è terminale: non deve comparire come chiave."""
|
||||
assert "anomaly" not in VALID_OUTBOUND_TRANSITIONS
|
||||
|
||||
|
||||
# ─── Test PecClassification dataclass ────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPecClassificationDataclass:
|
||||
"""Test dei campi del dataclass PecClassification."""
|
||||
|
||||
def test_tutti_i_campi_presenti(self):
|
||||
msg = make_msg({
|
||||
"X-Ricevuta": "accettazione",
|
||||
"X-TipoRicevuta": "avvenuta-consegna",
|
||||
"X-Riferimento-Message-ID": "<ref@pec.it>",
|
||||
})
|
||||
result = classify_pec_message(msg)
|
||||
assert isinstance(result, PecClassification)
|
||||
assert result.pec_type == "avvenuta_consegna"
|
||||
assert result.is_receipt is True
|
||||
assert result.riferimento_message_id == "<ref@pec.it>"
|
||||
assert result.x_ricevuta == "accettazione"
|
||||
assert result.x_tipo_ricevuta == "avvenuta-consegna"
|
||||
|
||||
def test_messaggio_puro_tutti_none(self):
|
||||
msg = make_msg({"From": "a@pec.it", "To": "b@pec.it"})
|
||||
result = classify_pec_message(msg)
|
||||
assert result.pec_type == "posta_certificata"
|
||||
assert result.is_receipt is False
|
||||
assert result.riferimento_message_id is None
|
||||
assert result.x_ricevuta is None
|
||||
assert result.x_tipo_ricevuta is None
|
||||
Reference in New Issue
Block a user