from __future__ import annotations import importlib.util import sys import unittest from pathlib import Path from unittest import mock REPO_DIR = Path(__file__).resolve().parents[1] SPEC = importlib.util.spec_from_file_location( "forgejo_package_prune", REPO_DIR / "scripts/forgejo_package_prune.py" ) prune = importlib.util.module_from_spec(SPEC) assert SPEC.loader is not None sys.modules[SPEC.name] = prune SPEC.loader.exec_module(prune) class ForgejoPackagePruneTests(unittest.TestCase): def test_collect_protected_versions_from_helm_values(self) -> None: import tempfile with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) helm = root / "helm" helm.mkdir() (helm / "vergabe-teilnahme-values.yaml").write_text( """ image: repository: forgejo.coulomb.social/coulomb/vergabe-teilnahme tag: "064d295" """.lstrip(), encoding="utf-8", ) protected = prune.collect_protected_versions(root) self.assertIn(("container", "vergabe-teilnahme", "064d295"), protected) def test_build_delete_plans_respects_depth_and_protection(self) -> None: protected = {("container", "vergabe-teilnahme", "old-prod")} # Forgejo's package list endpoint returns one entry per (name, version); # build_delete_plans groups these by name (no separate /versions call). packages = [ {"name": "vergabe-teilnahme", "version": "v4", "created_at": "2026-07-12T10:00:00Z"}, {"name": "vergabe-teilnahme", "version": "v3", "created_at": "2026-07-11T10:00:00Z"}, {"name": "vergabe-teilnahme", "version": "v2", "created_at": "2026-07-10T10:00:00Z"}, {"name": "vergabe-teilnahme", "version": "v1", "created_at": "2026-07-09T10:00:00Z"}, {"name": "vergabe-teilnahme", "version": "old-prod", "created_at": "2026-07-08T10:00:00Z"}, ] with mock.patch.object(prune, "list_packages", return_value=packages): plans, errors = prune.build_delete_plans( base_url="https://forgejo.example", token="token", owner="coulomb", package_types=["container"], max_versions=3, protected=protected, ) self.assertEqual(errors, []) deletable = {(p.name, p.version) for p in plans if not p.protected} protected_plans = {(p.name, p.version) for p in plans if p.protected} self.assertEqual(deletable, {("vergabe-teilnahme", "v1")}) self.assertEqual(protected_plans, {("vergabe-teilnahme", "old-prod")}) def test_collect_live_cluster_versions_protects_forgejo_tags(self) -> None: images = "\n".join([ "forgejo.coulomb.social/coulomb/state-hub:f2e042a", "forgejo.coulomb.social/coulomb/vergabe-teilnahme:064d295", "registry.k8s.io/pause:3.9", # unrelated registry → ignored "gitea.coulomb.social/coulomb/old-app:x", # non-forgejo → ignored "", ]) with mock.patch.object(prune.shutil, "which", return_value="/usr/bin/kubectl"), \ mock.patch.object( prune.subprocess, "run", return_value=mock.Mock(stdout=images, returncode=0), ): protected, notes = prune.collect_live_cluster_versions() self.assertEqual(notes, []) self.assertEqual( protected, { ("container", "state-hub", "f2e042a"), ("container", "vergabe-teilnahme", "064d295"), }, ) def test_collect_live_cluster_versions_skips_without_kubectl(self) -> None: with mock.patch.object(prune.shutil, "which", return_value=None): protected, notes = prune.collect_live_cluster_versions() self.assertEqual(protected, set()) self.assertTrue(notes and "kubectl not found" in notes[0]) if __name__ == "__main__": unittest.main()