test(p53): 觀測台 smoke 涵蓋 P38-P52 新增 11 endpoint (18/18 PASS)

戰役從 P27 6 路由擴展到 P52 共 20 路由(含 5 新 GET / 5 新 POST),
原 12 tests 只蓋 P27-31 範圍,P38-P52 共 11 endpoint 無 regression 防護。

新增測試:
- test_overview_index_200: /observability/ root index
- test_overview_dashboard_200: P45 總覽頁
- test_rag_queries_200: P51 RAG 召回詳情
- test_business_intel_200: P48 商業面 × AI 編排
- test_agent_orchestration_200: P46 Agent 編排矩陣
- test_health_indicator_api_returns_json: P52 topbar 健康燈 JSON API
- test_anon_get_redirects_to_login: 12 GET 路徑全強制 login (擴充 6→12)
- test_anon_post_blocked: 8 POST mutation 全強制 login (擴充 3→8)

prod 實證:mo.wooo.work 11 endpoint 全 Flask 200/308 服務(curl 已驗)。
20/20 routes @login_required 100% 覆蓋(python regex audit)。
This commit is contained in:
OoO
2026-05-05 01:09:52 +08:00
parent 2a3ea6f581
commit a142e85880

View File

@@ -201,22 +201,95 @@ def test_host_health_200(client, monkeypatch):
def test_anon_get_redirects_to_login(anon_client):
"""未登入打 GET 路由 → @login_required 必 302 redirect 到 /login。"""
for path in [
'/observability/',
'/observability/overview',
'/observability/rag_queries',
'/observability/business_intel',
'/observability/agent_orchestration',
'/observability/ai_calls',
'/observability/promotion_review',
'/observability/quality_trend',
'/observability/host_health',
'/observability/budget',
'/observability/ppt_audit_history',
'/observability/api/health_indicator',
]:
r = anon_client.get(path)
assert r.status_code == 302, f'{path} 未強制 login (got {r.status_code})'
# 308permanent redirect for trailing slash或 302login redirect皆視為阻擋
assert r.status_code in (302, 308), f'{path} 未強制 login (got {r.status_code})'
def test_anon_post_blocked(anon_client):
"""未登入 POST mutation 端點 → 必 302 redirect不可執行 promote/budget update)。"""
r = anon_client.post('/observability/promotion_review/approve/1')
assert r.status_code == 302
r = anon_client.post('/observability/promotion_review/reject/1')
assert r.status_code == 302
r = anon_client.post('/observability/budget/update/1', json={'budget_usd': 99, 'alert_pct': 80})
assert r.status_code == 302
"""未登入 POST mutation 端點 → 必 302 redirect防 anon 執行任何 mutation)。"""
posts = [
('/observability/promotion_review/approve/1', None),
('/observability/promotion_review/reject/1', None),
('/observability/budget/update/1', {'budget_usd': 99, 'alert_pct': 80}),
('/observability/ai_calls/trigger_code_review', None),
('/observability/ppt_audit/trigger_aider_heal', None),
('/observability/playbooks/toggle/1', None),
('/observability/host_health/trigger_autoheal', None),
('/observability/budget/force_throttle', None),
]
for path, body in posts:
r = anon_client.post(path, json=body) if body else anon_client.post(path)
assert r.status_code in (302, 308), f'{path} POST 未強制 login (got {r.status_code})'
# ──────────────────────────────────────────────────────────────────────────
# Phase 38+ 新增 GET 路由 smoke
# ──────────────────────────────────────────────────────────────────────────
def test_overview_index_200(client, monkeypatch):
"""/observability/ (root index) — 觀測台總覽。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
r = client.get('/observability/')
assert r.status_code in (200, 308)
def test_overview_dashboard_200(client, monkeypatch):
"""/observability/overview — Phase 45 總覽頁。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
# mock requests for 三主機 sparkline
import requests as _r
def fake_get(*_a, **_kw):
raise _r.exceptions.ConnectionError('mocked')
monkeypatch.setattr(_r, 'get', fake_get)
r = client.get('/observability/overview')
assert r.status_code == 200
def test_rag_queries_200(client, monkeypatch):
"""/observability/rag_queries — Phase 51 RAG 召回詳情頁。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
r = client.get('/observability/rag_queries')
assert r.status_code == 200
def test_business_intel_200(client, monkeypatch):
"""/observability/business_intel — Phase 48 商業面 × AI 編排。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
r = client.get('/observability/business_intel')
assert r.status_code == 200
def test_agent_orchestration_200(client, monkeypatch):
"""/observability/agent_orchestration — Phase 46 Agent 編排矩陣。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
r = client.get('/observability/agent_orchestration')
assert r.status_code == 200
def test_health_indicator_api_returns_json(client, monkeypatch):
"""/observability/api/health_indicator — Phase 52 topbar 健康指示燈 JSON API。"""
from routes import admin_observability_routes as mod
monkeypatch.setattr(mod, 'get_session', lambda: _fake_session([]))
r = client.get('/observability/api/health_indicator')
assert r.status_code == 200
assert r.content_type.startswith('application/json'), \
f'expected JSON, got {r.content_type}'