"""Step definitions for server CLI coverage boost scenarios. Targets uncovered lines in src/cleveragents/cli/commands/server.py: - Lines 58-59: resolve_server_mode exception branches - Lines 132-142: server_connect rich format Panel rendering - Lines 157-204: server_status command (entire function) """ from __future__ import annotations from io import StringIO from typing import Any from unittest.mock import MagicMock, patch from behave import given, then, when from behave.runner import Context from rich.console import Console from cleveragents.application.services.config_service import ( ConfigLevel, ResolvedValue, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_resolved(key: str, value: Any) -> ResolvedValue: """Build a ResolvedValue with DEFAULT source.""" return ResolvedValue(key=key, value=value, source=ConfigLevel.DEFAULT) def _build_mock_svc( resolve_map: dict[str, Any] | None = None, raise_map: dict[str, type[Exception]] | None = None, ) -> MagicMock: """Build a mock ConfigService whose resolve() returns controlled values. Parameters ---------- resolve_map: ``{key: value}`` — the mock returns a ``ResolvedValue`` with this value. raise_map: ``{key: ExceptionClass}`` — the mock raises the given exception. """ resolve_map = resolve_map or {} raise_map = raise_map or {} svc = MagicMock() def _resolve(key: str, **_kw: Any) -> ResolvedValue: if key in raise_map: raise raise_map[key](f"mock error for {key}") val = resolve_map.get(key) return _make_resolved(key, val) svc.resolve.side_effect = _resolve svc.read_config.return_value = {} svc.write_config.return_value = None return svc # --------------------------------------------------------------------------- # Given — mock config services # --------------------------------------------------------------------------- @given("a mock config service that raises ValueError on resolve") def step_mock_svc_valueerror(context: Context) -> None: context.mock_svc = _build_mock_svc(raise_map={"server.url": ValueError}) @given("a mock config service that raises KeyError on resolve") def step_mock_svc_keyerror(context: Context) -> None: context.mock_svc = _build_mock_svc(raise_map={"server.url": KeyError}) @given("a mock config service that accepts writes") def step_mock_svc_writes(context: Context) -> None: context.mock_svc = _build_mock_svc() @given("a mock config service that resolves with no server config") def step_mock_svc_no_config(context: Context) -> None: context.mock_svc = _build_mock_svc( resolve_map={ "server.url": None, "server.namespace": None, "server.tls-verify": None, }, ) @given("a mock config service that resolves with full server config") def step_mock_svc_full_config(context: Context) -> None: context.mock_svc = _build_mock_svc( resolve_map={ "server.url": "https://configured.example.com", "server.namespace": "production", "server.tls-verify": False, }, ) @given("a mock config service where namespace resolve raises ValueError") def step_mock_svc_ns_error(context: Context) -> None: context.mock_svc = _build_mock_svc( resolve_map={ "server.url": "https://ns-error.example.com", "server.tls-verify": True, }, raise_map={"server.namespace": ValueError}, ) @given("a mock config service where tls-verify resolve raises KeyError") def step_mock_svc_tls_error(context: Context) -> None: context.mock_svc = _build_mock_svc( resolve_map={ "server.url": "https://tls-error.example.com", "server.namespace": "default", }, raise_map={"server.tls-verify": KeyError}, ) @given("a mock config service where all resolves raise exceptions") def step_mock_svc_all_errors(context: Context) -> None: context.mock_svc = _build_mock_svc( raise_map={ "server.url": ValueError, "server.namespace": KeyError, "server.tls-verify": ValueError, }, ) # --------------------------------------------------------------------------- # When — resolve_server_mode # --------------------------------------------------------------------------- @when("I call resolve_server_mode with the mock") def step_call_resolve_mode(context: Context) -> None: from cleveragents.cli.commands.server import resolve_server_mode with patch( "cleveragents.cli.commands.server._get_config_service", return_value=context.mock_svc, ): context.server_mode_result = resolve_server_mode() # --------------------------------------------------------------------------- # When — server_connect with rich format # --------------------------------------------------------------------------- @when('I call server_connect with url "{url}" and rich format') def step_call_connect_rich(context: Context, url: str) -> None: from cleveragents.cli.commands.server import server_connect buf = StringIO() capture_console = Console(file=buf, width=200, no_color=True) with ( patch( "cleveragents.cli.commands.server._get_config_service", return_value=context.mock_svc, ), patch( "cleveragents.cli.commands.server.console", capture_console, ), ): server_connect(server_url=url, namespace="default", tls_verify=True, fmt="rich") context.captured_console_output = buf.getvalue() @when( 'I call server_connect with url "{url}" namespace "{ns}" tls_verify false and rich format' ) def step_call_connect_rich_ns_tls(context: Context, url: str, ns: str) -> None: from cleveragents.cli.commands.server import server_connect buf = StringIO() capture_console = Console(file=buf, width=200, no_color=True) with ( patch( "cleveragents.cli.commands.server._get_config_service", return_value=context.mock_svc, ), patch( "cleveragents.cli.commands.server.console", capture_console, ), ): server_connect(server_url=url, namespace=ns, tls_verify=False, fmt="rich") context.captured_console_output = buf.getvalue() # --------------------------------------------------------------------------- # When — server_status # --------------------------------------------------------------------------- @when('I call server_status with format "{fmt}"') def step_call_status(context: Context, fmt: str) -> None: from cleveragents.cli.commands.server import server_status echo_buf = StringIO() console_buf = StringIO() capture_console = Console(file=console_buf, width=200, no_color=True) def _capture_echo(msg: str = "", **_kw: Any) -> None: echo_buf.write(str(msg)) echo_buf.write("\n") # We need to also patch resolve_server_mode since it calls _get_config_service # internally. We compute the expected mode from the mock_svc. mock_svc = context.mock_svc # Determine what resolve_server_mode would return given this mock try: rv = mock_svc.resolve("server.url") if rv.value is not None and str(rv.value).strip(): expected_mode = "stubbed" else: expected_mode = "disabled" except (ValueError, KeyError): expected_mode = "disabled" with ( patch( "cleveragents.cli.commands.server._get_config_service", return_value=mock_svc, ), patch( "cleveragents.cli.commands.server.resolve_server_mode", return_value=expected_mode, ), patch( "cleveragents.cli.commands.server.typer.echo", side_effect=_capture_echo, ), patch( "cleveragents.cli.commands.server.console", capture_console, ), ): server_status(fmt=fmt) context.echoed_output = echo_buf.getvalue() context.captured_console_output = console_buf.getvalue() # --------------------------------------------------------------------------- # Then — assertions # --------------------------------------------------------------------------- @then('the server mode should be "{expected}"') def step_assert_mode(context: Context, expected: str) -> None: assert context.server_mode_result == expected, ( f"Expected mode '{expected}', got '{context.server_mode_result}'" ) @then('the captured console output should contain "{text}"') def step_assert_console_contains(context: Context, text: str) -> None: output = getattr(context, "captured_console_output", "") assert text in output, f"Expected '{text}' in console output:\n{output!r}" @then('the echoed output should contain "{text}"') def step_assert_echo_contains(context: Context, text: str) -> None: output = getattr(context, "echoed_output", "") assert text.lower() in output.lower(), ( f"Expected '{text}' in echoed output:\n{output!r}" )