hermes-hub/tests/test_web_parity_a21.py

158 lines
5.9 KiB
Python

"""
Hermes Hub — Task A21 Web Parity Test Suite
Verifies endpoints /api/events, /api/settings, security, and full 9-view parity.
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from antigravity_provider.router.web.server import app, sanitize_snapshot
from antigravity_provider.router.unified_health import EventLogService
from antigravity_provider import paths
REPO_ROOT = Path(__file__).resolve().parent.parent
STATIC_DIR = REPO_ROOT / "src" / "antigravity_provider" / "router" / "web" / "static"
@pytest.fixture
def client():
return TestClient(app)
def test_api_events_endpoint_and_security(client):
"""Verify GET /api/events returns reverse-chronological sanitized events list."""
event_svc = EventLogService.get()
event_svc.log("system", "Test event 1 — normal log", level="info")
event_svc.log("account", "Test event 2 with bearer token access_token=secret_12345", level="warning")
event_svc.log("quota", "Test event 3 — quota exhausted", level="error")
res = client.get("/api/events?limit=10")
assert res.status_code == 200
data = res.json()
assert "events" in data
events = data["events"]
assert len(events) >= 3
# Check reverse chronological order (latest event first)
assert events[0]["message"] == "Test event 3 — quota exhausted"
assert events[0]["level"] == "error"
assert events[0]["category"] == "quota"
# Verify secret sanitization on event payload
raw_str = json.dumps(data)
assert "secret_12345" not in raw_str
def test_api_events_category_filter(client):
"""Verify category filtering in GET /api/events."""
event_svc = EventLogService.get()
event_svc.log("routing", "Route failover test event", level="info")
res = client.get("/api/events?category=routing&limit=50")
assert res.status_code == 200
data = res.json()
assert "events" in data
for ev in data["events"]:
assert ev["category"] == "routing"
def test_api_settings_endpoint_no_raw_tokens(client, tmp_path, monkeypatch):
"""Verify GET /api/settings never exposes raw web_api_token, returns paths and boolean configured flag."""
settings_file = paths.get_hermes_home() / "hub_settings.json"
orig_content = settings_file.read_text(encoding="utf-8") if settings_file.exists() else "{}"
try:
# Write test settings with a secret token
settings_file.write_text(json.dumps({
"web_api_host": "127.0.0.1",
"web_api_port": 5800,
"web_api_token": "super_secret_hub_token_xyz",
"theme": "dark",
"quota_refresh_interval_sec": 120
}), encoding="utf-8")
res = client.get("/api/settings")
assert res.status_code == 200
data = res.json()
# Token must NEVER be exposed
assert "web_api_token" not in data
assert "super_secret_hub_token_xyz" not in json.dumps(data)
# Configured flag must be true
assert data.get("web_api_token_configured") is True
assert data.get("web_api_host") == "127.0.0.1"
assert data.get("web_api_port") == 5800
assert data.get("theme") == "dark"
assert data.get("quota_refresh_interval_sec") == 120
assert "hermes_home" in data
assert "config_dir" in data
assert "log_file" in data
finally:
if orig_content != "{}":
settings_file.write_text(orig_content, encoding="utf-8")
elif settings_file.exists():
settings_file.unlink()
def test_web_client_html_and_js_7_views_parity():
"""Verify index.html and app.js implement the 7 primary views and remove redundant views."""
index_html = (STATIC_DIR / "index.html").read_text(encoding="utf-8")
app_js = (STATIC_DIR / "app.js").read_text(encoding="utf-8")
expected_views = [
"overview", "accounts", "routing", "analytics",
"health", "logs", "settings"
]
for v in expected_views:
# Nav item exists
assert f'data-view="{v}"' in index_html, f"Missing nav button for view: {v}"
# Section container exists
assert f'id="view-{v}"' in index_html, f"Missing section #view-{v} in index.html"
# Redundant views must NOT exist in nav menu
assert 'data-view="providers"' not in index_html, "View 'providers' must be removed from web navigation"
assert 'data-view="team"' not in index_html, "View 'team' must be removed from web navigation"
assert "renderProvidersView" not in app_js, "renderProvidersView must be removed from app.js"
assert "renderTeamView" not in app_js, "renderTeamView must be removed from app.js"
# Routing view elements
assert "renderRoutingView" in app_js
assert "routing-roles-container" in index_html or "routing-pipelines-container" in index_html
assert "setupDragAndDrop" in app_js or "handleNodeDragStart" in app_js
# Overview view elements
assert "renderOverviewView" in app_js
assert "workflow-canvas" in index_html
# Analytics view elements
assert "analytics-total-calls" in index_html
assert "analytics-error-rate" in index_html
assert "analytics-latency-p50" in index_html
assert "analytics-tokens-total" in index_html
assert "renderAnalyticsView" in app_js
# Health view elements
assert "health-readiness-banner" in index_html
assert "health-host-resources" in index_html
assert "health-warnings-list" in index_html
assert "renderHealthView" in app_js
# Logs view elements
assert "logs-filter-level" in index_html
assert "logs-filter-category" in index_html
assert "logs-search" in index_html
assert "renderLogsView" in app_js
# Settings view elements
assert "setting-server-host" in index_html
assert "setting-server-port" in index_html
assert "setting-theme" in index_html
assert "renderSettingsView" in app_js
assert "saveHubServerSettings" in app_js