"""Behave steps to cover uncovered branches in context_service.""" import os import tempfile from pathlib import Path from behave import given, then, when from cleveragents.application.services.context_service import ContextService from cleveragents.application.services.plan_service import PlanService from cleveragents.application.services.project_service import ProjectService from cleveragents.config.settings import Settings from cleveragents.infrastructure.database.unit_of_work import UnitOfWork from features.mocks.mock_ai_provider import MockAIProvider @given("a context service coverage workspace") def step_context_service_workspace(context): temp_dir = Path(tempfile.mkdtemp()) context.temp_dir = temp_dir os.chdir(temp_dir) (temp_dir / ".cleveragents").mkdir(exist_ok=True) settings = Settings() db_path = temp_dir / "test_coverage.db" unit_of_work = UnitOfWork(f"sqlite:///{db_path}") unit_of_work.init_database() project_service = ProjectService(settings, unit_of_work) project = project_service.initialize_project( name="coverage-project", path=temp_dir, force=True ) plan_service = PlanService(settings, unit_of_work, ai_provider=MockAIProvider()) plan_service.create_plan(project=project, prompt="coverage plan") context.settings = settings context.unit_of_work = unit_of_work context.project = project context.context_service = ContextService(settings, unit_of_work) @when("I try to add a default ignored file") def step_add_default_ignored_file(context): ignored_dir = context.temp_dir / "__pycache__" ignored_dir.mkdir(exist_ok=True) ignored_file = ignored_dir / "ignored.pyc" ignored_file.write_text("ignored cache") context.added_files, context.already_in_context = ( context.context_service.add_to_context( project=context.project, path=ignored_file ) ) @then("the file is skipped without context entries") def step_verify_default_ignored_skip(context): assert context.added_files == [] assert context.already_in_context == [] assert context.context_service.list_context(context.project) == [] @given("a .agentsignore file with directory and anchored rules") def step_create_agentsignore(context): ignore_file = context.temp_dir / ".agentsignore" ignore_file.write_text("# comment line\nignored_dir/\n/anchored.txt\n") @when("I check ignore status for matching files") def step_check_agentsignore_matches(context): dir_path = context.temp_dir / "ignored_dir" dir_path.mkdir(exist_ok=True) dir_file = dir_path / "nested.txt" dir_file.write_text("nested") anchored_file = context.temp_dir / "anchored.txt" anchored_file.write_text("anchored") context.dir_ignored = context.context_service._should_ignore( context.project, dir_file ) context.anchored_ignored = context.context_service._should_ignore( context.project, anchored_file ) @then("the directory rule and anchored rule are both applied") def step_verify_agentsignore_rules(context): assert context.dir_ignored is True assert context.anchored_ignored is True @given("project include paths are set to allow only matching files") def step_set_include_paths(context): context.project.settings.include_paths = ["allowed/**"] @given("project exclude paths block temporary files") def step_set_exclude_paths(context): context.project.settings.exclude_paths = ["*.tmp"] @when("I check ignore status for included and excluded files") def step_check_include_exclude(context): allowed_dir = context.temp_dir / "allowed" allowed_dir.mkdir(exist_ok=True) included_file = allowed_dir / "keep.py" included_file.write_text("keep") excluded_file = context.temp_dir / "notes.tmp" excluded_file.write_text("skip") outside_include = context.temp_dir / "other" / "skip.py" outside_include.parent.mkdir(parents=True, exist_ok=True) outside_include.write_text("skip") context.included_ignored = context.context_service._should_ignore( context.project, included_file ) context.excluded_ignored = context.context_service._should_ignore( context.project, excluded_file ) context.outside_include_ignored = context.context_service._should_ignore( context.project, outside_include ) @then("excluded files are ignored and included files allowed") def step_verify_include_exclude(context): assert context.excluded_ignored is True assert context.included_ignored is False @then("files outside the include globs are ignored") def step_verify_include_guardrail(context): assert context.outside_include_ignored is True @then("paths outside the project are treated as non matches") def step_verify_project_path_mismatch(context): external_path = context.temp_dir.parent / "external.txt" external_path.write_text("outside") assert ( context.context_service._project_path_matches( context.project, external_path, "*.txt" ) is False ) @when("I run ignore matching against an unrelated base directory") def step_non_relative_match(context): unrelated_base = context.temp_dir / "unrelated" unrelated_base.mkdir(exist_ok=True) target_file = context.temp_dir / "nested" / "file.txt" target_file.parent.mkdir(parents=True, exist_ok=True) target_file.write_text("data") context.non_relative_match = context.context_service._matches_ignore( unrelated_base, "*.txt", target_file ) @then("non-relative paths are treated as not ignored") def step_verify_non_relative(context): assert context.non_relative_match is False