orgstate / tests /test_dashboard_structure.py
Legal-i's picture
Initial OrgState deploy via Stage 150 free-tier stack
d2d1903 verified
"""
Tests for Stage 90 β€” customer dashboard structure.
This is mostly a static asset (React + Vite SPA). The Python
test suite verifies:
1. Every key file exists at the expected path.
2. package.json declares the expected runtime + dev deps.
3. vite.config.ts proxies /api/v1 to the backend (dev-loop
sanity β€” without this, sign-in doesn't work locally).
4. tsconfig.json has strict mode on.
5. The API client (api.ts) references all the routes the
dashboard pages depend on β€” drift between pages and the
client surface would silently break a page.
6. Each page file exists and is non-trivial.
7. The dashboard README has the right anchors (dev / build /
deploy / API URL).
Component-level tests live in JS land (future work via Vitest);
this file validates the contract that customer-facing code
expects the OrgState API to honor.
"""
import json
from pathlib import Path
import pytest
ROOT = Path(__file__).parent.parent
DASH = ROOT / "dashboard"
SRC = DASH / "src"
PAGES = SRC / "pages"
# =========================================================
# File layout
# =========================================================
def test_dashboard_dir_exists():
assert DASH.is_dir()
def test_root_config_files_present():
for name in ("package.json", "vite.config.ts", "tsconfig.json",
"tsconfig.node.json", "tailwind.config.js",
"postcss.config.js", "index.html", "README.md",
".gitignore"):
assert (DASH / name).exists(), f"dashboard/{name} missing"
def test_src_files_present():
for name in ("main.tsx", "App.tsx", "auth.ts", "api.ts",
"index.css"):
assert (SRC / name).exists(), f"dashboard/src/{name} missing"
def test_all_pages_present():
for page in ("Login.tsx", "Overview.tsx", "Runs.tsx",
"RunDetail.tsx", "Webhooks.tsx"):
path = PAGES / page
assert path.exists(), f"dashboard/src/pages/{page} missing"
# non-trivial β€” at least 30 lines
assert len(path.read_text().splitlines()) > 30, \
f"{page} too short β€” likely placeholder"
# =========================================================
# package.json
# =========================================================
def test_package_json_parses():
spec = json.loads((DASH / "package.json").read_text())
assert spec["name"] == "orgstate-dashboard"
assert spec["type"] == "module"
def test_package_json_has_expected_scripts():
spec = json.loads((DASH / "package.json").read_text())
for script in ("dev", "build", "preview", "typecheck"):
assert script in spec["scripts"], \
f"package.json missing script: {script}"
def test_package_json_has_react_deps():
spec = json.loads((DASH / "package.json").read_text())
for dep in ("react", "react-dom", "react-router-dom"):
assert dep in spec["dependencies"], \
f"missing runtime dep: {dep}"
def test_package_json_has_build_devdeps():
spec = json.loads((DASH / "package.json").read_text())
for dep in ("vite", "typescript", "@vitejs/plugin-react",
"tailwindcss"):
assert dep in spec["devDependencies"], \
f"missing dev dep: {dep}"
# =========================================================
# vite.config.ts
# =========================================================
def test_vite_config_has_dev_proxy():
"""Without /api/v1 β†’ :8080 proxy, `npm run dev` can't talk
to the local backend β€” sign-in fails with CORS."""
text = (DASH / "vite.config.ts").read_text()
assert "/api/v1" in text
assert "8080" in text
assert "proxy" in text
def test_vite_config_uses_react_plugin():
text = (DASH / "vite.config.ts").read_text()
assert "@vitejs/plugin-react" in text
assert "react()" in text
# =========================================================
# tsconfig β€” strict + JSX
# =========================================================
def test_tsconfig_has_strict_mode():
spec = json.loads((DASH / "tsconfig.json").read_text())
assert spec["compilerOptions"]["strict"] is True
assert spec["compilerOptions"]["jsx"] == "react-jsx"
# =========================================================
# api.ts ↔ pages contract
# =========================================================
# Every method the pages call must exist in api.ts. Drift catches
# 'used a method that doesn't exist' BEFORE the page breaks at
# runtime.
def _api_text() -> str:
return (SRC / "api.ts").read_text()
def _all_pages_text() -> str:
return "\n".join(p.read_text() for p in PAGES.glob("*.tsx"))
def test_api_has_all_methods_pages_use():
api = _api_text()
pages = _all_pages_text()
# extract every `api.<method>(` call from pages
import re
used = set(re.findall(r"api\.(\w+)\(", pages))
for method in used:
assert f"{method}:" in api, \
f"page uses api.{method}() but api.ts doesn't export it"
def test_api_methods_have_known_routes():
"""Each API method's path should reference a route family
the backend actually exposes."""
api = _api_text()
# smoke check β€” sample expected substrings
for path in ("/tenants/", "/runs/", "/webhooks/", "/health",
"/usage"):
assert path in api, \
f"api.ts missing path family {path}"
# =========================================================
# README anchors
# =========================================================
def test_readme_has_install_dev_build_deploy_sections():
text = (DASH / "README.md").read_text()
for anchor in ("## Dev", "## Build", "## Deploying",
"VITE_API_BASE_URL"):
assert anchor in text, f"README missing anchor: {anchor}"
def test_readme_links_to_onboarding():
"""Customer reading the dashboard README should know where
their API key comes from (Stage 85's `infra onboard`)."""
text = (DASH / "README.md").read_text()
assert "infra onboard" in text
# =========================================================
# Auth + localStorage contract
# =========================================================
def test_auth_module_uses_localstorage():
"""Sanity β€” auth.ts must read/write localStorage, not
sessionStorage / cookies / etc. Stage 90 contract says
'sign-in persists across page refreshes'."""
text = (SRC / "auth.ts").read_text()
assert "localStorage" in text
assert "loadAuth" in text
assert "saveAuth" in text
assert "clearAuth" in text
# =========================================================
# Optional: npm build smoke (gated on Node availability)
# =========================================================
import shutil
import subprocess
@pytest.mark.skipif(
shutil.which("npm") is None,
reason="npm not installed; skipping JS build smoke",
)
def test_npm_install_and_build():
"""Heavy gate β€” runs `npm install` and `npm run build` to
confirm the dashboard actually builds. Gated on `npm`
availability so CI workers without Node still pass.
Skipped if node_modules absent AND we don't have time to
install (>30s on cold cache). We do `npm install --silent`
once, then `npm run build` β€” both must succeed."""
# install β€” usually 30-60s on cold cache, near-instant warm
r = subprocess.run(
["npm", "install", "--silent", "--no-audit",
"--no-fund", "--no-progress"],
cwd=str(DASH), capture_output=True, timeout=300,
)
assert r.returncode == 0, \
f"npm install failed:\n{r.stderr.decode()}"
# build β€” generous timeout for cold tsc + vite (warm: ~1s)
r = subprocess.run(
["npm", "run", "build"],
cwd=str(DASH), capture_output=True, timeout=300,
)
assert r.returncode == 0, (
f"npm run build failed:\nSTDOUT: {r.stdout.decode()}"
f"\nSTDERR: {r.stderr.decode()}"
)
# dist/ exists with an index.html
dist = DASH / "dist"
assert dist.is_dir()
assert (dist / "index.html").exists()