mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 20:55:41 +02:00
270 lines
9.3 KiB
Python
270 lines
9.3 KiB
Python
"""
|
||
Test unitari per SmtpSender.
|
||
|
||
Verifica la costruzione del messaggio MIME senza connessioni SMTP reali.
|
||
Il test del send() effettivo verso server reali è un test di integrazione
|
||
(eseguito separatamente con flag --real-smtp).
|
||
"""
|
||
|
||
import email as email_lib
|
||
import email.policy
|
||
from email.mime.multipart import MIMEMultipart
|
||
|
||
import pytest
|
||
|
||
# ─── Chiave test fissa – deve coincidere con ENCRYPTION_KEY ──────────────────
|
||
_TEST_KEY_HEX = "b" * 64
|
||
|
||
# Imposta la variabile d'ambiente e invalida la cache prima di qualsiasi import
|
||
import os as _os
|
||
_os.environ["ENCRYPTION_KEY"] = _TEST_KEY_HEX
|
||
_os.environ.setdefault("SECRET_KEY", "test-secret-worker")
|
||
|
||
# Invalida la cache di get_settings se già caricata
|
||
try:
|
||
from app.config import get_settings as _gs
|
||
_gs.cache_clear()
|
||
except Exception:
|
||
pass
|
||
# ─────────────────────────────────────────────────────────────────────────────
|
||
|
||
|
||
# ─── Fixtures helper ─────────────────────────────────────────────────────────
|
||
|
||
|
||
def _make_fake_mailbox():
|
||
"""Crea un oggetto mailbox-like con attributi minimi per SmtpSender."""
|
||
import base64
|
||
import os
|
||
from unittest.mock import MagicMock
|
||
|
||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||
|
||
key = bytes.fromhex(_TEST_KEY_HEX)
|
||
|
||
def _enc(value: str) -> str:
|
||
nonce = os.urandom(12)
|
||
ct = AESGCM(key).encrypt(nonce, value.encode(), None)
|
||
return base64.b64encode(nonce + ct).decode()
|
||
|
||
mailbox = MagicMock()
|
||
mailbox.email_address = "test@pec.example.it"
|
||
mailbox.smtp_host_enc = _enc("smtp.example.it")
|
||
mailbox.smtp_port_enc = _enc("465")
|
||
mailbox.smtp_user_enc = _enc("test@pec.example.it")
|
||
mailbox.smtp_pass_enc = _enc("secret")
|
||
mailbox.smtp_use_tls = True
|
||
|
||
return mailbox
|
||
|
||
|
||
# ─── Test costruzione MIME ────────────────────────────────────────────────────
|
||
|
||
|
||
class TestBuildMimeMessage:
|
||
"""Verifica la costruzione del messaggio MIME con varie combinazioni."""
|
||
|
||
def _get_sender(self):
|
||
"""Restituisce SmtpSender con mailbox mock."""
|
||
# La chiave è già impostata a livello di modulo (_TEST_KEY_HEX)
|
||
from app.config import get_settings
|
||
get_settings.cache_clear() # forza rilettura env var
|
||
|
||
from app.smtp.sender import SmtpSender
|
||
return SmtpSender(_make_fake_mailbox())
|
||
|
||
def test_build_plain_text_only(self):
|
||
"""Verifica struttura MIME con solo testo semplice."""
|
||
sender = self._get_sender()
|
||
msg, msg_id = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Test oggetto",
|
||
body_text="Testo del corpo",
|
||
)
|
||
|
||
assert isinstance(msg, MIMEMultipart)
|
||
assert msg["From"] == "test@pec.example.it"
|
||
assert msg["To"] == "dest@pec.it"
|
||
assert msg["Subject"] == "Test oggetto"
|
||
assert msg_id.startswith("<")
|
||
assert msg_id.endswith(">")
|
||
assert "Cc" not in msg
|
||
|
||
def test_build_with_cc(self):
|
||
"""Verifica che il campo Cc venga incluso correttamente."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest1@pec.it"],
|
||
cc_addresses=["cc@pec.it", "cc2@pec.it"],
|
||
subject="Test Cc",
|
||
body_text="corpo",
|
||
)
|
||
|
||
assert "Cc" in msg
|
||
assert "cc@pec.it" in msg["Cc"]
|
||
assert "cc2@pec.it" in msg["Cc"]
|
||
|
||
def test_build_multiple_to(self):
|
||
"""Verifica destinatari multipli nel campo To."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest1@pec.it", "dest2@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Multi dest",
|
||
body_text="corpo",
|
||
)
|
||
|
||
assert "dest1@pec.it" in msg["To"]
|
||
assert "dest2@pec.it" in msg["To"]
|
||
|
||
def test_build_with_html(self):
|
||
"""Verifica che corpo HTML venga aggiunto come parte MIME."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Test HTML",
|
||
body_text="Testo semplice",
|
||
body_html="<p>Testo <b>HTML</b></p>",
|
||
)
|
||
|
||
# Trova le parti del messaggio
|
||
raw = msg.as_string()
|
||
assert "text/plain" in raw
|
||
assert "text/html" in raw
|
||
|
||
def test_build_with_attachment(self):
|
||
"""Verifica che un allegato venga incluso nel messaggio."""
|
||
sender = self._get_sender()
|
||
attachments = [
|
||
{
|
||
"filename": "documento.pdf",
|
||
"content": b"%PDF-1.4 fake content",
|
||
"content_type": "application/pdf",
|
||
}
|
||
]
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Test allegato",
|
||
body_text="Vedi allegato",
|
||
attachments=attachments,
|
||
)
|
||
|
||
raw = msg.as_string()
|
||
assert "documento.pdf" in raw
|
||
|
||
def test_build_multiple_attachments(self):
|
||
"""Verifica più allegati in un unico messaggio."""
|
||
sender = self._get_sender()
|
||
attachments = [
|
||
{"filename": "file1.txt", "content": b"contenuto 1", "content_type": "text/plain"},
|
||
{"filename": "file2.txt", "content": b"contenuto 2", "content_type": "text/plain"},
|
||
]
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Multi allegati",
|
||
body_text="Due allegati",
|
||
attachments=attachments,
|
||
)
|
||
|
||
raw = msg.as_string()
|
||
assert "file1.txt" in raw
|
||
assert "file2.txt" in raw
|
||
|
||
def test_message_id_unique(self):
|
||
"""Verifica che ogni messaggio abbia un Message-ID unico."""
|
||
sender = self._get_sender()
|
||
_, id1 = sender.build_mime_message(
|
||
to_addresses=["a@pec.it"], cc_addresses=[], subject="A", body_text="a"
|
||
)
|
||
_, id2 = sender.build_mime_message(
|
||
to_addresses=["b@pec.it"], cc_addresses=[], subject="B", body_text="b"
|
||
)
|
||
assert id1 != id2
|
||
|
||
def test_required_headers_present(self):
|
||
"""Verifica che tutti gli header obbligatori siano presenti."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Test headers",
|
||
body_text="corpo",
|
||
)
|
||
|
||
required_headers = ["From", "To", "Subject", "Date", "Message-ID", "MIME-Version"]
|
||
for header in required_headers:
|
||
assert header in msg, f"Header mancante: {header}"
|
||
|
||
def test_eml_bytes_serializable(self):
|
||
"""Verifica che il messaggio sia serializzabile in bytes."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Serializzazione",
|
||
body_text="corpo",
|
||
)
|
||
|
||
raw = msg.as_bytes()
|
||
assert len(raw) > 0
|
||
assert isinstance(raw, bytes)
|
||
|
||
def test_empty_body_creates_valid_message(self):
|
||
"""Verifica che un messaggio con corpo vuoto sia comunque valido."""
|
||
sender = self._get_sender()
|
||
msg, _ = sender.build_mime_message(
|
||
to_addresses=["dest@pec.it"],
|
||
cc_addresses=[],
|
||
subject="Corpo vuoto",
|
||
body_text="",
|
||
)
|
||
|
||
raw = msg.as_bytes()
|
||
assert len(raw) > 0
|
||
|
||
|
||
# ─── Test decifrazione credenziali ───────────────────────────────────────────
|
||
|
||
|
||
class TestDecryptSmtpCredentials:
|
||
"""Verifica la decifrazione delle credenziali SMTP."""
|
||
|
||
def test_decrypt_returns_correct_values(self):
|
||
"""Le credenziali decifrate devono corrispondere ai valori originali."""
|
||
from app.config import get_settings
|
||
get_settings.cache_clear()
|
||
|
||
from app.smtp.sender import decrypt_smtp_credentials
|
||
|
||
mailbox = _make_fake_mailbox()
|
||
creds = decrypt_smtp_credentials(mailbox)
|
||
|
||
assert creds["host"] == "smtp.example.it"
|
||
assert creds["port"] == 465
|
||
assert creds["user"] == "test@pec.example.it"
|
||
assert creds["password"] == "secret"
|
||
assert creds["use_tls"] is True
|
||
|
||
def test_wrong_key_raises_error(self):
|
||
"""Una chiave errata deve sollevare un'eccezione."""
|
||
import os
|
||
from app.config import get_settings
|
||
|
||
# Imposta chiave sbagliata
|
||
os.environ["ENCRYPTION_KEY"] = "a" * 64
|
||
get_settings.cache_clear()
|
||
|
||
from app.smtp.sender import decrypt_smtp_credentials
|
||
mailbox = _make_fake_mailbox() # cifrato con chiave "b"*64
|
||
|
||
with pytest.raises(Exception):
|
||
decrypt_smtp_credentials(mailbox)
|
||
|
||
# Ripristina chiave corretta per test successivi
|
||
os.environ["ENCRYPTION_KEY"] = _TEST_KEY_HEX
|
||
get_settings.cache_clear()
|