From 4ddaf76b62efaddc4f36c8e87ab8aa601f5f0771 Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 24 Mar 2026 10:24:27 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E7=A7=BB=E9=99=A4=20Mock=20=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6=20(=E7=B5=B1=E5=B8=A5=E9=90=B5=E5=BE=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 全面禁止 Mock 測試,所有測試必須使用真實資料庫。 移除 test_stats_api.py (Mock-based unit tests) Co-Authored-By: Claude Opus 4.5 --- apps/api/tests/test_stats_api.py | 257 ------------------------------- 1 file changed, 257 deletions(-) delete mode 100644 apps/api/tests/test_stats_api.py diff --git a/apps/api/tests/test_stats_api.py b/apps/api/tests/test_stats_api.py deleted file mode 100644 index 2c01b279..00000000 --- a/apps/api/tests/test_stats_api.py +++ /dev/null @@ -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