from __future__ import annotations import tempfile import unittest from pathlib import Path from utils.cache.files import read_text from utils.roles.mapping import ROLE_FILE_META_SERVICES from utils.update.docker import ( _registry_cursor, collect_entries, sourced_services, update_config_versions, ) SOURCED = """bundled: image: example/app version: v1.0.0 update: type: http_regex url: https://example.invalid/bundle pattern: "app:(v[0-9.]+)" tracked: image: example/side version: v2.0.0 other_version: v3.0.0 update: - key: other_version type: git_tags repository: https://example.invalid/side.git plain: image: example/plain version: v4.0.0 """ class TestRegistryCursor(unittest.TestCase): def test_v_prefixed_pin_seeds_cursor(self) -> None: self.assertEqual(_registry_cursor("v19.1.1"), "v") def test_bare_numeric_pin_scans_from_start(self) -> None: self.assertIsNone(_registry_cursor("19.1.1")) class TestDeclaredSource(unittest.TestCase): def test_a_version_with_its_own_source_is_left_to_that_source(self) -> None: root = Path(tempfile.mkdtemp()) config = root / "roles" / "web-app-example" / ROLE_FILE_META_SERVICES config.parent.mkdir(parents=True) config.write_text(SOURCED, encoding="utf-8") self.assertEqual(sourced_services(config), {"bundled"}) self.assertEqual( sorted(entry.service for entry in collect_entries(root)), ["plain", "tracked"], ) class TestUpdateDocker(unittest.TestCase): def test_update_config_versions_updates_only_target_services(self) -> None: original = """moodle: version: "4.5" # Keep comment image: bitnamilegacy/moodle nginx: version: alpine image: nginx """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "main.yml" config_path.write_text(original, encoding="utf-8") changed = update_config_versions(config_path, {"moodle": "5.0"}) self.assertTrue(changed) updated = read_text(str(config_path)) self.assertIn('version: "5.0" # Keep comment', updated) self.assertIn("version: alpine", updated) self.assertNotIn('version: "4.5" # Keep comment', updated) def test_a_bump_drops_the_image_tag_suppression_of_the_old_tag(self) -> None: original = """penpot: image: penpotapp/exporter version: "2.18.5" security: suppressions: # nocheck: cve upstream ships no rebuild yet - image_tag: "2.18.5" until: 2026-12-31 # nocheck: shared nginx: image: nginx version: "1.29" security: suppressions: # nocheck: cve upstream ships no rebuild yet - image_tag: "1.29" until: 2026-12-31 """ expected = original.replace( """ version: "2.18.5" security: suppressions: # nocheck: cve upstream ships no rebuild yet - image_tag: "2.18.5" until: 2026-12-31 """, ' version: "2.18.6"\n', ) with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "main.yml" config_path.write_text(original, encoding="utf-8") update_config_versions(config_path, {"penpot": "2.18.6"}) self.assertEqual(read_text(str(config_path)), expected) if __name__ == "__main__": unittest.main()