diff --git a/features/steps/action_repository_coverage_steps.py b/features/steps/action_repository_coverage_steps.py index 9a6dd3d1f..c7474ab9a 100644 --- a/features/steps/action_repository_coverage_steps.py +++ b/features/steps/action_repository_coverage_steps.py @@ -247,7 +247,7 @@ def step_get_by_name(context: Context, name: str) -> None: # --------------------------------------------------------------------------- -@given("the following actions have been saved") +@given("the following actions have been saved:") def step_save_multiple(context: Context) -> None: """Persist several actions described in a Behave table.""" assert context.table is not None, "Step requires a data table" diff --git a/features/steps/actor_config_steps.py b/features/steps/actor_config_steps.py index 39fade424..aea32919a 100644 --- a/features/steps/actor_config_steps.py +++ b/features/steps/actor_config_steps.py @@ -54,7 +54,7 @@ def step_isolated_workspace(context) -> None: _ensure_workspace(context) -@given('an actor config file "{filename}" with content') +@given('an actor config file "{filename}" with content:') def step_actor_config_file_with_content(context, filename: str) -> None: workspace = _ensure_workspace(context) path = workspace / filename @@ -114,7 +114,7 @@ def step_load_actor_config_blob(context, filename: str) -> None: context.last_error = exc -@when('I parse the actor configuration from file "{filename}" with overrides') +@when('I parse the actor configuration from file "{filename}" with overrides:') def step_parse_actor_config_from_file(context, filename: str) -> None: workspace = _ensure_workspace(context) overrides: dict[str, Any] = json.loads(context.text or "{}") @@ -144,7 +144,9 @@ def step_build_actor_config_from_blob(context, blob_literal: str) -> None: context.last_error = exc -@when("I build an actor configuration from structured blob with defaults and overrides") +@when( + "I build an actor configuration from structured blob with defaults and overrides:" +) def step_build_actor_config_with_defaults(context) -> None: payload = json.loads(context.text or "{}") blob = payload.get("blob") diff --git a/features/steps/architecture_steps.py b/features/steps/architecture_steps.py index 109bd4181..9e941f4b4 100644 --- a/features/steps/architecture_steps.py +++ b/features/steps/architecture_steps.py @@ -94,7 +94,7 @@ def step_check_package_structure(context): raise AssertionError(f"Failed to check package structure: {e}") from e -@then("I should find these main packages") +@then("I should find these main packages:") def step_verify_packages(context): """Verify expected packages exist and are proper Python packages.""" # Check if packages were properly collected diff --git a/features/steps/ci_workflow_validation_steps.py b/features/steps/ci_workflow_validation_steps.py index b3a49ebbc..dac3b5e29 100644 --- a/features/steps/ci_workflow_validation_steps.py +++ b/features/steps/ci_workflow_validation_steps.py @@ -94,7 +94,7 @@ def step_then_job_depends_on(context, job_name, dependency): ) -@then("the workflow should reference these nox sessions") +@then("the workflow should reference these nox sessions:") def step_then_workflow_references_nox_sessions(context): """Verify all required nox sessions are referenced in the workflow.""" jobs = context.ci_workflow.get("jobs", {}) diff --git a/features/steps/cli_output_formats_steps.py b/features/steps/cli_output_formats_steps.py index 8c1e4bd6b..f0db25526 100644 --- a/features/steps/cli_output_formats_steps.py +++ b/features/steps/cli_output_formats_steps.py @@ -11,6 +11,8 @@ from behave import given, then, when from behave.runner import Context from typer.testing import CliRunner +import cleveragents.cli.commands.action as _action_mod +import cleveragents.cli.commands.plan as _plan_mod from cleveragents.cli.commands.action import app as action_app from cleveragents.cli.commands.plan import app as plan_app from cleveragents.cli.formatting import _serialize_value, format_output @@ -80,12 +82,14 @@ def step_format_test_runner(context: Context) -> None: def step_mocked_lifecycle_for_formats(context: Context) -> None: context.mock_action_service = MagicMock() context.mock_plan_service = MagicMock() - context.action_patcher = patch( - "cleveragents.cli.commands.action._get_lifecycle_service", + context.action_patcher = patch.object( + _action_mod, + "_get_lifecycle_service", return_value=context.mock_action_service, ) - context.plan_patcher = patch( - "cleveragents.cli.commands.plan._get_lifecycle_service", + context.plan_patcher = patch.object( + _plan_mod, + "_get_lifecycle_service", return_value=context.mock_plan_service, ) context.action_patcher.start() diff --git a/features/steps/resource_registry_tables_steps.py b/features/steps/resource_registry_tables_steps.py index 7bae29a5f..677a0ee72 100644 --- a/features/steps/resource_registry_tables_steps.py +++ b/features/steps/resource_registry_tables_steps.py @@ -289,17 +289,6 @@ def step_resource_registry_list_types_kind_singular( assert len(results) == count, f"Expected {count}, got {len(results)}" -@then('listing resource types by kind "{kind}" returns {count:d} result') -def step_resource_registry_list_types_kind(context: Any, kind: str, count: int) -> None: - session: Session = context.rr_session - results = ( - session.query(ResourceTypeModel) - .filter(ResourceTypeModel.resource_kind == kind) - .all() - ) - assert len(results) == count, f"Expected {count}, got {len(results)}" - - # --------------------------------------------------------------------------- # ResourceModel CRUD - prerequisites # --------------------------------------------------------------------------- diff --git a/features/steps/sandbox_manager_coverage_steps.py b/features/steps/sandbox_manager_coverage_steps.py index dc48c8e5e..f3500eae7 100644 --- a/features/steps/sandbox_manager_coverage_steps.py +++ b/features/steps/sandbox_manager_coverage_steps.py @@ -95,7 +95,7 @@ def step_given_sandbox_exists( key = f"{plan_id}:{res_id}" try: refs = context._sandbox_refs - except KeyError: + except (KeyError, AttributeError): refs = {} context._sandbox_refs = refs refs[key] = sandbox @@ -154,7 +154,7 @@ def step_given_sandbox_rolled_back(context: Any, plan_id: str, res_id: str) -> N key = f"{plan_id}:{res_id}" try: context._sandbox_refs[key] = mock_sb - except KeyError: + except (KeyError, AttributeError): context._sandbox_refs = {key: mock_sb} diff --git a/features/steps/yaml_template_engine_steps.py b/features/steps/yaml_template_engine_steps.py index e7eeb3fe6..2a9f890c4 100644 --- a/features/steps/yaml_template_engine_steps.py +++ b/features/steps/yaml_template_engine_steps.py @@ -45,7 +45,7 @@ def step_init_engine(context): context.deferred_error = None -@given("I have YAML with specific line splitting pattern") +@given("I have YAML with specific line splitting pattern:") def step_yaml_line_pattern(context): context.yaml_content = context.text.strip("\n") @@ -74,7 +74,7 @@ def step_assert_restructured(context): ] -@given("I have plain YAML without templates") +@given("I have plain YAML without templates:") def step_plain_yaml_without_templates(context): context.yaml_content = context.text.strip("\n") @@ -95,12 +95,12 @@ def step_assert_value_error(context): assert "dict" in str(context.error) -@given("I have a YAML string with inline template placeholders") +@given("I have a YAML string with inline template placeholders:") def step_yaml_with_placeholders(context): context.yaml_content = context.text.strip("\n") -@given("I have invalid templated YAML for deferred rendering") +@given("I have invalid templated YAML for deferred rendering:") def step_invalid_deferred_yaml(context): context.yaml_content = context.text.strip("\n") @@ -136,7 +136,7 @@ def step_assert_deferred_error(context): assert isinstance(context.deferred_error, yaml.YAMLError) -@given("I have a YAML string with for loops requiring preprocessing") +@given("I have a YAML string with for loops requiring preprocessing:") def step_yaml_with_for_loops(context): context.yaml_content = context.text.strip("\n") @@ -155,7 +155,7 @@ def step_assert_preprocess_hints(context): assert "{# indent:" in lines[loop_index + 1] -@given("I have a templated YAML that renders malformed mapping") +@given("I have a templated YAML that renders malformed mapping:") def step_yaml_malformed(context): context.yaml_content = context.text.strip("\n") @@ -168,7 +168,7 @@ def step_context_colon_rich(context): } -@given("I have a YAML string with inline Jinja2 templates") +@given("I have a YAML string with inline Jinja2 templates:") def step_yaml_with_templates(context): context.yaml_content = context.text.strip("\n") @@ -252,7 +252,7 @@ def step_assert_filters(context): assert "a: 1" in context.yaml_dump -@given("I have YAML with mixed content for structure analysis") +@given("I have YAML with mixed content for structure analysis:") def step_yaml_mixed_structure(context): context.yaml_content = context.text.strip("\n") @@ -270,7 +270,7 @@ def step_assert_structure(context): assert any(entry["key"] == "root" for entry in context.analysis["hierarchy"]) -@given("I have a temporary YAML file with template content") +@given("I have a temporary YAML file with template content:") def step_temp_yaml_file(context): context.yaml_file_content = context.text.strip("\n") context.temp_dir = tempfile.TemporaryDirectory() @@ -296,7 +296,7 @@ def step_assert_file_result(context): assert context.file_result["agent"]["name"] == "file-agent" -@given("I have a templated YAML that renders to a list root") +@given("I have a templated YAML that renders to a list root:") def step_yaml_renders_list(context): context.yaml_content = context.text.strip("\n") @@ -319,7 +319,7 @@ def step_assert_non_mapping_error(context): assert "dict" in str(context.error) -@given("I have templated YAML that renders invalid mappings") +@given("I have templated YAML that renders invalid mappings:") def step_yaml_invalid_mapping(context): context.yaml_content = context.text.strip("\n") @@ -347,7 +347,7 @@ def step_assert_malformed_error(context): assert isinstance(context.malformed_error, yaml.YAMLError) -@given("I have a deferred YAML template that becomes a list") +@given("I have a deferred YAML template that becomes a list:") def step_deferred_list_template(context): context.yaml_content = context.text.strip("\n") @@ -357,7 +357,7 @@ def step_assert_deferred_value_error(context): assert isinstance(context.deferred_error, ValueError) -@given("I have YAML with a placeholder causing parse retry") +@given("I have YAML with a placeholder causing parse retry:") def step_yaml_placeholder_retry(context): context.yaml_content = context.text.strip("\n") context.retry_context = None @@ -404,7 +404,7 @@ def step_assert_retry_value_error(context): assert "dict" in str(context.retry_error) -@given("I have inline YAML with multiple colons on one line") +@given("I have inline YAML with multiple colons on one line:") def step_inline_multicolon_yaml(context): context.inline_yaml = context.text.strip("\n") @@ -430,7 +430,7 @@ def step_assert_common_fixes(context): assert context.fixed_common == expected -@given("I have synthetic YAML content for postprocess") +@given("I have synthetic YAML content for postprocess:") def step_synthetic_postprocess_input(context): context.synthetic_yaml = context.text.strip("\n") @@ -459,7 +459,7 @@ def step_assert_synthetic_postprocess(context): assert context.synthetic_postprocessed.strip() == "config: alpha beta: gamma" -@given("I have a YAML file without templates") +@given("I have a YAML file without templates:") def step_yaml_file_without_templates(context: Context) -> None: context.yaml_file_dir = tempfile.TemporaryDirectory() context.yaml_file_path = Path(context.yaml_file_dir.name) / "plain.yaml" @@ -491,7 +491,7 @@ def step_assert_yaml_file_name(context: Context, name: str) -> None: assert config_section.get("name") == name -@given("I have a YAML string without templates") +@given("I have a YAML string without templates:") def step_yaml_string_without_templates(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -520,7 +520,7 @@ def step_assert_yaml_string_value(context: Context, value: str) -> None: assert context.direct_load_result["simple"]["key"] == value -@given("I have a template using nested context and overrides") +@given("I have a template using nested context and overrides:") def step_template_with_nested_context(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -555,7 +555,7 @@ def step_assert_merged_context_render(context: Context) -> None: assert result.get("context_copy") == "nested-explicit" -@given("I have template using utility functions") +@given("I have template using utility functions:") def step_template_with_utilities(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -607,7 +607,7 @@ def step_assert_sum_filter_error(context: Context) -> None: # -- New step definitions for additional coverage scenarios -- -@given("I have rendered YAML with blank lines between sections") +@given("I have rendered YAML with blank lines between sections:") def step_yaml_blank_lines_between_sections(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -636,7 +636,7 @@ def step_assert_blank_lines_removed(context: Context) -> None: ) -@given("I have rendered YAML with blank lines before indented content") +@given("I have rendered YAML with blank lines before indented content:") def step_yaml_blank_lines_before_indented(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -685,7 +685,7 @@ def step_assert_non_dict_context_override(context: Context) -> None: assert context.render_ctx["extra"] == "data" -@given("I have YAML with empty and blank lines for structure analysis") +@given("I have YAML with empty and blank lines for structure analysis:") def step_yaml_empty_blank_lines(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -704,7 +704,7 @@ def step_assert_blank_lines_skipped(context: Context) -> None: assert entry["key"].strip() != "" -@given("I have YAML with dedented keys for structure analysis") +@given("I have YAML with dedented keys for structure analysis:") def step_yaml_dedented_keys(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -719,7 +719,7 @@ def step_assert_dedentation_paths(context: Context) -> None: assert sibling_entry["path"] == ["sibling"] -@given("I have YAML with colon-ending tokens") +@given("I have YAML with colon-ending tokens:") def step_yaml_colon_ending_tokens(context: Context) -> None: context.inline_yaml = context.text.strip("\n") @@ -734,7 +734,7 @@ def step_assert_colon_tokens_split(context: Context) -> None: assert any("key2:" in line for line in lines[1:]) -@given("I have YAML without multiple colons per line") +@given("I have YAML without multiple colons per line:") def step_yaml_no_multiple_colons(context: Context) -> None: context.inline_yaml = context.text.strip("\n") @@ -744,7 +744,7 @@ def step_assert_no_changes(context: Context) -> None: assert context.fixed_common.strip() == context.inline_yaml.strip() -@given("I have YAML with mixed colon tokens for fixing") +@given("I have YAML with mixed colon tokens for fixing:") def step_yaml_mixed_colon_tokens(context: Context) -> None: context.inline_yaml = context.text.strip("\n") @@ -757,7 +757,7 @@ def step_assert_trailing_tokens(context: Context) -> None: assert lines[0] == "config:" -@given("I have rendered YAML with colon in value words") +@given("I have rendered YAML with colon in value words:") def step_yaml_colon_in_value_words(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -776,7 +776,7 @@ def step_assert_colon_value_split(context: Context) -> None: assert "endpoint" in context.postprocessed -@given("I have YAML with non-loop Jinja blocks") +@given("I have YAML with non-loop Jinja blocks:") def step_yaml_non_loop_jinja(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -786,7 +786,7 @@ def step_assert_no_hints_for_non_loops(context: Context) -> None: assert "{# indent:" not in context.preprocessed -@given("I have invalid YAML for simple template extraction") +@given("I have invalid YAML for simple template extraction:") def step_invalid_yaml_for_extraction(context: Context) -> None: context.yaml_content = context.text.strip("\n") @@ -896,7 +896,7 @@ def step_assert_valid_yaml_text(context: Context) -> None: assert parsed["nested"]["key"] == "value" -@given("I have YAML with block-level templates for deferred loading") +@given("I have YAML with block-level templates for deferred loading:") def step_yaml_block_templates_deferred(context: Context) -> None: context.yaml_content = context.text.strip("\n") diff --git a/pyproject.toml b/pyproject.toml index 2e1bb2959..8e8635446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ dev = [ "types-pyyaml>=6.0.0", "types-aiofiles>=23.0.0", # Testing - "behave==1.2.6", + "behave==1.3.3", "pytest>=8.0.0", "pytest-asyncio>=0.23.0", "pytest-cov>=4.1.0", @@ -70,7 +70,7 @@ dev = [ "radon>=6.0.1", ] tests = [ - "behave==1.2.6", + "behave==1.3.3", "coverage>=7.11.0", "asv>=0.6.5", "robotframework>=7.3.2",