import contextlib import io import tempfile import unittest from pathlib import Path from ampg.cli import main from ampg.config import load_config from ampg.selection import ( parse_protocol_filters, protocols_for_selection, select_profile, select_protocols, ) def _write_config(root: Path) -> Path: source = root / "site" source.mkdir() (source / "index.html").write_text( '

Hello

', encoding="utf-8", ) (source / "app.js").write_text("boot()", encoding="utf-8") config_path = root / "gateway.toml" config_path.write_text( """ [profiles.mobile-i2p] protocols = ["i2p"] platform = "android-termux" [profiles.tor-i2p] protocols = ["tor", "i2p"] [profiles.artifact-profile] protocols = ["i2p"] platform = "android-termux" write_artifacts = true [[site]] id = "example" domain = "example.test" [site.source] kind = "static-html" path = "./site" [site.outputs] root = "./out" plan_root = "./plan" [site.protocols.clearnet] enabled = true renderer = "clearnet" daemon = "nginx" daemon_policy = "adopt" [site.protocols.tor] enabled = true renderer = "privacy-html" daemon = "tor" daemon_policy = "auto" [site.protocols.i2p] enabled = true renderer = "privacy-html" daemon = "i2pd" daemon_policy = "auto" """, encoding="utf-8", ) return config_path class ProtocolSelectionTest(unittest.TestCase): def test_parse_protocol_filters_supports_repeated_and_comma_values(self): self.assertEqual( ("tor", "i2p", "gemini"), parse_protocol_filters(["tor,i2p", "gemini", "tor"]), ) def test_select_protocols_filters_sites(self): with tempfile.TemporaryDirectory() as tmp: config = load_config(_write_config(Path(tmp))) selected = select_protocols(config, ("i2p",)) self.assertEqual(["i2p"], list(selected.sites[0].protocols)) def test_config_parses_profiles(self): with tempfile.TemporaryDirectory() as tmp: config = load_config(_write_config(Path(tmp))) profile = select_profile(config, "mobile-i2p") self.assertIsNotNone(profile) self.assertEqual(("i2p",), profile.protocols) self.assertEqual("android-termux", profile.platform) self.assertFalse(profile.write_artifacts) def test_profile_supplies_protocols_when_no_cli_protocol_is_given(self): with tempfile.TemporaryDirectory() as tmp: config = load_config(_write_config(Path(tmp))) self.assertEqual( ("i2p",), protocols_for_selection( config, raw_protocols=[], profile_name="mobile-i2p", ), ) self.assertEqual( ("tor",), protocols_for_selection( config, raw_protocols=["tor"], profile_name="mobile-i2p", ), ) def test_select_protocols_rejects_unknown_protocol(self): with tempfile.TemporaryDirectory() as tmp: config = load_config(_write_config(Path(tmp))) with self.assertRaisesRegex(ValueError, "not declared"): select_protocols(config, ("gemini",)) def test_build_protocol_filter_only_writes_selected_output(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) config_path = _write_config(root) stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main(["--config", str(config_path), "build", "--protocol", "i2p"]) self.assertEqual(0, status) self.assertTrue((root / "out/i2p/index.html").exists()) self.assertFalse((root / "out/clearnet").exists()) self.assertFalse((root / "out/tor").exists()) self.assertIn("protocol=i2p", stdout.getvalue()) self.assertNotIn("protocol=clearnet", stdout.getvalue()) def test_apply_protocol_filter_avoids_unselected_clearnet_block(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) 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") stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main( [ "--config", str(config_path), "apply", "--dry-run", "--protocol", "i2p", "--platform", "android-termux", ] ) output_text = stdout.getvalue() self.assertEqual(0, status) self.assertIn("protocol=i2p", output_text) self.assertIn("blocked=0", output_text) self.assertNotIn("protocol=clearnet", output_text) def test_apply_profile_filters_protocol_and_uses_platform(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) 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") stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main( [ "--config", str(config_path), "apply", "--dry-run", "--profile", "mobile-i2p", ] ) output_text = stdout.getvalue() self.assertEqual(0, status) self.assertIn("protocol=i2p", output_text) self.assertIn("i2pd is missing; AMPG would prepare managed setup", output_text) self.assertIn("blocked=0", output_text) self.assertNotIn("protocol=clearnet", output_text) def test_cli_protocol_overrides_profile_protocols(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_config(Path(tmp)) stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main( [ "--config", str(config_path), "status", "--profile", "mobile-i2p", "--protocol", "tor", ] ) output_text = stdout.getvalue() self.assertEqual(0, status) self.assertIn("protocol=tor", output_text) self.assertIn("platform=android-termux", output_text) self.assertNotIn("protocol=i2p", output_text) def test_profile_write_artifacts_defaults_apply_artifacts(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) 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") stdout = io.StringIO() with contextlib.redirect_stdout(stdout): status = main( [ "--config", str(config_path), "apply", "--dry-run", "--profile", "artifact-profile", ] ) output_text = stdout.getvalue() self.assertEqual(0, status) self.assertIn("AMPG_APPLY_ARTIFACT", output_text) self.assertIn("blocked=0", output_text) def test_cli_rejects_unknown_protocol_filter(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_config(Path(tmp)) stderr = io.StringIO() with contextlib.redirect_stderr(stderr): status = main(["--config", str(config_path), "status", "--protocol", "gemini"]) self.assertEqual(1, status) self.assertIn("selected protocol(s) not declared", stderr.getvalue()) def test_cli_rejects_unknown_profile(self): with tempfile.TemporaryDirectory() as tmp: config_path = _write_config(Path(tmp)) stderr = io.StringIO() with contextlib.redirect_stderr(stderr): status = main(["--config", str(config_path), "status", "--profile", "nope"]) self.assertEqual(1, status) self.assertIn("unknown profile", stderr.getvalue()) if __name__ == "__main__": unittest.main()