""" Auto Repair Service Tests - #8 自動升級決策 ========================================== 測試自動修復服務層功能 版本: v1.0 建立: 2026-03-26 (台北時區) 建立者: Claude Code (#8 自動升級決策) """ import pytest from src.models.incident import Incident, IncidentStatus, Severity, Signal from src.models.playbook import ( ActionType, Playbook, PlaybookStatus, RepairStep, RiskLevel, SymptomPattern, ) from src.services.auto_repair_service import AutoRepairService from src.utils.timezone import now_taipei class MockPlaybookService: """Mock playbook service for testing""" def __init__(self): self._playbooks: dict[str, Playbook] = {} self._recommendations: list = [] def add_playbook(self, playbook: Playbook): self._playbooks[playbook.playbook_id] = playbook def set_recommendations(self, recommendations: list): self._recommendations = recommendations async def get_recommendations(self, symptoms, top_k=3): return self._recommendations async def get_by_id(self, playbook_id: str): return self._playbooks.get(playbook_id) async def record_execution(self, playbook_id: str, success: bool): playbook = self._playbooks.get(playbook_id) if playbook: if success: playbook.success_count += 1 else: playbook.failure_count += 1 return playbook is not None def create_test_incident( incident_id: str = "INC-TEST-001", severity: Severity = Severity.P2, ) -> Incident: """Create a test incident""" now = now_taipei() return Incident( incident_id=incident_id, status=IncidentStatus.OPEN, severity=severity, affected_services=["test-service"], signals=[ Signal( alert_name="HighCPU", severity=severity, source="prometheus", fired_at=now, labels={"namespace": "prod"}, ), ], ) def create_high_quality_playbook( playbook_id: str = "PB-TEST-001", risk_level: RiskLevel = RiskLevel.MEDIUM, ) -> Playbook: """Create a high quality playbook (success_rate >= 95%, count >= 10)""" return Playbook( playbook_id=playbook_id, name="HighCPU - test-service 修復劇本", description="High quality playbook for auto repair", status=PlaybookStatus.APPROVED, symptom_pattern=SymptomPattern( alert_names=["HighCPU"], affected_services=["test-service"], severity_range=["P2"], ), repair_steps=[ RepairStep( step_number=1, action_type=ActionType.KUBECTL, command="kubectl rollout restart deployment/{target}", risk_level=risk_level, ), ], success_count=20, # >= 10 failure_count=1, # success_rate = 95.2% ai_confidence=0.9, ) class MockPlaybookRecommendation: """Mock recommendation for testing""" def __init__(self, playbook: Playbook, similarity_score: float): self.playbook = playbook self.similarity_score = similarity_score class TestAutoRepairService: """Auto Repair Service unit tests""" @pytest.fixture def mock_playbook_service(self): return MockPlaybookService() @pytest.fixture def service(self, mock_playbook_service): return AutoRepairService(playbook_service=mock_playbook_service) @pytest.mark.asyncio async def test_evaluate_blocks_p1_severity(self, service): """Test that P1 severity incidents are blocked""" incident = create_test_incident(severity=Severity.P1) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "HIGH_SEVERITY" @pytest.mark.asyncio async def test_evaluate_blocks_p0_severity(self, service): """Test that P0 severity incidents are blocked""" incident = create_test_incident(severity=Severity.P0) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "HIGH_SEVERITY" @pytest.mark.asyncio async def test_evaluate_no_playbook_match(self, service, mock_playbook_service): """Test when no playbook matches""" mock_playbook_service.set_recommendations([]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "NO_MATCH" @pytest.mark.asyncio async def test_evaluate_low_similarity(self, service, mock_playbook_service): """Test when similarity is too low""" playbook = create_high_quality_playbook() mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.5) # Below 0.7 ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "LOW_SIMILARITY" @pytest.mark.asyncio async def test_evaluate_not_high_quality(self, service, mock_playbook_service): """Test when playbook is not high quality""" playbook = Playbook( playbook_id="PB-LOW-QUALITY", name="Low quality playbook", description="Not enough executions", status=PlaybookStatus.APPROVED, symptom_pattern=SymptomPattern( alert_names=["HighCPU"], affected_services=["test-service"], ), repair_steps=[], success_count=5, # < 10 failure_count=0, ) mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.9) ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "NOT_HIGH_QUALITY" @pytest.mark.asyncio async def test_evaluate_high_risk_blocked(self, service, mock_playbook_service): """Test when playbook contains HIGH risk actions""" playbook = create_high_quality_playbook(risk_level=RiskLevel.HIGH) mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.9) ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "HIGH_RISK" @pytest.mark.asyncio async def test_evaluate_critical_risk_blocked(self, service, mock_playbook_service): """Test when playbook contains CRITICAL risk actions""" playbook = create_high_quality_playbook(risk_level=RiskLevel.CRITICAL) mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.9) ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is False assert decision.blocked_by == "HIGH_RISK" @pytest.mark.asyncio async def test_evaluate_success(self, service, mock_playbook_service): """Test successful auto repair evaluation""" playbook = create_high_quality_playbook(risk_level=RiskLevel.MEDIUM) mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.85) ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is True assert decision.playbook is not None assert decision.playbook.playbook_id == playbook.playbook_id assert decision.blocked_by is None @pytest.mark.asyncio async def test_evaluate_low_risk_allowed(self, service, mock_playbook_service): """Test that LOW risk actions are allowed""" playbook = create_high_quality_playbook(risk_level=RiskLevel.LOW) mock_playbook_service.add_playbook(playbook) mock_playbook_service.set_recommendations([ MockPlaybookRecommendation(playbook, similarity_score=0.85) ]) incident = create_test_incident(severity=Severity.P2) decision = await service.evaluate_auto_repair(incident) assert decision.can_auto_repair is True assert decision.risk_level == RiskLevel.LOW @pytest.mark.asyncio async def test_is_high_quality_calculation(self): """Test is_high_quality property""" # High quality: APPROVED + 95%+ success rate + 10+ successes playbook = create_high_quality_playbook() assert playbook.is_high_quality is True assert playbook.success_rate >= 0.95 assert playbook.success_count >= 10 @pytest.mark.asyncio async def test_not_high_quality_low_success_rate(self): """Test playbook with low success rate is not high quality""" playbook = Playbook( playbook_id="PB-LOW-RATE", name="Low success rate", description="Too many failures", status=PlaybookStatus.APPROVED, symptom_pattern=SymptomPattern( alert_names=["Test"], affected_services=["test"], ), repair_steps=[], success_count=15, failure_count=5, # 75% success rate ) assert playbook.is_high_quality is False assert playbook.success_rate < 0.95