diff --git a/tests/unit/test_cli_dispatch.py b/tests/unit/test_cli_dispatch.py index 033d16a..64cf655 100644 --- a/tests/unit/test_cli_dispatch.py +++ b/tests/unit/test_cli_dispatch.py @@ -64,6 +64,15 @@ class TestMainDispatch(unittest.TestCase): main(["x", "a", "b"]) self.assertEqual([["a", "b"]], seen) + def test_missing_env_var_error_maps_to_1(self) -> None: + from bot_bottle.errors import MissingEnvVarError + + def boom(_rest: list[str]) -> int: + raise MissingEnvVarError("MY_VAR", "MY_VAR is not set") + + with patch.dict(climod.COMMANDS, {"x": boom}), patch("sys.stderr", io.StringIO()): + self.assertEqual(1, main(["x"])) + def test_manifest_error_maps_to_1(self) -> None: def boom(_rest: list[str]) -> int: raise ManifestError("bad manifest") diff --git a/tests/unit/test_cli_start_headless.py b/tests/unit/test_cli_start_headless.py index b956452..e4e6717 100644 --- a/tests/unit/test_cli_start_headless.py +++ b/tests/unit/test_cli_start_headless.py @@ -9,6 +9,7 @@ is created. from __future__ import annotations +import io import os import unittest from unittest.mock import MagicMock, patch @@ -184,5 +185,55 @@ class TestCmdStartHeadless(unittest.TestCase): self.assertEqual("docker", self._launch_mock.call_args[1]["backend_name"]) +class TestPrepareWithPreflight(unittest.TestCase): + """prepare_with_preflight calls render_preflight with the plan and backend name.""" + + def test_render_preflight_called(self): + from pathlib import Path + mock_backend = MagicMock() + mock_plan = MagicMock() + mock_backend.prepare.return_value = mock_plan + mock_backend.name = "test-backend" + render = MagicMock() + + with patch("bot_bottle.cli.start.get_bottle_backend", return_value=mock_backend), \ + patch("bot_bottle.cli.start._identity_from_plan", return_value="id"), \ + patch("bot_bottle.cli.start.info"): + start_mod.prepare_with_preflight( + MagicMock(), + stage_dir=Path("/tmp"), + render_preflight=render, + prompt_yes=MagicMock(), + dry_run=True, + ) + + render.assert_called_once_with(mock_plan, "test-backend") + + +class TestNoAgentsDefined(unittest.TestCase): + """cmd_start prints an error and returns 1 when the manifest has no agents.""" + + def test_no_agents_defined_returns_1(self): + manifest = _make_manifest([], []) + with patch( + "bot_bottle.cli.start.ManifestIndex.resolve", return_value=manifest + ), patch("sys.stderr", io.StringIO()) as err: + rc = start_mod.cmd_start([]) + self.assertEqual(1, rc) + self.assertIn("no agents defined", err.getvalue()) + + +class TestTextRenderPreflight(unittest.TestCase): + """_text_render_preflight returns a renderer that prints the backend name.""" + + def test_backend_name_in_output(self): + render = start_mod._text_render_preflight() + plan = MagicMock() + with patch("bot_bottle.cli.start._manifest_to_yaml", return_value=""), \ + patch("sys.stderr", io.StringIO()) as err: + render(plan, "my-backend") + self.assertIn("my-backend", err.getvalue()) + + if __name__ == "__main__": unittest.main() diff --git a/tests/unit/test_manifest_validation.py b/tests/unit/test_manifest_validation.py index 45e05cf..a60ba14 100644 --- a/tests/unit/test_manifest_validation.py +++ b/tests/unit/test_manifest_validation.py @@ -9,7 +9,11 @@ from __future__ import annotations import unittest -from bot_bottle.manifest import ManifestBottle, ManifestIndex +from unittest.mock import patch + +from bot_bottle.env import resolve_env +from bot_bottle.errors import MissingEnvVarError +from bot_bottle.manifest import Manifest, ManifestBottle, ManifestIndex from bot_bottle.manifest_agent import ( ManifestAgent, ManifestAgentProvider, @@ -238,5 +242,26 @@ class TestEagerIndexLookups(unittest.TestCase): self.assertIsNone(m.git_identity_summary()) +class TestResolveEnv(unittest.TestCase): + """resolve_env raises MissingEnvVarError when an interpolated entry's + host var is unset.""" + + def _manifest(self, env_dict: dict[str, str]) -> Manifest: + idx = ManifestIndex.from_json_obj({ + "bottles": {"dev": {"env": env_dict}}, + "agents": {"demo": {"bottle": "dev"}}, + }) + return idx.load_for_agent("demo") + + def test_missing_interpolated_host_var_raises(self): + manifest = self._manifest({"API_KEY": "${HOST_API_KEY}"}) + with patch.dict("os.environ", {}, clear=False): + import os + os.environ.pop("HOST_API_KEY", None) + with self.assertRaises(MissingEnvVarError) as cm: + resolve_env(manifest) + self.assertIn("HOST_API_KEY", str(cm.exception)) + + if __name__ == "__main__": unittest.main() diff --git a/tests/unit/test_smolmachines_provision.py b/tests/unit/test_smolmachines_provision.py index ef2604f..695187d 100644 --- a/tests/unit/test_smolmachines_provision.py +++ b/tests/unit/test_smolmachines_provision.py @@ -506,5 +506,54 @@ class TestProvisionGitUser(unittest.TestCase): self.assertIn("bot@example.com", calls[0][0]) +class TestProxyHost(unittest.TestCase): + """_proxy_host returns the bridge gateway on Linux and the loopback + alias on other platforms.""" + + def test_linux_returns_bundle_gateway(self): + plan = _plan() + with patch("bot_bottle.backend.smolmachines.launch.platform.system", + return_value="Linux"): + result = _launch._proxy_host(plan, "127.0.0.16") + self.assertEqual(plan.bundle_gateway, result) + + def test_non_linux_returns_loopback(self): + plan = _plan() + with patch("bot_bottle.backend.smolmachines.launch.platform.system", + return_value="Darwin"): + result = _launch._proxy_host(plan, "127.0.0.16") + self.assertEqual("127.0.0.16", result) + + +class TestDiscoverUrls(unittest.TestCase): + """_discover_urls stamps git-gate host + supervise URL into the plan.""" + + def test_git_gate_host_set_when_upstreams_present(self): + plan = _plan() + plan = replace( + plan, + git_gate_plan=replace( + plan.git_gate_plan, + upstreams=(GitGateUpstream( + name="bot-bottle", + upstream_url="ssh://git@host/repo.git", + upstream_host="host", + upstream_port="22", + identity_file="/tmp/key", + known_host_key="", + ),), + ), + ) + with patch.object(_launch._bundle, "bundle_host_port", return_value="9420"): + stamped = _launch._discover_urls(plan, "127.0.0.16") + self.assertEqual("127.0.0.16:9420", stamped.agent_git_gate_host) + + def test_supervise_url_set_when_supervise_present(self): + plan = _plan(supervise=True) + with patch.object(_launch._bundle, "bundle_host_port", return_value="55556"): + stamped = _launch._discover_urls(plan, "127.0.0.16") + self.assertEqual("http://127.0.0.16:55556/", stamped.agent_supervise_url) + + if __name__ == "__main__": unittest.main()