Brain-5D-Space / tests /test_dashboard_single_instance.py
github-actions[bot]
Sync: publish Space API fix
5e0b58b
Raw History Blame Contribute Delete
10.9 kB
"""Single-instance binding regression tests for the Brain-5D dashboard.
These tests verify the P0 process-architecture contract from
``docs/TODO.md`` (section *v0.5.0-alpha.5 — Integration Hardening*):
* Browser and API requests always reach the same ``DashboardServer`` instance.
* ``/api/debug/bridge`` reports ``bridge_exists = true`` and
``controller_exists = true`` when a bridge is attached.
* ``/api/structural/status`` never reports the bridge as missing when the
startup path attached a bridge.
* Unknown ``/api/...`` paths return a JSON 404 and never fall through to
``index.html``.
* The bridge identity (``server_id``) remains stable across requests.
The dashboard server owns its ``OperatorBridge`` directly on the instance
(no module-global bridge state), so every HTTP request handled by that
server observes the same bridge object. These tests lock that invariant
in place.
"""
from __future__ import annotations
import json
from http.client import HTTPConnection
from threading import Thread
from typing import Any, cast
from src.dashboard.models import JSONValue
from src.dashboard.operator_bridge import OperatorBridge
from src.dashboard.server import DashboardServer
from src.dashboard.state import DashboardStateStore
from src.dashboard.structural_api import JSONMapping, StructuralCommandResult
class _StubController:
"""Minimal controller stub satisfying the bridge contract."""
def __init__(self) -> None:
self.label = "stub-controller"
class _Bridge:
"""Minimal bridge stub used to verify identity and routing.
Every method mirrors the surface used by the dashboard HTTP layer so
the test can exercise the real dispatch code paths.
"""
def __init__(self, controller: _StubController) -> None:
self.controller: Any = controller
self.coordinator = None
self.plasticity = None
self.approval_policy = None
self.structural_heatmaps = None
# -- structural / runtime surface ------------------------------------
def structural_status(self) -> JSONMapping:
return {"configured": True, "proposal_count": 0, "history_count": 0}
def structural_proposals(self) -> list[JSONMapping]:
return []
def structural_history(self, _limit: int) -> list[JSONMapping]:
return []
def structural_heatmap(self, kind: str) -> JSONMapping:
return {"kind": kind, "values": []}
def structural_config(self) -> JSONMapping:
return {"configured": True}
def approve_structural(self, _proposal_id: str) -> StructuralCommandResult:
return StructuralCommandResult(True, "approved")
def reject_structural(self, _proposal_id: str) -> StructuralCommandResult:
return StructuralCommandResult(True, "rejected")
def undo_structural(self) -> StructuralCommandResult:
return StructuralCommandResult(True, "undone")
def set_auto_approval(self, enabled: bool) -> StructuralCommandResult:
return StructuralCommandResult(True, str(enabled))
def run_ticks(self, count: int) -> StructuralCommandResult:
return StructuralCommandResult(True, str(count))
def single_step(self) -> StructuralCommandResult:
return StructuralCommandResult(True, "one")
def request_snapshot(self) -> StructuralCommandResult:
return StructuralCommandResult(True, "snapshot")
def command(self, command: str, **_kwargs: Any) -> dict[str, JSONValue]:
return {"ok": True, "status": command}
def update_structural_config(self, **_body: Any) -> StructuralCommandResult:
return StructuralCommandResult(True, "updated")
def _start_server(bridge: _Bridge | None) -> tuple[DashboardServer, Thread, str, int]:
server = DashboardServer(
("127.0.0.1", 0),
DashboardStateStore(),
None,
cast(OperatorBridge | None, bridge),
)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
host, port = server.server_address[:2]
if not isinstance(host, str):
raise AssertionError("dashboard server did not expose an IP address")
return server, thread, host, port
def _stop_server(server: DashboardServer, thread: Thread) -> None:
server.shutdown()
server.server_close()
thread.join(timeout=1.0)
def _get(host: str, port: int, path: str) -> tuple[int, dict[str, JSONValue] | str]:
connection = HTTPConnection(host, port)
try:
connection.request("GET", path)
response = connection.getresponse()
body = response.read()
content_type = response.getheader("Content-Type", "")
if "application/json" in content_type:
return response.status, json.loads(body)
return response.status, body.decode("utf-8", errors="replace")
finally:
connection.close()
def test_debug_bridge_reports_bridge_and_controller_present() -> None:
"""``/api/debug/bridge`` must confirm bridge + controller attachment."""
controller = _StubController()
bridge = _Bridge(controller)
server, thread, host, port = _start_server(bridge)
try:
status, payload = _get(host, port, "/api/debug/bridge")
assert status == 200
assert isinstance(payload, dict)
assert payload["bridge_exists"] is True
assert payload["controller_exists"] is True
assert payload["bridge_type"] is not None
assert payload["server_id"] == id(server)
finally:
_stop_server(server, thread)
def test_debug_bridge_reports_absent_without_bridge() -> None:
"""Without a bridge the diagnostic must honestly report absence."""
server, thread, host, port = _start_server(None)
try:
status, payload = _get(host, port, "/api/debug/bridge")
assert status == 200
assert isinstance(payload, dict)
assert payload["bridge_exists"] is False
assert payload["controller_exists"] is False
assert payload["bridge_type"] is None
finally:
_stop_server(server, thread)
def test_bridge_identity_stable_across_requests() -> None:
"""Every request must observe the same ``server_id`` (same instance)."""
bridge = _Bridge(_StubController())
server, thread, host, port = _start_server(bridge)
try:
ids: set[int] = set()
for _ in range(5):
status, payload = _get(host, port, "/api/debug/bridge")
assert status == 200
assert isinstance(payload, dict)
ids.add(int(payload["server_id"])) # type: ignore[arg-type]
assert ids == {
id(server)
}, f"server_id must be stable across requests, got {ids}"
finally:
_stop_server(server, thread)
def test_structural_status_never_reports_bridge_missing_when_attached() -> None:
"""When a bridge is attached, ``/api/structural/status`` must not fail."""
bridge = _Bridge(_StubController())
server, thread, host, port = _start_server(bridge)
try:
status, payload = _get(host, port, "/api/structural/status")
assert status == 200
assert isinstance(payload, dict)
# The route must not raise BridgeNotConfiguredError (which would be a
# 503/409) and must not return a "not configured" sentinel.
assert payload.get("configured") is True
assert "error" not in payload
finally:
_stop_server(server, thread)
def test_structural_status_reports_missing_when_no_bridge() -> None:
"""Without a bridge the structural route must fail loudly, not silently."""
server, thread, host, port = _start_server(None)
try:
status, payload = _get(host, port, "/api/structural/status")
# BridgeNotConfiguredError surfaces as a JSON error, never as HTML.
assert status in (409, 503)
assert isinstance(payload, dict)
assert "error" in payload
finally:
_stop_server(server, thread)
def test_unknown_api_paths_return_json_404_not_index_html() -> None:
"""Unknown ``/api/...`` paths must return JSON 404, never ``index.html``."""
bridge = _Bridge(_StubController())
server, thread, host, port = _start_server(bridge)
try:
status, payload = _get(host, port, "/api/does-not-exist")
assert status == 404
assert isinstance(payload, dict)
assert "error" in payload
assert "index" not in str(payload).lower()
finally:
_stop_server(server, thread)
def test_unknown_api_subpath_under_structural_returns_json_404() -> None:
"""Unknown structural subpaths must also return a JSON 404."""
bridge = _Bridge(_StubController())
server, thread, host, port = _start_server(bridge)
try:
status, payload = _get(host, port, "/api/structural/unknown-subroute")
assert status == 404
assert isinstance(payload, dict)
assert "error" in payload
finally:
_stop_server(server, thread)
def test_only_one_listener_owns_bound_port() -> None:
"""A second server bound to the same live port must not share identity.
``DashboardServer.__init__`` binds the listening socket during
construction. ``allow_reuse_address`` is True, so on some platforms a
second bind to the same port may succeed at socket level. This test
therefore does not assume the bind fails; instead it asserts that any
second ``DashboardServer`` is a distinct object from the first and
carries its own identity. The single-listener guarantee in production
comes from the launcher starting exactly one ``src.main`` process, not
from the socket layer alone.
"""
bridge = _Bridge(_StubController())
server, thread, host, port = _start_server(bridge)
second = None
try:
try:
second = DashboardServer(
(host, port),
DashboardStateStore(),
None,
cast(OperatorBridge | None, bridge),
)
except OSError:
# The OS refused to bind a second listener — ideal case.
return
# If the bind was accepted, the two servers must still be distinct
# instances with distinct identities.
assert second is not server
assert id(second) != id(server)
finally:
if second is not None:
second.server_close()
_stop_server(server, thread)
def test_bridge_object_identity_matches_server_attachment() -> None:
"""The bridge exposed via HTTP must be the exact object attached at start."""
bridge = _Bridge(_StubController())
server, thread, _host, _port = _start_server(bridge)
try:
# The server instance must own the exact bridge object.
assert server.structural_bridge is bridge
# And the controller on that bridge must be the exact stub.
assert bridge.controller is not None
finally:
_stop_server(server, thread)