mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
lookup('i18n_content', …) already returns every language in one dict, and the
role took it apart again: one stack_host_template action per language, a file
task for the directory, a find, and a second loop dropping the catalog of a
language core no longer translates. Four tasks and two loops for what is one
mapping.
The template they looped over was not a template. i18n-content.yaml.j2 held a
single line, `{{ dashboard_i18n_content[item] | to_nice_yaml(width=100000) }}`
— serialisation wearing a costume — so it is deleted.
library/stack_host_files.py takes the whole mapping, serialises each entry,
writes only what differs, drops what the caller no longer wants and creates the
directory, in one module run. The IS_STACK_HOST gate moves to the task's `when`
because a module runs on the target rather than on the controller, where the
sibling stack_host_template and stack_host_copy action plugins gate it.
The per-language file layout stays: files/Dockerfile copies i18n-content/ into
the image, and port-ui reads one yaml per language from /app/i18n/content.
main() stays thin so render, survey, plan and apply can be unit-tested without
mocking AnsibleModule, the way library/database_query.py is tested. The cycle
test asserts the second pass finds nothing to do.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
144 lines
6.0 KiB
Python
144 lines
6.0 KiB
Python
"""Unit tests for library/stack_host_files.py.
|
|
|
|
Exercises the building blocks (`render`, `survey`, `plan`, `apply`)
|
|
directly. The `main()` entrypoint is integration-tested through the
|
|
dashboard role's playbook — mocking AnsibleModule stdin/exit_json
|
|
end-to-end adds churn without catching additional logic bugs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
from . import PROJECT_ROOT
|
|
|
|
if TYPE_CHECKING:
|
|
from types import ModuleType
|
|
|
|
MODULE_PATH = PROJECT_ROOT / "library" / "stack_host_files.py"
|
|
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
|
|
|
|
def _load_stack_host_files() -> ModuleType:
|
|
spec = importlib.util.spec_from_file_location(
|
|
"library_stack_host_files", str(MODULE_PATH)
|
|
)
|
|
if spec is None or spec.loader is None:
|
|
raise ImportError(f"Could not load: {MODULE_PATH}")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod) # type: ignore[attr-defined]
|
|
return mod
|
|
|
|
|
|
_MOD = _load_stack_host_files()
|
|
|
|
|
|
class TestRender(unittest.TestCase):
|
|
def test_every_document_becomes_its_own_suffixed_file(self) -> None:
|
|
out = _MOD.render({"de": {"Home": "Startseite"}, "en": {}}, ".yaml", 4, 100)
|
|
self.assertEqual(sorted(out), ["de.yaml", "en.yaml"])
|
|
|
|
def test_the_document_is_serialised_as_block_yaml(self) -> None:
|
|
out = _MOD.render({"de": {"Home": "Startseite"}}, ".yaml", 4, 100)
|
|
self.assertEqual(out["de.yaml"], "Home: Startseite\n")
|
|
|
|
def test_non_ascii_survives_unescaped(self) -> None:
|
|
out = _MOD.render({"de": {"Mail": "E-Mäil"}}, ".yaml", 4, 100)
|
|
self.assertIn("E-Mäil", out["de.yaml"])
|
|
|
|
def test_a_wide_width_keeps_a_long_scalar_on_one_line(self) -> None:
|
|
long_value = "word " * 40
|
|
out = _MOD.render({"de": {"K": long_value}}, ".yaml", 4, 100_000)
|
|
self.assertEqual(len(out["de.yaml"].splitlines()), 1)
|
|
|
|
|
|
class TestSurvey(unittest.TestCase):
|
|
def test_a_missing_directory_surveys_as_empty(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
self.assertEqual(_MOD.survey(Path(tmp) / "absent", ".yaml"), {})
|
|
|
|
def test_only_files_carrying_the_suffix_are_reported(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
directory = Path(tmp)
|
|
(directory / "de.yaml").write_text("a\n", encoding="utf-8")
|
|
(directory / "notes.txt").write_text("b\n", encoding="utf-8")
|
|
self.assertEqual(sorted(_MOD.survey(directory, ".yaml")), ["de.yaml"])
|
|
|
|
|
|
class TestPlan(unittest.TestCase):
|
|
def _directory(self, tmp: str, files: dict[str, str]) -> dict[str, Path]:
|
|
directory = Path(tmp)
|
|
for name, content in files.items():
|
|
(directory / name).write_text(content, encoding="utf-8")
|
|
return _MOD.survey(directory, ".yaml")
|
|
|
|
def test_a_missing_file_is_written(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
present = self._directory(tmp, {})
|
|
written, removed = _MOD.plan(present, {"de.yaml": "a\n"}, True)
|
|
self.assertEqual((written, removed), (["de.yaml"], []))
|
|
|
|
def test_an_identical_file_is_left_alone(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
present = self._directory(tmp, {"de.yaml": "a\n"})
|
|
written, removed = _MOD.plan(present, {"de.yaml": "a\n"}, True)
|
|
self.assertEqual((written, removed), ([], []))
|
|
|
|
def test_a_differing_file_is_rewritten(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
present = self._directory(tmp, {"de.yaml": "old\n"})
|
|
written, removed = _MOD.plan(present, {"de.yaml": "new\n"}, True)
|
|
self.assertEqual((written, removed), (["de.yaml"], []))
|
|
|
|
def test_an_unwanted_file_is_removed_when_pruning(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
present = self._directory(tmp, {"de.yaml": "a\n", "en.yaml": "b\n"})
|
|
written, removed = _MOD.plan(present, {"en.yaml": "b\n"}, True)
|
|
self.assertEqual((written, removed), ([], ["de.yaml"]))
|
|
|
|
def test_an_unwanted_file_survives_without_pruning(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
present = self._directory(tmp, {"de.yaml": "a\n"})
|
|
written, removed = _MOD.plan(present, {}, False)
|
|
self.assertEqual((written, removed), ([], []))
|
|
|
|
|
|
class TestApply(unittest.TestCase):
|
|
def test_it_creates_the_directory_and_writes_with_the_given_modes(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
directory = Path(tmp) / "content"
|
|
wanted = {"de.yaml": "Home: Startseite\n"}
|
|
_MOD.apply(directory, {}, wanted, ["de.yaml"], [], "0640", "0750")
|
|
target = directory / "de.yaml"
|
|
self.assertEqual(target.read_text(encoding="utf-8"), wanted["de.yaml"])
|
|
self.assertEqual(target.stat().st_mode & 0o777, 0o640)
|
|
self.assertEqual(directory.stat().st_mode & 0o777, 0o750)
|
|
|
|
def test_it_drops_the_files_the_plan_marked_removed(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
directory = Path(tmp)
|
|
stale = directory / "de.yaml"
|
|
stale.write_text("a\n", encoding="utf-8")
|
|
present = _MOD.survey(directory, ".yaml")
|
|
_MOD.apply(directory, present, {}, [], ["de.yaml"], "0644", "0755")
|
|
self.assertFalse(stale.exists())
|
|
|
|
def test_a_full_cycle_converges(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
directory = Path(tmp) / "content"
|
|
wanted = _MOD.render({"de": {"Home": "Startseite"}}, ".yaml", 4, 100)
|
|
written, removed = _MOD.plan(_MOD.survey(directory, ".yaml"), wanted, True)
|
|
_MOD.apply(directory, {}, wanted, written, removed, "0644", "0755")
|
|
again = _MOD.plan(_MOD.survey(directory, ".yaml"), wanted, True)
|
|
self.assertEqual(again, ([], []))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|