286 lines
10 KiB
Python
286 lines
10 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_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
|