Files
computer-playbook/tests/unit/python/utils/packages/test_registry.py
T
kevinveenbirkenbachandClaude Opus 5.5 c9e7f950d9 Merge branch 'feature/updater' into feature/store-i18n
68 conflicts, resolved per hunk; resolutions agreed with the Updater
session were applied as agreed:

- svc-db-postgres (superuser password script and core) and
  sys-svc-rdbms realign_dedicated_db: updater's argv/stdin redesign with
  expand_argument_vars: false, nested into store-i18n's block structure.
- svc-db-redis 01_init: store-i18n's ACL password on stdin.
- sys-svc-proxy forwarded.conf.j2: store-i18n's handover-aware file,
  without the forwarded_base include (Host and X-Forwarded-Proto would
  be sent twice); forwarded_base serves the inline consumers, e.g.
  web-app-seaweedfs nginx.conf.j2.
- basic.conf.j2 and web-app-openproject proxy.conf.j2: issuer redirect,
  then handover, else updater's location/sso_acl.conf.j2; the
  server-level webserver_extra_configuration hook stays.
- web-app-keycloak: updater's admin-REST settings step and renumbered
  update steps; update/01_client.yml removed; store-i18n's design step
  and LDAP block kept; a73435cc9d arrived through rename detection.
- web-app-nextcloud OpenProject provisioning: updater's rewrite, all
  credentials on the Ruby runner's stdin.
- web-app-semaphore: store-i18n's canonical web root (a1d7835863).
- Role READMEs: no generated Cosmos/Architecture/Quick Setup/Credits
  sections; desk-* paths resolved to the dsk-* renames.

Integration fixes for what merged cleanly but did not fit together:

- web-app-matrix ansible vars: drop updater's second
  matrix_client_element_configuration_extension_json, which silently
  overrode store-i18n's design-aware value.
- url.origin instead of a stripped url.base in 19 places store-i18n
  added since 44aaa68924, same lookup targets (the double-slash lint).
- Package batch (18ea75049e, cb4d340de9): installs the deploy-start
  batch covers are removed (caffeine, cursor, blender, npm in dsk-gnt-*,
  with shared references), the rest carry batch: false and a nocheck
  with the verified reason (PyPI fallback, before the batch, vendor
  repository, configured flavor only, addon gate); dev-npm references
  the root npm id.
- sys-token-store write_many.yml: store-i18n's change-gated block and
  IS_SWARM_MODE.
- web-app-openproject: IS_COMPOSE_MODE in 02_migrate/run.yml; the
  seeded-admin lock (ccbf8f168d) ported into 04_admin/provision.yml and
  covered by the admin-state digest.
- Bucket policy (3b4ac40df5 and follow-ups) moved to web-svc-seaweedfs,
  where store-i18n's object store lives; the S3 admin secret travels on
  stdin instead of a task-level environment.
- act workflow.sh: no Exception-labelled comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 02:28:16 +01:00

358 lines
13 KiB
Python

