mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 19:56:53 +00:00
Every desk- role becomes dsk-, and the three coding agents move to dsk-gnt- under a category of their own. The category carries a title, hanzi and icon but no invokable flag: an invokable child under an invokable parent is rejected outright, which is also why web itself is not invokable. Three things a search and replace would have broken without a sound. get_entity_name now strips dsk-gnt-, so the primary entity keys agnt-claude, agnt-cursor and agnt-pi no longer resolved and all three roles dropped out of the CI plan. Eighteen run_once guards carried the old name against a convention its own test enforces. DESKTOP_ROLE_PREFIX in the addon spec lint is a literal, so the desktop exemption it grants would have matched nothing and the lint would have moved to the wrong rule in silence. docs/requirements/035 is not a historical document but a register a lint holds to every role with an application_id, so its list carries the new ids. In 026 only the links move; the prose stays as a statement about the tree at the time it was written. Measured against the plan with the rename normalised, the deployed role#variant set is unchanged. A stale id in an inventory is caught before any deploy starts: the pre-flight validator reports Invalid group 'desk-firefox' (not in application_ids). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
160 lines
5.5 KiB
Python
160 lines
5.5 KiB
Python
# tests/unit/python/utils/test_invokable.py
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest import TestCase, main
|
|
from unittest.mock import patch
|
|
|
|
import utils.roles.validation.invokable as inv
|
|
from utils.cache.yaml import dump_yaml
|
|
from utils.roles.categories import categories_file
|
|
from utils.roles.mapping import ROLE_FILE_VARS_MAIN
|
|
|
|
|
|
class TestInvokable(TestCase):
|
|
def setUp(self) -> None:
|
|
self.tmp = Path(tempfile.mkdtemp(prefix="invokable_modutils_test_"))
|
|
self.roles_dir = self.tmp / "roles"
|
|
self.roles_dir.mkdir(parents=True, exist_ok=True)
|
|
self.categories_file = categories_file(self.tmp)
|
|
|
|
dump_yaml(
|
|
self.categories_file,
|
|
{"categories": [{"invokable_paths": ["web-app", "update", "util-dsk"]}]},
|
|
)
|
|
|
|
def mk_role(name: str, application_id: str | None = None) -> None:
|
|
rd = self.roles_dir / name
|
|
(rd / "vars").mkdir(parents=True, exist_ok=True)
|
|
if application_id is not None:
|
|
dump_yaml(rd / ROLE_FILE_VARS_MAIN, {"application_id": application_id})
|
|
else:
|
|
dump_yaml(rd / ROLE_FILE_VARS_MAIN, {})
|
|
|
|
mk_role("web-app-nextcloud", "web-app-nextcloud")
|
|
mk_role("web-app-matomo", "matomo-app")
|
|
mk_role("web-svc-nginx", None) # not invokable by our patched paths
|
|
mk_role("update", None) # exact match
|
|
mk_role("util-dsk-custom", None) # prefix match
|
|
|
|
def tearDown(self) -> None:
|
|
shutil.rmtree(self.tmp)
|
|
|
|
def test_list_invokable_app_ids(self) -> None:
|
|
with (
|
|
patch.object(inv, "PROJECT_ROOT", self.tmp),
|
|
patch.object(
|
|
inv,
|
|
"_get_invokable_paths",
|
|
return_value=["web-app", "update", "util-dsk"],
|
|
),
|
|
):
|
|
got = inv.list_invokable_app_ids()
|
|
|
|
self.assertEqual(
|
|
got,
|
|
sorted(["web-app-nextcloud", "matomo-app", "update", "util-dsk-custom"]),
|
|
)
|
|
|
|
def test_list_invokables_by_type(self) -> None:
|
|
with (
|
|
patch.object(inv, "PROJECT_ROOT", self.tmp),
|
|
patch.object(
|
|
inv,
|
|
"_get_invokable_paths",
|
|
return_value=["web-app", "update", "util-dsk"],
|
|
),
|
|
):
|
|
grouped = inv.list_invokables_by_type()
|
|
|
|
self.assertIn("server", grouped)
|
|
self.assertIn("workstation", grouped)
|
|
self.assertIn("universal", grouped)
|
|
|
|
self.assertEqual(grouped["server"], sorted(["web-app-nextcloud", "matomo-app"]))
|
|
self.assertEqual(grouped["workstation"], sorted(["util-dsk-custom"]))
|
|
|
|
self.assertEqual(grouped["universal"], ["update"])
|
|
|
|
def test_list_invokables_by_type_exclude(self) -> None:
|
|
"""A synthetic rule exercises the exclude_re path of
|
|
DeploymentTypeRule. The historical DEFAULT_RULES exclude (the now
|
|
dissolved oauth2-proxy helper role) was removed when its sidecar
|
|
logic was absorbed into web-app-keycloak."""
|
|
rd = self.roles_dir / "web-app-excluded"
|
|
(rd / "vars").mkdir(parents=True, exist_ok=True)
|
|
dump_yaml(rd / ROLE_FILE_VARS_MAIN, {})
|
|
|
|
rules = (
|
|
inv.DeploymentTypeRule(
|
|
name="server",
|
|
include_re=re.compile(r"^(web-app-|web-svc-)"),
|
|
exclude_re=re.compile(r"^web-app-excluded$"),
|
|
),
|
|
inv.DeploymentTypeRule(
|
|
name="universal", include_re=re.compile(r".*"), exclude_re=None
|
|
),
|
|
)
|
|
with (
|
|
patch.object(inv, "PROJECT_ROOT", self.tmp),
|
|
patch.object(
|
|
inv,
|
|
"_get_invokable_paths",
|
|
return_value=["web-app", "update", "util-dsk"],
|
|
),
|
|
):
|
|
grouped = inv.list_invokables_by_type(rules=rules)
|
|
|
|
self.assertNotIn("web-app-excluded", grouped["server"])
|
|
|
|
def test_list_invokables_by_type_skip_mode(self) -> None:
|
|
def fake_skip(role_dir):
|
|
return ["compose"] if role_dir.name == "web-app-matomo" else []
|
|
|
|
with (
|
|
patch.object(inv, "PROJECT_ROOT", self.tmp),
|
|
patch.object(
|
|
inv,
|
|
"_get_invokable_paths",
|
|
return_value=["web-app", "update", "util-dsk"],
|
|
),
|
|
patch.object(inv, "_role_skip_modes", side_effect=fake_skip),
|
|
):
|
|
excluded = inv.list_invokables_by_type(skip_mode="compose")
|
|
kept = inv.list_invokables_by_type(skip_mode="swarm")
|
|
|
|
self.assertNotIn("matomo-app", excluded["server"])
|
|
self.assertIn("matomo-app", kept["server"])
|
|
self.assertIn("web-app-nextcloud", excluded["server"])
|
|
|
|
def test_types_from_group_names(self) -> None:
|
|
"""``all`` and ``foo`` are not invokable and drop out; the three
|
|
invokable names map to server, workstation and the universal
|
|
leftover respectively."""
|
|
with (
|
|
patch.object(inv, "PROJECT_ROOT", self.tmp),
|
|
patch.object(
|
|
inv,
|
|
"_get_invokable_paths",
|
|
return_value=["web-app", "update", "util-dsk"],
|
|
),
|
|
):
|
|
got = inv.types_from_group_names(
|
|
[
|
|
"all",
|
|
"web-app-nextcloud",
|
|
"util-dsk-custom",
|
|
"update",
|
|
"foo",
|
|
]
|
|
)
|
|
|
|
self.assertEqual(got, ["server", "universal", "workstation"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|