Files
awoooi/apps/api/tests/test_approval_field_alignment.py
OG T d89f0520f9 fix(api): 修復 34 個 Ruff lint 錯誤
- 自動修復 import 排序、unused imports
- 手動修復 raise from、isinstance union、unused variable
- scripts/ 暫時保留 (非 CI 阻擋)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-29 15:27:49 +08:00

301 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
P1-3: ApprovalRequestCreate 欄位對齊測試
=========================================
驗證 SignOz + GitHub Webhook 的 ApprovalRequestCreate 符合 ApprovalRequestBase schema
測試策略 (遵循 feedback_no_mock_testing.md):
- 直接測試 Pydantic Model 驗證
- 使用真實 Schema 驗證欄位
- 不使用 Mock
版本: v1.0
建立: 2026-03-29 (台北時區)
建立者: Claude Code (P1-3 Unit Test)
"""
import pytest
from pydantic import ValidationError
from src.models.approval import (
ApprovalRequestCreate,
BlastRadius,
DataImpact,
RiskLevel,
)
# =============================================================================
# Test: ApprovalRequestCreate Schema 驗證
# =============================================================================
class TestApprovalRequestCreateSchema:
"""測試 ApprovalRequestCreate 必填欄位"""
def test_required_fields_present(self):
"""✅ 必填欄位: action, description, risk_level, requested_by"""
request = ApprovalRequestCreate(
action="Test Action",
description="Test Description",
risk_level=RiskLevel.MEDIUM,
requested_by="test-webhook",
)
assert request.action == "Test Action"
assert request.description == "Test Description"
assert request.risk_level == RiskLevel.MEDIUM
assert request.requested_by == "test-webhook"
def test_missing_action_raises_error(self):
"""❌ 缺少 action 應拋出 ValidationError"""
with pytest.raises(ValidationError) as exc_info:
ApprovalRequestCreate(
description="Test",
risk_level=RiskLevel.MEDIUM,
requested_by="test",
)
assert "action" in str(exc_info.value)
def test_missing_description_raises_error(self):
"""❌ 缺少 description 應拋出 ValidationError"""
with pytest.raises(ValidationError) as exc_info:
ApprovalRequestCreate(
action="Test",
risk_level=RiskLevel.MEDIUM,
requested_by="test",
)
assert "description" in str(exc_info.value)
def test_missing_requested_by_raises_error(self):
"""❌ 缺少 requested_by 應拋出 ValidationError"""
with pytest.raises(ValidationError) as exc_info:
ApprovalRequestCreate(
action="Test",
description="Test",
risk_level=RiskLevel.MEDIUM,
)
assert "requested_by" in str(exc_info.value)
# =============================================================================
# Test: BlastRadius Model 驗證
# =============================================================================
class TestBlastRadiusSchema:
"""測試 BlastRadius 是 Pydantic Model不是 Enum"""
def test_blast_radius_is_model(self):
"""✅ BlastRadius 是 BaseModel可接受參數"""
br = BlastRadius(
affected_pods=1,
estimated_downtime="0",
related_services=["api"],
data_impact=DataImpact.READ_ONLY,
)
assert br.affected_pods == 1
assert br.estimated_downtime == "0"
assert br.related_services == ["api"]
assert br.data_impact == DataImpact.READ_ONLY
def test_blast_radius_default_values(self):
"""✅ BlastRadius 有預設值"""
br = BlastRadius()
assert br.affected_pods == 0
assert br.estimated_downtime == "0"
assert br.related_services == []
assert br.data_impact == DataImpact.NONE
# =============================================================================
# Test: SignOz Webhook ApprovalRequestCreate 格式
# =============================================================================
class TestSignOzWebhookApproval:
"""測試 SignOz Webhook 的 ApprovalRequestCreate 格式"""
def test_signoz_approval_format(self):
"""✅ SignOz Webhook 應使用正確欄位格式"""
# 模擬 signoz_webhook.py 的建立方式
alert_name = "HighErrorRate"
service_name = "api-service"
description = "Error rate > 5%"
request = ApprovalRequestCreate(
action=f"SignOz Alert: {alert_name}",
description=description,
risk_level=RiskLevel.HIGH,
blast_radius=BlastRadius(
affected_pods=1,
estimated_downtime="0",
related_services=[service_name],
data_impact=DataImpact.READ_ONLY,
),
dry_run_checks=[],
requested_by="signoz-webhook",
metadata={
"source": "signoz",
"alert_name": alert_name,
"labels": {"service": service_name},
},
)
assert request.action == "SignOz Alert: HighErrorRate"
assert request.description == description
assert request.requested_by == "signoz-webhook"
assert request.blast_radius.related_services == [service_name]
assert request.metadata["source"] == "signoz"
# =============================================================================
# Test: GitHub Webhook ApprovalRequestCreate 格式
# =============================================================================
class TestGitHubWebhookApproval:
"""測試 GitHub Webhook 的 ApprovalRequestCreate 格式"""
def test_github_ci_failure_approval_format(self):
"""✅ GitHub CI Failure 應使用正確欄位格式"""
repo = "wooo-ai/awoooi"
root_cause = "Build failed due to missing dependency"
suggestion = "npm install missing-package"
request = ApprovalRequestCreate(
action=f"CI Failure Repair: {repo}",
description=f"Root Cause: {root_cause}\nSuggestion: {suggestion}",
risk_level=RiskLevel.MEDIUM,
blast_radius=BlastRadius(
affected_pods=1,
estimated_downtime="~5min",
related_services=[repo],
data_impact=DataImpact.NONE,
),
dry_run_checks=[],
requested_by="github-webhook",
metadata={
"source": "github",
"alert_type": "ci_failure_repair",
"target_resource": repo,
"namespace": "github-actions",
},
)
assert request.action == f"CI Failure Repair: {repo}"
assert "Root Cause:" in request.description
assert request.requested_by == "github-webhook"
assert request.metadata["source"] == "github"
assert request.metadata["alert_type"] == "ci_failure_repair"
def test_github_code_review_approval_format(self):
"""✅ GitHub Code Review 應使用正確欄位格式"""
repo = "wooo-ai/awoooi"
target = "src/main.py"
request = ApprovalRequestCreate(
action=f"Code Review Security: {repo}",
description=f"Root Cause: Code review found security concerns in {target}",
risk_level=RiskLevel.HIGH,
blast_radius=BlastRadius(
affected_pods=1,
estimated_downtime="0",
related_services=[repo],
data_impact=DataImpact.READ_ONLY,
),
dry_run_checks=[],
requested_by="github-webhook",
metadata={
"source": "github",
"alert_type": "code_review_security",
"target": target,
},
)
assert request.action == f"Code Review Security: {repo}"
assert request.requested_by == "github-webhook"
assert request.metadata["alert_type"] == "code_review_security"
# =============================================================================
# Test: Sentry Webhook ApprovalRequestCreate 格式 (參照用)
# =============================================================================
class TestSentryWebhookApproval:
"""測試 Sentry Webhook 的 ApprovalRequestCreate 格式 (已正確)"""
def test_sentry_approval_format(self):
"""✅ Sentry Webhook 格式正確 (參照用)"""
level = "error"
culprit = "api.views.handler"
project = "awoooi-api"
request = ApprovalRequestCreate(
action=f"Sentry {level.upper()} Alert: {culprit}",
description="TypeError: Cannot read property 'x' of undefined",
risk_level=RiskLevel.HIGH,
blast_radius=BlastRadius(
affected_pods=1,
estimated_downtime="0",
related_services=[project],
data_impact=DataImpact.READ_ONLY,
),
dry_run_checks=[],
requested_by="sentry-webhook",
metadata={
"source": "sentry",
"issue_id": "12345",
},
)
assert request.action == "Sentry ERROR Alert: api.views.handler"
assert request.requested_by == "sentry-webhook"
# =============================================================================
# Test: 錯誤欄位驗證 (Pydantic v2 預設忽略額外欄位)
# =============================================================================
class TestInvalidFieldsBehavior:
"""
測試舊格式 (錯誤欄位) 行為
注意: Pydantic v2 預設 extra='ignore',額外欄位不會拋出錯誤
但是缺少必填欄位仍會拋出 ValidationError
"""
def test_action_type_without_action_raises_error(self):
"""❌ 只有 action_type 而沒有 action 會失敗 (缺少必填欄位)"""
with pytest.raises(ValidationError) as exc_info:
ApprovalRequestCreate(
action_type="SignOz Alert", # 錯誤: 這不是有效欄位
description="Test",
risk_level=RiskLevel.MEDIUM,
requested_by="test",
# 缺少 action (必填)
)
assert "action" in str(exc_info.value)
def test_extra_fields_are_ignored(self):
"""✅ 額外欄位會被忽略 (Pydantic v2 預設行為)"""
# 這不會拋出錯誤,額外欄位會被忽略
request = ApprovalRequestCreate(
action="Test",
description="Test",
risk_level=RiskLevel.MEDIUM,
requested_by="test",
target_resource="api-service", # 額外欄位,會被忽略
context={"key": "value"}, # 額外欄位,會被忽略
)
# 驗證 request 物件沒有這些屬性
assert not hasattr(request, "target_resource")
assert not hasattr(request, "context")
# 但必填欄位存在
assert request.action == "Test"
assert request.requested_by == "test"
def test_metadata_is_valid_field(self):
"""✅ metadata 是有效欄位 (context 應改用 metadata)"""
request = ApprovalRequestCreate(
action="Test",
description="Test",
risk_level=RiskLevel.MEDIUM,
requested_by="test",
metadata={"key": "value"}, # 正確欄位
)
assert request.metadata == {"key": "value"}