""" 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: 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" acc-12345 accettazione --====acc==== Content-Type: message/rfc822 Content-Disposition: inline From: mittente@pec.it To: destinatario@pec.it Subject: Test PEC originale Message-ID: 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: 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" --====cons==== Content-Type: message/rfc822 Content-Disposition: inline From: mittente@pec.it To: destinatario@pec.it Subject: Test PEC originale Message-ID: 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: 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: 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: 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 == "" 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 == "" 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 == "" 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") == "" # ─── 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="", subject="Test", from_address="a@pec.it", to_addresses=["b@pec.it"], ) assert info.raw_bytes == b"raw" assert info.message_id == "" 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 == "" # Estrae EML annidato nested = extract_nested_eml(msg) assert nested is not None assert nested.message_id == "" # 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 == "" nested = extract_nested_eml(msg) assert nested is not None assert nested.message_id == "" 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 == "" nested = extract_nested_eml(msg) assert nested is not None assert nested.message_id == ""