Files
awoooi/apps/api/tests/test_adr030_learning_service.py
OG T 59c9eff83a fix(api): 修復 10 個 Lint 錯誤 (imports 排序 + unused imports + set comprehension)
- F401: 移除未使用的 imports (TerminalSessionStatus, AutoApproveDecision, TerminalSession)
- I001: 修正 import blocks 排序
- C401: set(generator) → {set comprehension}

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-28 18:51:52 +08:00

327 lines
11 KiB
Python

"""
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"