From 34a65ee9d0f6b6e905c6f0590002bef03e88dafb Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Wed, 4 Mar 2026 20:13:36 +0000 Subject: [PATCH 1/7] test(resource): add failing tests for built-in git-checkout type bootstrap Add TDD-style failing tests that verify the built-in git-checkout resource type is available after initialization. Tests assert the correct expected behavior: after agents init, the git-checkout type should exist in the registry and 'agents resource add git-checkout' should succeed. Tests are expected to fail until bug #524 is fixed, because bootstrap_builtin_types() is never called during initialization. The fix branch should be based on this branch so the fix commit inherits these tests. Files added: - features/resource_type_bootstrap_git.feature (2 Behave scenarios) - features/steps/resource_type_bootstrap_git_steps.py (step definitions) - robot/resource_type_bootstrap_git.robot (Robot Framework smoke test) ISSUES CLOSED: #553 --- features/resource_type_bootstrap_git.feature | 26 +++ .../resource_type_bootstrap_git_steps.py | 167 ++++++++++++++++++ robot/resource_type_bootstrap_git.robot | 50 ++++++ 3 files changed, 243 insertions(+) create mode 100644 features/resource_type_bootstrap_git.feature create mode 100644 features/steps/resource_type_bootstrap_git_steps.py create mode 100644 robot/resource_type_bootstrap_git.robot diff --git a/features/resource_type_bootstrap_git.feature b/features/resource_type_bootstrap_git.feature new file mode 100644 index 000000000..3b52913fc --- /dev/null +++ b/features/resource_type_bootstrap_git.feature @@ -0,0 +1,26 @@ +# These tests target bug #524 and are expected to fail until the fix is applied. +# bootstrap_builtin_types() is never called during initialization, so the +# git-checkout built-in resource type is missing from the registry at runtime. +Feature: Built-in git-checkout type bootstrap on initialization + As a CleverAgents user + I want the built-in git-checkout resource type to be available after initialization + So that I can run "agents resource add git-checkout" without "Resource type not found" + + # ── Registry presence after bootstrap ────────────────────── + + Scenario: After initialization the git-checkout type exists in the resource type registry + Given a freshly initialised resource registry service + When bootstrap_builtin_types is called during initialisation + Then the resource type "git-checkout" should be present in the registry + And the resource type "git-checkout" should have kind "physical" + And the resource type "git-checkout" should have sandbox_strategy "git_worktree" + And the resource type "git-checkout" should be user_addable + + # ── CLI resource add succeeds ────────────────────────────── + + Scenario: agents resource add git-checkout succeeds without Resource type not found error + Given a freshly initialised resource registry service + And bootstrap_builtin_types is called during initialisation + When I run "agents resource add git-checkout local/test --path /tmp/repo --branch main" + Then the CLI exit code should be 0 + And the CLI output should not contain "Resource type not found" diff --git a/features/steps/resource_type_bootstrap_git_steps.py b/features/steps/resource_type_bootstrap_git_steps.py new file mode 100644 index 000000000..fbf20015e --- /dev/null +++ b/features/steps/resource_type_bootstrap_git_steps.py @@ -0,0 +1,167 @@ +"""Step definitions for resource_type_bootstrap_git.feature. + +These tests target bug #524 and are expected to fail until the fix is applied. +bootstrap_builtin_types() is never called during initialization, so the +git-checkout built-in resource type is missing from the registry at runtime. +""" + +from __future__ import annotations + +from unittest.mock import MagicMock, patch + +from behave import given, then, when # type: ignore[import-untyped] +from behave.runner import Context # type: ignore[import-untyped] +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from typer.testing import CliRunner + +from cleveragents.application.services.resource_registry_service import ( + ResourceRegistryService, +) +from cleveragents.infrastructure.database.models import Base + +runner = CliRunner() + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _fresh_service() -> ResourceRegistryService: + """Create a ResourceRegistryService backed by an in-memory SQLite DB.""" + engine = create_engine("sqlite:///:memory:", echo=False) + Base.metadata.create_all(engine) + factory = sessionmaker(bind=engine, expire_on_commit=False) + return ResourceRegistryService(session_factory=factory) + + +# --------------------------------------------------------------------------- +# Given steps +# --------------------------------------------------------------------------- + + +@given("a freshly initialised resource registry service") +def step_fresh_registry(context: Context) -> None: + """Create a fresh in-memory registry service (no bootstrap yet).""" + context.bootstrap_service = _fresh_service() # type: ignore[attr-defined] + + +# --------------------------------------------------------------------------- +# When steps +# --------------------------------------------------------------------------- + + +@when("bootstrap_builtin_types is called during initialisation") +def step_call_bootstrap(context: Context) -> None: + """Call bootstrap_builtin_types, which should seed built-in types.""" + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + context.bootstrap_registered = service.bootstrap_builtin_types() # type: ignore[attr-defined] + + +@given("bootstrap_builtin_types is called during initialisation") +def step_given_bootstrap(context: Context) -> None: + """Call bootstrap as a precondition (Given variant).""" + step_call_bootstrap(context) + + +@when( + 'I run "agents resource add git-checkout local/test --path /tmp/repo --branch main"' +) +def step_run_resource_add_cli(context: Context) -> None: + """Invoke the resource add CLI command via CliRunner with mocked DI.""" + from cleveragents.cli.commands.resource import app as resource_app + + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + + mock_container = MagicMock() + mock_container.resource_registry_service.return_value = service + + with patch( + "cleveragents.cli.commands.resource.get_container", + return_value=mock_container, + ): + result = runner.invoke( + resource_app, + [ + "add", + "git-checkout", + "local/test", + "--path", + "/tmp/repo", + "--branch", + "main", + ], + ) + + context.cli_result = result # type: ignore[attr-defined] + + +# --------------------------------------------------------------------------- +# Then steps +# --------------------------------------------------------------------------- + + +@then('the resource type "{name}" should be present in the registry') +def step_type_present(context: Context, name: str) -> None: + """Assert the named resource type exists in the registry.""" + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + all_types = service.list_types() + type_names = [t.name for t in all_types] + assert name in type_names, f"Expected '{name}' in registry, got: {type_names}" + + +@then('the resource type "{name}" should have kind "{kind}"') +def step_type_kind(context: Context, name: str, kind: str) -> None: + """Assert the resource type has the expected resource_kind.""" + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + spec = service.show_type(name) + actual = ( + spec.resource_kind.value + if hasattr(spec.resource_kind, "value") + else str(spec.resource_kind) + ) + assert actual == kind, f"Expected kind '{kind}', got '{actual}'" + + +@then('the resource type "{name}" should have sandbox_strategy "{strategy}"') +def step_type_sandbox(context: Context, name: str, strategy: str) -> None: + """Assert the resource type has the expected sandbox_strategy.""" + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + spec = service.show_type(name) + actual = ( + spec.sandbox_strategy.value + if hasattr(spec.sandbox_strategy, "value") + else str(spec.sandbox_strategy) + ) + assert actual == strategy, f"Expected strategy '{strategy}', got '{actual}'" + + +@then('the resource type "{name}" should be user_addable') +def step_type_user_addable(context: Context, name: str) -> None: + """Assert the resource type is user-addable.""" + service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] + spec = service.show_type(name) + assert spec.user_addable is True, ( + f"Expected user_addable=True, got {spec.user_addable}" + ) + + +@then("the CLI exit code should be {code:d}") +def step_cli_exit_code(context: Context, code: int) -> None: + """Assert the CLI runner result exit code.""" + result = context.cli_result # type: ignore[attr-defined] + assert result.exit_code == code, ( + f"Expected exit code {code}, got {result.exit_code}.\n" + f"Output: {result.output}\n" + f"Exception: {result.exception!r}" + ) + + +@then('the CLI output should not contain "{text}"') +def step_cli_no_text(context: Context, text: str) -> None: + """Assert the CLI output does not contain the given text.""" + result = context.cli_result # type: ignore[attr-defined] + assert text not in result.output, ( + f"Did not expect '{text}' in CLI output, but found it:\n{result.output}" + ) diff --git a/robot/resource_type_bootstrap_git.robot b/robot/resource_type_bootstrap_git.robot new file mode 100644 index 000000000..67d7f3db6 --- /dev/null +++ b/robot/resource_type_bootstrap_git.robot @@ -0,0 +1,50 @@ +*** Settings *** +Documentation Integration smoke tests for built-in git-checkout type bootstrap (bug #524). +... These tests are expected to FAIL until the fix is applied because +... bootstrap_builtin_types() is never called during initialization. +Library Process +Library OperatingSystem +Resource common.resource + +Suite Setup Setup Test Environment +Suite Teardown Cleanup Test Environment + +*** Test Cases *** +Resource Add Git Checkout Should Not Fail With Type Not Found + [Documentation] Run "agents resource add git-checkout" and verify it does not + ... produce a "Resource type not found" error. This currently fails + ... because bootstrap_builtin_types() is never called. + ${result}= Run Process ${PYTHON} -m cleveragents resource add + ... git-checkout local/test --path /tmp/repo --branch main + ... timeout=60s + Should Not Contain ${result.stdout} Resource type not found + Should Not Contain ${result.stderr} Resource type not found + Should Be Equal As Integers ${result.rc} 0 + ... msg=Expected exit code 0 but got ${result.rc}. stdout: ${result.stdout} stderr: ${result.stderr} + +Git Checkout Type Exists After Bootstrap + [Documentation] Directly call bootstrap_builtin_types() and verify git-checkout + ... is present in the type registry. + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... registered = service.bootstrap_builtin_types() + ... assert "git-checkout" in registered, f"git-checkout not in registered: {registered}" + ... spec = service.show_type("git-checkout") + ... assert spec.name == "git-checkout", f"name mismatch: {spec.name}" + ... rk = spec.resource_kind.value if hasattr(spec.resource_kind, "value") else str(spec.resource_kind) + ... assert rk == "physical", f"kind mismatch: {rk}" + ... ss = spec.sandbox_strategy.value if hasattr(spec.sandbox_strategy, "value") else str(spec.sandbox_strategy) + ... assert ss == "git_worktree", f"sandbox mismatch: {ss}" + ... assert spec.user_addable is True, f"user_addable: {spec.user_addable}" + ... print("git-checkout bootstrap validated successfully") + ${result}= Run Process ${PYTHON} -c ${script} timeout=60s + Should Be Equal As Integers ${result.rc} 0 + ... msg=Bootstrap validation failed: ${result.stderr} + Should Contain ${result.stdout} git-checkout bootstrap validated successfully -- 2.52.0 From 26ee1d632d83307ab3ee76ca860d12fa31baaa63 Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Wed, 4 Mar 2026 20:19:27 +0000 Subject: [PATCH 2/7] docs(changelog): add entry for git-checkout bootstrap test scaffolding ISSUES CLOSED: #553 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86a131d9a..f13d14431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Added TDD-style failing Behave BDD tests for the built-in `git-checkout` resource type + bootstrap. Two scenarios verify that `git-checkout` exists in the registry after init and + that `agents resource add git-checkout` succeeds. Includes Robot Framework smoke tests. + Tests are intentionally failing until the bug fix for #524 is applied. (#553) - Extended `CorrectionService._compute_affected_subtree()` to BFS over both the structural decision tree (parent-child) and the influence DAG (`decision_dependencies` edges). Added cycle detection guard via visited set. Updated `DecisionService.record_decision()` to accept -- 2.52.0 From 5a9995716b5d57181131ef49d18f32a7faca7393 Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Wed, 4 Mar 2026 22:40:09 +0000 Subject: [PATCH 3/7] fix(test): address self-review findings on git-checkout bootstrap tests - Add @tdd @bug524 tags to both feature scenarios for selective execution - Move module-level CliRunner singleton to per-step instantiation for consistency with PR #566 pattern Refs: #553 --- features/resource_type_bootstrap_git.feature | 2 ++ features/steps/resource_type_bootstrap_git_steps.py | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/features/resource_type_bootstrap_git.feature b/features/resource_type_bootstrap_git.feature index 3b52913fc..26fa10521 100644 --- a/features/resource_type_bootstrap_git.feature +++ b/features/resource_type_bootstrap_git.feature @@ -8,6 +8,7 @@ Feature: Built-in git-checkout type bootstrap on initialization # ── Registry presence after bootstrap ────────────────────── + @tdd @bug524 Scenario: After initialization the git-checkout type exists in the resource type registry Given a freshly initialised resource registry service When bootstrap_builtin_types is called during initialisation @@ -18,6 +19,7 @@ Feature: Built-in git-checkout type bootstrap on initialization # ── CLI resource add succeeds ────────────────────────────── + @tdd @bug524 Scenario: agents resource add git-checkout succeeds without Resource type not found error Given a freshly initialised resource registry service And bootstrap_builtin_types is called during initialisation diff --git a/features/steps/resource_type_bootstrap_git_steps.py b/features/steps/resource_type_bootstrap_git_steps.py index fbf20015e..bec719019 100644 --- a/features/steps/resource_type_bootstrap_git_steps.py +++ b/features/steps/resource_type_bootstrap_git_steps.py @@ -20,9 +20,6 @@ from cleveragents.application.services.resource_registry_service import ( ) from cleveragents.infrastructure.database.models import Base -runner = CliRunner() - - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -81,7 +78,7 @@ def step_run_resource_add_cli(context: Context) -> None: "cleveragents.cli.commands.resource.get_container", return_value=mock_container, ): - result = runner.invoke( + result = CliRunner().invoke( resource_app, [ "add", -- 2.52.0 From 63764d68eb2e09dc57b1fe79407c733f485ddb83 Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Thu, 5 Mar 2026 20:35:55 +0000 Subject: [PATCH 4/7] fix(test): address hamza.khyari review #1986 findings on git-checkout bootstrap tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SPEC-1: Added genuine TDD failing Scenario 1 (@wip) that creates registry WITHOUT bootstrap and asserts git-checkout exists — reproduces bug #524. Existing scenarios retained as regression tests (no @wip since they pass). Added NOTE FOR FIX AUTHOR comment documenting fix-path expectations. - BUG-1: Removed colliding @when('I run "agents resource add..."') step. Replaced with uniquely-prefixed bootstrap-git step pattern that invokes resource_add() directly with mocked DI, avoiding AmbiguousStep collision with wildcard @when('I run "{command}"') in cli_plan_context_commands_steps. - BUG-2: Removed duplicate @then('the CLI exit code should be {code:d}'). Replaced with prefixed bootstrap-git assertion steps. - BUG-3: Removed duplicate @then('the CLI output should not contain..."). Replaced with prefixed bootstrap-git assertion steps. - TEST-1: Replaced bare MagicMock() with direct service patching via _PATCH_SERVICE, consistent with PR #567 pattern. - TEST-2: Updated Robot docs from 'expected to FAIL' to 'regression tests' since both Robot tests call bootstrap explicitly and pass. - CODE-1: Simplified hasattr guards on enum fields — removed redundant hasattr checks, using .value directly since ResourceKind and SandboxStrategy are always enums. - TEST-3: Added assertion on bootstrap_builtin_types() return value via new Then step 'the bootstrap-git registered types should include'. - Updated CHANGELOG from 'Two scenarios' to 'Three scenarios'. Refs: #553 --- CHANGELOG.md | 9 +- features/resource_type_bootstrap_git.feature | 40 ++- .../resource_type_bootstrap_git_steps.py | 250 +++++++++++------- robot/resource_type_bootstrap_git.robot | 7 +- 4 files changed, 185 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a0dc824..bb54faf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ ## Unreleased -- Added TDD-style failing Behave BDD tests for the built-in `git-checkout` resource type - bootstrap. Two scenarios verify that `git-checkout` exists in the registry after init and - that `agents resource add git-checkout` succeeds. Includes Robot Framework smoke tests. - Tests are intentionally failing until the bug fix for #524 is applied. (#553) +- Added TDD-style Behave BDD tests for the built-in `git-checkout` resource type + bootstrap. Three scenarios: one failing TDD test reproducing bug #524 (no bootstrap + called during init), and two regression tests verifying `bootstrap_builtin_types()` + seeds correct data and `agents resource add git-checkout` succeeds. Includes Robot + Framework regression tests. (#553) - Extended `CorrectionService._compute_affected_subtree()` to BFS over both the structural decision tree (parent-child) and the influence DAG (`decision_dependencies` edges). Added cycle detection guard via visited set. Updated `DecisionService.record_decision()` to accept diff --git a/features/resource_type_bootstrap_git.feature b/features/resource_type_bootstrap_git.feature index 26fa10521..6f92ebc83 100644 --- a/features/resource_type_bootstrap_git.feature +++ b/features/resource_type_bootstrap_git.feature @@ -6,23 +6,39 @@ Feature: Built-in git-checkout type bootstrap on initialization I want the built-in git-checkout resource type to be available after initialization So that I can run "agents resource add git-checkout" without "Resource type not found" - # ── Registry presence after bootstrap ────────────────────── + # ── Bug reproduction: init path does NOT call bootstrap ──── + # This scenario is the TDD failing test. It creates a registry WITHOUT + # calling bootstrap_builtin_types() and asserts git-checkout exists. + # It will fail until the fix integrates bootstrap into the init path. + # + # NOTE FOR FIX AUTHOR: If the chosen fix is to call bootstrap inside + # init_command() / initialize_project() rather than in + # ResourceRegistryService.__init__(), you will need to update the Given + # step to exercise the init path instead of constructing a bare service. + + @tdd @bug524 @wip + Scenario: git-checkout type is missing when bootstrap is not called during init + Given a bootstrap-git fresh in-memory resource registry without bootstrap + When I query the bootstrap-git resource type registry for "git-checkout" + Then the bootstrap-git resource type "git-checkout" should exist + + # ── Regression: bootstrap function itself works correctly ── @tdd @bug524 Scenario: After initialization the git-checkout type exists in the resource type registry - Given a freshly initialised resource registry service - When bootstrap_builtin_types is called during initialisation - Then the resource type "git-checkout" should be present in the registry - And the resource type "git-checkout" should have kind "physical" - And the resource type "git-checkout" should have sandbox_strategy "git_worktree" - And the resource type "git-checkout" should be user_addable + Given a bootstrap-git fresh in-memory resource registry with bootstrap + When I query the bootstrap-git resource type registry for "git-checkout" + Then the bootstrap-git resource type "git-checkout" should exist + And the bootstrap-git resource type "git-checkout" should have kind "physical" + And the bootstrap-git resource type "git-checkout" should have sandbox_strategy "git_worktree" + And the bootstrap-git resource type "git-checkout" should be user_addable + And the bootstrap-git registered types should include "git-checkout" # ── CLI resource add succeeds ────────────────────────────── @tdd @bug524 Scenario: agents resource add git-checkout succeeds without Resource type not found error - Given a freshly initialised resource registry service - And bootstrap_builtin_types is called during initialisation - When I run "agents resource add git-checkout local/test --path /tmp/repo --branch main" - Then the CLI exit code should be 0 - And the CLI output should not contain "Resource type not found" + Given a bootstrap-git fresh in-memory resource registry with bootstrap + When I run bootstrap-git resource add for type "git-checkout" named "local/test" with path "/tmp/repo" and branch "main" + Then the bootstrap-git resource add command should succeed + And the bootstrap-git resource add output should not contain "Resource type not found" diff --git a/features/steps/resource_type_bootstrap_git_steps.py b/features/steps/resource_type_bootstrap_git_steps.py index bec719019..c12c6f488 100644 --- a/features/steps/resource_type_bootstrap_git_steps.py +++ b/features/steps/resource_type_bootstrap_git_steps.py @@ -1,164 +1,210 @@ """Step definitions for resource_type_bootstrap_git.feature. -These tests target bug #524 and are expected to fail until the fix is applied. -bootstrap_builtin_types() is never called during initialization, so the -git-checkout built-in resource type is missing from the registry at runtime. +These tests target bug #524. The first scenario (without bootstrap) is a +genuine TDD failing test: it creates a fresh registry *without* calling +``bootstrap_builtin_types()`` and asserts ``git-checkout`` exists — which +fails until the init path is fixed to call bootstrap. + +The remaining scenarios verify that ``bootstrap_builtin_types()`` itself +seeds the correct data and that the CLI ``resource add`` path works once +the types are present. """ from __future__ import annotations -from unittest.mock import MagicMock, patch +from io import StringIO +from typing import Any +from unittest.mock import patch from behave import given, then, when # type: ignore[import-untyped] from behave.runner import Context # type: ignore[import-untyped] +from rich.console import Console from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker -from typer.testing import CliRunner from cleveragents.application.services.resource_registry_service import ( ResourceRegistryService, ) +from cleveragents.core.exceptions import NotFoundError from cleveragents.infrastructure.database.models import Base -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- +# ── Helpers ────────────────────────────────────────────────── + +_PATCH_SERVICE = "cleveragents.cli.commands.resource._get_registry_service" +_PATCH_CONSOLE = "cleveragents.cli.commands.resource.console" -def _fresh_service() -> ResourceRegistryService: - """Create a ResourceRegistryService backed by an in-memory SQLite DB.""" +def _make_service(context: Context, *, run_bootstrap: bool) -> ResourceRegistryService: + """Create an in-memory ResourceRegistryService. + + When *run_bootstrap* is ``True`` the built-in types are seeded; when + ``False`` the registry is left empty (reproducing the bug). + """ engine = create_engine("sqlite:///:memory:", echo=False) Base.metadata.create_all(engine) factory = sessionmaker(bind=engine, expire_on_commit=False) - return ResourceRegistryService(session_factory=factory) + service = ResourceRegistryService(session_factory=factory) + if run_bootstrap: + context.bootstrap_git_registered = service.bootstrap_builtin_types() # type: ignore[attr-defined] + context.bootstrap_git_service = service # type: ignore[attr-defined] + return service -# --------------------------------------------------------------------------- -# Given steps -# --------------------------------------------------------------------------- +def _capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]: + """Run a CLI function capturing its console output and success status.""" + buf = StringIO() + console = Console( + file=buf, width=200, no_color=True, highlight=False, force_terminal=False + ) + + failed = False + failure_reason = "" + with patch(_PATCH_CONSOLE, console): + try: + func(*args, **kwargs) + except SystemExit as exc: + failed = True + failure_reason = f"SystemExit: {exc.code}" + except Exception as exc: + failed = True + failure_reason = f"{type(exc).__name__}: {exc}" + + output = buf.getvalue() + if failure_reason: + output = output + "\n" + failure_reason + return output, failed -@given("a freshly initialised resource registry service") -def step_fresh_registry(context: Context) -> None: - """Create a fresh in-memory registry service (no bootstrap yet).""" - context.bootstrap_service = _fresh_service() # type: ignore[attr-defined] +# ── Given steps ────────────────────────────────────────────── -# --------------------------------------------------------------------------- -# When steps -# --------------------------------------------------------------------------- +@given("a bootstrap-git fresh in-memory resource registry with bootstrap") +def step_fresh_registry_with_bootstrap(context: Context) -> None: + """Set up a fresh in-memory database and run bootstrap_builtin_types.""" + _make_service(context, run_bootstrap=True) + context.bootstrap_git_output = "" # type: ignore[attr-defined] + context.bootstrap_git_failed = False # type: ignore[attr-defined] -@when("bootstrap_builtin_types is called during initialisation") -def step_call_bootstrap(context: Context) -> None: - """Call bootstrap_builtin_types, which should seed built-in types.""" - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - context.bootstrap_registered = service.bootstrap_builtin_types() # type: ignore[attr-defined] +@given("a bootstrap-git fresh in-memory resource registry without bootstrap") +def step_fresh_registry_without_bootstrap(context: Context) -> None: + """Set up a fresh in-memory database WITHOUT running bootstrap. + + This reproduces bug #524: the registry is empty because + ``bootstrap_builtin_types()`` is never called during initialization. + """ + _make_service(context, run_bootstrap=False) + context.bootstrap_git_output = "" # type: ignore[attr-defined] + context.bootstrap_git_failed = False # type: ignore[attr-defined] -@given("bootstrap_builtin_types is called during initialisation") -def step_given_bootstrap(context: Context) -> None: - """Call bootstrap as a precondition (Given variant).""" - step_call_bootstrap(context) +# ── When steps ─────────────────────────────────────────────── + + +@when('I query the bootstrap-git resource type registry for "{name}"') +def step_query_registry_for_type(context: Context, name: str) -> None: + """Query the registry for a specific resource type by name.""" + service: ResourceRegistryService = context.bootstrap_git_service # type: ignore[attr-defined] + try: + context.bootstrap_git_type_spec = service.show_type(name) # type: ignore[attr-defined] + context.bootstrap_git_type_found = True # type: ignore[attr-defined] + except NotFoundError: + context.bootstrap_git_type_spec = None # type: ignore[attr-defined] + context.bootstrap_git_type_found = False # type: ignore[attr-defined] @when( - 'I run "agents resource add git-checkout local/test --path /tmp/repo --branch main"' + 'I run bootstrap-git resource add for type "{type_name}" named "{name}" with path "{path}" and branch "{branch}"' ) -def step_run_resource_add_cli(context: Context) -> None: - """Invoke the resource add CLI command via CliRunner with mocked DI.""" - from cleveragents.cli.commands.resource import app as resource_app - - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - - mock_container = MagicMock() - mock_container.resource_registry_service.return_value = service +def step_run_resource_add_git( + context: Context, type_name: str, name: str, path: str, branch: str +) -> None: + """Run resource add command via the CLI function.""" + from cleveragents.cli.commands.resource import resource_add with patch( - "cleveragents.cli.commands.resource.get_container", - return_value=mock_container, + _PATCH_SERVICE, + return_value=context.bootstrap_git_service, # type: ignore[attr-defined] ): - result = CliRunner().invoke( - resource_app, - [ - "add", - "git-checkout", - "local/test", - "--path", - "/tmp/repo", - "--branch", - "main", - ], + output, failed = _capture_output( + resource_add, + type_name=type_name, + name=name, + path=path, + branch=branch, + description=None, + image=None, + read_only=False, + fmt="rich", ) - - context.cli_result = result # type: ignore[attr-defined] + context.bootstrap_git_output = output # type: ignore[attr-defined] + context.bootstrap_git_failed = failed # type: ignore[attr-defined] -# --------------------------------------------------------------------------- -# Then steps -# --------------------------------------------------------------------------- +# ── Then steps ─────────────────────────────────────────────── -@then('the resource type "{name}" should be present in the registry') -def step_type_present(context: Context, name: str) -> None: - """Assert the named resource type exists in the registry.""" - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - all_types = service.list_types() - type_names = [t.name for t in all_types] - assert name in type_names, f"Expected '{name}' in registry, got: {type_names}" - - -@then('the resource type "{name}" should have kind "{kind}"') -def step_type_kind(context: Context, name: str, kind: str) -> None: - """Assert the resource type has the expected resource_kind.""" - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - spec = service.show_type(name) - actual = ( - spec.resource_kind.value - if hasattr(spec.resource_kind, "value") - else str(spec.resource_kind) +@then('the bootstrap-git resource type "{name}" should exist') +def step_type_should_exist(context: Context, name: str) -> None: + """Assert the resource type was found in the registry.""" + assert context.bootstrap_git_type_found is True, ( # type: ignore[attr-defined] + f"Expected resource type '{name}' to exist in the registry after " + f"bootstrap, but it was not found. This is bug #524: " + f"bootstrap_builtin_types() is never called during initialization." ) + + +@then('the bootstrap-git resource type "{name}" should have kind "{kind}"') +def step_type_kind(context: Context, name: str, kind: str) -> None: + """Assert the resource type has the expected kind.""" + spec = context.bootstrap_git_type_spec # type: ignore[attr-defined] + actual = spec.resource_kind.value assert actual == kind, f"Expected kind '{kind}', got '{actual}'" -@then('the resource type "{name}" should have sandbox_strategy "{strategy}"') +@then( + 'the bootstrap-git resource type "{name}" should have sandbox_strategy "{strategy}"' +) def step_type_sandbox(context: Context, name: str, strategy: str) -> None: - """Assert the resource type has the expected sandbox_strategy.""" - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - spec = service.show_type(name) - actual = ( - spec.sandbox_strategy.value - if hasattr(spec.sandbox_strategy, "value") - else str(spec.sandbox_strategy) - ) - assert actual == strategy, f"Expected strategy '{strategy}', got '{actual}'" + """Assert the resource type has the expected sandbox strategy.""" + spec = context.bootstrap_git_type_spec # type: ignore[attr-defined] + actual = spec.sandbox_strategy.value + assert actual == strategy, f"Expected sandbox_strategy '{strategy}', got '{actual}'" -@then('the resource type "{name}" should be user_addable') +@then('the bootstrap-git resource type "{name}" should be user_addable') def step_type_user_addable(context: Context, name: str) -> None: """Assert the resource type is user-addable.""" - service: ResourceRegistryService = context.bootstrap_service # type: ignore[attr-defined] - spec = service.show_type(name) + spec = context.bootstrap_git_type_spec # type: ignore[attr-defined] assert spec.user_addable is True, ( f"Expected user_addable=True, got {spec.user_addable}" ) -@then("the CLI exit code should be {code:d}") -def step_cli_exit_code(context: Context, code: int) -> None: - """Assert the CLI runner result exit code.""" - result = context.cli_result # type: ignore[attr-defined] - assert result.exit_code == code, ( - f"Expected exit code {code}, got {result.exit_code}.\n" - f"Output: {result.output}\n" - f"Exception: {result.exception!r}" +@then('the bootstrap-git registered types should include "{name}"') +def step_registered_includes(context: Context, name: str) -> None: + """Assert the bootstrap return value includes the named type.""" + registered = context.bootstrap_git_registered # type: ignore[attr-defined] + assert name in registered, ( + f"Expected '{name}' in bootstrap_builtin_types() return value, " + f"got: {registered}" ) -@then('the CLI output should not contain "{text}"') -def step_cli_no_text(context: Context, text: str) -> None: - """Assert the CLI output does not contain the given text.""" - result = context.cli_result # type: ignore[attr-defined] - assert text not in result.output, ( - f"Did not expect '{text}' in CLI output, but found it:\n{result.output}" +@then("the bootstrap-git resource add command should succeed") +def step_resource_add_should_succeed(context: Context) -> None: + """Assert the resource add command did not fail.""" + assert context.bootstrap_git_failed is not True, ( # type: ignore[attr-defined] + f"Expected 'resource add' to succeed, but it failed. " + f"Output: {context.bootstrap_git_output!r}. " + f"This is bug #524: bootstrap_builtin_types() is never called." + ) + + +@then('the bootstrap-git resource add output should not contain "{text}"') +def step_output_should_not_contain(context: Context, text: str) -> None: + """Assert the output does not contain the given text.""" + assert text not in context.bootstrap_git_output, ( # type: ignore[attr-defined] + f"Output unexpectedly contained '{text}'. " + f"Full output: {context.bootstrap_git_output!r}" ) diff --git a/robot/resource_type_bootstrap_git.robot b/robot/resource_type_bootstrap_git.robot index 67d7f3db6..15d5e1b95 100644 --- a/robot/resource_type_bootstrap_git.robot +++ b/robot/resource_type_bootstrap_git.robot @@ -1,7 +1,8 @@ *** Settings *** -Documentation Integration smoke tests for built-in git-checkout type bootstrap (bug #524). -... These tests are expected to FAIL until the fix is applied because -... bootstrap_builtin_types() is never called during initialization. +Documentation Regression tests for built-in git-checkout type bootstrap (bug #524). +... These tests verify that bootstrap_builtin_types() correctly seeds +... git-checkout into the registry and that resource registration succeeds +... after bootstrap. Both tests call bootstrap explicitly and should pass. Library Process Library OperatingSystem Resource common.resource -- 2.52.0 From 5733ab4045e5595881f1d823df2c5a370f8d474f Mon Sep 17 00:00:00 2001 From: Brent Edwards Date: Thu, 5 Mar 2026 22:02:05 +0000 Subject: [PATCH 5/7] fix(test): add tags = ~@wip to behave.ini to exclude @wip scenarios Addresses BUG-4 from hamza.khyari Round 2 review: @wip scenarios broke `nox -s unit_tests` because behave.ini had no wip exclusion. --- behave.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/behave.ini b/behave.ini index b19e710cd..25881a92c 100644 --- a/behave.ini +++ b/behave.ini @@ -1,4 +1,5 @@ [behave] paths = features +tags = ~@wip stdout_capture = no stderr_capture = no -- 2.52.0 From f4a6660badaa9cb9447fda959cf3da20c563efa3 Mon Sep 17 00:00:00 2001 From: Brent Edwards Date: Thu, 5 Mar 2026 22:02:32 +0000 Subject: [PATCH 6/7] fix(test): fix _capture_output to treat SystemExit(0) as success (TEST-4) SystemExit(0) and SystemExit(None) are normal termination, not failures. Only set failed=True when exit code is non-zero. --- features/steps/resource_type_bootstrap_git_steps.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/features/steps/resource_type_bootstrap_git_steps.py b/features/steps/resource_type_bootstrap_git_steps.py index c12c6f488..b57ee7919 100644 --- a/features/steps/resource_type_bootstrap_git_steps.py +++ b/features/steps/resource_type_bootstrap_git_steps.py @@ -63,8 +63,9 @@ def _capture_output(func: Any, *args: Any, **kwargs: Any) -> tuple[str, bool]: try: func(*args, **kwargs) except SystemExit as exc: - failed = True - failure_reason = f"SystemExit: {exc.code}" + if exc.code not in (None, 0): + failed = True + failure_reason = f"SystemExit: {exc.code}" except Exception as exc: failed = True failure_reason = f"{type(exc).__name__}: {exc}" -- 2.52.0 From 50228dfe4e93ef98e90ded14f6d8320e1717a050 Mon Sep 17 00:00:00 2001 From: "Brent E. Edwards" Date: Sat, 7 Mar 2026 02:43:49 +0000 Subject: [PATCH 7/7] fix(test): use in-memory SQLite for Robot git-checkout resource add test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Resource Add Git Checkout Should Not Fail With Type Not Found" Robot test was calling the CLI process directly, which hits the real database file that does not exist in CI — causing sqlite3.OperationalError. Rewrite the test to use an in-memory SQLite database with register_resource(), matching the pattern used by the fs-directory Robot tests. Also fix common.resource path to use ${CURDIR}/common.resource. Refs: #524 --- robot/resource_type_bootstrap_git.robot | 36 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/robot/resource_type_bootstrap_git.robot b/robot/resource_type_bootstrap_git.robot index 15d5e1b95..15b49edef 100644 --- a/robot/resource_type_bootstrap_git.robot +++ b/robot/resource_type_bootstrap_git.robot @@ -5,23 +5,39 @@ Documentation Regression tests for built-in git-checkout type bootstrap (b ... after bootstrap. Both tests call bootstrap explicitly and should pass. Library Process Library OperatingSystem -Resource common.resource +Resource ${CURDIR}/common.resource Suite Setup Setup Test Environment Suite Teardown Cleanup Test Environment *** Test Cases *** Resource Add Git Checkout Should Not Fail With Type Not Found - [Documentation] Run "agents resource add git-checkout" and verify it does not - ... produce a "Resource type not found" error. This currently fails - ... because bootstrap_builtin_types() is never called. - ${result}= Run Process ${PYTHON} -m cleveragents resource add - ... git-checkout local/test --path /tmp/repo --branch main - ... timeout=60s - Should Not Contain ${result.stdout} Resource type not found - Should Not Contain ${result.stderr} Resource type not found + [Documentation] Verify that after bootstrap, registering a git-checkout resource + ... instance succeeds (no "Resource type not found" error). + ${script}= Catenate SEPARATOR=\n + ... from sqlalchemy import create_engine + ... from sqlalchemy.orm import sessionmaker + ... from cleveragents.infrastructure.database.models import Base + ... from cleveragents.application.services.resource_registry_service import ResourceRegistryService + ... engine = create_engine("sqlite:///:memory:", echo=False) + ... Base.metadata.create_all(engine) + ... factory = sessionmaker(bind=engine, expire_on_commit=False) + ... service = ResourceRegistryService(session_factory=factory) + ... service.bootstrap_builtin_types() + ... resource = service.register_resource( + ... ${SPACE}${SPACE}${SPACE}${SPACE}type_name="git-checkout", + ... ${SPACE}${SPACE}${SPACE}${SPACE}name="local/test", + ... ${SPACE}${SPACE}${SPACE}${SPACE}location="/tmp/repo", + ... ${SPACE}${SPACE}${SPACE}${SPACE}description="Test git-checkout resource", + ... ${SPACE}${SPACE}${SPACE}${SPACE}properties={"branch": "main"}, + ... ) + ... assert resource.resource_type_name == "git-checkout", f"type: {resource.resource_type_name}" + ... assert resource.name == "local/test", f"name: {resource.name}" + ... print("resource add git-checkout succeeded after bootstrap") + ${result}= Run Process ${PYTHON} -c ${script} timeout=60s Should Be Equal As Integers ${result.rc} 0 - ... msg=Expected exit code 0 but got ${result.rc}. stdout: ${result.stdout} stderr: ${result.stderr} + ... msg=resource add git-checkout failed: ${result.stderr} + Should Contain ${result.stdout} resource add git-checkout succeeded after bootstrap Git Checkout Type Exists After Bootstrap [Documentation] Directly call bootstrap_builtin_types() and verify git-checkout -- 2.52.0