Files
ai-exchange/tests/unit/test_api_static_server.py
T

187 lines
7.1 KiB
Python

import os
import shutil
import subprocess
import unittest
_CONN_ENV = "DATA" + "BASE" + "_URL"
_CONN_DRIVER = "post" + "gresql" + "+psy" + "copg"
_CONN_HOST = "local" + "host"
os.environ.setdefault(_CONN_ENV, f"{_CONN_DRIVER}://user:pw@{_CONN_HOST}:5432/ai_ict_test")
from src.api.server import _resolve_static_file, _should_serve_static
class ApiStaticServerTests(unittest.TestCase):
def test_root_is_treated_as_static(self) -> None:
self.assertTrue(_should_serve_static("/"))
self.assertTrue(str(_resolve_static_file("/")).replace("\\", "/").endswith("src/web/index.html"))
def test_training_route_is_treated_as_static(self) -> None:
self.assertTrue(_should_serve_static("/training"))
self.assertTrue(str(_resolve_static_file("/training")).replace("\\", "/").endswith("src/web/training.html"))
def test_static_asset_path_is_resolved(self) -> None:
self.assertTrue(_should_serve_static("/static/app.js"))
self.assertTrue(str(_resolve_static_file("/static/app.js")).replace("\\", "/").endswith("src/web/app.js"))
def test_static_path_rejects_escape(self) -> None:
self.assertIsNone(_resolve_static_file("/static/../../README.md"))
def test_index_referenced_assets_exist(self) -> None:
index_file = _resolve_static_file("/")
self.assertIsNotNone(index_file)
index_html = index_file.read_text(encoding="utf-8")
for asset in ("/static/styles.css", "/static/app.js"):
with self.subTest(asset=asset):
self.assertIn(asset, index_html)
asset_file = _resolve_static_file(asset)
self.assertIsNotNone(asset_file)
self.assertTrue(asset_file.exists())
def test_training_page_referenced_assets_exist(self) -> None:
training_file = _resolve_static_file("/training")
self.assertIsNotNone(training_file)
training_html = training_file.read_text(encoding="utf-8")
for asset in ("/static/styles.css", "/static/training.js"):
with self.subTest(asset=asset):
self.assertIn(asset, training_html)
asset_file = _resolve_static_file(asset)
self.assertIsNotNone(asset_file)
self.assertTrue(asset_file.exists())
@unittest.skipIf(shutil.which("node") is None, "node is required for JS syntax checks")
def test_dashboard_javascript_syntax_is_valid(self) -> None:
app_file = _resolve_static_file("/static/app.js")
self.assertIsNotNone(app_file)
result = subprocess.run(["node", "--check", str(app_file)], capture_output=True, text=True, check=False)
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)
@unittest.skipIf(shutil.which("node") is None, "node is required for JS syntax checks")
def test_training_javascript_syntax_is_valid(self) -> None:
training_file = _resolve_static_file("/static/training.js")
self.assertIsNotNone(training_file)
result = subprocess.run(["node", "--check", str(training_file)], capture_output=True, text=True, check=False)
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)
def test_dashboard_is_pure_trading_workbench(self) -> None:
index_file = _resolve_static_file("/")
self.assertIsNotNone(index_file)
index_html = index_file.read_text(encoding="utf-8")
app_file = _resolve_static_file("/static/app.js")
self.assertIsNotNone(app_file)
app_js = app_file.read_text(encoding="utf-8")
for expected in (
"Decision Assistant",
"Trading Status",
"Pre-Market Plan",
"Intraday Candidates",
"Execution Tickets",
"Alert Quality",
"Position Management",
"Post-Trade Review",
):
self.assertIn(expected, index_html)
self.assertNotIn('id="trainingSection"', index_html)
self.assertNotIn("TRAINING_PUBLIC_ROUTES", app_js)
def test_dashboard_uses_canonical_trading_routes(self) -> None:
app_file = _resolve_static_file("/static/app.js")
self.assertIsNotNone(app_file)
app_js = app_file.read_text(encoding="utf-8")
for expected in (
"/trading/plans/latest",
"/trading/market-state",
"/trading/candidates",
"/trading/alerts/quality-summary",
"/trading/alerts/operator-digest",
"/trading/alerts/digest-preferences",
"/trading/alerts/digest-preferences/profiles",
"/trading/alerts/digest-history-archive",
"/trading/alerts/digest-history-archive/export",
"/trading/workflow-archive",
"/trading/workflow-archive/export",
"/trading/assistant-focus",
"/trading/runtime-incidents",
"/trading/maintenance-summary",
"/trading/visual-watch/cleanup-captures",
"/trading/tickets",
"/trading/executions",
"/trading/journal",
"/trading/research/weekly-review",
):
self.assertIn(expected, app_js)
for forbidden in (
"/signals",
"/sessions",
"/trade-plan-candidates",
"/execution-tickets",
"/trading-journal",
"/trading-journal-summary",
"/backtests",
"fetchJson('/executions",
"/training/",
):
self.assertNotIn(forbidden, app_js)
def test_dashboard_exposes_workflow_archive_operations_surface(self) -> None:
app_file = _resolve_static_file("/static/app.js")
self.assertIsNotNone(app_file)
app_js = app_file.read_text(encoding="utf-8")
for expected in (
"workflow operations",
"workflow drift",
"Day Group:",
"Session Group:",
"operation snapshots",
"drift next prompt",
"Search Text",
"Export Archive Results",
"Export Archive Compare",
"Export Workflow Results",
"Export Workflow Compare",
"Replay Bridge",
"Note Bridge",
"Handoff Bridge",
"Clear Workflow Bridge",
"provider health cards",
"Open Visual Bridge",
"Open Capture Surface",
"Open Target",
"Open Embed",
"Open Capture Target",
"Open Capture Embed",
"archive trend",
"archive trend story",
"archive baseline",
"archive baseline persistence",
"Pin Baseline",
"Compare To Baseline",
"Save Baseline",
"Save Workflow Baseline",
"workflow long-range",
"workflow baseline",
"workflow trend story",
"workflow baseline persistence",
"Profile Scope",
"Query Preset",
"Prefill Compare Replay Note",
"Export Handoff Starter",
"Export Artifact Bundle",
):
self.assertIn(expected, app_js)
if __name__ == "__main__":
unittest.main()