Add email delivery for generated reports
This commit is contained in:
+49
-1
@@ -9,7 +9,7 @@ from pve_backup_report.cli import (
|
||||
ensure_writable_directory,
|
||||
run,
|
||||
)
|
||||
from pve_backup_report.config import AppConfig, PbsServerConfig
|
||||
from pve_backup_report.config import AppConfig, EmailConfig, PbsServerConfig
|
||||
|
||||
|
||||
def test_cli_check_config(tmp_path, monkeypatch) -> None:
|
||||
@@ -198,3 +198,51 @@ def test_configured_pbs_clients_uses_every_configured_server() -> None:
|
||||
finally:
|
||||
for client in clients:
|
||||
client.close()
|
||||
|
||||
|
||||
def test_generate_pdf_sends_email_when_enabled(tmp_path, monkeypatch) -> None:
|
||||
pdf_path = tmp_path / "rapport.pdf"
|
||||
pdf_path.write_bytes(b"%PDF-1.7")
|
||||
config = AppConfig(
|
||||
pve_api_url="https://pve.example.invalid:8006",
|
||||
pve_api_token_id="backup-report@pve!report",
|
||||
pve_api_token_secret="secret",
|
||||
report_output_dir=tmp_path,
|
||||
report_timezone="Europe/Paris",
|
||||
pve_verify_tls=True,
|
||||
pve_ca_bundle=None,
|
||||
pve_timeout_seconds=30,
|
||||
pve_backup_jobs_endpoint="/cluster/backup",
|
||||
pve_task_history_limit=500,
|
||||
pve_task_log_limit=5000,
|
||||
pbs_hostnames={},
|
||||
pbs_servers=(),
|
||||
log_level="INFO",
|
||||
report_filename_prefix="rapport-sauvegardes-pve",
|
||||
email=EmailConfig(
|
||||
enabled=True,
|
||||
smtp_host="smtp.example.invalid",
|
||||
smtp_from="report@example.invalid",
|
||||
smtp_to=("admin@example.invalid",),
|
||||
),
|
||||
)
|
||||
sent = []
|
||||
|
||||
monkeypatch.setattr("pve_backup_report.cli.load_config", lambda: config)
|
||||
monkeypatch.setattr(
|
||||
"pve_backup_report.cli.collect_data_or_log_error",
|
||||
lambda loaded_config, label: ReportData(),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"pve_backup_report.cli.render_pdf",
|
||||
lambda *args: pdf_path,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"pve_backup_report.cli.send_report_email",
|
||||
lambda email_config, generated_pdf_path: sent.append(
|
||||
(email_config.smtp_host, generated_pdf_path)
|
||||
),
|
||||
)
|
||||
|
||||
assert run(["--generate-pdf"]) == 0
|
||||
assert sent == [("smtp.example.invalid", pdf_path)]
|
||||
|
||||
+54
-1
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
import pytest
|
||||
|
||||
from pve_backup_report.config import ConfigError, load_config, parse_pbs_servers
|
||||
from pve_backup_report.config import ConfigError, load_config, parse_email_config, parse_pbs_servers
|
||||
|
||||
|
||||
def test_load_config_from_env_file(tmp_path, monkeypatch) -> None:
|
||||
@@ -123,3 +123,56 @@ def test_load_config_rejects_invalid_report_language(tmp_path, monkeypatch) -> N
|
||||
|
||||
with pytest.raises(ConfigError, match="REPORT_LANGUAGE doit valoir fr ou en"):
|
||||
load_config(env_file)
|
||||
|
||||
|
||||
def test_parse_email_config_disabled_by_default() -> None:
|
||||
config = parse_email_config({})
|
||||
|
||||
assert config.enabled is False
|
||||
assert config.smtp_port == 587
|
||||
assert config.smtp_starttls is True
|
||||
assert config.smtp_ssl is False
|
||||
assert config.smtp_to == ()
|
||||
|
||||
|
||||
def test_parse_email_config_enabled() -> None:
|
||||
config = parse_email_config(
|
||||
{
|
||||
"REPORT_EMAIL_ENABLED": "true",
|
||||
"REPORT_EMAIL_SMTP_HOST": "smtp.example.invalid",
|
||||
"REPORT_EMAIL_SMTP_PORT": "465",
|
||||
"REPORT_EMAIL_SMTP_SSL": "true",
|
||||
"REPORT_EMAIL_SMTP_STARTTLS": "false",
|
||||
"REPORT_EMAIL_SMTP_USERNAME": "report@example.invalid",
|
||||
"REPORT_EMAIL_SMTP_PASSWORD": "secret",
|
||||
"REPORT_EMAIL_FROM": "report@example.invalid",
|
||||
"REPORT_EMAIL_TO": "admin@example.invalid,audit@example.invalid",
|
||||
"REPORT_EMAIL_SUBJECT": "Rapport PVE",
|
||||
}
|
||||
)
|
||||
|
||||
assert config.enabled is True
|
||||
assert config.smtp_host == "smtp.example.invalid"
|
||||
assert config.smtp_port == 465
|
||||
assert config.smtp_ssl is True
|
||||
assert config.smtp_starttls is False
|
||||
assert config.smtp_username == "report@example.invalid"
|
||||
assert config.smtp_password == "secret"
|
||||
assert config.smtp_from == "report@example.invalid"
|
||||
assert config.smtp_to == ("admin@example.invalid", "audit@example.invalid")
|
||||
assert config.subject == "Rapport PVE"
|
||||
|
||||
|
||||
def test_parse_email_config_rejects_incomplete_enabled_config() -> None:
|
||||
with pytest.raises(ConfigError, match="configuration email incomplete"):
|
||||
parse_email_config({"REPORT_EMAIL_ENABLED": "true"})
|
||||
|
||||
|
||||
def test_parse_email_config_rejects_conflicting_tls_modes() -> None:
|
||||
with pytest.raises(ConfigError, match="ne peuvent pas etre actifs ensemble"):
|
||||
parse_email_config(
|
||||
{
|
||||
"REPORT_EMAIL_SMTP_SSL": "true",
|
||||
"REPORT_EMAIL_SMTP_STARTTLS": "true",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
from pathlib import Path
|
||||
|
||||
from pve_backup_report.config import EmailConfig
|
||||
from pve_backup_report.email_report import build_report_message, send_report_email
|
||||
|
||||
|
||||
def test_build_report_message_attaches_pdf(tmp_path: Path) -> None:
|
||||
pdf_path = tmp_path / "rapport.pdf"
|
||||
pdf_path.write_bytes(b"%PDF-1.7")
|
||||
config = EmailConfig(
|
||||
enabled=True,
|
||||
smtp_host="smtp.example.invalid",
|
||||
smtp_from="report@example.invalid",
|
||||
smtp_to=("admin@example.invalid",),
|
||||
subject="Rapport PVE",
|
||||
)
|
||||
|
||||
message = build_report_message(config, pdf_path)
|
||||
|
||||
assert message["Subject"] == "Rapport PVE"
|
||||
assert message["From"] == "report@example.invalid"
|
||||
assert message["To"] == "admin@example.invalid"
|
||||
attachments = list(message.iter_attachments())
|
||||
assert len(attachments) == 1
|
||||
assert attachments[0].get_filename() == "rapport.pdf"
|
||||
assert attachments[0].get_content_type() == "application/pdf"
|
||||
assert attachments[0].get_payload(decode=True) == b"%PDF-1.7"
|
||||
|
||||
|
||||
def test_send_report_email_uses_starttls_and_auth(tmp_path: Path, monkeypatch) -> None:
|
||||
pdf_path = tmp_path / "rapport.pdf"
|
||||
pdf_path.write_bytes(b"%PDF-1.7")
|
||||
calls = []
|
||||
|
||||
class FakeSmtp:
|
||||
def __init__(self, host: str, port: int, timeout: int) -> None:
|
||||
calls.append(("connect", host, port, timeout))
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, traceback) -> None:
|
||||
calls.append(("close",))
|
||||
|
||||
def starttls(self) -> None:
|
||||
calls.append(("starttls",))
|
||||
|
||||
def login(self, username: str, password: str) -> None:
|
||||
calls.append(("login", username, password))
|
||||
|
||||
def send_message(self, message) -> None:
|
||||
calls.append(("send", message["To"]))
|
||||
|
||||
monkeypatch.setattr("pve_backup_report.email_report.smtplib.SMTP", FakeSmtp)
|
||||
config = EmailConfig(
|
||||
enabled=True,
|
||||
smtp_host="smtp.example.invalid",
|
||||
smtp_port=587,
|
||||
smtp_username="report@example.invalid",
|
||||
smtp_password="secret",
|
||||
smtp_from="report@example.invalid",
|
||||
smtp_to=("admin@example.invalid",),
|
||||
smtp_starttls=True,
|
||||
smtp_timeout_seconds=12,
|
||||
)
|
||||
|
||||
send_report_email(config, pdf_path)
|
||||
|
||||
assert calls == [
|
||||
("connect", "smtp.example.invalid", 587, 12),
|
||||
("starttls",),
|
||||
("login", "report@example.invalid", "secret"),
|
||||
("send", "admin@example.invalid"),
|
||||
("close",),
|
||||
]
|
||||
Reference in New Issue
Block a user