mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
test_file_spot failed the gate on the pathlib chain that spelled meta/services.yml out by hand. Both paths now come from utils.roles.mapping as ROLE_FILE_META_SERVICES and ROLE_DIR_META_ADDONS, followingea37027f49and8243aa571b. All four chains move, not only the reported one. The lint reads the role-file shape and lets a "meta" / "addons" directory chain through, so leaving those behind would have split one fixture across two sources of truth, which is the split the lint exists to prevent. The constants are slash-bearing strings that Path accepts as a single segment, so the paths are unchanged. Only the throwaway role built under tempfile in TestCollectEntriesCoversAddons is affected; its assertions on entity_path and config_path.name are untouched. autoformat then reflowed the two write_text concatenations, which fit on one line once the names shortened, and the generator in test_oidc_setting_order.py. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
264 lines
8.9 KiB
Python
264 lines
8.9 KiB
Python
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_DIR_META_ADDONS, ROLE_FILE_META_SERVICES
|
|
from utils.update.repository import (
|
|
RepositoryRefEntry,
|
|
RepositoryRefUpdate,
|
|
apply_updates,
|
|
collect_entries,
|
|
suppressed_ref_lines,
|
|
update_config_refs,
|
|
walk_repo_ref_pairs,
|
|
)
|
|
|
|
|
|
class TestWalkRepoRefPairs(unittest.TestCase):
|
|
def test_finds_top_level_entity(self) -> None:
|
|
data = {
|
|
"bookwyrm": {
|
|
"repository": "https://github.com/bookwyrm-social/bookwyrm.git",
|
|
"ref": "v0.8.5",
|
|
},
|
|
}
|
|
|
|
pairs = list(walk_repo_ref_pairs(data, ()))
|
|
|
|
self.assertEqual(
|
|
pairs,
|
|
[
|
|
(
|
|
("bookwyrm",),
|
|
"https://github.com/bookwyrm-social/bookwyrm.git",
|
|
"v0.8.5",
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_descends_into_nested_plugin_map(self) -> None:
|
|
data = {
|
|
"discourse": {
|
|
"plugins": {
|
|
"discourse-ldap-auth": {
|
|
"repository": "https://github.com/jonmbake/discourse-ldap-auth.git",
|
|
"ref": "master",
|
|
},
|
|
"discourse-prometheus": {
|
|
"repository": "https://github.com/discourse/discourse-prometheus.git",
|
|
"ref": "v1.0.0",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
pairs = list(walk_repo_ref_pairs(data, ()))
|
|
entity_paths = {pair[0] for pair in pairs}
|
|
|
|
self.assertIn(("discourse", "plugins", "discourse-ldap-auth"), entity_paths)
|
|
self.assertIn(("discourse", "plugins", "discourse-prometheus"), entity_paths)
|
|
|
|
def test_skips_when_only_repository_or_only_ref(self) -> None:
|
|
data = {
|
|
"only-repo": {"repository": "https://example.invalid/x.git"},
|
|
"only-ref": {"ref": "v1.0.0"},
|
|
"both": {
|
|
"repository": "https://example.invalid/y.git",
|
|
"ref": "v2.0.0",
|
|
},
|
|
}
|
|
|
|
pairs = list(walk_repo_ref_pairs(data, ()))
|
|
|
|
self.assertEqual(len(pairs), 1)
|
|
self.assertEqual(pairs[0][0], ("both",))
|
|
|
|
def test_walks_lists(self) -> None:
|
|
data = {
|
|
"repos": [
|
|
{
|
|
"repository": "https://example.invalid/a.git",
|
|
"ref": "v1",
|
|
},
|
|
{
|
|
"repository": "https://example.invalid/b.git",
|
|
"ref": "v2",
|
|
},
|
|
],
|
|
}
|
|
|
|
pairs = list(walk_repo_ref_pairs(data, ()))
|
|
|
|
self.assertEqual(len(pairs), 2)
|
|
self.assertEqual(pairs[0][0], ("repos", "[0]"))
|
|
self.assertEqual(pairs[1][0], ("repos", "[1]"))
|
|
|
|
|
|
class TestUpdateConfigRefs(unittest.TestCase):
|
|
def test_rewrites_target_line_and_preserves_others(self) -> None:
|
|
original = """plugin-a:
|
|
repository: https://example.invalid/a.git
|
|
ref: v1.0.0
|
|
plugin-b:
|
|
repository: https://example.invalid/b.git
|
|
ref: v2.0.0
|
|
"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "services.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
changed = update_config_refs(config_path, {3: "v1.1.0"})
|
|
|
|
self.assertTrue(changed)
|
|
updated = read_text(str(config_path))
|
|
self.assertIn(" ref: v1.1.0\n", updated)
|
|
self.assertIn(" ref: v2.0.0\n", updated)
|
|
|
|
def test_preserves_quoting_and_trailing_comment(self) -> None:
|
|
original = ' ref: "v0.7.5" # pin\n'
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "services.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
changed = update_config_refs(config_path, {1: "v0.8.5"})
|
|
|
|
self.assertTrue(changed)
|
|
self.assertEqual(
|
|
read_text(str(config_path)),
|
|
' ref: "v0.8.5" # pin\n',
|
|
)
|
|
|
|
def test_no_op_when_target_value_already_matches(self) -> None:
|
|
original = " ref: v1.0.0\n"
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "services.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
changed = update_config_refs(config_path, {1: "v1.0.0"})
|
|
|
|
self.assertFalse(changed)
|
|
self.assertEqual(read_text(str(config_path)), original)
|
|
|
|
def test_skips_out_of_range_line_numbers(self) -> None:
|
|
original = " ref: v1.0.0\n"
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "services.yml"
|
|
config_path.write_text(original, encoding="utf-8")
|
|
|
|
changed = update_config_refs(config_path, {99: "v2.0.0"})
|
|
|
|
self.assertFalse(changed)
|
|
self.assertEqual(read_text(str(config_path)), original)
|
|
|
|
|
|
class TestSuppressedRefLines(unittest.TestCase):
|
|
def test_recognises_line_above_marker(self) -> None:
|
|
content = """plugin-a:
|
|
repository: https://example.invalid/a.git
|
|
# nocheck: repository-version
|
|
ref: v1.0.0
|
|
plugin-b:
|
|
repository: https://example.invalid/b.git
|
|
ref: v2.0.0
|
|
"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_path = Path(tmpdir) / "services.yml"
|
|
config_path.write_text(content, encoding="utf-8")
|
|
|
|
suppressed = suppressed_ref_lines(config_path)
|
|
|
|
self.assertEqual(suppressed, {4})
|
|
|
|
|
|
class TestApplyUpdates(unittest.TestCase):
|
|
def test_groups_updates_per_config_file(self) -> None:
|
|
file_a_content = " ref: v1.0.0\n"
|
|
file_b_content = " ref: v2.0.0\n"
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
config_a = Path(tmpdir) / "a" / "services.yml"
|
|
config_b = Path(tmpdir) / "b" / "services.yml"
|
|
config_a.parent.mkdir()
|
|
config_b.parent.mkdir()
|
|
config_a.write_text(file_a_content, encoding="utf-8")
|
|
config_b.write_text(file_b_content, encoding="utf-8")
|
|
|
|
updates = [
|
|
RepositoryRefUpdate(
|
|
entry=RepositoryRefEntry(
|
|
role="role-a",
|
|
entity_path=("plugin-a",),
|
|
repository="https://example.invalid/a.git",
|
|
ref="v1.0.0",
|
|
config_path=config_a,
|
|
line=1,
|
|
),
|
|
latest="v1.1.0",
|
|
),
|
|
RepositoryRefUpdate(
|
|
entry=RepositoryRefEntry(
|
|
role="role-b",
|
|
entity_path=("plugin-b",),
|
|
repository="https://example.invalid/b.git",
|
|
ref="v2.0.0",
|
|
config_path=config_b,
|
|
line=1,
|
|
),
|
|
latest="v2.1.0",
|
|
),
|
|
]
|
|
|
|
changed = apply_updates(updates)
|
|
|
|
self.assertEqual(set(changed), {config_a, config_b})
|
|
self.assertEqual(read_text(str(config_a)), " ref: v1.1.0\n")
|
|
self.assertEqual(read_text(str(config_b)), " ref: v2.1.0\n")
|
|
|
|
|
|
class TestCollectEntriesCoversAddons(unittest.TestCase):
|
|
def _role(self, root: Path) -> Path:
|
|
role = root / "roles" / "web-app-example"
|
|
(role / ROLE_DIR_META_ADDONS).mkdir(parents=True)
|
|
return role
|
|
|
|
def test_addon_ref_is_collected_beside_the_service_ref(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
root = Path(tmpdir)
|
|
role = self._role(root)
|
|
(role / ROLE_FILE_META_SERVICES).write_text(
|
|
"app:\n repository: https://example.test/app.git\n ref: v1.0.0\n",
|
|
encoding="utf-8",
|
|
)
|
|
(role / ROLE_DIR_META_ADDONS / "plug.yml").write_text(
|
|
"config:\n repository: https://example.test/plug.git\n ref: v2.0.0\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
by_ref = {entry.ref: entry for entry in collect_entries(root)}
|
|
|
|
self.assertEqual(set(by_ref), {"v1.0.0", "v2.0.0"})
|
|
addon = by_ref["v2.0.0"]
|
|
self.assertEqual(addon.entity_path[:2], ("addons", "plug"))
|
|
self.assertEqual(addon.config_path.name, "plug.yml")
|
|
|
|
def test_addon_ref_honours_its_own_suppression_marker(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
root = Path(tmpdir)
|
|
role = self._role(root)
|
|
(role / ROLE_DIR_META_ADDONS / "plug.yml").write_text(
|
|
"config:\n"
|
|
" repository: https://example.test/plug.git\n"
|
|
" # nocheck: repository-version\n"
|
|
" ref: v2.0.0\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
self.assertEqual(collect_entries(root), [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|