Files
ProxMenux/oci/tests/test_volume_preparations_curated.py
T

81 lines
3.9 KiB
Python

"""Regression tests for volume_preparations coverage across generated and
curated-profile catalog entries.
Covers the original OCI lost+found fix: a fresh ext4 managed volume carries
a lost+found directory, which stops images that take ownership of their own
data directories. volume_preparations (remove_lost_found +
only_when_mount_type: managed-volume) must be present for every persistent
mount, for both regular generated apps/*.json entries and curated-profile
entries (curated/*.json) — the curated-profile gap (Jellyfin Official first
surfaced it) is exactly what this suite guards against regressing.
"""
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from proxmenux_oci.catalog import Catalog
class VolumePreparationsCoverageTests(unittest.TestCase):
def _preparations_by_path(self, template):
preparations = template["proxmox"]["installer_profile"].get("volume_preparations", [])
return {item["container_path"]: item for item in preparations}
def test_generated_app_volume_preparations_match_persistent_mounts(self):
"""Regular generated app (Uptime Kuma, apps/uptimekuma.json): every
declared volume has a matching volume_preparations entry, and every
entry is scoped to only_when_mount_type: managed-volume."""
template = Catalog(ROOT).compose("uptimekuma")
volume_paths = {v["container_path"] for v in template["container_contract"].get("volumes", [])}
preparations = self._preparations_by_path(template)
self.assertTrue(volume_paths, "fixture app must declare at least one volume")
self.assertEqual(set(preparations.keys()), volume_paths)
for path, entry in preparations.items():
self.assertTrue(entry.get("remove_lost_found"))
self.assertEqual(entry.get("only_when_mount_type"), "managed-volume")
def test_curated_profile_app_volume_preparations_match_persistent_mounts(self):
"""Curated-profile app (Jellyfin Official, curated/jellyfin-official.json,
3 mounts: /config, /cache, /media): same coverage requirement as
generated apps. This is the regression guard for the gap first found
via live testing — the original fix only touched apps/*.json and
missed curated/*.json entirely."""
template = Catalog(ROOT).compose("jellyfin-official")
volume_paths = {v["container_path"] for v in template["container_contract"].get("volumes", [])}
preparations = self._preparations_by_path(template)
self.assertEqual(volume_paths, {"/config", "/cache", "/media"})
self.assertEqual(set(preparations.keys()), volume_paths)
for path, entry in preparations.items():
self.assertTrue(entry.get("remove_lost_found"))
self.assertEqual(entry.get("only_when_mount_type"), "managed-volume")
def test_overlay_app_volume_preparations_match_the_overlay_remapped_mount(self):
"""PocketBase (apps/pocketbase.json + overlays/pocketbase.json): the
overlay remaps the container_path from /pb_data to
/pocketbase/pb_data. volume_preparations must follow the overlay's
remapped path, not the pre-overlay source path — this is the
regression guard for the overlay/mount desync first found via
read-only audit (2026-09-29)."""
template = Catalog(ROOT).compose("pocketbase")
volume_paths = {v["container_path"] for v in template["container_contract"].get("volumes", [])}
preparations = self._preparations_by_path(template)
self.assertEqual(volume_paths, {"/pocketbase/pb_data"})
self.assertEqual(set(preparations.keys()), volume_paths)
for path, entry in preparations.items():
self.assertTrue(entry.get("remove_lost_found"))
self.assertEqual(entry.get("only_when_mount_type"), "managed-volume")
if __name__ == "__main__":
unittest.main()