Files
mizan/backends/mizan-fastapi/tests/test_ssr.py
Ryth Azhur 6c5f6f1fba AFI parity: close all 35 gaps — every adapter wires every AFI-common capability
The conformance board (tests/afi/test_capability_parity.py) is now fully green:
90 capability cells + 4 meta-locks + 3 codegen byte-parity = 97 passed. The
gaps the prose table used to launder as "Django-only" / "out of scope" are
wired, against the pinned-spec model (single-authored spec, byte-identical
conformance across languages) — never per-language reimplementation.

FastAPI — edge_manifest + PSR (logic single-sourced in mizan_core.manifest),
WebSocket RPC (/ws/ through the shared dispatch), SSR (the framework-agnostic
SSRBridge relocated to mizan_core.ssr; Django rides it from there), Shapes
(SQLAlchemy projection, same declaration surface as django-readers), Forms
(Pydantic schema/validate/submit).

Rust (Axum + Tauri + cores/mizan-rust) — X-Mizan-Invalidate header, auth=
enforcement, origin HMAC cache, edge manifest + PSR, WebSocket handler / IPC
subscription channel, multipart upload, SSR bridge, Shapes, Forms; JWT/MWT
mint+verify and cache-key derivation byte-pinned to the Python reference
(cache_keys_pin, token_pin, invalidate_header_pin).

TypeScript — a KDL IR emitter byte-identical to the Python build_ir (so a TS
backend can feed the codegen — the largest gap), multipart upload, session-init,
WebSocket transport, SSR bridge, JWT/MWT mint (pinned to Python), Shapes, Forms.

Verified in the merged tree: core 25, fastapi 74, django 353/21-skip,
mizan-rust (incl. cross-language pins) green, axum 10, tauri 8, mizan-ts 103/2-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 13:44:35 -04:00

139 lines
4.6 KiB
Python

"""
SSR behavior — the genuine capability behind the `ssr_bridge` probe.
The SSR subprocess lifecycle + JSON-RPC protocol live in the shared
`mizan_core.ssr.SSRBridge`; the FastAPI `SSRRenderer` resolves a component path
against `dirs`, drives the bridge, and wraps the result with the hydration script
the client reads on mount.
Bun is not assumed present in CI, so the bridge is driven against a stand-in
worker that speaks the SAME newline-delimited JSON-RPC protocol (ready signal +
`render` → `{id, html}`). That exercises the real bridge code path (spawn,
message-ID correlation, threaded reader) — only the renderer binary is swapped.
The path-resolution and hydration-wrapping are tested directly.
"""
from __future__ import annotations
import sys
import textwrap
import pytest
from mizan_core.ssr import SSRBridge
from mizan_fastapi.ssr import SSRRenderer
# A Python stand-in for the Bun worker: emits the ready signal, then for each
# render request echoes a deterministic HTML fragment built from the props.
_FAKE_WORKER = textwrap.dedent(
"""
import json, sys
sys.stdout.write(json.dumps({"id": 0, "ready": True}) + "\\n"); sys.stdout.flush()
for line in sys.stdin:
line = line.strip()
if not line:
continue
msg = json.loads(line)
if msg.get("method") == "render":
props = msg["params"]["props"]
html = "<p>" + props.get("name", "") + "</p>"
sys.stdout.write(json.dumps({"id": msg["id"], "html": html}) + "\\n")
sys.stdout.flush()
"""
)
@pytest.fixture
def fake_worker(tmp_path):
worker = tmp_path / "fake_worker.py"
worker.write_text(_FAKE_WORKER, encoding="utf-8")
return str(worker)
@pytest.fixture
def python_bridge(fake_worker, monkeypatch):
"""An `SSRBridge` whose subprocess is python (not bun), driving the fake worker."""
import subprocess
real_popen = subprocess.Popen
def fake_popen(cmd, *args, **kwargs):
# Swap the `bun run <worker>` invocation for `python <worker>`.
if cmd[:2] == ["bun", "run"]:
cmd = [sys.executable, cmd[2]]
return real_popen(cmd, *args, **kwargs)
monkeypatch.setattr(subprocess, "Popen", fake_popen)
bridge = SSRBridge(worker_path=fake_worker, timeout=5.0)
yield bridge
bridge.shutdown()
def test_bridge_round_trips_render(python_bridge):
result = python_bridge.render("/abs/Hello.tsx", {"name": "World"})
assert result.html == "<p>World</p>"
def test_bridge_correlates_concurrent_renders(python_bridge):
# Two renders on the persistent subprocess return their own results.
a = python_bridge.render("/abs/A.tsx", {"name": "A"})
b = python_bridge.render("/abs/B.tsx", {"name": "B"})
assert (a.html, b.html) == ("<p>A</p>", "<p>B</p>")
def test_renderer_resolves_against_dirs_and_wraps_hydration(fake_worker, monkeypatch, tmp_path):
import subprocess
real_popen = subprocess.Popen
monkeypatch.setattr(
subprocess, "Popen",
lambda cmd, *a, **k: real_popen([sys.executable, cmd[2]] if cmd[:2] == ["bun", "run"] else cmd, *a, **k),
)
components = tmp_path / "frontend"
components.mkdir()
(components / "Hello.tsx").write_text("export default () => null", encoding="utf-8")
renderer = SSRRenderer(worker=fake_worker, dirs=[str(components)])
try:
html = renderer.render_to_string("Hello.tsx", {"name": "Mizan"})
finally:
renderer.shutdown()
assert '<div id="mizan-root"><p>Mizan</p></div>' in html
assert 'window.__MIZAN_SSR_DATA__={"name": "Mizan"}' in html
def test_renderer_returns_html_response(fake_worker, monkeypatch, tmp_path):
import subprocess
from fastapi.responses import HTMLResponse
real_popen = subprocess.Popen
monkeypatch.setattr(
subprocess, "Popen",
lambda cmd, *a, **k: real_popen([sys.executable, cmd[2]] if cmd[:2] == ["bun", "run"] else cmd, *a, **k),
)
components = tmp_path / "frontend"
components.mkdir()
(components / "Card.tsx").write_text("export default () => null", encoding="utf-8")
renderer = SSRRenderer(worker=fake_worker, dirs=[str(components)])
try:
response = renderer.render("Card.tsx", {"name": "x"})
finally:
renderer.shutdown()
assert isinstance(response, HTMLResponse)
assert response.status_code == 200
def test_renderer_raises_on_missing_component(fake_worker, tmp_path):
renderer = SSRRenderer(worker=fake_worker, dirs=[str(tmp_path)])
try:
with pytest.raises(FileNotFoundError):
renderer.render_to_string("Nope.tsx", {})
finally:
renderer.shutdown()