Files
cleveragents-core/features/steps/system_diagnostics_coverage_steps.py
freemo 583e6b7ea2
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / build (pull_request) Successful in 16s
CI / quality (pull_request) Successful in 17s
CI / security (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 54s
CI / unit_tests (pull_request) Successful in 2m39s
CI / integration_tests (pull_request) Successful in 3m10s
CI / docker (pull_request) Successful in 40s
CI / coverage (pull_request) Successful in 5m0s
CI / lint (push) Successful in 14s
CI / build (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 34s
CI / typecheck (push) Successful in 35s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m23s
CI / docker (push) Successful in 38s
CI / integration_tests (push) Successful in 3m8s
CI / coverage (push) Successful in 5m10s
CI / benchmark-publish (push) Successful in 16m26s
CI / benchmark-regression (pull_request) Successful in 30m12s
feat(correcting-plans): implement Predictive Error Prevention (Layer 4) with Error Pattern Database
Implement spec-mandated Layer 4 Predictive Error Prevention system:

- ErrorPattern domain model with pattern text, historical failures,
  preventive checks, frequency tracking, and keyword-based matching.
- ErrorPatternRepository with in-memory CRUD + context-matching query.
- ErrorPatternService with record_failure(), match_patterns(), and
  get_statistics() methods.
- Wire into plan execution via plan_lifecycle_service pre-execution hook.
- Add error pattern statistics to CLI diagnostics output.

Behave BDD: 11 scenarios covering recording, matching, formatting, stats.
Robot Framework: 3 integration smoke tests.
ASV benchmarks: pattern matching performance.

ISSUES CLOSED: #571
2026-03-08 21:53:21 -04:00

237 lines
8.4 KiB
Python

"""Step definitions for system_diagnostics_coverage.feature.
Covers uncovered paths in cli/commands/system.py:
- _check_file_permissions: writable-but-not-readable (line 344)
- _check_stale_locks: locks table exists paths (lines 367-388)
- _check_async_worker_health: async enabled path (lines 405-417)
- _check_error_patterns: exception path (lines 438-442)
All step patterns use the 'sysdiag_' prefix to avoid AmbiguousStep collisions
with existing steps in cli_core_steps.py and coverage_security_template_boost_steps.py.
"""
from __future__ import annotations
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
__all__: list[str] = []
# ===========================================================================
# Helpers
# ===========================================================================
def _sysdiag_mock_settings(**overrides: Any) -> Any:
"""Create mock settings with sensible defaults for diagnostic tests."""
tmpdir = tempfile.mkdtemp()
s = MagicMock()
s.database_url = overrides.get("database_url", f"sqlite:///{tmpdir}/test.db")
s.data_dir = overrides.get("data_dir", Path(tmpdir))
s.storage_path = overrides.get("storage_path", Path(tmpdir))
s.log_dir = overrides.get("log_dir", Path(tmpdir) / "logs")
s.default_automation_profile = "auto"
s.debug_enabled = False
s.async_enabled = overrides.get("async_enabled", False)
s.async_max_workers = overrides.get("async_max_workers", 4)
s.async_poll_interval = overrides.get("async_poll_interval", 1.0)
s.has_provider_configured = MagicMock(return_value=False)
s.configured_providers = []
return s
# ===========================================================================
# Given steps
# ===========================================================================
@given("a sysdiag test environment")
def step_sysdiag_env(context: Context) -> None:
context.sysdiag_result = {}
context.sysdiag_tmpdir = tempfile.mkdtemp()
@given("a data dir that exists")
def step_sysdiag_data_dir_exists(context: Context) -> None:
data_dir = Path(context.sysdiag_tmpdir) / "data"
data_dir.mkdir(parents=True, exist_ok=True)
context.sysdiag_data_dir = data_dir
# ===========================================================================
# When steps — _check_file_permissions edge cases
# ===========================================================================
@when("I run the file permissions check with read access denied")
def step_sysdiag_perms_no_read(context: Context) -> None:
from cleveragents.cli.commands.system import _check_file_permissions
ms = _sysdiag_mock_settings(data_dir=context.sysdiag_data_dir)
def fake_access(path: Any, mode: int) -> bool:
"""Simulate writable-but-not-readable directory."""
import os as _os
if mode == _os.R_OK:
return False
return mode == _os.W_OK
with (
patch("cleveragents.config.settings.get_settings", return_value=ms),
patch("cleveragents.cli.commands.system.os.access", side_effect=fake_access),
):
context.sysdiag_result = _check_file_permissions()
@when("I run the file permissions check with no access at all")
def step_sysdiag_perms_no_access(context: Context) -> None:
from cleveragents.cli.commands.system import _check_file_permissions
ms = _sysdiag_mock_settings(data_dir=context.sysdiag_data_dir)
with (
patch("cleveragents.config.settings.get_settings", return_value=ms),
patch("cleveragents.cli.commands.system.os.access", return_value=False),
):
context.sysdiag_result = _check_file_permissions()
# ===========================================================================
# When steps — _check_stale_locks edge cases
# ===========================================================================
@when("I run the stale locks check with {count:d} stale locks in the table")
def step_sysdiag_stale_locks_count(context: Context, count: int) -> None:
from cleveragents.cli.commands.system import _check_stale_locks
mock_engine = MagicMock()
mock_inspector = MagicMock()
mock_inspector.get_table_names.return_value = ["locks", "other_table"]
mock_lock_service = MagicMock()
mock_lock_service.count_stale_locks.return_value = count
with (
patch(
"cleveragents.cli.commands.system.get_database_url",
return_value="sqlite:///test.db",
),
patch(
"cleveragents.cli.commands.system.create_engine",
return_value=mock_engine,
),
patch(
"cleveragents.cli.commands.system.sa_inspect",
return_value=mock_inspector,
),
patch(
"cleveragents.cli.commands.system.LockService",
return_value=mock_lock_service,
),
):
context.sysdiag_result = _check_stale_locks()
@when("I run the stale locks check with a database error")
def step_sysdiag_stale_locks_error(context: Context) -> None:
from cleveragents.cli.commands.system import _check_stale_locks
with patch(
"cleveragents.cli.commands.system.get_database_url",
side_effect=RuntimeError("database unreachable"),
):
context.sysdiag_result = _check_stale_locks()
# ===========================================================================
# When steps — _check_async_worker_health edge cases
# ===========================================================================
@when("I run the async worker health check with async enabled")
def step_sysdiag_async_enabled(context: Context) -> None:
from cleveragents.cli.commands.system import _check_async_worker_health
ms = _sysdiag_mock_settings(
async_enabled=True,
async_max_workers=8,
async_poll_interval=2.5,
)
with patch("cleveragents.config.settings.get_settings", return_value=ms):
context.sysdiag_result = _check_async_worker_health()
@when("I run the async worker health check with a settings error")
def step_sysdiag_async_error(context: Context) -> None:
from cleveragents.cli.commands.system import _check_async_worker_health
with patch(
"cleveragents.config.settings.get_settings",
side_effect=RuntimeError("settings unavailable"),
):
context.sysdiag_result = _check_async_worker_health()
# ===========================================================================
# When steps — _check_error_patterns exception path
# ===========================================================================
@when("I run the error patterns check with a service error")
def step_sysdiag_error_patterns_error(context: Context) -> None:
from cleveragents.cli.commands.system import _check_error_patterns
with patch(
"cleveragents.application.services.error_pattern_service.ErrorPatternService",
side_effect=RuntimeError("service init failed"),
):
context.sysdiag_result = _check_error_patterns()
# ===========================================================================
# Then steps
# ===========================================================================
@then('the sysdiag check status should be "{expected}"')
def step_sysdiag_status(context: Context, expected: str) -> None:
from cleveragents.cli.commands.system import CheckStatus
status_map = {
"ok": CheckStatus.OK,
"warn": CheckStatus.WARN,
"error": CheckStatus.ERROR,
}
actual = context.sysdiag_result["status"]
assert actual == status_map[expected], (
f"Expected status '{expected}', got '{actual}'"
)
@then('the sysdiag check details should be "{expected}"')
def step_sysdiag_details_exact(context: Context, expected: str) -> None:
actual = context.sysdiag_result["details"]
assert actual == expected, f"Expected details '{expected}', got '{actual}'"
@then('the sysdiag check details should contain "{text}"')
def step_sysdiag_details_contains(context: Context, text: str) -> None:
details = context.sysdiag_result["details"]
assert text in details, f"Expected '{text}' in details: '{details}'"
@then("the sysdiag check should have a recommendation")
def step_sysdiag_has_recommendation(context: Context) -> None:
rec = context.sysdiag_result.get("recommendation")
assert rec is not None and len(rec) > 0, (
f"Expected a non-empty recommendation, got: {rec!r}"
)