import tempfile
import unittest
from pathlib import Path
from utils.cache.yaml import dump_yaml
from utils.packages.registry import (
build_registry,
iter_inventory_package_lists,
load_declarations,
load_references,
resolve,
)
from utils.packages.schema import (
DISTRO_FAMILY,
SOURCE_AUR,
SOURCE_REPO,
PackagesShapeError,
)
def _write_role(root: Path, role: str, payload) -> None:
meta = root / "roles" / role / "meta"
meta.mkdir(parents=True, exist_ok=True)
dump_yaml(str(meta / "packages.yml"), payload)
def _write_shared(root: Path, payload) -> None:
meta = root / "meta"
meta.mkdir(parents=True, exist_ok=True)
dump_yaml(str(meta / "packages.yml"), payload)
def _write_inventory(root: Path, name: str, payload) -> None:
directory = root / "inventories" / name
directory.mkdir(parents=True, exist_ok=True)
dump_yaml(str(directory / "inventory.yml"), payload)
class TestRegistry(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
self.root = Path(self._tmp.name)
self.addCleanup(self._tmp.cleanup)
def _declaration(self, package_id: str):
return build_registry(self.root)[package_id]
def test_bare_list_is_repo_source(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
spec = resolve(self._declaration("git"), "debian")
self.assertEqual(spec.names, ("git",))
self.assertEqual(spec.source, SOURCE_REPO)
self.assertIsNone(spec.repo)
def test_distro_override_beats_family(self):
_write_role(
self.root,
"role-a",
{
"nfs-ganesha": {
"RedHat": ["nfs-ganesha"],
"centos": {
"names": ["nfs-ganesha"],
"repo": {"baseurl": "https://sig.example/repo/"},
},
}
},
)
declaration = self._declaration("nfs-ganesha")
self.assertIsNone(resolve(declaration, "fedora").repo)
self.assertEqual(
resolve(declaration, "centos").repo["baseurl"], "https://sig.example/repo/"
)
def test_ubuntu_resolves_through_debian_family(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
self.assertEqual(resolve(self._declaration("git"), "ubuntu").names, ("git",))
def test_aur_source(self):
_write_role(
self.root,
"role-a",
{"nfs-ganesha": {"Archlinux": {"source": "aur", "names": ["nfs-ganesha"]}}},
)
spec = resolve(self._declaration("nfs-ganesha"), "arch")
self.assertEqual(spec.source, SOURCE_AUR)
def test_empty_list_is_covered_but_installs_nothing(self):
_write_role(self.root, "role-a", {"python-selinux": {"Debian": []}})
spec = resolve(self._declaration("python-selinux"), "debian")
self.assertIsNotNone(spec)
self.assertEqual(spec.names, ())
def test_virtual_flag_is_carried(self):
_write_role(
self.root,
"role-a",
{"nodejs": {"RedHat": {"names": ["nodejs"], "virtual": True}}},
)
self.assertTrue(resolve(self._declaration("nodejs"), "fedora").virtual)
def test_virtual_defaults_to_false(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
self.assertFalse(resolve(self._declaration("git"), "debian").virtual)
def test_non_boolean_virtual_raises(self):
_write_role(
self.root,
"role-a",
{"git": {"Debian": {"names": ["git"], "virtual": "yes"}}},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("git"), "debian")
def test_inline_repo_is_carried(self):
_write_role(
self.root,
"role-a",
{
"micro": {
"centos": {
"names": ["micro"],
"repo": {"bootstrap_package": "epel-release"},
}
}
},
)
spec = resolve(self._declaration("micro"), "centos")
self.assertEqual(spec.repo["bootstrap_package"], "epel-release")
def test_repo_as_string_raises(self):
_write_role(
self.root,
"role-a",
{"micro": {"centos": {"names": ["micro"], "repo": "epel"}}},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("micro"), "centos")
def test_repo_without_a_known_key_raises(self):
_write_role(
self.root,
"role-a",
{"micro": {"centos": {"names": ["micro"], "repo": {"name": "x"}}}},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("micro"), "centos")
def test_missing_key_is_a_gap(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
self.assertIsNone(resolve(self._declaration("git"), "arch"))
def test_duplicate_id_across_roles_raises(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
_write_role(self.root, "role-b", {"git": {"Debian": ["git"]}})
with self.assertRaises(PackagesShapeError) as ctx:
build_registry(self.root)
self.assertIn("declared twice", str(ctx.exception))
def test_unknown_source_raises(self):
_write_role(
self.root,
"role-a",
{"git": {"Debian": {"names": ["git"], "source": "snap"}}},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("git"), "debian")
def test_download_without_a_checksum_raises(self):
_write_role(
self.root,
"role-a",
{
"editor": {
"Debian": {
"names": ["editor"],
"source": "download",
"download": {"url": "https://vendor.example/editor.deb"},
}
}
},
)
with self.assertRaises(PackagesShapeError) as ctx:
resolve(self._declaration("editor"), "debian")
self.assertIn("checksum", str(ctx.exception))
def test_download_rejects_a_weak_digest(self):
_write_role(
self.root,
"role-a",
{
"editor": {
"Debian": {
"names": ["editor"],
"source": "download",
"download": {
"url": "https://vendor.example/editor.deb",
"checksum": "md5:" + "a" * 32,
},
}
}
},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("editor"), "debian")
def test_download_rejects_a_plaintext_url(self):
_write_role(
self.root,
"role-a",
{
"editor": {
"Debian": {
"names": ["editor"],
"source": "download",
"download": {
"url": "http://vendor.example/editor.deb",
"checksum": "sha256:" + "a" * 64,
},
}
}
},
)
with self.assertRaises(PackagesShapeError) as ctx:
resolve(self._declaration("editor"), "debian")
self.assertIn("https", str(ctx.exception))
def test_download_source_without_its_block_raises(self):
_write_role(
self.root,
"role-a",
{"editor": {"Debian": {"names": ["editor"], "source": "download"}}},
)
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("editor"), "debian")
def test_a_pinned_download_resolves(self):
_write_role(
self.root,
"role-a",
{
"editor": {
"Debian": {
"names": ["editor"],
"source": "download",
"download": {
"url": "https://vendor.example/editor.deb",
"checksum": "sha256:" + "a" * 64,
},
}
}
},
)
spec = resolve(self._declaration("editor"), "debian")
self.assertEqual(spec.source, "download")
self.assertEqual(spec.download["url"], "https://vendor.example/editor.deb")
def test_unknown_distro_raises_without_family(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
with self.assertRaises(PackagesShapeError):
resolve(self._declaration("git"), "gentoo")
def test_distro_outside_the_matrix_resolves_through_given_family(self):
_write_role(self.root, "role-a", {"sudo": {"Archlinux": ["sudo"]}})
spec = resolve(self._declaration("sudo"), "manjarolinux", "Archlinux")
self.assertEqual(spec.names, ("sudo",))
def test_given_family_still_loses_to_a_distro_override(self):
_write_role(
self.root,
"role-a",
{"pkg": {"RedHat": ["base"], "centos": ["override"]}},
)
spec = resolve(self._declaration("pkg"), "centos", "RedHat")
self.assertEqual(spec.names, ("override",))
def test_declarations_carry_role_provenance(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
declarations = load_declarations(self.root)
self.assertEqual(declarations[0].role, "role-a")
self.assertFalse(declarations[0].shared)
self.assertEqual(declarations[0].owner, "role-a")
def test_root_declarations_are_shared(self):
_write_shared(self.root, {"git": {"Debian": ["git"]}})
declaration = self._declaration("git")
self.assertIsNone(declaration.role)
self.assertTrue(declaration.shared)
self.assertEqual(resolve(declaration, "debian").names, ("git",))
def test_id_declared_both_shared_and_by_a_role_raises(self):
_write_shared(self.root, {"git": {"Debian": ["git"]}})
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
with self.assertRaises(PackagesShapeError) as ctx:
build_registry(self.root)
self.assertIn("declared twice", str(ctx.exception))
def test_inventory_package_lists_are_collected(self):
_write_inventory(
self.root, "bundle", {"all": {"vars": {"PACKAGES": ["git", "cmake"]}}}
)
collected = list(iter_inventory_package_lists(self.root))
self.assertEqual([ids for _path, ids in collected], [["git", "cmake"]])
def test_inventory_without_packages_is_skipped(self):
_write_inventory(self.root, "bundle", {"all": {"vars": {"OTHER": 1}}})
self.assertEqual(list(iter_inventory_package_lists(self.root)), [])
def test_inventory_packages_as_mapping_raises(self):
_write_inventory(
self.root, "bundle", {"all": {"vars": {"PACKAGES": {"repo": ["git"]}}}}
)
with self.assertRaises(PackagesShapeError):
list(iter_inventory_package_lists(self.root))
def test_an_unflagged_id_is_batched(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"]}})
self.assertTrue(self._declaration("git").batch)
def test_batch_false_keeps_the_id_out_of_the_batch(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"], "batch": False}})
self.assertFalse(self._declaration("git").batch)
def test_the_batch_flag_is_no_distro_key(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"], "batch": False}})
self.assertIsNone(resolve(self._declaration("git"), "arch"))
def test_non_boolean_batch_raises(self):
_write_role(self.root, "role-a", {"git": {"Debian": ["git"], "batch": "no"}})
with self.assertRaises(PackagesShapeError):
load_declarations(self.root)
def test_a_shared_reference_is_no_declaration(self):
_write_shared(self.root, {"git": {"Debian": ["git"]}})
_write_role(self.root, "role-a", {"git": {"shared": True}})
declaration = self._declaration("git")
self.assertTrue(declaration.shared)
def test_shared_references_are_collected_per_role(self):
_write_shared(self.root, {"git": {"Debian": ["git"]}})
_write_role(self.root, "role-a", {"git": {"shared": True}})
_write_role(self.root, "role-b", {"curl": {"Debian": ["curl"]}})
self.assertEqual(load_references(self.root), {"role-a": {"git"}})
def test_shared_flag_in_the_root_file_is_no_reference(self):
_write_shared(self.root, {"git": {"shared": True}})
self.assertEqual(load_references(self.root), {})
self.assertIn("git", build_registry(self.root))
def test_default_matrix_covers_five_distros(self):
self.assertEqual(
sorted(DISTRO_FAMILY), ["arch", "centos", "debian", "fedora", "ubuntu"]
)
if __name__ == "__main__":
unittest.main()