refactor(api): Phase B P1 可靠性強化 (2 項)
Some checks failed
E2E Health Check / e2e-health (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

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:
OG T
2026-03-30 01:52:59 +08:00
parent 64e51bb4fd
commit 13bb1496b0
5 changed files with 395 additions and 116 deletions

View File

@@ -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(