more test coverage

This commit is contained in:
Miguel Jacq 2026-05-31 16:50:57 +10:00
parent b25dd1e314
commit 1544dc0295
Signed by: mig5
GPG key ID: 03906B4110AAD3B8
15 changed files with 3150 additions and 424 deletions

View file

@ -87,3 +87,995 @@ def test_bundle_from_input_missing_path(tmp_path: Path):
with pytest.raises(RuntimeError, match="not found"):
d._bundle_from_input(str(tmp_path / "nope"), sops_mode=False)
import json
import sys
from enroll.diff import (
_bundle_from_input,
_file_index,
_iter_managed_files,
_load_state,
_pkg_version_display,
_pkg_version_key,
_progress_enabled,
_roles,
_service_units,
_sha256,
_users_by_name,
compare_harvests,
)
from enroll.sopsutil import SopsError
def test_progress_enabled_when_tty(monkeypatch):
monkeypatch.setattr(sys.stderr, "isatty", lambda: True)
monkeypatch.delenv("ENROLL_NO_PROGRESS", raising=False)
assert _progress_enabled() is True
def test_progress_enabled_when_not_tty(monkeypatch):
monkeypatch.setattr(sys.stderr, "isatty", lambda: False)
monkeypatch.delenv("ENROLL_NO_PROGRESS", raising=False)
assert _progress_enabled() is False
def test_progress_enabled_with_env_var(monkeypatch):
monkeypatch.setattr(sys.stderr, "isatty", lambda: True)
monkeypatch.setenv("ENROLL_NO_PROGRESS", "1")
assert _progress_enabled() is False
monkeypatch.setenv("ENROLL_NO_PROGRESS", "true")
assert _progress_enabled() is False
monkeypatch.setenv("ENROLL_NO_PROGRESS", "yes")
assert _progress_enabled() is False
def test_sha256(tmp_path: Path):
test_file = tmp_path / "test.txt"
test_file.write_text("hello world", encoding="utf-8")
hash_result = _sha256(test_file)
assert len(hash_result) == 64
def test_sha256_empty_file(tmp_path: Path):
test_file = tmp_path / "empty.txt"
test_file.write_bytes(b"")
hash_result = _sha256(test_file)
assert (
hash_result
== "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
def test_bundle_from_input_directory(tmp_path: Path):
result = _bundle_from_input(str(tmp_path), sops_mode=False)
assert result.dir == tmp_path
assert result.tempdir is None
def test_bundle_from_input_state_json_path(tmp_path: Path):
state_file = tmp_path / "state.json"
state_file.write_text("{}", encoding="utf-8")
result = _bundle_from_input(str(state_file), sops_mode=False)
assert result.dir == tmp_path
assert result.tempdir is None
def test_bundle_from_input_not_found():
with pytest.raises(RuntimeError) as exc_info:
_bundle_from_input("/nonexistent/path", sops_mode=False)
assert "not found" in str(exc_info.value).lower()
def test_bundle_from_input_tarball(tmp_path: Path):
bundle_dir = tmp_path / "bundle"
bundle_dir.mkdir()
state_file = bundle_dir / "state.json"
state_file.write_text("{}", encoding="utf-8")
tar_path = tmp_path / "bundle.tar.gz"
with tarfile.open(tar_path, "w:gz") as tf:
tf.add(bundle_dir, arcname="bundle")
result = _bundle_from_input(str(tar_path), sops_mode=False)
assert result.dir.exists()
assert result.tempdir is not None
result.tempdir.cleanup()
def test_bundle_from_input_invalid_type(tmp_path: Path):
test_file = tmp_path / "test.txt"
test_file.write_text("not a bundle", encoding="utf-8")
with pytest.raises(RuntimeError) as exc_info:
_bundle_from_input(str(test_file), sops_mode=False)
assert "not a directory" in str(exc_info.value).lower()
def test_load_state(tmp_path: Path):
state_file = tmp_path / "state.json"
state_file.write_text('{"host": {"hostname": "test"}}', encoding="utf-8")
result = _load_state(tmp_path)
assert result["host"]["hostname"] == "test"
def test_roles_empty_state():
assert _roles({}) == {}
def test_roles_with_roles():
state = {"roles": {"users": {}, "services": []}}
result = _roles(state)
assert "users" in result
def test_service_units_empty():
assert _service_units({}) == {}
def test_service_units_with_services():
state = {
"roles": {
"services": [
{"unit": "nginx.service", "active_state": "active"},
{"unit": "ssh.service", "active_state": "inactive"},
]
}
}
result = _service_units(state)
assert "nginx.service" in result
assert "ssh.service" in result
assert result["nginx.service"]["active_state"] == "active"
def test_users_by_name_empty():
assert _users_by_name({}) == {}
def test_users_by_name_with_users():
state = {
"roles": {
"users": {
"users": [
{"name": "alice", "uid": 1000},
{"name": "bob", "uid": 1001},
]
}
}
}
result = _users_by_name(state)
assert "alice" in result
assert "bob" in result
assert result["alice"]["uid"] == 1000
def test_pkg_version_key_with_version():
entry = {"version": "1.2.3"}
assert _pkg_version_key(entry) == "1.2.3"
def test_pkg_version_key_with_installations():
entry = {
"installations": [
{"arch": "x86_64", "version": "1.2.3"},
{"arch": "aarch64", "version": "1.2.3"},
]
}
result = _pkg_version_key(entry)
assert "x86_64:1.2.3" in result
assert "aarch64:1.2.3" in result
def test_pkg_version_key_with_empty_version():
entry = {"version": None}
assert _pkg_version_key(entry) is None
def test_pkg_version_key_with_invalid_installations():
entry = {"installations": ["not_a_dict", {"arch": "x86_64", "version": "1.0"}]}
result = _pkg_version_key(entry)
assert "x86_64:1.0" in result
def test_pkg_version_display_with_version():
entry = {"version": "1.2.3"}
assert _pkg_version_display(entry) == "1.2.3"
def test_pkg_version_display_with_installations():
entry = {
"installations": [
{"arch": "x86_64", "version": "1.2.3"},
]
}
assert _pkg_version_display(entry) == "1.2.3 (x86_64)"
def test_pkg_version_display_empty():
assert _pkg_version_display({}) is None
def test_iter_managed_files_empty():
state = {"roles": {}}
files = list(_iter_managed_files(state))
assert files == []
def test_iter_managed_files_services():
state = {
"roles": {
"services": [
{
"role_name": "nginx",
"managed_files": [
{"path": "/etc/nginx/nginx.conf", "src_rel": "nginx.conf"}
],
}
]
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0] == (
"nginx",
{"path": "/etc/nginx/nginx.conf", "src_rel": "nginx.conf"},
)
def test_iter_managed_files_packages():
state = {
"roles": {
"packages": [
{
"role_name": "vim",
"managed_files": [{"path": "/usr/bin/vim", "src_rel": "bin/vim"}],
}
]
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "vim"
def test_iter_managed_files_users():
state = {
"roles": {
"users": {
"role_name": "users",
"managed_files": [{"path": "/etc/passwd", "src_rel": "passwd"}],
}
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "users"
def test_iter_managed_files_apt_config():
state = {
"roles": {
"apt_config": {
"role_name": "apt_config",
"managed_files": [
{"path": "/etc/apt/sources.list", "src_rel": "sources.list"}
],
}
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "apt_config"
def test_iter_managed_files_etc_custom():
state = {
"roles": {
"etc_custom": {
"role_name": "etc_custom",
"managed_files": [
{"path": "/etc/custom.conf", "src_rel": "custom.conf"}
],
}
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "etc_custom"
def test_iter_managed_files_usr_local_custom():
state = {
"roles": {
"usr_local_custom": {
"role_name": "usr_local_custom",
"managed_files": [
{"path": "/usr/local/bin/script", "src_rel": "bin/script"}
],
}
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "usr_local_custom"
def test_iter_managed_files_extra_paths():
state = {
"roles": {
"extra_paths": {
"role_name": "extra_paths",
"managed_files": [{"path": "/opt/app/config", "src_rel": "config"}],
}
}
}
files = list(_iter_managed_files(state))
assert len(files) == 1
assert files[0][0] == "extra_paths"
def test_file_index_empty():
state = {"roles": {}}
index = _file_index(Path("/tmp"), state)
assert index == {}
def test_file_index_with_files(tmp_path: Path):
state = {
"roles": {
"users": {
"managed_files": [
{"path": "/etc/passwd", "src_rel": "passwd", "owner": "root"},
]
}
}
}
index = _file_index(tmp_path, state)
assert "/etc/passwd" in index
assert index["/etc/passwd"].role == "users"
assert index["/etc/passwd"].owner == "root"
def test_file_index_duplicates_first_wins(tmp_path: Path):
state = {
"roles": {
"users": {
"managed_files": [
{"path": "/etc/passwd", "src_rel": "passwd"},
]
},
"etc_custom": {
"managed_files": [
{"path": "/etc/passwd", "src_rel": "custom_passwd"},
]
},
}
}
index = _file_index(tmp_path, state)
assert "/etc/passwd" in index
assert index["/etc/passwd"].src_rel == "passwd"
def test_file_index_skips_missing_path_or_src_rel(tmp_path: Path):
state = {
"roles": {
"users": {
"managed_files": [
{"path": "/etc/passwd"}, # missing src_rel
{"src_rel": "passwd"}, # missing path
]
}
}
}
index = _file_index(tmp_path, state)
assert index == {}
def test_compare_harvests_no_changes(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {"vim": {"version": "1.0"}}},
"roles": {},
}
),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {"vim": {"version": "1.0"}}},
"roles": {},
}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is False
assert report["packages"]["added"] == []
assert report["packages"]["removed"] == []
def test_compare_harvests_package_added(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps({"inventory": {"packages": {}}, "roles": {}}),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "1.0"}}}, "roles": {}}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is True
assert "vim" in report["packages"]["added"]
def test_compare_harvests_package_removed(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "1.0"}}}, "roles": {}}
),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps({"inventory": {"packages": {}}, "roles": {}}),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is True
assert "vim" in report["packages"]["removed"]
def test_compare_harvests_package_version_changed(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "1.0"}}}, "roles": {}}
),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "2.0"}}}, "roles": {}}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is True
assert len(report["packages"]["version_changed"]) == 1
def test_compare_harvests_ignore_package_versions(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "1.0"}}}, "roles": {}}
),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{"inventory": {"packages": {"vim": {"version": "2.0"}}}, "roles": {}}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(
str(old_bundle), str(new_bundle), ignore_package_versions=True
)
assert report["packages"]["version_changed_ignored_count"] == 1
def test_compare_harvests_service_added(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps({"inventory": {"packages": {}}, "roles": {"services": []}}),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {}},
"roles": {"services": [{"unit": "nginx.service"}]},
}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is True
assert "nginx.service" in report["services"]["enabled_added"]
def test_compare_harvests_user_added(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
(old_bundle / "state.json").write_text(
json.dumps({"inventory": {"packages": {}}, "roles": {"users": {"users": []}}}),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
(new_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {}},
"roles": {"users": {"users": [{"name": "alice", "uid": 1000}]}},
}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(str(old_bundle), str(new_bundle))
assert has_changes is True
assert "alice" in report["users"]["added"]
def test_compare_harvests_with_exclude_paths(tmp_path: Path):
old_bundle = tmp_path / "old"
old_bundle.mkdir()
old_artifacts = old_bundle / "artifacts" / "users"
old_artifacts.mkdir(parents=True)
(old_artifacts / "passwd").write_text("old", encoding="utf-8")
(old_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {}},
"roles": {
"users": {
"managed_files": [{"path": "/etc/passwd", "src_rel": "passwd"}]
}
},
}
),
encoding="utf-8",
)
new_bundle = tmp_path / "new"
new_bundle.mkdir()
new_artifacts = new_bundle / "artifacts" / "users"
new_artifacts.mkdir(parents=True)
(new_artifacts / "passwd").write_text("new", encoding="utf-8")
(new_bundle / "state.json").write_text(
json.dumps(
{
"inventory": {"packages": {}},
"roles": {
"users": {
"managed_files": [{"path": "/etc/passwd", "src_rel": "passwd"}]
}
},
}
),
encoding="utf-8",
)
report, has_changes = compare_harvests(
str(old_bundle), str(new_bundle), exclude_paths=["/etc/passwd"]
)
assert "/etc/passwd" not in [f["path"] for f in report["files"]["added"]]
assert "/etc/passwd" not in [f["path"] for f in report["files"]["removed"]]
assert "/etc/passwd" not in [f["path"] for f in report["files"]["changed"]]
from enroll.diff import (
_Spinner,
_enforcement_plan,
has_enforceable_drift,
_role_tag,
_utc_now_iso,
_report_markdown,
)
def test_utc_now_iso():
result = _utc_now_iso()
assert "T" in result
assert "+" in result or "Z" in result
def test_spinner_start_stop(monkeypatch):
# Mock sys.stderr to avoid actual writes
class FakeStderr:
def write(self, s):
pass
def flush(self):
pass
def isatty(self):
return True
monkeypatch.setattr(sys, "stderr", FakeStderr())
spinner = _Spinner("Test")
spinner.start()
spinner.stop(final_line="Done")
# Should not raise
def test_spinner_stop_without_start():
spinner = _Spinner("Test")
spinner.stop(final_line="Done")
# Should not raise
def test_spinner_run_exception(monkeypatch):
class FakeStderr:
def write(self, s):
raise Exception("Write error")
def flush(self):
pass
monkeypatch.setattr(sys, "stderr", FakeStderr())
spinner = _Spinner("Test")
spinner.start()
spinner.stop()
def test_spinner_double_start():
spinner = _Spinner("Test")
spinner.start()
spinner.start() # Should not raise or spawn another thread
spinner.stop()
def test_role_tag_normal():
assert _role_tag("nginx") == "role_nginx"
assert _role_tag("my-app") == "role_my-app"
def test_role_tag_with_special_chars():
assert _role_tag("my.app") == "role_my_app"
assert _role_tag("my app") == "role_my_app"
def test_role_tag_empty():
assert _role_tag("") == "role_other"
assert _role_tag(" ") == "role_other"
def test_has_enforceable_drift_packages_removed():
report = {"packages": {"removed": ["vim"]}}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_services_removed():
report = {"services": {"enabled_removed": ["nginx.service"]}}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_service_changed():
report = {
"services": {
"changed": [
{
"unit": "nginx.service",
"changes": {"active_state": {"old": "active", "new": "inactive"}},
}
]
}
}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_service_package_only_changed():
# Service changed only in packages - should NOT be enforceable
report = {
"services": {
"changed": [
{
"unit": "nginx.service",
"changes": {"packages": {"added": ["nginx-extra"]}},
}
]
}
}
assert has_enforceable_drift(report) is False
def test_has_enforceable_drift_users_removed():
report = {"users": {"removed": ["alice"]}}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_users_changed():
report = {
"users": {
"changed": [
{"name": "alice", "changes": {"uid": {"old": 1000, "new": 1001}}}
]
}
}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_files_removed():
report = {
"files": {
"removed": [{"path": "/etc/passwd", "role": "users", "reason": "conffile"}]
}
}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_files_changed():
report = {
"files": {
"changed": [
{
"path": "/etc/passwd",
"changes": {"content": {"old": "sha1", "new": "sha2"}},
}
]
}
}
assert has_enforceable_drift(report) is True
def test_has_enforceable_drift_no_drift():
report = {
"packages": {"added": ["newpkg"]},
"services": {"enabled_added": ["new.service"]},
"users": {"added": ["bob"]},
"files": {"added": ["/opt/newfile"]},
}
assert has_enforceable_drift(report) is False
def test_enforcement_plan_packages_removed(monkeypatch, tmp_path: Path):
old_state = {
"roles": {
"services": [{"role_name": "nginx", "packages": ["nginx"]}],
"packages": [{"role_name": "vim", "package": "vim"}],
}
}
report = {"packages": {"removed": ["nginx", "vim"]}}
result = _enforcement_plan(report, old_state, tmp_path)
assert "nginx" in result.get("roles", [])
assert "vim" in result.get("roles", [])
assert "role_nginx" in result.get("tags", [])
def test_enforcement_plan_users_changed():
old_state = {
"roles": {"users": {"role_name": "users", "users": [{"name": "alice"}]}}
}
report = {"users": {"changed": [{"name": "alice", "changes": {"uid": {}}}]}}
result = _enforcement_plan(report, old_state, Path("/tmp"))
assert "users" in result.get("roles", [])
def test_enforcement_plan_files_removed(tmp_path: Path):
# Create the artifacts directory structure that _file_index expects
artifacts_dir = tmp_path / "artifacts" / "etc_custom"
artifacts_dir.mkdir(parents=True)
old_state = {
"roles": {
"etc_custom": {
"role_name": "etc_custom",
"managed_files": [
{"path": "/etc/custom.conf", "src_rel": "custom.conf"}
],
}
}
}
report = {
"files": {"removed": [{"path": "/etc/custom.conf", "role": "etc_custom"}]}
}
result = _enforcement_plan(report, old_state, tmp_path)
assert "etc_custom" in result.get("roles", [])
def test_enforcement_plan_no_drift():
old_state = {"roles": {}}
report = {"packages": {"added": ["newpkg"]}}
result = _enforcement_plan(report, old_state, Path("/tmp"))
assert result.get("roles", []) == []
def test_bundle_from_input_tgz(monkeypatch, tmp_path: Path):
bundle_dir = tmp_path / "bundle"
bundle_dir.mkdir()
state_file = bundle_dir / "state.json"
state_file.write_text("{}", encoding="utf-8")
tar_path = tmp_path / "bundle.tgz"
with tarfile.open(tar_path, "w:gz") as tf:
tf.add(bundle_dir, arcname="bundle")
result = _bundle_from_input(str(tar_path), sops_mode=False)
assert result.dir.exists()
assert result.tempdir is not None
result.tempdir.cleanup()
def test_bundle_from_input_sops_mode_no_sops(monkeypatch, tmp_path: Path):
# Create a fake .sops file
sops_file = tmp_path / "harvest.sops"
sops_file.write_bytes(b"encrypted")
def fake_require():
raise SopsError("sops not found")
import enroll.diff as d
monkeypatch.setattr(d, "require_sops_cmd", fake_require)
with pytest.raises(SopsError):
_bundle_from_input(str(sops_file), sops_mode=True)
def test_report_markdown_basic():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz", "host": "host1"},
"new": {"input": "new.tar.gz", "host": "host2"},
"packages": {"added": ["vim"], "removed": [], "version_changed": []},
"services": {"enabled_added": [], "enabled_removed": [], "changed": []},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
}
result = _report_markdown(report)
assert "## Packages" in result
assert "+ vim" in result
def test_report_markdown_with_enforcement_applied():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {"added": [], "removed": [], "version_changed": []},
"services": {"enabled_added": [], "enabled_removed": [], "changed": []},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
"enforcement": {
"status": "applied",
"tags": ["role_users"],
"returncode": 0,
"finished_at": "2024-01-01T00:01:00Z",
},
}
result = _report_markdown(report)
assert "Applied old harvest" in result
assert "role_users" in result
def test_report_markdown_with_enforcement_failed():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {"added": [], "removed": [], "version_changed": []},
"services": {"enabled_added": [], "enabled_removed": [], "changed": []},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
"enforcement": {
"status": "failed",
"returncode": 1,
},
}
result = _report_markdown(report)
assert "ansible-playbook failed" in result
def test_report_markdown_with_enforcement_skipped():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {"added": [], "removed": [], "version_changed": []},
"services": {"enabled_added": [], "enabled_removed": [], "changed": []},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
"enforcement": {
"status": "skipped",
"reason": "no drift",
},
}
result = _report_markdown(report)
assert "Skipped" in result
assert "no drift" in result
def test_report_markdown_with_version_ignored():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {
"added": [],
"removed": [],
"version_changed": [{"package": "vim", "old": "1.0", "new": "2.0"}],
"version_changed_ignored_count": 1,
},
"services": {"enabled_added": [], "enabled_removed": [], "changed": []},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
}
result = _report_markdown(report)
assert "ignored 1" in result
def test_report_markdown_with_service_package_changes():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {"added": [], "removed": [], "version_changed": []},
"services": {
"enabled_added": [],
"enabled_removed": [],
"changed": [
{
"unit": "nginx.service",
"changes": {"packages": {"added": ["nginx-extra"], "removed": []}},
}
],
},
"users": {"added": [], "removed": [], "changed": []},
"files": {"added": [], "removed": [], "changed": []},
}
result = _report_markdown(report)
assert "packages added" in result
def test_report_markdown_empty():
report = {
"generated_at": "2024-01-01T00:00:00Z",
"old": {"input": "old.tar.gz"},
"new": {"input": "new.tar.gz"},
"packages": {},
"services": {},
"users": {},
"files": {},
}
result = _report_markdown(report)
assert "## Packages" in result
assert "## Services" in result