Files
computer-playbook/tests/unit/python/cli/meta/ci/test_plan.py
T
kevinveenbirkenbachandClaude Opus 5 7c13e96c96 Merge branch 'main' into feature/store-i18n
Twelve files conflicted; most resolutions keep both sides rather than pick
one.

cli env.py and compose.py: both branches extend the same spots. The tools
override is appended before write_override, because that call filters the
mirror override to the compose files it is handed. compose.py keeps this
branch's CACHE_ROLE_SHELL_DIR, since the cache scripts moved into a role and
main's literal scripts/docker/cache path is their old home, and takes main's
mirrored init image alongside it.

utils/docker/image/discovery.py: this branch's version_variants expansion
plus main's whole runner-image block, which runner_mirror imports.

sys-svc-compose network create: main's side wins on substance and this
branch's on structure. main carries eda6249d7b, the fix for a probe that
matched an id prefix and let a sibling shadow the exact name; this branch had
refactored the task into create/overlay.yml and create/bridge.yml but brought
back the docker_network_info probe that fix replaced. The subfiles stay and
now probe through the exact-name lookup. main's half also read
SWARM_CLUSTER_AVAILABLE, which this branch removed in favour of
IS_SWARM_MODE, as the merge base confirms, so the gates follow this branch.
The lookup's import follows this branch's get_entity_name to entity_name
rename.

web-app-pixelfed: the custom entrypoints stay removed, since the new image
brings its own and the variables behind them no longer exist, and main's
host_alias=true comes along.

web-app-suitecrm: main's session.yaml and its config/packages path, because
only that version wires framework.session and only that directory is read by
Symfony.

web-app-odoo: main's onionOptions import is required by the test.use below
it; this branch's test-seaweedfs registration stays.

Four defects surfaced that no conflict marker shows.

The auto-merge of web-app-suitecrm/vars/main.yml duplicated
SUITECRM_SESSION_CONFIG_HOST, which ansible-lint rejects. Removing it exposed
the second: SUITECRM_SESSION_CONFIG_DOCKER still named config/session.yaml
while the mount had moved to config/packages, so the freshness comparison in
tasks/01_manager_ops.yml would have tested a path nothing writes.

svc-db-elasticsearch carried environment: on two include_tasks, which Ansible
accepts on include_role only. ELASTIC_PW now sits on the five tasks that read
it, which is also the tighter scope for a credential.

The openclaw templates render NETWORK_TRUSTED_PROXY_CIDRS while their test
renderer passes only its explicit kwargs, so seven tests raised Undefined.
That predates the merge: the template line is on this branch and not on main,
and both sides share the same test file.

test_runner_mirror neutralises four environment variables under clear=False
and missed three, so the gpu and tools overrides leaked in from the container
and the assertion on the layered list broke. It now neutralises all seven.

make quality-high passes both phases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-13 10:57:22 +02:00

174 lines
6.9 KiB
Python

