Fix tests
All checks were successful
CI / test (push) Successful in 5m36s
Lint / test (push) Successful in 27s
Trivy / test (push) Successful in 21s

This commit is contained in:
Miguel Jacq 2025-12-16 20:48:08 +11:00
parent f40b9d834d
commit 026416d158
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
6 changed files with 313 additions and 22 deletions

View file

@ -22,9 +22,12 @@ def test_cli_harvest_subcommand_calls_harvest(monkeypatch, capsys, tmp_path):
def test_cli_manifest_subcommand_calls_manifest(monkeypatch, tmp_path):
called = {}
def fake_manifest(harvest_dir: str, out_dir: str):
def fake_manifest(harvest_dir: str, out_dir: str, **kwargs):
called["harvest"] = harvest_dir
called["out"] = out_dir
# Common manifest args should be passed through by the CLI.
called["fqdn"] = kwargs.get("fqdn")
called["jinjaturtle"] = kwargs.get("jinjaturtle")
monkeypatch.setattr(cli, "manifest", fake_manifest)
monkeypatch.setattr(
@ -43,6 +46,8 @@ def test_cli_manifest_subcommand_calls_manifest(monkeypatch, tmp_path):
cli.main()
assert called["harvest"] == str(tmp_path / "bundle")
assert called["out"] == str(tmp_path / "ansible")
assert called["fqdn"] is None
assert called["jinjaturtle"] == "auto"
def test_cli_enroll_subcommand_runs_harvest_then_manifest(monkeypatch, tmp_path):
@ -52,8 +57,16 @@ def test_cli_enroll_subcommand_runs_harvest_then_manifest(monkeypatch, tmp_path)
calls.append(("harvest", bundle_dir))
return str(tmp_path / "bundle" / "state.json")
def fake_manifest(bundle_dir: str, out_dir: str):
calls.append(("manifest", bundle_dir, out_dir))
def fake_manifest(bundle_dir: str, out_dir: str, **kwargs):
calls.append(
(
"manifest",
bundle_dir,
out_dir,
kwargs.get("fqdn"),
kwargs.get("jinjaturtle"),
)
)
monkeypatch.setattr(cli, "harvest", fake_harvest)
monkeypatch.setattr(cli, "manifest", fake_manifest)
@ -62,7 +75,7 @@ def test_cli_enroll_subcommand_runs_harvest_then_manifest(monkeypatch, tmp_path)
"argv",
[
"enroll",
"enroll",
"single-shot",
"--harvest",
str(tmp_path / "bundle"),
"--out",
@ -73,5 +86,38 @@ def test_cli_enroll_subcommand_runs_harvest_then_manifest(monkeypatch, tmp_path)
cli.main()
assert calls == [
("harvest", str(tmp_path / "bundle")),
("manifest", str(tmp_path / "bundle"), str(tmp_path / "ansible")),
("manifest", str(tmp_path / "bundle"), str(tmp_path / "ansible"), None, "auto"),
]
def test_cli_manifest_common_args(monkeypatch, tmp_path):
"""Ensure --fqdn and jinjaturtle mode flags are forwarded correctly."""
called = {}
def fake_manifest(harvest_dir: str, out_dir: str, **kwargs):
called["harvest"] = harvest_dir
called["out"] = out_dir
called["fqdn"] = kwargs.get("fqdn")
called["jinjaturtle"] = kwargs.get("jinjaturtle")
monkeypatch.setattr(cli, "manifest", fake_manifest)
monkeypatch.setattr(
sys,
"argv",
[
"enroll",
"manifest",
"--harvest",
str(tmp_path / "bundle"),
"--out",
str(tmp_path / "ansible"),
"--fqdn",
"example.test",
"--no-jinjaturtle",
],
)
cli.main()
assert called["fqdn"] == "example.test"
assert called["jinjaturtle"] == "off"

99
tests/test_jinjaturtle.py Normal file
View file

@ -0,0 +1,99 @@
import json
from pathlib import Path
import enroll.manifest as manifest_mod
from enroll.jinjaturtle import JinjifyResult
def test_manifest_uses_jinjaturtle_templates_and_does_not_copy_raw(
monkeypatch, tmp_path: Path
):
"""If jinjaturtle can templatisize a file, we should store a template in the role
and avoid keeping the raw file copy in the destination files area.
This test stubs out jinjaturtle execution so it doesn't depend on the external tool.
"""
bundle = tmp_path / "bundle"
out = tmp_path / "ansible"
# A jinjaturtle-compatible config file.
(bundle / "artifacts" / "foo" / "etc").mkdir(parents=True, exist_ok=True)
(bundle / "artifacts" / "foo" / "etc" / "foo.ini").write_text(
"[main]\nkey = 1\n", encoding="utf-8"
)
state = {
"host": {"hostname": "test", "os": "debian"},
"users": {
"role_name": "users",
"users": [],
"managed_files": [],
"excluded": [],
"notes": [],
},
"etc_custom": {
"role_name": "etc_custom",
"managed_files": [],
"excluded": [],
"notes": [],
},
"services": [
{
"unit": "foo.service",
"role_name": "foo",
"packages": ["foo"],
"active_state": "inactive",
"sub_state": "dead",
"unit_file_state": "disabled",
"condition_result": "no",
"managed_files": [
{
"path": "/etc/foo.ini",
"src_rel": "etc/foo.ini",
"owner": "root",
"group": "root",
"mode": "0644",
"reason": "modified_conffile",
}
],
"excluded": [],
"notes": [],
}
],
"package_roles": [],
}
bundle.mkdir(parents=True, exist_ok=True)
(bundle / "state.json").write_text(json.dumps(state, indent=2), encoding="utf-8")
# Pretend jinjaturtle exists.
monkeypatch.setattr(
manifest_mod, "find_jinjaturtle_cmd", lambda: "/usr/bin/jinjaturtle"
)
# Stub jinjaturtle output.
def fake_run_jinjaturtle(
jt_exe: str, src_path: str, *, role_name: str, force_format=None
):
assert role_name == "foo"
return JinjifyResult(
template_text="[main]\nkey = {{ foo_key }}\n",
vars_text="foo_key: 1\n",
)
monkeypatch.setattr(manifest_mod, "run_jinjaturtle", fake_run_jinjaturtle)
manifest_mod.manifest(str(bundle), str(out), jinjaturtle="on")
# Template should exist in the role.
assert (out / "roles" / "foo" / "templates" / "etc" / "foo.ini.j2").exists()
# Raw file should NOT be copied into role files/ because it was templatised.
assert not (out / "roles" / "foo" / "files" / "etc" / "foo.ini").exists()
# Defaults should include jinjaturtle vars.
defaults = (out / "roles" / "foo" / "defaults" / "main.yml").read_text(
encoding="utf-8"
)
assert "foo_key: 1" in defaults

View file

@ -94,10 +94,16 @@ def test_manifest_writes_roles_and_playbook_with_clean_when(tmp_path: Path):
manifest(str(bundle), str(out))
# Service role: conditional start must be a clean Ansible expression
# Service role: systemd management should be gated on foo_manage_unit and a probe.
tasks = (out / "roles" / "foo" / "tasks" / "main.yml").read_text(encoding="utf-8")
assert "when:\n - _unit_probe is succeeded\n - foo_start | bool\n" in tasks
# Ensure we didn't emit deprecated/broken '{{ }}' delimiters in when:
assert "- name: Probe whether systemd unit exists and is manageable" in tasks
assert "when: foo_manage_unit | default(false)" in tasks
assert (
"when:\n - foo_manage_unit | default(false)\n - _unit_probe is succeeded\n"
in tasks
)
# Ensure we didn't emit deprecated/broken '{{ }}' delimiters in when: lines.
for line in tasks.splitlines():
if line.lstrip().startswith("when:"):
assert "{{" not in line and "}}" not in line
@ -105,7 +111,9 @@ def test_manifest_writes_roles_and_playbook_with_clean_when(tmp_path: Path):
defaults = (out / "roles" / "foo" / "defaults" / "main.yml").read_text(
encoding="utf-8"
)
assert "foo_start: false" in defaults
assert "foo_manage_unit: true" in defaults
assert "foo_systemd_enabled: true" in defaults
assert "foo_systemd_state: stopped" in defaults
# Playbook should include users, etc_custom, packages, and services
pb = (out / "playbook.yml").read_text(encoding="utf-8")
@ -113,3 +121,105 @@ def test_manifest_writes_roles_and_playbook_with_clean_when(tmp_path: Path):
assert "- etc_custom" in pb
assert "- curl" in pb
assert "- foo" in pb
def test_manifest_site_mode_creates_host_inventory_and_raw_files(tmp_path: Path):
"""In --fqdn mode, host-specific state goes into inventory/host_vars."""
fqdn = "host1.example.test"
bundle = tmp_path / "bundle"
out = tmp_path / "ansible"
# Artifacts for a service-managed file.
(bundle / "artifacts" / "foo" / "etc").mkdir(parents=True, exist_ok=True)
(bundle / "artifacts" / "foo" / "etc" / "foo.conf").write_text(
"x", encoding="utf-8"
)
# Artifacts for etc_custom file so copy works.
(bundle / "artifacts" / "etc_custom" / "etc" / "default").mkdir(
parents=True, exist_ok=True
)
(bundle / "artifacts" / "etc_custom" / "etc" / "default" / "keyboard").write_text(
"kbd", encoding="utf-8"
)
state = {
"host": {"hostname": "test", "os": "debian"},
"users": {
"role_name": "users",
"users": [],
"managed_files": [],
"excluded": [],
"notes": [],
},
"etc_custom": {
"role_name": "etc_custom",
"managed_files": [
{
"path": "/etc/default/keyboard",
"src_rel": "etc/default/keyboard",
"owner": "root",
"group": "root",
"mode": "0644",
"reason": "custom_unowned",
}
],
"excluded": [],
"notes": [],
},
"services": [
{
"unit": "foo.service",
"role_name": "foo",
"packages": ["foo"],
"active_state": "active",
"sub_state": "running",
"unit_file_state": "enabled",
"condition_result": "yes",
"managed_files": [
{
"path": "/etc/foo.conf",
"src_rel": "etc/foo.conf",
"owner": "root",
"group": "root",
"mode": "0644",
"reason": "modified_conffile",
}
],
"excluded": [],
"notes": [],
}
],
"package_roles": [],
}
bundle.mkdir(parents=True, exist_ok=True)
(bundle / "state.json").write_text(json.dumps(state, indent=2), encoding="utf-8")
manifest(str(bundle), str(out), fqdn=fqdn)
# Host playbook exists.
assert (out / "playbooks" / f"{fqdn}.yml").exists()
# Role defaults are safe/host-agnostic in site mode.
foo_defaults = (out / "roles" / "foo" / "defaults" / "main.yml").read_text(
encoding="utf-8"
)
assert "foo_packages: []" in foo_defaults
assert "foo_managed_files: []" in foo_defaults
assert "foo_manage_unit: false" in foo_defaults
# Host vars contain host-specific state.
foo_hostvars = (out / "inventory" / "host_vars" / fqdn / "foo.yml").read_text(
encoding="utf-8"
)
assert "foo_packages" in foo_hostvars
assert "foo_managed_files" in foo_hostvars
assert "foo_manage_unit: true" in foo_hostvars
assert "foo_systemd_state: started" in foo_hostvars
# Non-templated raw config is stored per-host under .files.
assert (
out / "inventory" / "host_vars" / fqdn / "foo" / ".files" / "etc" / "foo.conf"
).exists()