refactor(api): Phase B P1 可靠性強化 (2 項)
1. send_cicd_progress 重試機制 (指數退避 1,2,4 秒) 2. K8s Repository 封裝 (IK8sRepository + K8sRepository) 首席架構師審查 P1 改進 - 模組化合規 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,16 +14,17 @@ Omni-Terminal SSE 架構測試
|
||||
@date 2026-03-28 (台北時間)
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from src.models.approval import ApprovalRequest, ApprovalStatus, RiskLevel
|
||||
from src.models.terminal import (
|
||||
SpatialContext,
|
||||
TerminalIntentRequest,
|
||||
TerminalSessionStatus,
|
||||
)
|
||||
from src.models.approval import ApprovalRequest, ApprovalStatus, RiskLevel
|
||||
from src.services.terminal_service import (
|
||||
IntentType,
|
||||
TerminalService,
|
||||
@@ -345,44 +346,30 @@ class TestHandleApprovalAction:
|
||||
|
||||
|
||||
class TestHandleStatusQuery:
|
||||
"""_handle_status_query Phase 19.4 測試"""
|
||||
"""_handle_status_query Phase 19.4 測試 (P1: 使用 K8sRepository)"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_query_success(self, mock_publisher):
|
||||
"""測試 K8s 狀態查詢成功"""
|
||||
"""測試 K8s 狀態查詢成功 (P1: 使用 K8sRepository)"""
|
||||
service = TerminalService()
|
||||
|
||||
# 模擬 K8s client
|
||||
mock_pod = MagicMock()
|
||||
mock_pod.metadata.name = "awoooi-api-xxx"
|
||||
mock_pod.status.phase = "Running"
|
||||
mock_pod.status.container_statuses = [MagicMock(ready=True)]
|
||||
|
||||
mock_pods_list = MagicMock()
|
||||
mock_pods_list.items = [mock_pod]
|
||||
|
||||
mock_deployment = MagicMock()
|
||||
mock_deployment.status.ready_replicas = 1
|
||||
mock_deployment.spec.replicas = 1
|
||||
|
||||
mock_deployments_list = MagicMock()
|
||||
mock_deployments_list.items = [mock_deployment]
|
||||
|
||||
mock_v1 = MagicMock()
|
||||
mock_v1.list_namespaced_pod = AsyncMock(return_value=mock_pods_list)
|
||||
|
||||
mock_apps_v1 = MagicMock()
|
||||
mock_apps_v1.list_namespaced_deployment = AsyncMock(
|
||||
return_value=mock_deployments_list
|
||||
)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.CoreV1Api.return_value = mock_v1
|
||||
mock_client.AppsV1Api.return_value = mock_apps_v1
|
||||
# Mock K8sRepository
|
||||
mock_k8s_repo = MagicMock()
|
||||
mock_k8s_repo.is_available = AsyncMock(return_value=True)
|
||||
mock_k8s_repo.get_pod_status_summary = AsyncMock(return_value={
|
||||
"total": 1,
|
||||
"running": 1,
|
||||
"pending": 0,
|
||||
"failed": 0,
|
||||
"problem_pods": [],
|
||||
})
|
||||
mock_k8s_repo.list_deployments = AsyncMock(return_value=[
|
||||
{"name": "awoooi-api", "ready_replicas": 1, "replicas": 1, "available": 1}
|
||||
])
|
||||
|
||||
with patch(
|
||||
"src.services.k8s_diagnostics._get_k8s_client",
|
||||
new=AsyncMock(return_value=mock_client),
|
||||
"src.services.terminal_service.get_k8s_repository",
|
||||
return_value=mock_k8s_repo,
|
||||
):
|
||||
await service._handle_status_query(
|
||||
mock_publisher,
|
||||
@@ -396,41 +383,26 @@ class TestHandleStatusQuery:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_query_with_problem_pods(self, mock_publisher):
|
||||
"""測試有問題 Pods 時的狀態查詢"""
|
||||
"""測試有問題 Pods 時的狀態查詢 (P1: 使用 K8sRepository)"""
|
||||
service = TerminalService()
|
||||
|
||||
# 一個正常 Pod,一個有問題的 Pod
|
||||
mock_pod_ok = MagicMock()
|
||||
mock_pod_ok.metadata.name = "awoooi-api-ok"
|
||||
mock_pod_ok.status.phase = "Running"
|
||||
mock_pod_ok.status.container_statuses = [MagicMock(ready=True)]
|
||||
|
||||
mock_pod_bad = MagicMock()
|
||||
mock_pod_bad.metadata.name = "awoooi-worker-bad"
|
||||
mock_pod_bad.status.phase = "Pending"
|
||||
mock_pod_bad.status.container_statuses = []
|
||||
|
||||
mock_pods_list = MagicMock()
|
||||
mock_pods_list.items = [mock_pod_ok, mock_pod_bad]
|
||||
|
||||
mock_deployments_list = MagicMock()
|
||||
mock_deployments_list.items = []
|
||||
|
||||
mock_v1 = MagicMock()
|
||||
mock_v1.list_namespaced_pod = AsyncMock(return_value=mock_pods_list)
|
||||
|
||||
mock_apps_v1 = MagicMock()
|
||||
mock_apps_v1.list_namespaced_deployment = AsyncMock(
|
||||
return_value=mock_deployments_list
|
||||
)
|
||||
|
||||
mock_client = MagicMock()
|
||||
mock_client.CoreV1Api.return_value = mock_v1
|
||||
mock_client.AppsV1Api.return_value = mock_apps_v1
|
||||
# Mock K8sRepository
|
||||
mock_k8s_repo = MagicMock()
|
||||
mock_k8s_repo.is_available = AsyncMock(return_value=True)
|
||||
mock_k8s_repo.get_pod_status_summary = AsyncMock(return_value={
|
||||
"total": 2,
|
||||
"running": 1,
|
||||
"pending": 1,
|
||||
"failed": 0,
|
||||
"problem_pods": [
|
||||
{"name": "awoooi-worker-bad", "phase": "Pending", "ready": False, "restarts": 0}
|
||||
],
|
||||
})
|
||||
mock_k8s_repo.list_deployments = AsyncMock(return_value=[])
|
||||
|
||||
with patch(
|
||||
"src.services.k8s_diagnostics._get_k8s_client",
|
||||
new=AsyncMock(return_value=mock_client),
|
||||
"src.services.terminal_service.get_k8s_repository",
|
||||
return_value=mock_k8s_repo,
|
||||
):
|
||||
await service._handle_status_query(
|
||||
mock_publisher,
|
||||
@@ -454,12 +426,16 @@ class TestHandleStatusQuery:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_status_query_k8s_unavailable(self, mock_publisher):
|
||||
"""測試 K8s 連線失敗時的處理"""
|
||||
"""測試 K8s 連線失敗時的處理 (P1: 使用 K8sRepository)"""
|
||||
service = TerminalService()
|
||||
|
||||
# Mock K8sRepository - 不可用
|
||||
mock_k8s_repo = MagicMock()
|
||||
mock_k8s_repo.is_available = AsyncMock(return_value=False)
|
||||
|
||||
with patch(
|
||||
"src.services.k8s_diagnostics._get_k8s_client",
|
||||
new=AsyncMock(return_value=None),
|
||||
"src.services.terminal_service.get_k8s_repository",
|
||||
return_value=mock_k8s_repo,
|
||||
):
|
||||
# 不應拋出例外
|
||||
await service._handle_status_query(
|
||||
|
||||
Reference in New Issue
Block a user