"""Step definitions for ``resource_cli_flags_904.feature`` (issue #904). Tests the new CLI flags: - resource add --update, --mount (container types), --clone-into - resource list --all - resource tree --depth default changed to 3 - resource type list [REGEX] - lsp list [REGEX] """ from __future__ import annotations from datetime import UTC, datetime from io import StringIO from typing import Any from behave import given, then, when from behave.runner import Context from rich.console import Console from sqlalchemy import create_engine, event from sqlalchemy.orm import sessionmaker from cleveragents.application.services.resource_registry_service import ( ResourceRegistryService, ) from cleveragents.cli.commands.lsp import _reset_registry from cleveragents.infrastructure.database.models import Base, ResourceModel from cleveragents.lsp.models import LspCapability, LspServerConfig # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_flags_service(context: Context) -> ResourceRegistryService: """Create or return a cached ResourceRegistryService for flags tests.""" if not hasattr(context, "flags_cli_service"): engine = create_engine("sqlite:///:memory:", echo=False) @event.listens_for(engine, "connect") def _fk(conn: Any, _rec: Any) -> None: conn.cursor().execute("PRAGMA foreign_keys=ON") Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) context.flags_cli_service = ResourceRegistryService(session_factory=factory) return context.flags_cli_service def _capture_flags_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]: """Run a CLI function capturing its console output and success status.""" import contextlib buf = StringIO() console = Console( file=buf, width=200, no_color=True, highlight=False, force_terminal=False ) import cleveragents.cli.commands.resource as resource_mod orig_console = resource_mod.console resource_mod.console = console failed = False try: with contextlib.redirect_stdout(buf): func(*args, **kwargs) except SystemExit: failed = True except Exception: failed = True finally: resource_mod.console = orig_console return buf.getvalue(), failed def _patch_flags_service(context: Context) -> Any: """Monkey-patch the container to return our in-memory service.""" import cleveragents.cli.commands.resource as resource_mod orig_fn = resource_mod._get_registry_service def _mock_get() -> ResourceRegistryService: return _make_flags_service(context) resource_mod._get_registry_service = _mock_get return orig_fn def _unpatch_flags_service(orig_fn: Any) -> None: """Restore original service getter.""" import cleveragents.cli.commands.resource as resource_mod resource_mod._get_registry_service = orig_fn # --------------------------------------------------------------------------- # Background / Given # --------------------------------------------------------------------------- @given("a fresh resource flags test registry") def step_fresh_flags_registry(context: Context) -> None: engine = create_engine("sqlite:///:memory:", echo=False) @event.listens_for(engine, "connect") def _fk(conn: Any, _rec: Any) -> None: conn.cursor().execute("PRAGMA foreign_keys=ON") Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) context.flags_cli_service = ResourceRegistryService(session_factory=factory) context.flags_cli_output = "" context.flags_cli_failed = False context.flags_tree_depth_used = None @given("resource flags built-in types are bootstrapped") def step_flags_bootstrap(context: Context) -> None: service = _make_flags_service(context) service.bootstrap_builtin_types() @given( 'I add a resource flags resource "{type_name}" named "{name}" with path "{path}"' ) def step_flags_add_resource_given( context: Context, type_name: str, name: str, path: str ) -> None: from cleveragents.cli.commands.resource import resource_add orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_add, type_name=type_name, name=name, path=path, branch=None, description=None, read_only=False, clone_into=None, update=False, fmt="rich", ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @given('a resource flags resource "{type_name}" named "{name}" with path "{path}"') def step_flags_add_resource_given_alt( context: Context, type_name: str, name: str, path: str ) -> None: service = _make_flags_service(context) service.register_resource( type_name=type_name, name=name, location=path, description=None, read_only=False, properties={"path": path}, ) @given( 'a user-added resource flags resource "{type_name}" named "{name}" with path "{path}"' ) def step_flags_add_user_resource( context: Context, type_name: str, name: str, path: str ) -> None: service = _make_flags_service(context) service.register_resource( type_name=type_name, name=name, location=path, description=None, read_only=False, properties={"path": path}, ) @given('an auto-discovered resource flags resource named "{name}"') def step_flags_add_auto_discovered(context: Context, name: str) -> None: """Directly insert an auto-discovered resource into the DB.""" from ulid import ULID service = _make_flags_service(context) session = service._session() try: now_iso = datetime.now(tz=UTC).isoformat() db_resource = ResourceModel( resource_id=str(ULID()), namespaced_name=name, namespace=name.split("/", 1)[0] if "/" in name else None, type_name="git-checkout", resource_kind="physical", location="/tmp/auto", description="Auto-discovered", read_only=False, auto_discovered=True, sandbox_strategy=None, content_hash=None, properties_json=None, metadata_json=None, created_at=now_iso, updated_at=now_iso, ) session.add(db_resource) session.commit() finally: session.close() # --------------------------------------------------------------------------- # LSP Given # --------------------------------------------------------------------------- @given("a clean LSP flags test environment") def step_clean_lsp_flags(context: Context) -> None: _reset_registry() context.lsp_flags_output = "" context.lsp_flags_failed = False @given('a registered LSP flags server "{name}" for language "{lang}"') def step_register_lsp_flags_server(context: Context, name: str, lang: str) -> None: from cleveragents.cli.commands.lsp import _get_registry registry = _get_registry() config = LspServerConfig( name=name, command="test-server", args=["--stdio"], languages=[lang], capabilities=[LspCapability.DIAGNOSTICS], ) registry.register(config) # --------------------------------------------------------------------------- # When steps — resource add # --------------------------------------------------------------------------- @when('I run resource flags add "{type_name}" "{name}" with path "{path}" and update') def step_flags_add_update( context: Context, type_name: str, name: str, path: str ) -> None: from cleveragents.cli.commands.resource import resource_add orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_add, type_name=type_name, name=name, path=path, branch=None, description=None, read_only=False, clone_into=None, update=True, fmt="rich", ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @when( 'I run resource flags add "{type_name}" "{name}" with path "{path}" and mount "{mount_spec}"' ) def step_flags_add_mount( context: Context, type_name: str, name: str, path: str, mount_spec: str ) -> None: from cleveragents.cli.commands.resource import resource_add orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_add, type_name=type_name, name=name, path=path, branch=None, description=None, read_only=False, mount=[mount_spec], clone_into=None, update=False, fmt="rich", ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @when( 'I run resource flags add "{type_name}" "{name}" with path "{path}" and clone-into "{clone_spec}"' ) def step_flags_add_clone_into( context: Context, type_name: str, name: str, path: str, clone_spec: str ) -> None: from cleveragents.cli.commands.resource import resource_add orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_add, type_name=type_name, name=name, path=path, branch=None, description=None, read_only=False, clone_into=clone_spec, update=False, fmt="rich", ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @when('I run resource flags add "{type_name}" "{name}" with clone-into "{clone_spec}"') def step_flags_add_clone_into_no_path( context: Context, type_name: str, name: str, clone_spec: str ) -> None: from cleveragents.cli.commands.resource import resource_add orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_add, type_name=type_name, name=name, path=None, branch=None, description=None, read_only=False, clone_into=clone_spec, update=False, fmt="rich", ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) # --------------------------------------------------------------------------- # When steps — resource list # --------------------------------------------------------------------------- @when("I run resource flags list") def step_flags_list(context: Context) -> None: from cleveragents.cli.commands.resource import resource_list orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_list, type_filter=None, show_all=False, fmt="rich" ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @when("I run resource flags list with --all") def step_flags_list_all(context: Context) -> None: from cleveragents.cli.commands.resource import resource_list orig = _patch_flags_service(context) try: output, failed = _capture_flags_output( resource_list, type_filter=None, show_all=True, fmt="rich" ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) # --------------------------------------------------------------------------- # When steps — resource tree # --------------------------------------------------------------------------- @when('I run resource flags tree for "{name}" with default depth') def step_flags_tree_default(context: Context, name: str) -> None: """Run resource tree and capture the depth that was actually used.""" from cleveragents.cli.commands.resource import resource_tree orig = _patch_flags_service(context) try: # We call resource_tree *without* passing depth so the default is used. # We capture what depth the function uses by inspecting its default. import inspect sig = inspect.signature(resource_tree) default_depth = sig.parameters["depth"].default context.flags_tree_depth_used = default_depth output, failed = _capture_flags_output( resource_tree, resource=name, type_filter=None, fmt="rich" ) context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) # --------------------------------------------------------------------------- # When steps — resource type list [REGEX] # --------------------------------------------------------------------------- @when('I run resource flags type list with regex "{regex}"') def step_flags_type_list_regex(context: Context, regex: str) -> None: from cleveragents.cli.commands.resource import type_list orig = _patch_flags_service(context) try: output, failed = _capture_flags_output(type_list, regex=regex, fmt="rich") context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) @when("I run resource flags type list without regex") def step_flags_type_list_no_regex(context: Context) -> None: from cleveragents.cli.commands.resource import type_list orig = _patch_flags_service(context) try: output, failed = _capture_flags_output(type_list, regex=None, fmt="rich") context.flags_cli_output = output context.flags_cli_failed = failed finally: _unpatch_flags_service(orig) # --------------------------------------------------------------------------- # When steps — lsp list [REGEX] # --------------------------------------------------------------------------- def _capture_lsp_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]: """Run an LSP CLI function capturing its console output.""" import contextlib buf = StringIO() console = Console( file=buf, width=200, no_color=True, highlight=False, force_terminal=False ) import cleveragents.cli.commands.lsp as lsp_mod orig_console = lsp_mod.console lsp_mod.console = console failed = False try: with contextlib.redirect_stdout(buf): func(*args, **kwargs) except SystemExit: failed = True except Exception: failed = True finally: lsp_mod.console = orig_console return buf.getvalue(), failed @when('I run lsp flags list with regex "{regex}"') def step_lsp_flags_list_regex(context: Context, regex: str) -> None: from cleveragents.cli.commands.lsp import list_servers output, failed = _capture_lsp_output(list_servers, regex=regex, fmt="rich") context.lsp_flags_output = output context.lsp_flags_failed = failed @when("I run lsp flags list without regex") def step_lsp_flags_list_no_regex(context: Context) -> None: from cleveragents.cli.commands.lsp import list_servers output, failed = _capture_lsp_output(list_servers, regex=None, fmt="rich") context.lsp_flags_output = output context.lsp_flags_failed = failed # --------------------------------------------------------------------------- # Then steps # --------------------------------------------------------------------------- @then('the resource flags output should contain "{text}"') def step_flags_output_contains(context: Context, text: str) -> None: output = context.flags_cli_output assert text.lower() in output.lower(), ( f"Expected '{text}' in output, got:\n{output}" ) @then('the resource flags output should not contain "{text}"') def step_flags_output_not_contains(context: Context, text: str) -> None: output = context.flags_cli_output assert text.lower() not in output.lower(), ( f"Did not expect '{text}' in output, got:\n{output}" ) @then("the resource flags command should fail") def step_flags_command_failed(context: Context) -> None: assert context.flags_cli_failed, "Expected command to fail but it succeeded" @then("the resource flags tree depth used should be {expected_depth:d}") def step_flags_tree_depth(context: Context, expected_depth: int) -> None: assert context.flags_tree_depth_used == expected_depth, ( f"Expected default depth {expected_depth}, got {context.flags_tree_depth_used}" ) @then('the lsp flags output should contain "{text}"') def step_lsp_flags_output_contains(context: Context, text: str) -> None: output = context.lsp_flags_output assert text.lower() in output.lower(), ( f"Expected '{text}' in output, got:\n{output}" ) @then('the lsp flags output should not contain "{text}"') def step_lsp_flags_output_not_contains(context: Context, text: str) -> None: output = context.lsp_flags_output assert text.lower() not in output.lower(), ( f"Did not expect '{text}' in output, got:\n{output}" )