"""Step definitions for predictive_error_prevention.feature. Exercises the Error Pattern Database (Layer 4: Predictive Error Prevention) including recording, matching, formatting, statistics, and keyword extraction. """ from __future__ import annotations from typing import Any from behave import given, then, when from cleveragents.application.services.error_pattern_service import ( ErrorPatternService, ) from cleveragents.domain.models.core.error_pattern import ( ErrorPattern, PreventiveGuidance, ) from cleveragents.infrastructure.database.error_pattern_repository import ( ErrorPatternRepository, ) @given("a fresh error pattern service") def step_pep_fresh_service(context: Any) -> None: context.pep_service = ErrorPatternService() context.pep_last_pattern = None context.pep_guidance: PreventiveGuidance | None = None context.pep_statistics: dict[str, int | float] | None = None @given( 'a recorded pattern "{pattern}" with preventive check "{check}" and keywords "{keywords}"' ) def step_pep_record_pattern_with_check_and_keywords( context: Any, pattern: str, check: str, keywords: str ) -> None: kw_tuple = tuple(k.strip() for k in keywords.split(",")) context.pep_last_pattern = context.pep_service.record_failure( pattern_text=pattern, failure_description=f"Historical failure for {pattern}", preventive_checks=(check,), keywords=kw_tuple, ) @given('a recorded pattern "{pattern}" with preventive check "{check}"') def step_pep_record_pattern_with_check(context: Any, pattern: str, check: str) -> None: context.pep_last_pattern = context.pep_service.record_failure( pattern_text=pattern, failure_description=f"Historical failure for {pattern}", preventive_checks=(check,), ) @when('I record a failure with pattern "{pattern}" and failure "{failure}"') def step_pep_record_failure(context: Any, pattern: str, failure: str) -> None: context.pep_last_pattern = context.pep_service.record_failure( pattern_text=pattern, failure_description=failure, ) @when('I match context "{text}"') def step_pep_match_context(context: Any, text: str) -> None: context.pep_guidance = context.pep_service.match_patterns(text) @when("I request error pattern statistics") def step_pep_request_statistics(context: Any) -> None: context.pep_statistics = context.pep_service.get_statistics() @then("the error pattern database should contain {count:d} pattern") def step_pep_assert_pattern_count(context: Any, count: int) -> None: patterns = context.pep_service.list_patterns() assert len(patterns) == count, f"Expected {count} patterns, got {len(patterns)}" @then("the pattern should have frequency {freq:d}") def step_pep_assert_frequency(context: Any, freq: int) -> None: patterns = context.pep_service.list_patterns() assert len(patterns) > 0, "No patterns found" actual = patterns[0].frequency assert actual == freq, f"Expected frequency {freq}, got {actual}" @then("the pattern should have {count:d} historical failures") def step_pep_assert_historical_failures_count(context: Any, count: int) -> None: patterns = context.pep_service.list_patterns() assert len(patterns) > 0, "No patterns found" actual = len(patterns[0].historical_failures) assert actual == count, f"Expected {count} historical failures, got {actual}" @then('preventive guidance should contain "{text}"') def step_pep_assert_guidance_contains(context: Any, text: str) -> None: assert context.pep_guidance is not None, "No guidance available" all_checks = context.pep_guidance.preventive_checks assert any(text in c for c in all_checks), ( f"Expected guidance to contain '{text}', got {all_checks}" ) @then("preventive guidance should be empty") def step_pep_assert_guidance_empty(context: Any) -> None: assert context.pep_guidance is not None, "No guidance available" assert not context.pep_guidance.has_guidance, ( f"Expected empty guidance, got {context.pep_guidance.preventive_checks}" ) @then('the formatted guidance should contain "{text}"') def step_pep_assert_formatted_contains(context: Any, text: str) -> None: assert context.pep_guidance is not None, "No guidance available" formatted = context.pep_guidance.format_for_context() assert text in formatted, ( f"Expected formatted guidance to contain '{text}', got: {formatted}" ) @then("the statistics should show {count:d} total patterns") def step_pep_assert_stats_total(context: Any, count: int) -> None: assert context.pep_statistics is not None, "No statistics available" actual = context.pep_statistics["total_patterns"] assert actual == count, f"Expected {count} total patterns, got {actual}" @then('the recorded pattern should have keywords including "{keyword}"') def step_pep_assert_keyword(context: Any, keyword: str) -> None: assert context.pep_last_pattern is not None, "No pattern recorded" keywords_lower = [k.lower() for k in context.pep_last_pattern.keywords] assert keyword.lower() in keywords_lower, ( f"Expected keyword '{keyword}' in {context.pep_last_pattern.keywords}" ) @then("the recorded pattern can be retrieved by its ID") def step_pep_retrieve_by_id(context: Any) -> None: assert context.pep_last_pattern is not None, "No pattern recorded" repo = ErrorPatternRepository() repo.create(context.pep_last_pattern) retrieved = repo.get(context.pep_last_pattern.pattern_id) assert retrieved is not None, "Pattern not found by ID" assert retrieved.pattern_id == context.pep_last_pattern.pattern_id @when("I delete the last recorded pattern") def step_pep_delete_last_pattern(context: Any) -> None: assert context.pep_last_pattern is not None, "No pattern recorded" # Access the internal repo to delete deleted = context.pep_service._repo.delete(context.pep_last_pattern.pattern_id) assert deleted, "Delete returned False" @then("the error pattern database should contain {count:d} patterns") def step_pep_assert_pattern_count_plural(context: Any, count: int) -> None: patterns = context.pep_service.list_patterns() assert len(patterns) == count, f"Expected {count} patterns, got {len(patterns)}" @given('a recorded pattern with no keywords for "{pattern}" with check "{check}"') def step_pep_record_pattern_no_keywords(context: Any, pattern: str, check: str) -> None: ep = ErrorPattern( pattern=pattern, historical_failures=("Initial failure",), preventive_checks=(check,), keywords=(), # No keywords — forces fallback matching ) context.pep_service._repo.create(ep) context.pep_last_pattern = ep