test: 移除 Mock 測試 (統帥鐵律)
全面禁止 Mock 測試,所有測試必須使用真實資料庫。 移除 test_stats_api.py (Mock-based unit tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,257 +0,0 @@
|
||||
# =============================================================================
|
||||
# AWOOOI Statistics API 單元測試
|
||||
# =============================================================================
|
||||
# Phase 6.5: 測試統計分析 API 端點
|
||||
#
|
||||
# 測試項目:
|
||||
# - /stats/incidents/summary
|
||||
# - /stats/incidents/resolution
|
||||
# - /stats/incidents/trends
|
||||
# - /stats/ai-performance
|
||||
# - /stats/services/affected
|
||||
# - /stats/feedback/summary
|
||||
# =============================================================================
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from src.api.v1.stats import (
|
||||
get_incident_summary,
|
||||
get_resolution_stats,
|
||||
get_incident_trends,
|
||||
get_ai_performance,
|
||||
get_affected_services,
|
||||
get_feedback_summary,
|
||||
IncidentSummary,
|
||||
ResolutionStats,
|
||||
IncidentTrends,
|
||||
AIPerformance,
|
||||
ServiceImpact,
|
||||
FeedbackSummary,
|
||||
)
|
||||
|
||||
|
||||
class TestIncidentSummary:
|
||||
"""事件總覽統計測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_database_returns_zero_counts(self):
|
||||
"""空資料庫應返回零計數"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
scalar=MagicMock(return_value=0),
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_incident_summary(days=30, db=mock_db)
|
||||
|
||||
assert result.total_incidents == 0
|
||||
assert result.resolved_rate == 0.0
|
||||
assert len(result.status_distribution) == 0
|
||||
assert len(result.severity_distribution) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolved_rate_calculation(self):
|
||||
"""解決率計算正確"""
|
||||
# 10 總數,3 已解決 = 30%
|
||||
mock_db = AsyncMock()
|
||||
call_count = 0
|
||||
|
||||
def mock_execute(*args, **kwargs):
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
result = MagicMock()
|
||||
if call_count == 1: # 總數
|
||||
result.scalar.return_value = 10
|
||||
elif call_count == 2: # 狀態分佈
|
||||
result.all.return_value = []
|
||||
elif call_count == 3: # 嚴重度分佈
|
||||
result.all.return_value = []
|
||||
elif call_count == 4: # 已解決數
|
||||
result.scalar.return_value = 3
|
||||
else: # 平均 signals
|
||||
result.scalar.return_value = 2.5
|
||||
return result
|
||||
|
||||
mock_db.execute = AsyncMock(side_effect=mock_execute)
|
||||
|
||||
result = await get_incident_summary(days=30, db=mock_db)
|
||||
|
||||
assert result.total_incidents == 10
|
||||
assert result.resolved_rate == 30.0
|
||||
|
||||
|
||||
class TestResolutionStats:
|
||||
"""解決時間統計測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_results_return_none(self):
|
||||
"""無已解決事件應返回 None"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_resolution_stats(days=30, db=mock_db)
|
||||
|
||||
assert result.avg_minutes is None
|
||||
assert result.p50_minutes is None
|
||||
assert result.p95_minutes is None
|
||||
assert result.sample_size == 0
|
||||
|
||||
|
||||
class TestIncidentTrends:
|
||||
"""事件趨勢測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_data_returns_empty_list(self):
|
||||
"""無資料應返回空列表"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_incident_trends(days=30, period="daily", db=mock_db)
|
||||
|
||||
assert result.period == "daily"
|
||||
assert len(result.data) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_period_validation(self):
|
||||
"""週期參數驗證"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
for period in ["daily", "weekly", "monthly"]:
|
||||
result = await get_incident_trends(days=30, period=period, db=mock_db)
|
||||
assert result.period == period
|
||||
|
||||
|
||||
class TestAIPerformance:
|
||||
"""AI 效能統計測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_outcomes(self):
|
||||
"""無 outcome 資料應返回零"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_ai_performance(days=30, db=mock_db)
|
||||
|
||||
assert result.total_proposals == 0
|
||||
assert result.executed_count == 0
|
||||
assert result.execution_rate == 0.0
|
||||
assert result.success_rate == 0.0
|
||||
assert result.avg_effectiveness is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_effectiveness_distribution_initialized(self):
|
||||
"""有效性評分分佈初始化正確"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_ai_performance(days=30, db=mock_db)
|
||||
|
||||
# 應該有 1-5 的所有評分
|
||||
assert set(result.effectiveness_distribution.keys()) == {1, 2, 3, 4, 5}
|
||||
|
||||
|
||||
class TestAffectedServices:
|
||||
"""受影響服務統計測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_results(self):
|
||||
"""無資料應返回空列表"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_affected_services(days=30, limit=10, db=mock_db)
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_limit_parameter(self):
|
||||
"""limit 參數應限制結果數量"""
|
||||
mock_db = AsyncMock()
|
||||
# 模擬 5 個服務的資料
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[
|
||||
(["svc1"], "P0"),
|
||||
(["svc2"], "P1"),
|
||||
(["svc3"], "P2"),
|
||||
(["svc1"], "P0"), # svc1 重複
|
||||
(["svc2"], "P1"), # svc2 重複
|
||||
])
|
||||
))
|
||||
|
||||
result = await get_affected_services(days=30, limit=2, db=mock_db)
|
||||
|
||||
# 應該只返回前 2 個 (按計數排序)
|
||||
assert len(result) <= 2
|
||||
|
||||
|
||||
class TestFeedbackSummary:
|
||||
"""人類回饋摘要測試"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_feedback(self):
|
||||
"""無回饋應返回零計數"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[])
|
||||
))
|
||||
|
||||
result = await get_feedback_summary(days=30, db=mock_db)
|
||||
|
||||
assert result.total_feedback == 0
|
||||
assert result.positive_count == 0
|
||||
assert result.neutral_count == 0
|
||||
assert result.negative_count == 0
|
||||
assert len(result.common_themes) == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_score_categorization(self):
|
||||
"""評分分類正確 (>=4 正面, =3 中性, <=2 負面)"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[
|
||||
({"effectiveness_score": 5},), # 正面
|
||||
({"effectiveness_score": 4},), # 正面
|
||||
({"effectiveness_score": 3},), # 中性
|
||||
({"effectiveness_score": 2},), # 負面
|
||||
({"effectiveness_score": 1},), # 負面
|
||||
])
|
||||
))
|
||||
|
||||
result = await get_feedback_summary(days=30, db=mock_db)
|
||||
|
||||
assert result.positive_count == 2
|
||||
assert result.neutral_count == 1
|
||||
assert result.negative_count == 2
|
||||
assert result.total_feedback == 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_theme_extraction(self):
|
||||
"""主題萃取包含關鍵字"""
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock(return_value=MagicMock(
|
||||
all=MagicMock(return_value=[
|
||||
({"learning_notes": "Connection timeout issue"},),
|
||||
({"learning_notes": "Memory leak detected"},),
|
||||
({"learning_notes": "timeout again"},),
|
||||
])
|
||||
))
|
||||
|
||||
result = await get_feedback_summary(days=30, db=mock_db)
|
||||
|
||||
# timeout 出現 2 次,應該在常見主題中
|
||||
assert "timeout" in result.common_themes or "connection" in result.common_themes
|
||||
Reference in New Issue
Block a user