mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
394 lines
12 KiB
Python
394 lines
12 KiB
Python
"""
|
|
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>"
|