Files
awoooi/apps/api/tests/test_awooop_ansible_post_verifier.py
ogt 6e24f4cba4
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m24s
CD Pipeline / build-and-deploy (push) Successful in 7m55s
CD Pipeline / post-deploy-checks (push) Successful in 1m45s
feat(ai): verify ansible apply from independent state
2026-07-11 06:38:11 +08:00

182 lines
5.7 KiB
Python

from __future__ import annotations
import inspect
from pathlib import Path
import pytest
from src.services.awooop_ansible_audit_service import list_ansible_catalog
from src.services.awooop_ansible_check_mode_service import (
AnsibleRunResult,
_post_apply_verification_result,
_record_apply_post_verifier_terminal,
run_pending_check_modes_once,
)
from src.services.awooop_ansible_post_verifier import (
AssetPostcondition,
ReadOnlyProbeResult,
build_read_only_probe_command,
registered_catalog_ids,
run_ansible_asset_post_verifier,
)
def _verifier_root(tmp_path: Path) -> tuple[Path, Path, Path]:
root = tmp_path / "infra" / "ansible"
inventory = root / "inventory" / "hosts.yml"
inventory.parent.mkdir(parents=True)
inventory.write_text(
"""
all:
children:
ai_web:
hosts:
host_188:
ansible_host: 192.168.0.188
ansible_user: ollama
""".strip()
+ "\n",
encoding="utf-8",
)
key = tmp_path / "id_ed25519"
known_hosts = tmp_path / "known_hosts"
key.write_text("test-key-metadata-only\n", encoding="utf-8")
known_hosts.write_text("host-key-metadata-only\n", encoding="utf-8")
return root, key, known_hosts
def test_asset_postcondition_registry_covers_every_ansible_catalog() -> None:
catalog_ids = {row["catalog_id"] for row in list_ansible_catalog()}
assert registered_catalog_ids() == catalog_ids
def test_read_only_probe_command_uses_structured_inventory(tmp_path: Path) -> None:
root, key, known_hosts = _verifier_root(tmp_path)
condition = AssetPostcondition(
"momo_script",
"service",
"host_188",
"test -x /home/ollama/bin/momo-pg-backup.sh",
)
command = build_read_only_probe_command(
condition,
inventory_path=root / "inventory" / "hosts.yml",
ssh_key_path=key,
known_hosts_path=known_hosts,
)
assert command[0] == "ssh"
assert "ollama@192.168.0.188" in command
assert "ansible-playbook" not in command
assert command[-1] == condition.probe
@pytest.mark.asyncio
async def test_independent_post_verifier_requires_every_asset_probe(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
root, key, known_hosts = _verifier_root(tmp_path)
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", "source-sha-123")
calls = 0
async def probe_runner(
_command: list[str],
*,
timeout_seconds: int,
) -> ReadOnlyProbeResult:
nonlocal calls
assert timeout_seconds == 7
calls += 1
return ReadOnlyProbeResult(
returncode=0 if calls < 5 else 1,
stdout="runtime probe output must not be persisted",
stderr="",
duration_ms=2,
)
receipt = await run_ansible_asset_post_verifier(
catalog_id="ansible:188-momo-backup-user",
automation_run_id="run-1",
apply_op_id="apply-1",
inventory_hosts=("host_188",),
executor_returncode=0,
playbook_root=root,
ssh_key_path=key,
known_hosts_path=known_hosts,
timeout_seconds=7,
probe_runner=probe_runner,
)
assert calls == 5
assert receipt["verification_result"] == "failed"
assert receipt["all_postconditions_passed"] is False
assert receipt["passed_postcondition_count"] == 4
assert receipt["required_postcondition_count"] == 5
assert receipt["executor_returncode_trusted"] is False
assert receipt["raw_output_stored"] is False
assert "postcondition_failed:host_188_momo_backup_metric" in receipt[
"active_blockers"
]
assert all("stdout" not in item and "stderr" not in item for item in receipt["postconditions"])
assert all(len(item["output_sha256"]) == 64 for item in receipt["postconditions"])
@pytest.mark.asyncio
async def test_executor_failure_never_runs_or_passes_independent_verifier(
tmp_path: Path,
) -> None:
root, key, known_hosts = _verifier_root(tmp_path)
called = False
async def probe_runner(*_args, **_kwargs) -> ReadOnlyProbeResult:
nonlocal called
called = True
return ReadOnlyProbeResult(0, "", "", 1)
receipt = await run_ansible_asset_post_verifier(
catalog_id="ansible:188-momo-backup-user",
automation_run_id="run-1",
apply_op_id="apply-1",
inventory_hosts=("host_188",),
executor_returncode=2,
playbook_root=root,
ssh_key_path=key,
known_hosts_path=known_hosts,
probe_runner=probe_runner,
)
assert called is False
assert receipt["verification_result"] == "failed"
assert "executor_apply_failed_before_post_verifier" in receipt[
"active_blockers"
]
def test_executor_returncode_alone_cannot_claim_post_verifier_success() -> None:
result = AnsibleRunResult(returncode=0, stdout="", stderr="", duration_ms=1)
verified = {
"verification_result": "success",
"all_postconditions_passed": True,
"executor_returncode_trusted": False,
}
assert _post_apply_verification_result(result) == "failed"
assert _post_apply_verification_result(result, verified) == "success"
assert _post_apply_verification_result(
result,
{**verified, "all_postconditions_passed": False},
) == "failed"
def test_verifier_failure_enters_durable_failed_apply_and_retry_first() -> None:
terminal_source = inspect.getsource(_record_apply_post_verifier_terminal)
broker_source = inspect.getsource(run_pending_check_modes_once)
assert "ELSE 'failed'" in terminal_source
assert "independent_post_verifier_failed" in terminal_source
assert "run_failed_apply_check_mode_replay_once" in broker_source
assert "controlled_apply_verifier_failed" in broker_source