""" ADR-030 Learning Service Tests ============================== 測試持續學習服務 版本: v1.0 建立: 2026-03-26 (台北時區) 建立者: Claude Code (ADR-030 Phase 5) """ import uuid from unittest.mock import MagicMock, patch import pytest from src.models.approval import ApprovalRequest, RiskLevel from src.services.learning_service import ( ExecutionResult, FeedbackRequest, FeedbackType, LearningRecord, LearningService, ) class TestExecutionResult: """ExecutionResult 資料結構測試""" def test_create_success_result(self): """建立成功執行結果""" result = ExecutionResult( approval_id="APR-001", incident_id="INC-001", action="kubectl rollout restart", success=True, duration_seconds=2.5, ) assert result.success is True assert result.error_message is None assert result.duration_seconds == 2.5 def test_create_failure_result(self): """建立失敗執行結果""" result = ExecutionResult( approval_id="APR-001", incident_id="INC-001", action="kubectl rollout restart", success=False, error_message="Pod not found", ) assert result.success is False assert result.error_message == "Pod not found" def test_to_dict(self): """to_dict() 應該正常工作""" result = ExecutionResult( approval_id="APR-001", incident_id="INC-001", action="kubectl rollout restart", success=True, duration_seconds=1.5, ) data = result.to_dict() assert data["approval_id"] == "APR-001" assert data["incident_id"] == "INC-001" assert data["success"] is True assert "executed_at" in data class TestFeedbackRequest: """FeedbackRequest 資料結構測試""" def test_create_human_approve_feedback(self): """建立人工批准反饋""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.HUMAN_APPROVE, submitted_by="admin", ) assert feedback.feedback_type == FeedbackType.HUMAN_APPROVE assert feedback.submitted_by == "admin" def test_create_effectiveness_rating(self): """建立有效性評分反饋""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.EFFECTIVENESS_RATING, effectiveness_score=5, learning_notes="非常有效的修復", ) assert feedback.effectiveness_score == 5 assert feedback.learning_notes == "非常有效的修復" class TestLearningRecord: """LearningRecord 資料結構測試""" def test_create_learning_record(self): """建立學習記錄""" record = LearningRecord( incident_id="INC-001", feedback_type=FeedbackType.EXECUTION_SUCCESS, action_pattern="restart:test-app-*", trust_before=3, trust_after=4, playbook_updated=True, ) assert record.trust_before == 3 assert record.trust_after == 4 assert record.playbook_updated is True def test_to_dict(self): """to_dict() 應該正常工作""" record = LearningRecord( incident_id="INC-001", feedback_type=FeedbackType.EXECUTION_SUCCESS, action_pattern="restart:test-app-*", trust_before=3, trust_after=4, ) data = record.to_dict() assert data["incident_id"] == "INC-001" assert data["feedback_type"] == "execution_success" assert data["trust_before"] == 3 assert data["trust_after"] == 4 class TestLearningService: """LearningService 單元測試""" @pytest.fixture def mock_trust_manager(self): """建立 mock TrustScoreManager""" manager = MagicMock() manager.get_trust_record.return_value = MagicMock(score=3) manager.record_approval.return_value = None manager.record_rejection.return_value = None return manager @pytest.fixture def service(self, mock_trust_manager): """建立 LearningService with mocked dependencies""" with patch("src.services.learning_service.get_trust_manager", return_value=mock_trust_manager): return LearningService() def create_approval(self, action: str = "kubectl rollout restart deployment/test-app -n prod") -> ApprovalRequest: """建立測試用 ApprovalRequest""" return ApprovalRequest( id=uuid.uuid4(), action=action, description="Test approval", risk_level=RiskLevel.LOW, required_signatures=1, requested_by="test-system", ) # ========================================================================= # Action Pattern 提取測試 # ========================================================================= def test_extract_action_pattern_restart(self, service): """測試 restart 動作模式提取""" pattern = service._extract_action_pattern( "kubectl rollout restart deployment/test-app-abc123-def456 -n prod" ) # 應該移除 pod hash suffix assert "restart" in pattern assert "abc123" not in pattern def test_extract_action_pattern_delete(self, service): """測試 delete 動作模式提取""" pattern = service._extract_action_pattern( "kubectl delete pod test-pod-xyz789-abc123 -n staging" ) assert "delete" in pattern assert "xyz789" not in pattern def test_extract_action_pattern_empty(self, service): """測試空動作""" pattern = service._extract_action_pattern("") assert pattern == "unknown" def test_extract_action_pattern_short(self, service): """測試太短的動作""" pattern = service._extract_action_pattern("kubectl") assert pattern == "unknown" # ========================================================================= # 執行結果處理測試 # ========================================================================= @pytest.mark.asyncio async def test_process_success_result(self, service, mock_trust_manager): """處理成功執行結果""" approval = self.create_approval() result = ExecutionResult( approval_id="apr-001", incident_id="INC-001", action=approval.action, success=True, duration_seconds=2.0, ) record = await service.process_execution_result(approval, result) assert record.feedback_type == FeedbackType.EXECUTION_SUCCESS mock_trust_manager.record_approval.assert_called_once() @pytest.mark.asyncio async def test_process_failure_result(self, service, mock_trust_manager): """處理失敗執行結果""" approval = self.create_approval() result = ExecutionResult( approval_id="apr-001", incident_id="INC-001", action=approval.action, success=False, error_message="Pod not found", ) record = await service.process_execution_result(approval, result) assert record.feedback_type == FeedbackType.EXECUTION_FAILURE mock_trust_manager.record_rejection.assert_called_once() # ========================================================================= # 人工反饋處理測試 # ========================================================================= @pytest.mark.asyncio async def test_process_human_approve(self, service, mock_trust_manager): """處理人工批准反饋""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.HUMAN_APPROVE, submitted_by="admin", ) record = await service.process_human_feedback(feedback) assert record.feedback_type == FeedbackType.HUMAN_APPROVE mock_trust_manager.record_approval.assert_called_once() @pytest.mark.asyncio async def test_process_human_reject(self, service, mock_trust_manager): """處理人工拒絕反饋""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.HUMAN_REJECT, submitted_by="admin", ) record = await service.process_human_feedback(feedback) assert record.feedback_type == FeedbackType.HUMAN_REJECT mock_trust_manager.record_rejection.assert_called_once() @pytest.mark.asyncio async def test_process_high_effectiveness_rating(self, service, mock_trust_manager): """處理高有效性評分 (4-5 分)""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.EFFECTIVENESS_RATING, effectiveness_score=5, ) record = await service.process_human_feedback(feedback) assert record.feedback_type == FeedbackType.EFFECTIVENESS_RATING mock_trust_manager.record_approval.assert_called_once() @pytest.mark.asyncio async def test_process_low_effectiveness_rating(self, service, mock_trust_manager): """處理低有效性評分 (1-2 分)""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.EFFECTIVENESS_RATING, effectiveness_score=1, ) record = await service.process_human_feedback(feedback) assert record.feedback_type == FeedbackType.EFFECTIVENESS_RATING mock_trust_manager.record_rejection.assert_called_once() @pytest.mark.asyncio async def test_process_medium_effectiveness_rating(self, service, mock_trust_manager): """處理中等有效性評分 (3 分) - 不調整信任度""" feedback = FeedbackRequest( incident_id="INC-001", feedback_type=FeedbackType.EFFECTIVENESS_RATING, effectiveness_score=3, ) record = await service.process_human_feedback(feedback) assert record.feedback_type == FeedbackType.EFFECTIVENESS_RATING # 中等評分不應該調整信任度 mock_trust_manager.record_approval.assert_not_called() mock_trust_manager.record_rejection.assert_not_called() class TestFeedbackType: """FeedbackType Enum 測試""" def test_all_feedback_types_exist(self): """確認所有反饋類型都存在""" assert FeedbackType.EXECUTION_SUCCESS.value == "execution_success" assert FeedbackType.EXECUTION_FAILURE.value == "execution_failure" assert FeedbackType.HUMAN_APPROVE.value == "human_approve" assert FeedbackType.HUMAN_REJECT.value == "human_reject" assert FeedbackType.HUMAN_OVERRIDE.value == "human_override" assert FeedbackType.EFFECTIVENESS_RATING.value == "effectiveness_rating"