diff --git a/features/steps/plan_commands_new_coverage_steps.py b/features/steps/plan_commands_new_coverage_steps.py index 6688ff8c..935f8ee6 100644 --- a/features/steps/plan_commands_new_coverage_steps.py +++ b/features/steps/plan_commands_new_coverage_steps.py @@ -170,23 +170,40 @@ use_step_matcher("parse") @when("new_cov I call _get_apply_service directly") def step_new_cov_call_get_apply(context: Context) -> None: - """Call the real _get_apply_service, mocking only its dependencies. + """Call the real ``_get_apply_service``, mocking its dependencies. This exercises the actual function body (import, lifecycle lookup, - PlanApplyService construction) to cover lines 1817, 1821, 1822. + ``PlanApplyService`` construction) to cover lines 1817, 1821, 1822. + + Both ``_get_lifecycle_service`` **and** ``PlanApplyService`` are + patched. The class is patched at two locations — the canonical + source module and the plan module (with ``create=True``) — so the + lazy ``from … import PlanApplyService`` inside the function always + resolves to our mock, even under ``behave-parallel``'s + ``fork()``-based workers. """ from cleveragents.cli.commands.plan import _get_apply_service + mock_lifecycle = MagicMock() + mock_pas_cls = MagicMock() mock_pas_instance = MagicMock() - mock_pas_class = MagicMock(return_value=mock_pas_instance) + mock_pas_cls.return_value = mock_pas_instance with ( - patch(_PATCH_LIFECYCLE, return_value=context.new_cov_mock_lifecycle), - patch(_PATCH_PAS_CLASS, mock_pas_class), + patch(_PATCH_LIFECYCLE, return_value=mock_lifecycle), + patch( + "cleveragents.cli.commands.plan.PlanApplyService", + mock_pas_cls, + create=True, + ), + patch(_PATCH_PAS_CLASS, mock_pas_cls), ): - context.new_cov_apply_result = _get_apply_service() - context.new_cov_pas_class = mock_pas_class - context.new_cov_pas_instance = mock_pas_instance + result = _get_apply_service() + + context.new_cov_apply_result = result + context.new_cov_mock_lifecycle_used = mock_lifecycle + context.new_cov_mock_pas_cls = mock_pas_cls + context.new_cov_mock_pas_instance = mock_pas_instance # --------------------------------------------------------------------------- @@ -218,13 +235,16 @@ def step_new_cov_output_contains(context: Context, text: str) -> None: @then("new_cov the returned object should be a PlanApplyService instance") def step_new_cov_result_is_pas(context: Context) -> None: - assert context.new_cov_apply_result is context.new_cov_pas_instance, ( - f"Expected the mock PAS instance, got {type(context.new_cov_apply_result).__name__}" + result = context.new_cov_apply_result + expected = context.new_cov_mock_pas_instance + assert result is expected, ( + f"Expected _get_apply_service to return the mock PlanApplyService " + f"instance, got {type(result).__name__}" ) @then("new_cov PlanApplyService was constructed with the lifecycle service") def step_new_cov_pas_called_with_lifecycle(context: Context) -> None: - context.new_cov_pas_class.assert_called_once_with( - lifecycle_service=context.new_cov_mock_lifecycle, + context.new_cov_mock_pas_cls.assert_called_once_with( + lifecycle_service=context.new_cov_mock_lifecycle_used, ) diff --git a/robot/resource_type_bootstrap_git.robot b/robot/resource_type_bootstrap_git.robot index 15d5e1b9..493c1ad1 100644 --- a/robot/resource_type_bootstrap_git.robot +++ b/robot/resource_type_bootstrap_git.robot @@ -12,16 +12,23 @@ 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. + [Documentation] Initialize a project then run "agents resource add git-checkout" + ... and verify it does not produce a "Resource type not found" error. + ... bootstrap_builtin_types() is called during project init. + ${proj_dir}= Set Variable ${TEMPDIR}${/}git_checkout_bootstrap_test + Create Directory ${proj_dir} + ${init_result}= Run Process ${PYTHON} -m cleveragents init test-git-bootstrap + ... cwd=${proj_dir} timeout=120s + Should Be Equal As Integers ${init_result.rc} 0 + ... msg=Project init failed: rc=${init_result.rc} stderr=${init_result.stderr} ${result}= Run Process ${PYTHON} -m cleveragents resource add ... git-checkout local/test --path /tmp/repo --branch main - ... timeout=60s + ... cwd=${proj_dir} 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} + [Teardown] Run Keyword And Ignore Error Remove Directory ${proj_dir} recursive=True Git Checkout Type Exists After Bootstrap [Documentation] Directly call bootstrap_builtin_types() and verify git-checkout diff --git a/src/cleveragents/cli/commands/project.py b/src/cleveragents/cli/commands/project.py index e7503f13..1d45a246 100644 --- a/src/cleveragents/cli/commands/project.py +++ b/src/cleveragents/cli/commands/project.py @@ -200,6 +200,18 @@ def init_command( apply_default_filters=default_filters, ) + # Bootstrap built-in resource types (fs-directory, git-checkout, etc.) + # into the database after schema creation. This must happen after + # initialize_project() because the database tables are created there + # via unit_of_work.init_database(). The call is idempotent — calling + # it multiple times will not create duplicate types. + from cleveragents.application.services.resource_registry_service import ( + ResourceRegistryService, + ) + + resource_registry: ResourceRegistryService = container.resource_registry_service() + resource_registry.bootstrap_builtin_types() + console.print( Panel( f"[green]✓[/green] Project '{project.name}' "