from unittest.mock import patch from scripts import forgejo_package_prune as p IMAGE = 'forgejo.coulomb.social/coulomb/activity-core' def test_digest_and_tag_digest_protect_whole_package(tmp_path): for suffix in ['@sha256:' + 'a'*64, ':old@sha256:' + 'b'*64]: f = tmp_path / 'images'; f.write_text(IMAGE + suffix + '\n') protected, notes = p.collect_live_images_from_files([f]) assert notes == [] assert protected == {('container', 'activity-core', '*')} versions = [{'name': name, 'version': version, 'created_at': date} for name in ['activity-core', 'unrelated'] for version, date in [('new', '2026-09-27'), ('old', '2025-01-01')]] with patch.object(p, 'list_packages', return_value=versions): plans, errors = p.build_delete_plans(base_url='', token='', owner='coulomb', package_types=['container'], max_versions=1, protected=protected) assert not errors assert [(x.name, x.protected, x.reason) for x in plans] == [ ('activity-core', True, 'protected_digest_package'), ('unrelated', False, 'beyond_retention_depth')] def test_bad_digest_refuses_apply_before_credentials(tmp_path): f = tmp_path / 'images'; f.write_text(IMAGE + '@sha256:bad\n') with patch.object(p, 'load_token') as auth, patch.object(p, 'delete_version') as delete: assert p.main(['--apply', '--live-images-file', str(f)]) == 2 auth.assert_not_called(); delete.assert_not_called() def test_incomplete_inventory_never_deletes(): for errors, notes in [(['list failed'], []), ([], ['cluster unavailable'])]: with patch.object(p, 'load_token', return_value='fixture'), \ patch.object(p, 'collect_protected_versions', return_value=set()), \ patch.object(p, 'collect_live_cluster_versions', return_value=(set(), notes)), \ patch.object(p, 'build_delete_plans', return_value=([p.DeletePlan('container','x','old','',False,'old')], errors)), \ patch.object(p, 'delete_version') as delete: assert p.main(['--apply']) == 2 delete.assert_not_called() def test_cluster_digest_uses_same_protection(): import subprocess with patch.object(p.shutil, 'which', return_value='/bin/kubectl'), \ patch.object(p.subprocess, 'run', return_value=subprocess.CompletedProcess([],0,IMAGE+'@sha256:'+'a'*64+'\n','')): protected, notes = p.collect_live_cluster_versions() assert not notes assert ('container','activity-core','*') in protected