mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-08 22:46:41 +00:00
fix(oci): prepare managed volumes across stack installs
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user