Phase 6.4g (API 突觸對接):
- lewooogo-brain dependency binding in apps/api/pyproject.toml
- POST /api/v1/incidents/{id}/propose route (proposals.py)
- Guardrails integration (8/8 tests passed)
Phase 6.5a (視覺皮層建置):
- DualStateIncidentCard.tsx with Nothing.tech visual compliance
- Ping radar animation for alert state
- Tier-based decision layer UI (AI 執行中 / 等待親核)
Phase 6.5b (神經網路串接):
- Main warroom page integration (page.tsx)
- IncidentResponse → DualState mapper function
- Empty state: "系統穩定。0 活躍異常。"
Tests:
- test_guardrails.py (8/8)
- test_incident_engine.py (6/6)
- test_skill_loader.py (6/6)
- Frontend build: 0 errors
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
210 lines
6.6 KiB
Python
210 lines
6.6 KiB
Python
"""
|
||
Guardrails 單元測試
|
||
====================
|
||
|
||
Phase 6.4e 驗證點 3:
|
||
確認 GuardrailsValidator 能正確攔截危險指令
|
||
"""
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# 添加 src 到 Python Path
|
||
src_path = Path(__file__).parent.parent / "src"
|
||
sys.path.insert(0, str(src_path))
|
||
|
||
|
||
def test_guardrails_validator_import():
|
||
"""測試:能正確 import GuardrailsValidator"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
print("✅ GuardrailsValidator import 成功")
|
||
|
||
|
||
def test_guardrails_block_rm_rf():
|
||
"""測試:攔截 rm -rf 指令"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
dangerous_scripts = [
|
||
"rm -rf /",
|
||
"rm -rf /*",
|
||
"sudo rm -rf /home",
|
||
"RM -RF /var/log", # 大小寫
|
||
]
|
||
|
||
for script in dangerous_scripts:
|
||
is_safe, violation = GuardrailsValidator.validate_script(script)
|
||
assert not is_safe, f"Should block: {script}"
|
||
assert violation is not None
|
||
print(f"✅ 攔截: {script[:30]}...")
|
||
|
||
|
||
def test_guardrails_block_drop_database():
|
||
"""測試:攔截 DROP DATABASE 指令"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
scripts = [
|
||
"DROP DATABASE awoooi_prod",
|
||
"drop table users",
|
||
"TRUNCATE incidents",
|
||
]
|
||
|
||
for script in scripts:
|
||
is_safe, violation = GuardrailsValidator.validate_script(script)
|
||
assert not is_safe, f"Should block: {script}"
|
||
print(f"✅ 攔截: {script}")
|
||
|
||
|
||
def test_guardrails_block_kubectl_delete_ns():
|
||
"""測試:攔截 kubectl delete namespace 指令"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
scripts = [
|
||
"kubectl delete namespace awoooi-prod",
|
||
"kubectl delete ns kube-system",
|
||
"kubectl delete -A pods",
|
||
]
|
||
|
||
for script in scripts:
|
||
is_safe, violation = GuardrailsValidator.validate_script(script)
|
||
assert not is_safe, f"Should block: {script}"
|
||
print(f"✅ 攔截: {script}")
|
||
|
||
|
||
def test_guardrails_allow_safe_commands():
|
||
"""測試:允許安全指令"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
safe_scripts = [
|
||
"kubectl get pods -n awoooi-prod",
|
||
"kubectl rollout restart deployment/awoooi-api -n awoooi-prod",
|
||
"kubectl describe pod abc -n awoooi-prod",
|
||
"kubectl logs -f deployment/awoooi-api -n awoooi-prod",
|
||
]
|
||
|
||
for script in safe_scripts:
|
||
is_safe, violation = GuardrailsValidator.validate_script(script)
|
||
assert is_safe, f"Should allow: {script}, violation: {violation}"
|
||
print(f"✅ 允許: {script[:50]}...")
|
||
|
||
|
||
def test_guardrails_namespace_validation():
|
||
"""測試:Namespace 白名單驗證"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
# 允許的 namespace
|
||
is_ok, _ = GuardrailsValidator.validate_namespace("awoooi-prod")
|
||
assert is_ok, "awoooi-prod should be allowed"
|
||
print("✅ awoooi-prod 允許")
|
||
|
||
is_ok, _ = GuardrailsValidator.validate_namespace("awoooi-dev")
|
||
assert is_ok, "awoooi-dev should be allowed"
|
||
print("✅ awoooi-dev 允許")
|
||
|
||
# 禁止的 namespace
|
||
forbidden = ["kube-system", "kube-public", "default"]
|
||
for ns in forbidden:
|
||
is_ok, violation = GuardrailsValidator.validate_namespace(ns)
|
||
assert not is_ok, f"{ns} should be forbidden"
|
||
print(f"✅ {ns} 禁止: {violation}")
|
||
|
||
|
||
def test_guardrails_enforce_dry_run():
|
||
"""測試:強制 dry-run 標記"""
|
||
from lewooogo_brain.engines.proposal_engine import GuardrailsValidator
|
||
|
||
proposal = {
|
||
"action": "kubectl apply -f config.yaml",
|
||
"guardrails": {},
|
||
}
|
||
|
||
result = GuardrailsValidator.enforce_dry_run(proposal)
|
||
|
||
assert result["guardrails"]["require_dry_run"] == True
|
||
assert "awoooi-prod" in result["guardrails"]["allowed_namespace"]
|
||
print("✅ 強制 dry-run 設定成功")
|
||
print(f" - require_dry_run: {result['guardrails']['require_dry_run']}")
|
||
print(f" - allowed_namespace: {result['guardrails']['allowed_namespace']}")
|
||
|
||
|
||
def test_proposal_engine_guardrails_integration():
|
||
"""測試:ProposalEngine 整合 Guardrails"""
|
||
import asyncio
|
||
from lewooogo_brain.engines.proposal_engine import ProposalEngine
|
||
from lewooogo_brain.interfaces.proposal_engine import Proposal
|
||
|
||
class MockMemory:
|
||
async def load_incident(self, incident_id):
|
||
from lewooogo_brain.interfaces.incident_processor import (
|
||
Incident, IncidentStatus, Severity, Signal
|
||
)
|
||
from datetime import datetime, timezone
|
||
|
||
return Incident(
|
||
incident_id=incident_id,
|
||
status=IncidentStatus.INVESTIGATING,
|
||
severity=Severity.P1,
|
||
signals=[Signal(
|
||
alert_name="TestAlert",
|
||
severity=Severity.P1,
|
||
source="test",
|
||
fired_at=datetime.now(timezone.utc),
|
||
)],
|
||
affected_services=["test-service"],
|
||
)
|
||
|
||
async def update_incident(self, incident_id, updates):
|
||
return True
|
||
|
||
engine = ProposalEngine(memory=MockMemory())
|
||
|
||
# 取得預設 Guardrails
|
||
guardrails = engine.get_default_guardrails()
|
||
|
||
assert guardrails.require_dry_run == True
|
||
assert "awoooi-prod" in guardrails.allowed_namespace
|
||
assert any("rm -rf" in cmd.lower() for cmd in guardrails.forbidden_commands)
|
||
|
||
print("✅ ProposalEngine Guardrails 整合成功:")
|
||
print(f" - require_dry_run: {guardrails.require_dry_run}")
|
||
print(f" - allowed_namespace: {guardrails.allowed_namespace}")
|
||
print(f" - forbidden_commands: {len(guardrails.forbidden_commands)} 項")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
print("=" * 60)
|
||
print("🧪 Guardrails 單元測試")
|
||
print("=" * 60)
|
||
|
||
tests = [
|
||
test_guardrails_validator_import,
|
||
test_guardrails_block_rm_rf,
|
||
test_guardrails_block_drop_database,
|
||
test_guardrails_block_kubectl_delete_ns,
|
||
test_guardrails_allow_safe_commands,
|
||
test_guardrails_namespace_validation,
|
||
test_guardrails_enforce_dry_run,
|
||
test_proposal_engine_guardrails_integration,
|
||
]
|
||
|
||
passed = 0
|
||
failed = 0
|
||
|
||
for test in tests:
|
||
print(f"\n🔬 {test.__name__}")
|
||
try:
|
||
test()
|
||
passed += 1
|
||
except AssertionError as e:
|
||
print(f"❌ FAILED: {e}")
|
||
failed += 1
|
||
except Exception as e:
|
||
print(f"❌ ERROR: {type(e).__name__}: {e}")
|
||
failed += 1
|
||
|
||
print("\n" + "=" * 60)
|
||
print(f"📊 結果: {passed} 通過, {failed} 失敗")
|
||
print("=" * 60)
|
||
|
||
if failed > 0:
|
||
sys.exit(1)
|