Files
cleveragents-core/features/steps/project_commands_coverage_steps.py
T
khyari hamza 46f697bd95
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 22s
CI / security (pull_request) Successful in 26s
CI / typecheck (pull_request) Successful in 29s
CI / build (pull_request) Successful in 14s
CI / coverage (pull_request) Failing after 8m40s
CI / integration_tests (pull_request) Successful in 10m0s
CI / unit_tests (pull_request) Successful in 22m57s
CI / docker (pull_request) Successful in 40s
fix(test): add temp directory and DB bootstrap to project list scenario
The 'Test project list command with no projects' scenario was missing a
Given step to set up a writable working directory. Without it, the
container fell back to CWD/.cleveragents/db.sqlite which could not be
opened (directory did not exist), causing sqlite3.OperationalError.

- Add 'Given I have a temporary working directory' to the scenario
- Bootstrap the SQLite schema in the When step so the ns_projects table
  exists for the empty-list query
2026-02-17 14:40:16 +00:00

716 lines
25 KiB
Python

"""Step definitions for project commands coverage tests."""
from __future__ import annotations
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
from behave import given, then, when
from typer.testing import CliRunner
from cleveragents.application.services.context_service import DEFAULT_IGNORE_PATTERNS
from cleveragents.cli.commands import project
from cleveragents.core.exceptions import (
CleverAgentsError,
ConfigurationError,
ValidationError,
)
@given("I have a temporary working directory")
def step_temp_working_directory(context):
"""Create a temporary working directory."""
context.temp_dir = tempfile.mkdtemp()
context.original_cwd = Path.cwd()
import os
os.chdir(context.temp_dir)
@given('I create a subdirectory "{dirname}"')
def step_create_subdirectory(context, dirname):
"""Create a subdirectory in the temp directory."""
subdir = Path(context.temp_dir) / dirname
subdir.mkdir(parents=True, exist_ok=True)
context.subdirectory = subdir
@given("I have already initialized a project")
def step_already_initialized(context):
"""Simulate an already initialized project."""
project_dir = Path(context.temp_dir) / ".cleveragents"
project_dir.mkdir(exist_ok=True)
context.existing_project = True
@given("I have an initialized project with stats")
def step_initialized_with_stats(context):
"""Mock an initialized project with statistics."""
context.mock_project = MagicMock()
context.mock_project.name = "test-project"
context.mock_project.path = Path(context.temp_dir)
context.mock_project.created_at = "2024-01-01 10:00:00"
context.mock_stats = {
"plans": 5,
"context_files": 10,
"changes": 25,
"current_plan": "plan-001",
}
@given("there is no initialized project")
def step_no_project(context):
"""Ensure no project exists."""
context.no_project = True
@given("the project service will raise a ValidationError with details")
def step_mock_validation_error(context):
"""Mock project service to raise ValidationError."""
context.mock_error = ValidationError(
"Invalid project name",
details={"name": "contains invalid characters", "path": "does not exist"},
)
@given("the project service will raise a ConfigurationError")
def step_mock_configuration_error(context):
"""Mock project service to raise ConfigurationError."""
context.mock_error = ConfigurationError("Missing required configuration")
@given("the project service will raise a CleverAgentsError")
def step_mock_cleveragents_error(context):
"""Mock project service to raise CleverAgentsError."""
context.mock_error = CleverAgentsError("Generic project error")
@given("the project service will raise an unexpected exception")
def step_mock_unexpected_error(context):
"""Mock project service to raise unexpected exception."""
context.mock_error = Exception("Unexpected error occurred")
@given("the project service will raise a CleverAgentsError on status")
def step_mock_status_error(context):
"""Mock project service to raise error on status."""
context.mock_status_error = CleverAgentsError("Cannot get project status")
@when("I run project init without any parameters")
def step_run_project_init_default(context):
"""Run project init with default parameters."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_project = MagicMock()
mock_project.name = Path.cwd().name
mock_project.path = Path.cwd()
mock_service.initialize_project.return_value = mock_project
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init"])
context.result = result
context.mock_service = mock_service
@when('I run project init with name "{name}"')
def step_run_project_init_with_name(context, name):
"""Run project init with custom name."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_project = MagicMock()
mock_project.name = name
mock_project.path = Path.cwd()
mock_service.initialize_project.return_value = mock_project
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init", name])
context.result = result
context.project_name = name
@when('I run project init with path "{path}"')
def step_run_project_init_with_path(context, path):
"""Run project init with custom path."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_project = MagicMock()
project_path = Path(context.temp_dir) / path
mock_project.name = Path(path).name
mock_project.path = project_path
mock_service.initialize_project.return_value = mock_project
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init", "--path", str(project_path)])
context.result = result
context.project_path = project_path
@when("I execute project init with force option")
def step_execute_project_init_force(context):
"""Run project init with force flag."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_project = MagicMock()
mock_project.name = "forced-project"
mock_project.path = Path.cwd()
mock_service.initialize_project.return_value = mock_project
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init", "--force"])
context.result = result
@when("I run project init and it fails with validation error")
def step_run_project_init_validation_error(context):
"""Run project init that fails with ValidationError."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.initialize_project.side_effect = context.mock_error
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init"])
context.result = result
@when("I run project init and it fails with configuration error")
def step_run_project_init_config_error(context):
"""Run project init that fails with ConfigurationError."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.initialize_project.side_effect = context.mock_error
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init"])
context.result = result
@when("I run project init and it fails with CleverAgentsError")
def step_run_project_init_cleveragents_error(context):
"""Run project init that fails with CleverAgentsError."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.initialize_project.side_effect = context.mock_error
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init"])
context.result = result
@when("I run project init and it fails unexpectedly")
def step_run_project_init_unexpected_error(context):
"""Run project init that fails with unexpected error."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.initialize_project.side_effect = context.mock_error
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["init"])
context.result = result
@when("I execute project status command")
def step_execute_project_status(context):
"""Run project status command."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
if hasattr(context, "no_project") and context.no_project:
mock_service.get_current_project.return_value = None
elif hasattr(context, "mock_status_error"):
mock_service.get_current_project.side_effect = context.mock_status_error
else:
mock_service.get_current_project.return_value = context.mock_project
mock_service.get_project_stats.return_value = context.mock_stats
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["status"])
context.result = result
@when("I execute project status and it fails")
def step_execute_project_status_error(context):
"""Run project status that fails."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.side_effect = context.mock_status_error
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["status"])
context.result = result
@when("I run project list command")
def step_run_project_list(context):
"""Run project list command.
Ensures the default database path is writable by creating the
``.cleveragents`` directory and bootstrapping the schema so the
``list`` sub-command can query the ``ns_projects`` table even
when no projects exist.
"""
import os
from sqlalchemy import create_engine
from cleveragents.infrastructure.database.models import Base
# Ensure the fallback database directory exists inside the temp CWD
db_dir = Path.cwd() / ".cleveragents"
db_dir.mkdir(parents=True, exist_ok=True)
db_path = db_dir / "db.sqlite"
# Point the container at this database so it doesn't fail on open
db_url = f"sqlite:///{db_path.absolute()}"
os.environ["CLEVERAGENTS_DATABASE_URL"] = db_url
# Bootstrap the schema so the table exists for the query
engine = create_engine(db_url, echo=False)
Base.metadata.create_all(engine)
engine.dispose()
runner = CliRunner()
result = runner.invoke(project.app, ["list"])
context.result = result
@when("I run project clean command")
def step_run_project_clean(context):
"""Run project clean command."""
runner = CliRunner()
result = runner.invoke(project.app, ["clean"])
context.result = result
@when("I run project clean command with --yes flag")
def step_run_project_clean_yes(context):
"""Run project clean with confirmation."""
runner = CliRunner()
result = runner.invoke(project.app, ["clean", "--yes"])
context.result = result
@then("the project should be initialized in current directory")
def step_check_project_initialized(context):
"""Check project was initialized."""
assert context.result.exit_code == 0
assert "initialized successfully" in context.result.output
@then("the success message should show project name and location")
def step_check_success_message(context):
"""Check success message content."""
assert "Project" in context.result.output
assert "Location:" in context.result.output
@then("the .cleveragents directory should be created")
def step_check_cleveragents_dir(context):
"""Check .cleveragents directory creation."""
# This is mocked, but we check the service was called correctly
assert context.mock_service.initialize_project.called
@then('the project should be initialized with name "{name}"')
def step_check_project_name(context, name):
"""Check project initialized with correct name."""
assert context.result.exit_code == 0
assert name in context.result.output
@then('the success panel should display "{name}"')
def step_check_panel_name(context, name):
"""Check success panel displays name."""
assert name in context.result.output
@then('the project should be initialized in "{dirname}" directory')
def step_check_project_in_dir(context, dirname):
"""Check project initialized in correct directory."""
assert context.result.exit_code == 0
@then('the .cleveragents directory should exist in "{dirname}"')
def step_check_cleveragents_in_dir(context, dirname):
"""Check .cleveragents exists in specified directory."""
# This is mocked, just verify the call happened
assert context.result.exit_code == 0
@then("the project should be reinitialized successfully")
def step_check_reinitialized(context):
"""Check project was reinitialized."""
assert context.result.exit_code == 0
assert "initialized successfully" in context.result.output
@then("no error should be raised")
def step_check_no_error(context):
"""Check no error was raised."""
assert context.result.exit_code == 0
@then("the error message should be displayed in red")
def step_check_error_red(context):
"""Check error displayed in red."""
assert "Validation Error" in context.result.output
@then("the validation error details should be shown")
def step_check_validation_details(context):
"""Check validation error details shown."""
assert "name:" in context.result.output or "path:" in context.result.output
@then("the command should abort with ValidationError")
def step_check_abort_validation(context):
"""Check command aborted with validation error."""
assert context.result.exit_code != 0
@then("the configuration error message should be displayed")
def step_check_config_error(context):
"""Check configuration error displayed."""
assert "Configuration Error" in context.result.output
@then("the command should abort with ConfigurationError")
def step_check_abort_config(context):
"""Check command aborted with config error."""
assert context.result.exit_code != 0
@then("the generic error message should be displayed")
def step_check_generic_error(context):
"""Check generic error displayed."""
assert "Error:" in context.result.output
@then("the command should abort with CleverAgentsError")
def step_check_abort_cleveragents(context):
"""Check command aborted with CleverAgentsError."""
assert context.result.exit_code != 0
@then("the unexpected error message should be displayed")
def step_check_unexpected_error(context):
"""Check unexpected error displayed."""
assert "Unexpected error" in context.result.output
@then("the command should abort with generic exception")
def step_check_abort_generic(context):
"""Check command aborted with generic exception."""
assert context.result.exit_code != 0
@then("the project information should be displayed")
def step_check_project_info(context):
"""Check project info displayed."""
assert "Project Status" in context.result.output
assert "test-project" in context.result.output
@then("the statistics should include plans count")
def step_check_plans_count(context):
"""Check plans count in statistics."""
assert "Plans: 5" in context.result.output
@then("the statistics should include context files count")
def step_check_context_files(context):
"""Check context files count."""
assert "Context Files: 10" in context.result.output
@then("the statistics should include changes count")
def step_check_changes_count(context):
"""Check changes count."""
assert "Total Changes: 25" in context.result.output
@then("the current plan should be shown")
def step_check_current_plan(context):
"""Check current plan shown."""
assert "Current Plan: plan-001" in context.result.output
@then('an error should show "{message}"')
def step_check_error_message(context, message):
"""Check specific error message."""
assert message in context.result.output
@then('it should suggest running "{command}"')
def step_check_suggestion(context, command):
"""Check command suggestion."""
assert command in context.result.output
@then("the error message should be shown")
def step_check_error_shown(context):
"""Check error message shown."""
assert "Error:" in context.result.output
@then("the command should abort")
def step_check_abort(context):
"""Check command aborted."""
assert context.result.exit_code != 0
@then('it should display "{message}"')
def step_check_display_message(context, message):
"""Check specific message displayed."""
assert message in context.result.output
@then("the command should abort regardless of confirmation")
def step_check_abort_regardless(context):
"""Check command aborts even with confirmation."""
assert context.result.exit_code != 0
assert "not yet implemented" in context.result.output
@given("I have a mocked project with file filters")
def step_mock_project_filters(context):
"""Prepare a mocked project for file filter commands."""
context.mock_project = MagicMock()
context.mock_project.name = "filters-project"
context.mock_project.path = Path(getattr(context, "temp_dir", Path.cwd()))
context.project_includes = ["src/*"]
context.project_excludes = ["tests/*"]
@when("I run project file-filter show")
def step_run_file_filter_show(context):
"""Run the file-filter show command."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.return_value = context.mock_project
mock_service.get_project_filters.return_value = (
getattr(context, "project_includes", []),
getattr(context, "project_excludes", []),
)
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["file-filter", "show"])
context.result = result
context.mock_service = mock_service
@when("I run project file-filter show with no project")
def step_run_file_filter_show_no_project(context):
"""Run the file-filter show command without an active project."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.return_value = None
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["file-filter", "show"])
context.result = result
@when(
'I add file filters include "{include_glob}" exclude "{exclude_glob}" with defaults'
)
def step_add_file_filters_with_defaults(context, include_glob, exclude_glob):
"""Run the file-filter add command with include, exclude, and defaults."""
runner = CliRunner()
expected_excludes = [exclude_glob, *DEFAULT_IGNORE_PATTERNS]
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.return_value = context.mock_project
mock_service.update_file_filters.return_value = context.mock_project
mock_service.get_project_filters.return_value = (
[include_glob],
expected_excludes,
)
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(
project.app,
[
"file-filter",
"add",
"--include",
include_glob,
"--exclude",
exclude_glob,
"--defaults",
],
)
context.result = result
context.mock_service = mock_service
context.expected_include = include_glob
context.expected_excludes = expected_excludes
@when("I clear project file filters without flags")
def step_clear_file_filters(context):
"""Run the file-filter clear command without specifying flags."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.return_value = context.mock_project
mock_service.update_file_filters.return_value = context.mock_project
mock_service.get_project_filters.return_value = ([], [])
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(project.app, ["file-filter", "clear"])
context.result = result
context.mock_service = mock_service
@when('I remove project file filters include "{include_glob}" exclude "{exclude_glob}"')
def step_remove_file_filters(context, include_glob, exclude_glob):
"""Run the file-filter remove command with include and exclude globs."""
runner = CliRunner()
with patch("cleveragents.application.container.get_container") as mock_container:
mock_service = MagicMock()
mock_service.get_current_project.return_value = context.mock_project
mock_service.update_file_filters.return_value = context.mock_project
mock_service.get_project_filters.return_value = ([], [])
mock_container.return_value.project_service.return_value = mock_service
result = runner.invoke(
project.app,
[
"file-filter",
"remove",
"--include",
include_glob,
"--exclude",
exclude_glob,
],
)
context.result = result
context.mock_service = mock_service
context.remove_include = include_glob
context.remove_exclude = exclude_glob
@then("it should display the current file filters")
def step_check_file_filter_output(context):
"""Verify file-filter show output displays includes and excludes."""
assert context.result.exit_code == 0
assert "Project File Filters" in context.result.output
for pattern in context.project_includes + context.project_excludes:
assert pattern in context.result.output
@then("it should warn about the missing project")
def step_check_missing_project_warning(context):
"""Verify missing project warning and exit code."""
combined_output = context.result.output
if hasattr(context.result, "stderr") and context.result.stderr:
combined_output += context.result.stderr
assert "No project found in current directory" in combined_output
@then("it should update filters with defaults applied")
def step_check_filters_updated_with_defaults(context):
"""Validate that defaults were included when adding filters."""
assert context.result.exit_code == 0
context.mock_service.update_file_filters.assert_called_once_with(
context.mock_project,
include_add=[context.expected_include],
exclude_add=context.expected_excludes,
)
assert "Updated Project File Filters" in context.result.output
for pattern in context.expected_excludes:
assert pattern in context.result.output
@then("it should clear both include and exclude filters")
def step_check_clear_filters(context):
"""Ensure clear command cleared both include and exclude filters."""
assert context.result.exit_code == 0
context.mock_service.update_file_filters.assert_called_once_with(
context.mock_project,
clear_include=True,
clear_exclude=True,
)
assert "Cleared Project File Filters" in context.result.output
@then("it should remove the specified file filters")
def step_check_remove_filters(context):
"""Ensure remove command removed provided filters."""
assert context.result.exit_code == 0
context.mock_service.update_file_filters.assert_called_once_with(
context.mock_project,
include_remove=[context.remove_include],
exclude_remove=[context.remove_exclude],
)
assert "Updated Project File Filters" in context.result.output
@then("the project command should succeed")
def step_project_command_should_succeed(context):
"""Assert the project command completed successfully."""
assert context.result.exit_code == 0
@then("the project command should exit with code {code:d}")
def step_project_command_exit_code(context, code):
"""Assert the project command exited with the expected code."""
assert context.result.exit_code == code, (
f"Expected exit code {code}, got {context.result.exit_code}. Output: {context.result.output}"
)
@then('the project list output should contain "{text}"')
def step_project_list_output_contains(context, text):
"""Assert the project list output contains expected text."""
assert context.result.exit_code == 0, (
f"Expected exit code 0, got {context.result.exit_code}. Output: {context.result.output}"
)
assert text in context.result.output, (
f"Expected '{text}' in output: {context.result.output}"
)