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,393 @@
|
||||
"""
|
||||
Test unitari per app.parsers.receipt_extractor.
|
||||
|
||||
Copertura:
|
||||
- Estrazione EML annidato (message/rfc822) da ricevuta PEC
|
||||
- Campi estratti dall'EML annidato (message_id, subject, from, to)
|
||||
- Estrazione XML strutturato (daticert.xml)
|
||||
- Messaggio senza EML annidato (ritorna None)
|
||||
- Messaggio non-multipart (ritorna None)
|
||||
- Payload come lista di Message (forma canonica RFC)
|
||||
- Payload come bytes/stringa
|
||||
- Ricevute di tutti i tipi (accettazione, avvenuta-consegna, ecc.)
|
||||
"""
|
||||
|
||||
import email
|
||||
|
||||
import pytest
|
||||
|
||||
from app.parsers.receipt_extractor import (
|
||||
NestedEmlInfo,
|
||||
extract_nested_eml,
|
||||
extract_receipt_xml,
|
||||
)
|
||||
|
||||
|
||||
# ─── Fixture EML ──────────────────────────────────────────────────────────────
|
||||
|
||||
# Ricevuta di accettazione (Aruba style) con EML annidato
|
||||
ACCETTAZIONE_EML = b"""\
|
||||
From: posta-certificata@pec.aruba.it
|
||||
To: mittente@pec.it
|
||||
Subject: ACCETTAZIONE: Test PEC
|
||||
X-Ricevuta: accettazione
|
||||
X-Riferimento-Message-ID: <orig-001@pec.it>
|
||||
Date: Wed, 18 Mar 2026 14:01:00 +0100
|
||||
Content-Type: multipart/mixed; boundary="====acc===="
|
||||
|
||||
--====acc====
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Il messaggio e' stato accettato dal gestore.
|
||||
|
||||
--====acc====
|
||||
Content-Type: application/xml; name="daticert.xml"
|
||||
Content-Disposition: attachment; filename="daticert.xml"
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PostaCertificata versione="2.3">
|
||||
<Intestazione>
|
||||
<Identificativo>acc-12345</Identificativo>
|
||||
<TipoRicevuta>accettazione</TipoRicevuta>
|
||||
</Intestazione>
|
||||
</PostaCertificata>
|
||||
|
||||
--====acc====
|
||||
Content-Type: message/rfc822
|
||||
Content-Disposition: inline
|
||||
|
||||
From: mittente@pec.it
|
||||
To: destinatario@pec.it
|
||||
Subject: Test PEC originale
|
||||
Message-ID: <orig-001@pec.it>
|
||||
Date: Wed, 18 Mar 2026 14:00:00 +0100
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Corpo del messaggio originale inviato.
|
||||
|
||||
--====acc====--
|
||||
"""
|
||||
|
||||
# Ricevuta di avvenuta consegna con EML annidato
|
||||
AVVENUTA_CONSEGNA_EML = b"""\
|
||||
From: posta-certificata@pec.aruba.it
|
||||
To: mittente@pec.it
|
||||
Subject: CONSEGNA: Test PEC
|
||||
X-Ricevuta: avvenuta-consegna
|
||||
X-Riferimento-Message-ID: <orig-002@pec.it>
|
||||
Date: Wed, 18 Mar 2026 14:10:00 +0100
|
||||
Content-Type: multipart/mixed; boundary="====cons===="
|
||||
|
||||
--====cons====
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Il messaggio e' stato consegnato al destinatario.
|
||||
|
||||
--====cons====
|
||||
Content-Type: application/xml; name="daticert.xml"
|
||||
Content-Disposition: attachment; filename="daticert.xml"
|
||||
|
||||
<?xml version="1.0"?><PostaCertificata versione="2.3"/>
|
||||
|
||||
--====cons====
|
||||
Content-Type: message/rfc822
|
||||
Content-Disposition: inline
|
||||
|
||||
From: mittente@pec.it
|
||||
To: destinatario@pec.it
|
||||
Subject: Test PEC originale
|
||||
Message-ID: <orig-002@pec.it>
|
||||
Date: Wed, 18 Mar 2026 14:00:00 +0100
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Corpo originale.
|
||||
|
||||
--====cons====--
|
||||
"""
|
||||
|
||||
# Messaggio PEC originale (non ricevuta, senza EML annidato)
|
||||
POSTA_CERTIFICATA_EML = b"""\
|
||||
From: mittente@pec.it
|
||||
To: destinatario@pec.it
|
||||
Subject: Messaggio PEC originale
|
||||
Message-ID: <msg-123@pec.it>
|
||||
Date: Wed, 18 Mar 2026 14:00:00 +0100
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Questo e' il corpo del messaggio PEC.
|
||||
"""
|
||||
|
||||
# Messaggio multipart senza EML annidato
|
||||
MULTIPART_NO_NESTED = b"""\
|
||||
From: a@pec.it
|
||||
To: b@pec.it
|
||||
Subject: PEC con allegato PDF (nessun EML annidato)
|
||||
Date: Wed, 18 Mar 2026 10:00:00 +0100
|
||||
Content-Type: multipart/mixed; boundary="====notest===="
|
||||
|
||||
--====notest====
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Corpo.
|
||||
|
||||
--====notest====
|
||||
Content-Type: application/pdf; name="doc.pdf"
|
||||
Content-Disposition: attachment; filename="doc.pdf"
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
JVBERi0xLjQ=
|
||||
|
||||
--====notest====--
|
||||
"""
|
||||
|
||||
# Ricevuta di mancata consegna con EML annidato (Namirial style)
|
||||
MANCATA_CONSEGNA_EML = b"""\
|
||||
From: no-reply@namirial.com
|
||||
To: mittente@pec.it
|
||||
Subject: MANCATA CONSEGNA: Test
|
||||
X-TipoRicevuta: mancata-consegna
|
||||
X-Riferimento-Message-ID: <orig-003@pec.namirial.it>
|
||||
Date: Wed, 18 Mar 2026 15:00:00 +0100
|
||||
Content-Type: multipart/mixed; boundary="====mc===="
|
||||
|
||||
--====mc====
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Il messaggio non e' stato consegnato.
|
||||
|
||||
--====mc====
|
||||
Content-Type: message/rfc822
|
||||
Content-Disposition: inline
|
||||
|
||||
From: mittente@pec.namirial.it
|
||||
To: destinatario@pec.it
|
||||
Subject: Messaggio non consegnato
|
||||
Message-ID: <orig-003@pec.namirial.it>
|
||||
Date: Wed, 18 Mar 2026 14:30:00 +0100
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
Corpo originale che non e' stato consegnato.
|
||||
|
||||
--====mc====--
|
||||
"""
|
||||
|
||||
|
||||
# ─── Test extract_nested_eml ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestExtractNestedEml:
|
||||
|
||||
def test_accettazione_eml_annidato_trovato(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
|
||||
def test_accettazione_message_id(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert result.message_id == "<orig-001@pec.it>"
|
||||
|
||||
def test_accettazione_subject(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert result.subject == "Test PEC originale"
|
||||
|
||||
def test_accettazione_from_address(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert result.from_address == "mittente@pec.it"
|
||||
|
||||
def test_accettazione_to_addresses(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert "destinatario@pec.it" in result.to_addresses
|
||||
|
||||
def test_accettazione_raw_bytes_non_vuoti(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert len(result.raw_bytes) > 0
|
||||
|
||||
def test_avvenuta_consegna_eml_trovato(self):
|
||||
msg = email.message_from_bytes(AVVENUTA_CONSEGNA_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert result.message_id == "<orig-002@pec.it>"
|
||||
|
||||
def test_mancata_consegna_namirial(self):
|
||||
"""Test con ricevuta Namirial (X-TipoRicevuta)."""
|
||||
msg = email.message_from_bytes(MANCATA_CONSEGNA_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
assert result.message_id == "<orig-003@pec.namirial.it>"
|
||||
assert result.subject == "Messaggio non consegnato"
|
||||
|
||||
def test_messaggio_non_multipart_ritorna_none(self):
|
||||
"""Un messaggio non-multipart non può avere EML annidato."""
|
||||
msg = email.message_from_bytes(POSTA_CERTIFICATA_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is None
|
||||
|
||||
def test_multipart_senza_nested_ritorna_none(self):
|
||||
"""Un multipart senza parti message/rfc822 ritorna None."""
|
||||
msg = email.message_from_bytes(MULTIPART_NO_NESTED)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is None
|
||||
|
||||
def test_risultato_e_nested_eml_info(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert isinstance(result, NestedEmlInfo)
|
||||
|
||||
def test_raw_bytes_e_valido_eml(self):
|
||||
"""I raw_bytes estratti devono essere un EML valido (ri-parsabile)."""
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
result = extract_nested_eml(msg)
|
||||
assert result is not None
|
||||
# Verifica che i bytes siano ri-parsabili
|
||||
inner = email.message_from_bytes(result.raw_bytes)
|
||||
assert inner.get("Message-ID") == "<orig-001@pec.it>"
|
||||
|
||||
|
||||
# ─── Test extract_receipt_xml ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestExtractReceiptXml:
|
||||
|
||||
def test_daticert_xml_estratto(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
xml = extract_receipt_xml(msg)
|
||||
assert xml is not None
|
||||
assert "PostaCertificata" in xml
|
||||
|
||||
def test_xml_con_contenuto(self):
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
xml = extract_receipt_xml(msg)
|
||||
assert xml is not None
|
||||
assert "accettazione" in xml.lower()
|
||||
|
||||
def test_xml_avvenuta_consegna(self):
|
||||
msg = email.message_from_bytes(AVVENUTA_CONSEGNA_EML)
|
||||
xml = extract_receipt_xml(msg)
|
||||
assert xml is not None
|
||||
assert "PostaCertificata" in xml
|
||||
|
||||
def test_messaggio_senza_xml_ritorna_none(self):
|
||||
"""Un messaggio PEC senza allegato XML ritorna None."""
|
||||
msg = email.message_from_bytes(POSTA_CERTIFICATA_EML)
|
||||
xml = extract_receipt_xml(msg)
|
||||
assert xml is None
|
||||
|
||||
def test_multipart_senza_xml_ritorna_none(self):
|
||||
"""Multipart con solo PDF, senza XML."""
|
||||
msg = email.message_from_bytes(MULTIPART_NO_NESTED)
|
||||
xml = extract_receipt_xml(msg)
|
||||
assert xml is None
|
||||
|
||||
def test_mancata_consegna_senza_xml_ritorna_none(self):
|
||||
"""Alcune ricevute (Namirial) non hanno daticert.xml."""
|
||||
msg = email.message_from_bytes(MANCATA_CONSEGNA_EML)
|
||||
xml = extract_receipt_xml(msg)
|
||||
# Questa ricevuta non ha XML allegato → None
|
||||
assert xml is None
|
||||
|
||||
|
||||
# ─── Test NestedEmlInfo dataclass ─────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestNestedEmlInfoDataclass:
|
||||
|
||||
def test_campi_base(self):
|
||||
info = NestedEmlInfo(
|
||||
raw_bytes=b"raw",
|
||||
message_id="<test@pec.it>",
|
||||
subject="Test",
|
||||
from_address="a@pec.it",
|
||||
to_addresses=["b@pec.it"],
|
||||
)
|
||||
assert info.raw_bytes == b"raw"
|
||||
assert info.message_id == "<test@pec.it>"
|
||||
assert info.subject == "Test"
|
||||
assert info.from_address == "a@pec.it"
|
||||
assert "b@pec.it" in info.to_addresses
|
||||
|
||||
def test_to_addresses_default_lista_vuota(self):
|
||||
info = NestedEmlInfo(
|
||||
raw_bytes=b"",
|
||||
message_id=None,
|
||||
subject=None,
|
||||
from_address=None,
|
||||
)
|
||||
assert info.to_addresses == []
|
||||
|
||||
def test_campi_opzionali_possono_essere_none(self):
|
||||
info = NestedEmlInfo(
|
||||
raw_bytes=b"data",
|
||||
message_id=None,
|
||||
subject=None,
|
||||
from_address=None,
|
||||
)
|
||||
assert info.message_id is None
|
||||
assert info.subject is None
|
||||
assert info.from_address is None
|
||||
|
||||
|
||||
# ─── Test integrazione pec_parser + receipt_extractor ────────────────────────
|
||||
|
||||
|
||||
class TestIntegrationPecParserAndExtractor:
|
||||
"""
|
||||
Verifica che le informazioni estratte da receipt_extractor corrispondano
|
||||
al X-Riferimento-Message-ID usato da pec_parser per collegare i messaggi.
|
||||
"""
|
||||
|
||||
def test_riferimento_corrisponde_a_message_id_annidato(self):
|
||||
"""
|
||||
X-Riferimento-Message-ID nella ricevuta deve corrispondere
|
||||
al Message-ID dell'EML annidato.
|
||||
"""
|
||||
from app.parsers.pec_parser import classify_pec_message
|
||||
|
||||
msg = email.message_from_bytes(ACCETTAZIONE_EML)
|
||||
|
||||
# Classifica la ricevuta
|
||||
pec_class = classify_pec_message(msg)
|
||||
assert pec_class.riferimento_message_id == "<orig-001@pec.it>"
|
||||
|
||||
# Estrae EML annidato
|
||||
nested = extract_nested_eml(msg)
|
||||
assert nested is not None
|
||||
assert nested.message_id == "<orig-001@pec.it>"
|
||||
|
||||
# Deve corrispondere
|
||||
assert pec_class.riferimento_message_id == nested.message_id
|
||||
|
||||
def test_avvenuta_consegna_corrispondenza_completa(self):
|
||||
from app.parsers.pec_parser import classify_pec_message
|
||||
|
||||
msg = email.message_from_bytes(AVVENUTA_CONSEGNA_EML)
|
||||
|
||||
pec_class = classify_pec_message(msg)
|
||||
assert pec_class.pec_type == "avvenuta_consegna"
|
||||
assert pec_class.riferimento_message_id == "<orig-002@pec.it>"
|
||||
|
||||
nested = extract_nested_eml(msg)
|
||||
assert nested is not None
|
||||
assert nested.message_id == "<orig-002@pec.it>"
|
||||
|
||||
def test_mancata_consegna_namirial_corrispondenza(self):
|
||||
from app.parsers.pec_parser import classify_pec_message
|
||||
|
||||
msg = email.message_from_bytes(MANCATA_CONSEGNA_EML)
|
||||
|
||||
pec_class = classify_pec_message(msg)
|
||||
assert pec_class.pec_type == "mancata_consegna"
|
||||
assert pec_class.riferimento_message_id == "<orig-003@pec.namirial.it>"
|
||||
|
||||
nested = extract_nested_eml(msg)
|
||||
assert nested is not None
|
||||
assert nested.message_id == "<orig-003@pec.namirial.it>"
|
||||
Reference in New Issue
Block a user