187 lines
7.1 KiB
Python
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()
|