from __future__ import annotations
import unittest
from cli.meta.ci import plan
from utils.symbol_glossary import to_emoji
def _entry(
app: str,
variant: str,
mode: str,
*,
priority: bool = False,
identifier: str = "0",
covered: str = "0",
clone: bool = False,
instructions: str = "",
) -> dict:
return {
"apps": app,
"variant": variant,
"mode": mode,
"tor": "true" if variant == "0" else "false",
"disable": "",
"priority": "true" if priority else "false",
"instructions": instructions,
"id": identifier,
"covered": covered,
"clone": "true" if clone else "false",
"distro": "debian",
"filesystem": "zfs",
"architecture": "arm64",
"label": f"{to_emoji(mode)}{app} {variant}",
}
_PRIORITY = [_entry("web-app-a", "0", "compose", priority=True, identifier="3")]
_REGULAR = [
_entry("web-app-b", "0", "swarm", identifier="5"),
_entry("web-app-b", "1", "compose", identifier="9"),
]
_CUT = [_entry("web-app-c", "0", "compose", identifier="11", covered="5")]
_ENTRIES = _PRIORITY + _REGULAR + _CUT
class TestCells(unittest.TestCase):
def test_a_row_in_a_chunk_reports_that_chunk(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
chunk = plan._COLUMNS.index("chunk")
self.assertEqual([row[chunk] for row in rows], ["0", "1", "1", ""])
def test_a_priority_row_is_starred_not_ticked(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
status = plan._COLUMNS.index("triggered")
self.assertEqual(rows[0][status], to_emoji("priority"))
self.assertEqual(rows[1][status], to_emoji("enabled"))
def test_a_row_outside_the_sweep_is_marked_disabled(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY])
self.assertEqual(
rows[1][plan._COLUMNS.index("triggered")], to_emoji("disabled")
)
self.assertEqual(rows[1][plan._COLUMNS.index("chunk")], "")
def test_a_priority_row_the_budget_missed_keeps_its_star(self) -> None:
rows = plan.cells(_ENTRIES, [_REGULAR])
status = plan._COLUMNS.index("triggered")
self.assertEqual(rows[0][status], to_emoji("priority") + to_emoji("disabled"))
self.assertEqual(rows[0][plan._COLUMNS.index("chunk")], "")
def test_a_clone_is_named_in_its_own_column(self) -> None:
entries = [*_ENTRIES, _entry("web-app-d", "0", "compose", clone=True)]
rows = plan.cells(entries, [_PRIORITY, _REGULAR])
clone = plan._COLUMNS.index("clone")
self.assertEqual(rows[0][clone], "")
self.assertEqual(rows[-1][clone], to_emoji("clone"))
def test_the_instructions_column_names_the_mode_the_replay_runs_in(self) -> None:
entries = [
*_ENTRIES,
_entry("web-app-d", "0", "swarm", instructions="compose"),
]
rows = plan.cells(entries, [_PRIORITY, _REGULAR])
column = plan._COLUMNS.index("instructions")
self.assertEqual(rows[0][column], "")
self.assertEqual(rows[-1][column], to_emoji("compose"))
def test_the_mode_is_rendered_as_its_glyph(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
mode = plan._COLUMNS.index("mode")
self.assertEqual(rows[0][mode], to_emoji("compose"))
self.assertEqual(rows[1][mode], to_emoji("swarm"))
def test_a_covered_row_names_the_row_number_that_covers_it(self) -> None:
covered = plan._COLUMNS.index("covered_by")
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
self.assertEqual(rows[0][covered], "")
self.assertEqual(rows[3][covered], "2")
def test_a_covered_row_is_cut_instead_of_deployed(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
self.assertEqual(
rows[3][plan._COLUMNS.index("triggered")], to_emoji("redundant")
)
self.assertEqual(rows[3][plan._COLUMNS.index("chunk")], "")
def test_a_clone_is_cut_as_well(self) -> None:
entries = [*_ENTRIES, _entry("web-app-d", "0", "compose", clone=True)]
rows = plan.cells(entries, [_PRIORITY, _REGULAR])
self.assertEqual(
rows[-1][plan._COLUMNS.index("triggered")], to_emoji("redundant")
)
def test_a_pinned_clone_stays_in_the_run(self) -> None:
entries = [_entry("web-app-d", "0", "compose", priority=True, clone=True)]
rows = plan.cells(entries, [entries])
self.assertEqual(
rows[0][plan._COLUMNS.index("triggered")], to_emoji("priority")
)
def test_the_id_counts_the_rows_even_when_discovery_ids_repeat(self) -> None:
identifier = plan._COLUMNS.index("id")
entries = [*_ENTRIES, _entry("web-app-a", "0", "swarm", identifier="3")]
rows = plan.cells(entries, [_PRIORITY, _REGULAR])
self.assertEqual([row[identifier] for row in rows], ["1", "2", "3", "4", "5"])
def test_the_tor_state_is_rendered_as_its_glyph(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
tor = plan._COLUMNS.index("tor")
self.assertEqual(rows[0][tor], to_emoji("tor"))
self.assertEqual(rows[2][tor], to_emoji("clearnet"))
def test_every_row_carries_its_own_distro_and_filesystem_glyph(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
distro = plan._COLUMNS.index("distro")
filesystem = plan._COLUMNS.index("filesystem")
self.assertTrue(all(row[distro] == to_emoji("debian") for row in rows))
self.assertTrue(all(row[filesystem] == to_emoji("zfs") for row in rows))
def test_every_row_carries_its_architecture_glyph(self) -> None:
rows = plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
architecture = plan._COLUMNS.index("architecture")
self.assertTrue(all(row[architecture] == to_emoji("arm64") for row in rows))
class TestRender(unittest.TestCase):
def _rows(self) -> list[tuple[str, ...]]:
return plan.cells(_ENTRIES, [_PRIORITY, _REGULAR])
def test_markdown_is_one_table_with_a_chunk_column(self) -> None:
out = plan.render_markdown("sweep 0", self._rows())
self.assertEqual(out.count("| ---"), 0)
self.assertEqual(out.count("\n|---"), 1)
self.assertIn(f"{to_emoji('chunk')} Chunk", out)
self.assertIn("web-app-a", out)
def test_markdown_keeps_one_line_per_row(self) -> None:
out = plan.render_markdown("sweep 0", self._rows())
body = [line for line in out.splitlines() if line.startswith("| web")]
self.assertEqual(len(body), 0)
self.assertEqual(len([ln for ln in out.splitlines() if ln.startswith("| ")]), 5)
def test_cli_pads_to_display_width(self) -> None:
out = plan.render_cli("sweep 0", self._rows())
lines = out.splitlines()
self.assertEqual(lines[0], "sweep 0")
self.assertTrue(lines[2].startswith("---"))
self.assertEqual(len(lines), 3 + len(_ENTRIES))
if __name__ == "__main__":
unittest.main()