Replaces the separate Vue/Vite `console/` SPA with a same-origin, server-rendered console built on a module-level Jinja2 Environment with select_autoescape(["html","xml"]). - Add api/console_web.py with /ui/ routes (dashboard, tasks, task detail/timeline, config) and a _status_fragment polled every 10s. - Refactor api/console.py into a typed ConsoleService shared by the JSON and HTML routers so validation/persistence cannot drift. - Remove RUNTIME_CONSOLE_STATIC_DIR, SpaStaticFiles, and the wildcard CORS middleware from api/rest.py; GET / now redirects to /ui/. - Delete the top-level console/ project; add jinja2 and python-multipart as direct dependencies and ship templates/CSS/JS via package-data. - Add 31 tests (XSS probes, PRG flows, fragment refresh, no-static-dir and no-CORS regressions, wheel-packaging smoke test). /console/* JSON endpoints remain unchanged. The console keeps the trusted-network-only boundary; auth/CSRF is intentionally deferred. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""Smoke test that console templates and assets ship inside the Runtime wheel.
|
|
|
|
Builds ``device-agent-runtime`` into a temporary directory, installs it into an
|
|
isolated venv that cannot reach the source checkout, and asserts the packaged
|
|
``api`` package carries the Jinja2 templates and static assets needed by
|
|
``/ui/``. This guards against setuptools package-data regressions that would
|
|
let the console work from an editable checkout but break from a real install.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import venv
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def _run(cmd: list[str], *, cwd: Path | None = None) -> str:
|
|
return subprocess.check_output(
|
|
cmd,
|
|
cwd=cwd,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_runtime_wheel_packages_console_templates_and_assets(tmp_path: Path) -> None:
|
|
repo_root = Path(__file__).resolve().parent.parent
|
|
|
|
wheel_dir = tmp_path / "wheels"
|
|
wheel_dir.mkdir()
|
|
_run(
|
|
["uv", "build", "--package", "device-agent-runtime", "--wheel", "--no-sources"],
|
|
cwd=repo_root,
|
|
)
|
|
wheels = list(repo_root.glob("dist/*.whl"))
|
|
assert wheels, "uv build did not produce a wheel"
|
|
wheel_path = wheels[0]
|
|
|
|
venv_dir = tmp_path / "venv"
|
|
venv.create(venv_dir, with_pip=True, clear=True)
|
|
pip = str(venv_dir / "Scripts" / "pip.exe")
|
|
if not Path(pip).exists():
|
|
pip = str(venv_dir / "bin" / "pip")
|
|
_run([pip, "install", str(wheel_path)], cwd=tmp_path)
|
|
|
|
python = str(venv_dir / "Scripts" / "python.exe")
|
|
if not Path(python).exists():
|
|
python = str(venv_dir / "bin" / "python")
|
|
|
|
probe = _run(
|
|
[
|
|
python,
|
|
"-c",
|
|
(
|
|
"from importlib.resources import files; "
|
|
"api_root = files('api'); "
|
|
"templates = sorted(p.name for p in "
|
|
"(api_root / 'templates' / 'runtime_console').iterdir()); "
|
|
"assets = sorted(p.name for p in "
|
|
"(api_root / 'static' / 'runtime_console').iterdir()); "
|
|
"print(','.join(templates)); "
|
|
"print(','.join(assets))"
|
|
),
|
|
],
|
|
cwd=tmp_path,
|
|
)
|
|
template_names, asset_names = probe.strip().splitlines()
|
|
assert "base.html" in template_names
|
|
assert "dashboard.html" in template_names
|
|
assert "config.html" in template_names
|
|
assert "console.css" in asset_names
|
|
assert "dashboard.js" in asset_names
|
|
|
|
# Clean up the build artifact so it does not leak into the working tree.
|
|
for wheel in wheels:
|
|
wheel.unlink()
|