Files
agentic-mobile-control/tests/test_runtime_console_packaging.py
T
q792602257andClaude Opus 4.6 e00c50e703 feat(api): server-rendered Jinja2 Runtime console at /ui/
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>
2026-07-15 08:03:13 +08:00

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()