import contextlib import io import tempfile import unittest from pathlib import Path from ampg.cli import main def _write_config(root: Path) -> Path: source = root / "site" source.mkdir() (source / "index.html").write_text("

Hello

", encoding="utf-8") config_path = root / "gateway.toml" config_path.write_text( """ [[site]] id = "example" domain = "example.test" [site.source] kind = "static-html" path = "./site" [site.outputs] root = "./out" plan_root = "./plan" [site.protocols.i2p] enabled = true renderer = "privacy-html" daemon = "i2pd" daemon_policy = "auto" i2p_url = "ready.b32.i2p" """, encoding="utf-8", ) return config_path def _run_cli(args: list[str]) -> tuple[int, str, str]: stdout = io.StringIO() stderr = io.StringIO() with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): status = main(args) return status, stdout.getvalue(), stderr.getvalue() class ApprovalsTest(unittest.TestCase): def test_approval_cli_can_move_apply_preflight_to_ready(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp).resolve() config_path = _write_config(root) output = root / "out/i2p" output.mkdir(parents=True) (output / ".ampg-output").write_text("generated by AMPG\n", encoding="utf-8") status, output_text, _ = _run_cli( [ "--config", str(config_path), "apply", "--dry-run", "--write-artifacts", "--platform", "android-termux", ] ) self.assertEqual(0, status) self.assertIn("AMPG_APPLY_PREFLIGHT status=review", output_text) status, output_text, _ = _run_cli( [ "--config", str(config_path), "approvals", "approve", "--all", "--platform", "android-termux", ] ) self.assertEqual(0, status) self.assertIn("AMPG_APPROVAL_SUMMARY mode=approve candidates=6", output_text) self.assertIn("written=6", output_text) self.assertTrue((root / ".ampg/state/approvals.json").exists()) status, output_text, _ = _run_cli( [ "--config", str(config_path), "apply", "--dry-run", "--write-artifacts", "--platform", "android-termux", ] ) self.assertEqual(0, status) self.assertIn("AMPG_APPLY_PREFLIGHT status=ready", output_text) self.assertNotIn("AMPG_APPLY_PREFLIGHT_ITEM", output_text) def test_approval_cli_reports_stale_artifact_after_edit(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp).resolve() config_path = _write_config(root) output = root / "out/i2p" output.mkdir(parents=True) (output / ".ampg-output").write_text("generated by AMPG\n", encoding="utf-8") _run_cli( [ "--config", str(config_path), "apply", "--dry-run", "--write-artifacts", "--platform", "android-termux", ] ) _run_cli( [ "--config", str(config_path), "approvals", "approve", "--kind", "daemon-config", "--platform", "android-termux", ] ) artifact = root / "plan/example/i2p/managed/android-termux/i2pd-tunnels.conf" artifact.write_text(artifact.read_text(encoding="utf-8") + "# edited\n", encoding="utf-8") status, output_text, _ = _run_cli( [ "--config", str(config_path), "approvals", "list", "--kind", "daemon-config", "--platform", "android-termux", ] ) self.assertEqual(0, status) self.assertIn("status=stale", output_text) self.assertIn("artifact differs from current generator output", output_text) def test_approval_cli_requires_explicit_scope(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_config(Path(tmp)) status, _, error_text = _run_cli( [ "--config", str(config_path), "approvals", "approve", "--platform", "android-termux", ] ) self.assertEqual(1, status) self.assertIn("pass --all or --kind", error_text) if __name__ == "__main__": unittest.main()