Files
PVE-Backup-Report/tests/test_report_weasy_pdf.py

307 lines
11 KiB
Python

from datetime import datetime
from pve_backup_report.models import (
BackupCoverage,
BackupJob,
Guest,
PbsAccessUser,
PbsBackupSnapshotSummary,
PbsDatastoreUsage,
PbsGarbageCollectionStatus,
PbsRetentionPolicy,
PbsStorage,
ReportData,
ReportSummary,
)
from pve_backup_report.report_weasy_pdf import build_template_context
from pve_backup_report.report_weasy_pdf import render_html
def test_build_template_context_contains_sections() -> None:
report_data = ReportData(
pbs_storages=[
PbsStorage(
storage_id="BACKUP-PROD",
username="backup@pbs",
server="192.0.2.10",
datastore="RAID5",
namespace="serveurs",
enabled=True,
),
PbsStorage(
storage_id="BACKUP-LAB",
username="backup@pbs",
server="192.0.2.10",
datastore="RAID5",
namespace="lab",
enabled=True,
)
],
pbs_datastore_usages=[
PbsDatastoreUsage(
server_name="PBS01",
datastore="RAID5",
total_bytes=100,
used_bytes=60,
available_bytes=40,
)
],
pbs_access_users=[
PbsAccessUser(
server_name="PBS01",
auth_id="backup@pbs",
user_id="backup@pbs",
storage_id="BACKUP-PROD",
datastore="RAID5",
namespace="serveurs",
enabled=True,
expire=0,
email="admin@example.invalid",
permissions={"Datastore.Backup": True, "Datastore.Modify": False},
comment="Compte PVE",
)
],
pbs_gc_statuses=[
PbsGarbageCollectionStatus(
server_name="PBS01",
datastore="RAID5",
status="en_cours",
)
],
pbs_retention_policies=[
PbsRetentionPolicy(
policy_id="prune-serveurs",
server_name="PBS01",
datastore="RAID5",
namespace="serveurs",
keep_daily=3,
),
PbsRetentionPolicy(
policy_id="prune-lab",
server_name="PBS01",
datastore="RAID5",
namespace="lab",
keep_daily=3,
),
],
guests=[
Guest(vmid=100, name="srv", guest_type="qemu", notes="note production"),
Guest(vmid=101, name="lab", guest_type="qemu"),
],
coverage=[
BackupCoverage(
guest=Guest(vmid=100, name="srv", guest_type="qemu", notes="note production"),
status="sauvegarde_pbs_planifiee",
jobs=[BackupJob(job_id="backup-prod", schedule="22:00")],
storages=["BACKUP-PROD"],
),
BackupCoverage(
guest=Guest(vmid=101, name="lab", guest_type="qemu"),
status="sauvegarde_pbs_planifiee",
jobs=[BackupJob(job_id="backup-lab", schedule="21:00")],
storages=["BACKUP-LAB"],
)
],
pbs_snapshot_summaries={
("PBS01", "RAID5", "serveurs", "qemu", 100): PbsBackupSnapshotSummary(
server_name="PBS01",
vmid=100,
guest_type="qemu",
datastore="RAID5",
namespace="serveurs",
snapshot_count=3,
oldest_backup_at=datetime(2026, 5, 7, 22, 0),
newest_backup_at=datetime(2026, 5, 9, 22, 0),
),
("PBS01", "RAID5", "lab", "qemu", 101): PbsBackupSnapshotSummary(
server_name="PBS01",
vmid=101,
guest_type="qemu",
datastore="RAID5",
namespace="lab",
snapshot_count=2,
oldest_backup_at=datetime(2026, 5, 8, 21, 0),
newest_backup_at=datetime(2026, 5, 9, 21, 0),
),
},
summary=ReportSummary(generated_at=datetime(2026, 5, 9, 2, 0)),
)
context = build_template_context(
report_data,
{"192.0.2.10": "backup-display"},
)
assert context["generated_at"] == "2026-05-09 02:00"
assert [section.title for section in context["sections"]][:4] == [
"Resume",
"Stockages PBS déclarés sur PVE",
"Utilisateurs PBS - Audit des accès",
"Espaces de stockage PBS",
]
access_section = next(
section for section in context["sections"] if section.title == "Utilisateurs PBS - Audit des accès"
)
assert access_section.headers == [
"Serveur PBS",
"Auth-id",
"Storage PVE",
"Datastore",
"Namespace",
"Actif",
"Expiration",
"Email",
"Permissions",
"Commentaire",
]
assert access_section.rows[0] == [
"PBS01",
"backup@pbs",
"BACKUP-PROD",
"RAID5",
"serveurs",
"oui",
"aucune",
"admin@example.invalid",
"Datastore.Backup",
"Compte PVE",
]
missing_section = next(
section for section in context["sections"] if section.title == "VM/CT non sauvegardees"
)
assert missing_section.headers == ["VMID", "Nom", "Notes", "Type", "Noeud", "Etat", "Detail"]
coverage_group = next(
section for section in context["sections"] if section.title == "Sauvegarde des VM/CT"
)
assert coverage_group.level == 1
coverage_sections = [
section
for section in context["sections"]
if section.title.startswith("Sauvegarde des VM/CT -")
]
assert [section.title for section in coverage_sections] == [
"Sauvegarde des VM/CT - lab",
"Sauvegarde des VM/CT - serveurs",
]
coverage_section = next(
section for section in coverage_sections if section.title == "Sauvegarde des VM/CT - serveurs"
)
assert "Namespace" not in coverage_section.headers
assert coverage_section.headers[2] == "Notes"
assert coverage_section.rows[0][2] == "note production"
assert coverage_section.rows[0][7] == "192.0.2.10 (backup-display)"
assert coverage_section.rows[0][9] == "non renseigne"
retention_group = next(
section for section in context["sections"] if section.title == "Retention des sauvegardes VM/CT"
)
assert retention_group.level == 1
assert retention_group.warning is None
retention_sections = [
section
for section in context["sections"]
if section.title.startswith("Retention des sauvegardes VM/CT PBS01")
]
assert [section.title for section in retention_sections] == [
"Retention des sauvegardes VM/CT PBS01 - lab",
"Retention des sauvegardes VM/CT PBS01 - serveurs",
]
assert not any(
section.title.startswith("Retention des sauvegardes VM/CT PBS02")
for section in context["sections"]
)
assert not any(
section.title.startswith("Retention des sauvegardes VM/CT PBS03")
for section in context["sections"]
)
assert all("Namespace" not in section.headers for section in retention_sections)
assert all("Datastore" in section.headers for section in retention_sections)
assert all("Nombre attendu de versions" in section.headers for section in retention_sections)
assert all("Delta" in section.headers for section in retention_sections)
assert all("garbage collector" in (section.warning or "") for section in retention_sections)
assert retention_sections[0].rows[0][0] == 101
assert retention_sections[0].rows[0][2] == "RAID5"
assert retention_sections[0].rows[0][3] == "Active sur PVE"
assert retention_sections[0].rows[0][5] == "3"
assert retention_sections[0].rows[0][6] == "-1"
def test_render_html_keeps_css_unescaped() -> None:
html = render_html(ReportData())
assert 'content: "Rapport des sauvegardes Proxmox VE"' in html
assert """ not in html
assert '<h1 class="section-group-title">' in html
assert "Sauvegarde des VM/CT" in html
assert '<h1 class="section-group-title">Retention des sauvegardes VM/CT</h1>' not in html
def test_render_html_supports_english_labels() -> None:
report_data = ReportData(
coverage=[
BackupCoverage(
guest=Guest(vmid=100, name="srv", guest_type="qemu"),
status="non_sauvegardee",
)
],
summary=ReportSummary(generated_at=datetime(2026, 5, 9, 2, 0)),
)
html = render_html(report_data, language="en")
assert '<html lang="en">' in html
assert "Proxmox VE Backup Report" in html
assert "Table of contents" in html
assert "VM/CT without backup" in html
assert "not specified" in html
assert "Rapport des sauvegardes Proxmox VE" not in html
def test_pdf_pbs_access_users_table_keeps_expected_fields_without_raw_secrets() -> None:
report_data = ReportData(
pbs_access_users=[
PbsAccessUser(
server_name="PBS01",
auth_id="backup@pbs",
user_id="backup@pbs",
storage_id="BACKUP-PROD",
datastore="RAID5",
namespace="serveurs",
enabled=True,
expire=0,
email="admin@example.invalid",
permissions={"Datastore.Backup": True},
comment="Compte PVE",
raw={
"Authorization": "PBSAPIToken=abc:secret",
"api_token_secret": "secret-value",
"password": "secret-password",
},
)
],
)
html = render_html(report_data)
assert "Utilisateurs PBS - Audit des accès" in html
assert "backup@pbs" in html
assert "BACKUP-PROD" in html
assert "RAID5" in html
assert "serveurs" in html
assert "Datastore.Backup" in html
assert "Compte PVE" in html
assert "PBSAPIToken=abc:secret" not in html
assert "secret-value" not in html
assert "secret-password" not in html
assert "api_token_secret" not in html
def test_build_template_context_omits_unconfigured_retention_servers() -> None:
report_data = ReportData(pbs_server_names=["PBS01"])
context = build_template_context(report_data)
titles = [section.title for section in context["sections"]]
assert "Retention des sauvegardes VM/CT PBS01 - non renseigne" in titles
assert "Retention des sauvegardes VM/CT PBS02 - non renseigne" not in titles
assert "Retention des sauvegardes VM/CT PBS03 - non renseigne" not in titles