from __future__ import annotations from pathlib import Path import yaml ROOT = Path(__file__).resolve().parents[3] PLAYBOOK = ROOT / "infra" / "ansible" / "playbooks" / "110-backup-runtime-deploy.yml" EXPECTED_FILES = { "common.sh", "backup-all.sh", "backup-awoooi.sh", "backup-awoooi-frequent.sh", "backup-clawbot.sh", "backup-sentry.sh", "backup-host188-products.sh", "verify-host188-products-backup.sh", "verify-host188-products-archive.py", "check-backup-integrity.sh", "sync-offsite-backups.sh", "backup-offsite-readiness-gate.sh", "verify-offsite-full-sync.sh", "enforce-latest-only-retention.sh", } def test_backup_runtime_deploy_is_fixed_to_host_110_and_fourteen_files() -> None: plays = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8")) assert len(plays) == 1 play = plays[0] assert play["hosts"] == "host_110" assert play["gather_facts"] is False assert play["become"] is True assert play["serial"] == 1 assert play["any_errors_fatal"] is True assert play["vars"]["backup_runtime_root"] == "/backup/scripts" stage1 = set(play["vars"]["backup_runtime_stage1_files"]) stage2 = set(play["vars"]["backup_runtime_stage2_files"]) assert stage1 | stage2 == EXPECTED_FILES assert not stage1 & stage2 assert play["vars"]["backup_runtime_fault_injection_after_stage1"] is False def test_backup_runtime_deploy_has_validation_rollback_and_identity_readback() -> None: source = PLAYBOOK.read_text(encoding="utf-8") assert 'inventory_hostname == "host_110"' in source assert 'validate: "/bin/bash -n %s"' in source assert "backup: true" in source assert "checksum_algorithm: sha256" in source assert "rstrip=false" in source assert "backup_runtime_identity_failed" in source assert "backup_runtime_stage_identity_failed" in source assert "backup_runtime_fault_injection_after_stage1" in source assert "backup_runtime_promotion_failed_and_rolled_back" in source assert "rescue:" in source assert "ansible.builtin.slurp:" in source assert "remote_src: true" in source assert "backup-health-textfile-exporter.py" in source assert "changed_when: false" in source for forbidden in ( "ansible.builtin.service:", "ansible.builtin.systemd:", "ansible.builtin.cron:", "ansible.builtin.shell:", "ansible.builtin.command:", "ansible.builtin.git:", "docker_container", "kubectl", ): assert forbidden not in source def test_fault_injection_occurs_mid_promotion_and_rescue_restores_prior_state() -> None: play = yaml.safe_load(PLAYBOOK.read_text(encoding="utf-8"))[0] transaction = next( task for task in play["tasks"] if task["name"] == "Promote the staged backup runtime transaction" ) block_names = [task["name"] for task in transaction["block"]] rescue_names = [task["name"] for task in transaction["rescue"]] assert block_names.index("Promote transaction stage one") < block_names.index( "Exercise bounded rollback fault injection when explicitly requested" ) assert block_names.index( "Exercise bounded rollback fault injection when explicitly requested" ) < block_names.index("Promote transaction stage two") assert "Restore runtime files that existed before promotion" in rescue_names assert "Remove runtime files that did not exist before promotion" in rescue_names assert "Restore the exporter that existed before promotion" in rescue_names assert rescue_names[-1] == "Fail closed after transactional rollback"