test: four-rung suite that asserts on decoded pixels, not log lines

130 tests, ~20s, 94% coverage. Four rungs, each proving something the one
below it cannot:

  registry   admission, fan-out, signal authorization and teardown, no I/O
  wire       camelCase round-trip, malformed frames, WebSocket lifecycle
  e2e        real capture -> encode -> SRTP -> decode -> painted frames
  realdisplay the actual X11 capture backend on a private Xvfb display

The e2e rung drives Chromium's fake media device, which produces a genuine
moving 1280x720 monitor stream, so the assertion is that a watcher's <video>
has a size and that its pixels change over time -- a frozen or never-started
peer connection fails the suite, which a "signal arrived" assertion would
not have caught.

Findings encoded as flags and comments, because each cost a debugging cycle:
the headless shell has no media stack at all (use channel="chromium");
mediaDevices is undefined off a secure context (serve over 127.0.0.1); and
--use-fake-ui-for-media-stream breaks real X11 capture by approving the
request without ever selecting a source, so the realdisplay suite selects
the source explicitly instead.

The realdisplay fixture strips WAYLAND_DISPLAY and DBUS_SESSION_BUS_ADDRESS
from the browser environment. Without that, a headed Chromium reaches
xdg-desktop-portal, mirrors the developer's actual desktop and raises a
permission dialog on their real screen -- which is exactly what happened
before this was isolated. Skipped unless MIRROR_REALDISPLAY=1.

Also guards the frontend's structure: scripts must parse as ES modules,
every id a script looks up must exist in its page, ids must not collide
across pages that share one stylesheet, and nothing may load from a CDN.

LLM-Generated: true
This commit is contained in:
2026-11-01 16:54:28 -05:00
parent 555fc1c3b4
commit 7359ee4e07
10 changed files with 1738 additions and 0 deletions
View File
+32
View File
@@ -0,0 +1,32 @@
"""Shared fixtures for the non-browser test suite."""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from mirror.app import create_app
from mirror.config import Settings
from mirror.registry import Registry
@pytest.fixture
def settings() -> Settings:
return Settings(host="127.0.0.1", port=0, ice_servers=[], max_bitrate_bps=2_000_000)
@pytest.fixture
def registry() -> Registry:
counter = iter(f"p{i}" for i in range(1, 10_000))
return Registry(id_factory=lambda: next(counter))
@pytest.fixture
def client(app) -> TestClient:
with TestClient(app) as test_client:
yield test_client
@pytest.fixture
def app(settings) -> object:
return create_app(settings)
View File
+173
View File
@@ -0,0 +1,173 @@
"""Fixtures for the browser end-to-end suite.
The capture flags matter: with a fake UI and a fake device, Chromium answers
`getDisplayMedia()` with a real, *moving* 1280x720 monitor stream. That means
these tests exercise the genuine capture -> encode -> SRTP -> decode -> paint
path rather than a stubbed-out media source.
"""
from __future__ import annotations
import os
import socket
import subprocess
import sys
import time
from pathlib import Path
import pytest
from playwright.sync_api import expect, sync_playwright
pytestmark = pytest.mark.e2e
REPO_ROOT = Path(__file__).resolve().parents[2]
CHROMIUM_ARGS = [
"--use-fake-ui-for-media-stream",
"--use-fake-device-for-media-stream",
"--use-fake-device-for-audio-capture",
"--autoplay-policy=no-user-gesture-required",
# Two Chromium instances must each get their own UDP ports; never proxy.
"--no-sandbox",
"--disable-dev-shm-usage",
"--no-proxy-server",
]
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as probe:
probe.bind(("127.0.0.1", 0))
return probe.getsockname()[1]
@pytest.fixture(scope="session")
def base_url() -> str:
"""A real `mirror serve` process on a real port — not a test double."""
import httpx
port = _free_port()
process = subprocess.Popen(
[
sys.executable,
"-m",
"mirror",
"serve",
"--host",
"127.0.0.1",
"--port",
str(port),
"--log-level",
"warning",
],
cwd=REPO_ROOT,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env={
**os.environ,
"MIRROR_ICE_SERVERS": "",
"PYTHONUNBUFFERED": "1",
# A stray DEBUGPY in the developer's shell would stall the server.
"DEBUGPY": "0",
},
)
url = f"http://127.0.0.1:{port}"
deadline = time.time() + 25
last_error: Exception | None = None
while time.time() < deadline:
if process.poll() is not None:
output = process.stdout.read() if process.stdout else ""
raise RuntimeError(f"mirror serve exited early ({process.returncode}):\n{output}")
try:
if httpx.get(f"{url}/healthz", timeout=1.0).status_code == 200:
break
except Exception as exc: # still booting
last_error = exc
time.sleep(0.15)
else:
process.terminate()
raise RuntimeError(f"mirror serve never came up: {last_error}")
yield url
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired: # pragma: no cover
process.kill()
@pytest.fixture(scope="session")
def browser():
with sync_playwright() as playwright:
# channel="chromium" = the full Chromium binary in new headless mode.
# The trimmed "headless shell" has no media stack at all and hides
# navigator.mediaDevices entirely.
instance = playwright.chromium.launch(channel="chromium", headless=True, args=CHROMIUM_ARGS)
yield instance
instance.close()
@pytest.fixture
def context(browser):
"""One browser context == one device. Closed after every test."""
created = browser.new_context(viewport={"width": 1100, "height": 720})
yield created
created.close()
@pytest.fixture
def page(context):
return context.new_page()
def start_sharing(page) -> None:
page.locator("#start").click()
expect(page.locator("body")).to_have_attribute("data-live", "on", timeout=15_000)
def stop_sharing(page) -> None:
page.locator("#stop").click()
expect(page.locator("body")).to_have_attribute("data-live", "off", timeout=10_000)
# Returns {width, height, currentTime, distinct} for the first <video> matching.
FRAME_PROBE = """
async (selector) => {
const video = document.querySelector(selector);
if (!video) return {missing: true};
const canvas = document.createElement('canvas');
canvas.width = 48;
canvas.height = 32;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const seen = new Set();
for (let i = 0; i < 8; i++) {
await new Promise((resolve) => setTimeout(resolve, 120));
try { ctx.drawImage(video, 0, 0, 48, 32); } catch { /* not ready yet */ }
const data = ctx.getImageData(0, 0, 48, 32).data;
let hash = 0;
for (let j = 0; j < data.length; j += 4) {
hash = ((hash * 31) + data[j] + data[j + 1] * 3 + data[j + 2] * 7) >>> 0;
}
seen.add(hash);
}
return {
width: video.videoWidth,
height: video.videoHeight,
currentTime: video.currentTime,
paused: video.paused,
distinct: seen.size,
};
}
"""
def frames_matching(page, selector: str, attempts: int = 10) -> dict:
"""Poll until real, changing pixels show up (or give up and return last)."""
result: dict = {}
for _ in range(attempts):
result = page.evaluate(FRAME_PROBE, selector)
if result.get("distinct", 0) >= 2 and result.get("width", 0) > 0:
return result
time.sleep(0.4)
return result
+269
View File
@@ -0,0 +1,269 @@
"""End-to-end: a real `mirror serve`, real Chromium, real captured frames.
Every assertion about "it works" is made on pixels that reached the viewer's
<video>, not on log lines. If the media path breaks, these tests break.
"""
from __future__ import annotations
import re
import time
import pytest
from playwright.sync_api import expect
from .conftest import frames_matching, start_sharing, stop_sharing
pytestmark = [pytest.mark.e2e, pytest.mark.timeout(180)]
@pytest.fixture
def device(base_url, browser, request):
"""Open a page as its own device (isolated context, fresh peer id)."""
opened = []
def _open(path: str = "/", errors: list | None = None):
context = browser.new_context(viewport={"width": 1100, "height": 720})
opened.append(context)
request.addfinalizer(context.close)
page = context.new_page()
if errors is not None:
page.on("pageerror", lambda error: errors.append(str(error)))
page.goto(f"{base_url}{path}", wait_until="domcontentloaded")
expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=10_000)
return page
return _open
def tile_ids(page) -> list[str]:
return page.locator(".tile").evaluate_all("els => els.map(e => e.dataset.stream)")
class TestShareThenWatch:
def test_a_shared_window_reaches_a_viewer_as_moving_pixels(self, device):
errors: list = []
sharer = device("/mirror", errors)
start_sharing(sharer)
expect(sharer.locator("#stat-resolution")).to_have_text(
re.compile(r"\d{3,4}\s.\s\d{3,4}"), timeout=10_000
)
expect(sharer.locator("#stat-surface")).to_contain_text("Screen", timeout=5_000)
viewer = device("/watch", errors)
expect(viewer.locator(".tile")).to_have_count(1, timeout=15_000)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
frames = frames_matching(viewer, ".tile video")
assert not frames.get("missing"), "no <video> element in the tile"
assert frames["width"] > 0 and frames["height"] > 0, f"no decoded frame size: {frames}"
assert frames["distinct"] >= 2, f"video is frozen: {frames}"
# The sharer learns that someone is watching.
expect(sharer.locator("#viewer-count")).to_have_text("1", timeout=10_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_clicking_a_tile_takes_it_on_stage(self, device):
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
viewer.locator(".tile").click()
expect(viewer.locator("body")).to_have_class(re.compile("staged"), timeout=5_000)
expect(viewer.locator("#watch-stage .tile")).to_have_count(1)
expect(viewer.locator("#watch-stage video")).to_have_count(1)
# Still moving while staged — staging must not restart the peer connection.
frames = frames_matching(viewer, "#watch-stage video")
assert frames["distinct"] >= 2, f"staged video froze: {frames}"
# And the mirror survives the stage: the sharer still has one viewer.
expect(sharer.locator("#viewer-count")).to_have_text("1", timeout=10_000)
def test_stopping_the_share_clears_the_wall(self, device):
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile")).to_have_count(1, timeout=15_000)
stop_sharing(sharer)
expect(viewer.locator(".tile")).to_have_count(0, timeout=10_000)
expect(viewer.locator("#empty")).to_be_visible()
expect(viewer.locator("body")).not_to_have_class(re.compile("staged"))
def test_closing_the_sharer_tab_clears_the_wall(self, device):
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile")).to_have_count(1, timeout=15_000)
sharer.close()
expect(viewer.locator(".tile")).to_have_count(0, timeout=10_000)
class TestChromeVisibility:
"""The page must never advertise a control that does not apply to its state."""
def test_the_idle_share_page_offers_only_a_start(self, device):
sharer = device("/mirror")
expect(sharer.locator("#start")).to_be_visible()
expect(sharer.locator("#placeholder")).to_be_visible()
expect(sharer.locator("#stop")).not_to_be_visible()
expect(sharer.locator("#livetag")).not_to_be_visible()
start_sharing(sharer)
expect(sharer.locator("#stop")).to_be_visible()
expect(sharer.locator("#livetag")).to_be_visible()
expect(sharer.locator("#start")).not_to_be_visible()
expect(sharer.locator("#placeholder")).not_to_be_visible()
def test_exit_only_appears_on_the_tile_that_is_on_stage(self, device):
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile")).to_have_count(1, timeout=15_000)
expect(viewer.locator(".tile .exit")).not_to_be_visible()
viewer.locator(".tile").click()
expect(viewer.locator("#watch-stage .exit")).to_be_visible()
viewer.locator("#watch-stage .exit").click()
expect(viewer.locator("body")).not_to_have_class(re.compile("staged"), timeout=5_000)
expect(viewer.locator(".tile .exit")).not_to_be_visible()
# Leaving the stage must not have disturbed the stream.
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=20_000)
class TestManyToMany:
def test_one_mirror_fans_out_to_several_viewers(self, device):
errors: list = []
sharer = device("/mirror", errors)
start_sharing(sharer)
viewers = [device("/watch", errors) for _ in range(3)]
for viewer in viewers:
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
expect(sharer.locator("#viewer-count")).to_have_text("3", timeout=15_000)
expect(sharer.locator("#audience li")).to_have_count(3, timeout=10_000)
for viewer in viewers:
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"a viewer froze: {frames}"
# One viewer leaving must not disturb the others.
viewers[0].close()
expect(sharer.locator("#viewer-count")).to_have_text("2", timeout=15_000)
for viewer in viewers[1:]:
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"collateral damage: {frames}"
assert errors == [], f"uncaught browser errors: {errors}"
def test_several_mirrors_land_on_one_wall(self, device):
errors: list = []
sharers = []
for _ in range(2):
page = device("/mirror", errors)
start_sharing(page)
sharers.append(page)
viewer = device("/watch", errors)
expect(viewer.locator(".tile")).to_have_count(2, timeout=15_000)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(2, timeout=30_000)
ids = tile_ids(viewer)
assert len(ids) == 2 and len(set(ids)) == 2, "tiles must be distinct mirrors"
# Stage the second mirror, then jump to the first.
viewer.locator(".tile").nth(1).click()
expect(viewer.locator("#watch-stage .tile")).to_have_count(1)
staged = viewer.locator("#watch-stage .tile").get_attribute("data-stream")
assert staged == ids[1]
viewer.locator("#watch-stage .exit").click()
expect(viewer.locator("body")).not_to_have_class(re.compile("staged"), timeout=5_000)
viewer.locator(".tile").nth(0).click()
expect(viewer.locator("#watch-stage .tile")).to_have_count(1)
assert viewer.locator("#watch-stage .tile").get_attribute("data-stream") == ids[0]
# Both mirrors are still alive and untouched by the staging gymnastics.
expect(viewer.locator(".tile[data-state='live']")).to_have_count(2, timeout=20_000)
assert errors == [], f"uncaught browser errors: {errors}"
def test_stopping_one_mirror_leaves_the_other_running(self, device):
first = device("/mirror")
start_sharing(first)
second = device("/mirror")
start_sharing(second)
viewer = device("/watch")
expect(viewer.locator(".tile[data-state='live']")).to_have_count(2, timeout=30_000)
stop_sharing(first)
expect(viewer.locator(".tile")).to_have_count(1, timeout=10_000)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=20_000)
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"the surviving mirror froze: {frames}"
class TestTimingAndRecovery:
def test_a_viewer_already_on_the_wall_sees_a_late_mirror(self, device):
viewer = device("/watch")
expect(viewer.locator("#empty")).to_be_visible()
sharer = device("/mirror")
start_sharing(sharer)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"late mirror did not animate: {frames}"
def test_a_reloaded_viewer_recovers_the_live_mirror(self, device):
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
viewer.reload(wait_until="domcontentloaded")
expect(viewer.locator("#net")).to_have_attribute("data-state", "online", timeout=10_000)
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
frames = frames_matching(viewer, ".tile video")
assert frames["distinct"] >= 2, f"reload did not restore motion: {frames}"
expect(sharer.locator("#viewer-count")).to_have_text("1", timeout=15_000)
def test_the_index_page_counts_what_is_live(self, device):
index = device("/")
expect(index.locator("#livebar")).to_contain_text("No mirrors live", timeout=10_000)
sharer = device("/mirror")
start_sharing(sharer)
expect(index.locator("#livebar")).to_contain_text("1 mirror", timeout=10_000)
class TestRosterTruthfulness:
def test_the_api_agrees_with_what_the_browser_shows(self, device, base_url):
import httpx
sharer = device("/mirror")
start_sharing(sharer)
viewer = device("/watch")
expect(viewer.locator(".tile[data-state='live']")).to_have_count(1, timeout=25_000)
# Give the stats ticker a beat, then compare server truth to pixels.
time.sleep(1.0)
payload = httpx.get(f"{base_url}/api/streams", timeout=5).json()
assert payload["stats"]["mirrors"] == 1
assert payload["stats"]["watching"] == 1
mirror = payload["mirrors"][0]
assert mirror["width"] and mirror["height"]
assert mirror["displaySurface"] in ("monitor", "window", "browser")
+296
View File
@@ -0,0 +1,296 @@
"""The Linux-native capture path: a real X11 screen, no fake media devices.
The rest of the suite drives Chromium's fake capture device, which is
deterministic but synthetic. This module is the counterweight: it starts its own
Xvfb server, runs a *headed* Chromium on it, and asks that browser to mirror the
whole monitor through Chromium's real X11 capture backend. A second Chromium
process — a different machine as far as WebRTC cares — watches it.
Skipped unless ``MIRROR_REALDISPLAY=1``, because it needs an X server binary and
a headed browser::
MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay
"""
from __future__ import annotations
import os
import shutil
import socket
import subprocess
import time
import httpx
import pytest
from playwright.sync_api import expect, sync_playwright
pytestmark = [
pytest.mark.e2e,
pytest.mark.realdisplay,
pytest.mark.timeout(240),
pytest.mark.skipif(shutil.which("Xvfb") is None, reason="Xvfb is not installed"),
pytest.mark.skipif(
os.environ.get("MIRROR_REALDISPLAY", "") not in {"1", "true", "yes"},
reason="set MIRROR_REALDISPLAY=1 to exercise the real X11 capture backend",
),
]
# Deliberately not 16:9: Chromium's fake device reports 1280x720 no matter what,
# so an aspect ratio of exactly 1280/800 is a fingerprint of the real screen.
XVFB_GEOMETRY = "1280x800x24"
XVFB_ASPECT = 1280 / 800
FAKE_DEVICE_SIZE = (1280, 720)
# Flags learned the hard way, and worth keeping written down:
#
# * No `--use-fake-device-for-media-stream`: that is exactly what we are trying
# not to use here.
# * No `--use-fake-ui-for-media-stream` either. It short-circuits the picker
# *before* the X11 backend selects a source, and every capture then dies with
# `NotReadableError: Could not start video source` in ~55ms. Choosing the
# source with `--auto-select-desktop-capture-source` grants and selects in one
# step, which is what makes unattended desktop capture work.
# * `--ozone-platform=x11` keeps Chromium on its native X11 capturer instead of
# xdg-desktop-portal/PipeWire. Combined with `browser_env()` this makes a
# permission dialog on the developer's own session impossible.
REAL_CAPTURE_ARGS = [
"--auto-select-desktop-capture-source=Entire screen",
"--ozone-platform=x11",
"--autoplay-policy=no-user-gesture-required",
"--no-sandbox",
"--disable-dev-shm-usage",
"--no-proxy-server",
]
# Never reachable: without a session bus and without a Wayland socket there is no
# portal to ask, so these tests cannot interrupt a human.
UNREACHABLE_SESSION_VARS = (
"WAYLAND_DISPLAY",
"DBUS_SESSION_BUS_ADDRESS",
"XDG_CURRENT_DESKTOP",
"XDG_BACKEND",
)
def browser_env(display: str | None = None) -> dict[str, str]:
"""An environment that can only reach the display this test owns."""
env = {key: value for key, value in os.environ.items() if key not in UNREACHABLE_SESSION_VARS}
env["XDG_SESSION_TYPE"] = "x11"
env["MIRROR_ICE_SERVERS"] = ""
if display is None:
env.pop("DISPLAY", None)
else:
env["DISPLAY"] = display
return env
# Starts a pixel-sampling loop without blocking, so the test can drive motion on
# the captured screen while the watcher is measuring it.
PROBE = """() => {
const video = document.querySelector('.tile video');
const canvas = document.createElement('canvas');
canvas.width = 96;
canvas.height = 60;
const ctx = canvas.getContext('2d', {willReadFrequently: true});
window.__probe = (async () => {
const seen = new Set();
for (let i = 0; i < 14; i++) {
await new Promise((r) => setTimeout(r, 120));
try { ctx.drawImage(video, 0, 0, 96, 60); } catch {}
const d = ctx.getImageData(0, 0, 96, 60).data;
let h = 0;
for (let j = 0; j < d.length; j += 4) {
h = ((h * 31) + d[j] + d[j + 1] * 3 + d[j + 2] * 7) >>> 0;
}
seen.add(h);
}
return {distinct: seen.size, width: video.videoWidth, height: video.videoHeight};
})();
return 'started';
}"""
def _free_display() -> str:
for number in range(90, 120):
path = f"/tmp/.X11-unix/X{number}"
if os.path.exists(path):
continue
with socket.socket(socket.AF_UNIX) as probe:
try:
probe.connect(path)
except OSError:
return f":{number}"
else:
continue
raise RuntimeError("no free X display in :90..:119")
def _socket_path(display: str) -> str:
"""`:90` -> `/tmp/.X11-unix/X90`, the file whose existence means "ready"."""
return f"/tmp/.X11-unix/X{display.lstrip(':')}"
@pytest.fixture(scope="module")
def x_display():
display = _free_display()
ready = _socket_path(display)
server = subprocess.Popen(
["Xvfb", display, "-screen", "0", XVFB_GEOMETRY, "-nolisten", "tcp"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
deadline = time.time() + 20
while time.time() < deadline:
if os.path.exists(ready):
break
if server.poll() is not None: # pragma: no cover
raise RuntimeError(f"Xvfb exited immediately with code {server.returncode}")
time.sleep(0.15)
else: # pragma: no cover
server.terminate()
raise RuntimeError(f"Xvfb never created {ready}")
yield display
server.terminate()
try:
server.wait(timeout=5)
except subprocess.TimeoutExpired: # pragma: no cover
server.kill()
@pytest.fixture(scope="module")
def browsers(x_display):
"""Two Chromium *processes* from one Playwright driver.
The sharer is headed and lives on the Xvfb display, so what it captures is a
genuine desktop. The watcher is headless: it never touches the display, which
is exactly why it can be a separate process.
"""
previous = os.environ.get("DISPLAY")
os.environ["DISPLAY"] = x_display
try:
with sync_playwright() as playwright:
sharer = playwright.chromium.launch(
channel="chromium",
headless=False,
args=REAL_CAPTURE_ARGS,
env=browser_env(x_display),
)
watcher = playwright.chromium.launch(
channel="chromium",
headless=True,
args=[
"--no-sandbox",
"--disable-dev-shm-usage",
"--no-proxy-server",
"--ozone-platform=x11",
],
env=browser_env(),
)
try:
yield {"sharer": sharer, "watcher": watcher}
finally:
sharer.close()
watcher.close()
finally:
if previous is not None:
os.environ["DISPLAY"] = previous
else: # pragma: no cover
os.environ.pop("DISPLAY", None)
@pytest.fixture
def share_page(browsers, base_url):
page = browsers["sharer"].new_context(viewport={"width": 1200, "height": 760}).new_page()
page.goto(f"{base_url}/mirror", wait_until="domcontentloaded")
expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000)
yield page
page.close()
@pytest.fixture
def watch_page(browsers, base_url):
page = browsers["watcher"].new_context().new_page()
page.goto(f"{base_url}/watch", wait_until="domcontentloaded")
expect(page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000)
yield page
page.close()
class TestRealX11Capture:
def test_the_real_desktop_is_captured_not_a_synthetic_one(self, base_url, share_page):
errors: list[str] = []
share_page.on("pageerror", lambda error: errors.append(str(error)))
share_page.locator("#start").click()
expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000)
settings = share_page.evaluate(
"""() => {
const track = document.querySelector('#preview').srcObject.getVideoTracks()[0];
return {label: track.label, ...track.getSettings()};
}"""
)
assert settings["displaySurface"] == "monitor", f"not a monitor capture: {settings}"
assert settings["label"], "a real capture always names its source"
assert not settings["label"].startswith("screen:-"), (
f"{settings['label']} is Chromium's synthetic capture device"
)
size = (settings["width"], settings["height"])
assert size != FAKE_DEVICE_SIZE, (
f"got exactly {size}: that is Chromium's fake capture device, not the X11 screen"
)
assert settings["width"] >= 640, f"implausibly small capture: {settings}"
# Chromium scales a monitor capture to the capturing window, so compare
# the aspect ratio: 1280x800 in, 1200x750 out is the real screen.
assert abs(settings["width"] / settings["height"] - XVFB_ASPECT) < 0.06, (
f"{size} does not match the {XVFB_GEOMETRY} desktop aspect "
f"({XVFB_ASPECT:.3f}); this is not our Xvfb screen"
)
# The roster must carry the real geometry, not a made-up one.
time.sleep(0.6)
mirror = httpx.get(f"{base_url}/api/streams", timeout=5).json()["mirrors"][0]
assert (mirror["width"], mirror["height"]) == size
assert mirror["displaySurface"] == "monitor"
assert errors == [], f"uncaught browser errors: {errors}"
def test_a_real_desktop_reaches_a_second_machine_and_moves(self, base_url, browsers):
share_page = (
browsers["sharer"].new_context(viewport={"width": 1200, "height": 760}).new_page()
)
share_page.goto(f"{base_url}/mirror", wait_until="domcontentloaded")
expect(share_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000)
share_page.locator("#start").click()
expect(share_page.locator("body")).to_have_attribute("data-live", "on", timeout=40_000)
watch_page = browsers["watcher"].new_context().new_page()
watch_page.goto(f"{base_url}/watch", wait_until="domcontentloaded")
expect(watch_page.locator("#net")).to_have_attribute("data-state", "online", timeout=20_000)
expect(watch_page.locator(".tile[data-state='live']")).to_have_count(1, timeout=60_000)
# Start measuring on the watcher, then paint: cycling the captured
# machine's background is what a real presenter's screen does constantly.
assert watch_page.evaluate(PROBE) == "started"
for colour in ("#c81e1e", "#0b0d10", "#1e6ec8", "#0b0d10", "#1ec85a"):
share_page.evaluate("(colour) => { document.body.style.background = colour; }", colour)
time.sleep(0.28)
result = watch_page.evaluate("() => window.__probe")
assert result["width"] >= 640 and result["height"] >= 400, (
f"the watcher decoded no picture: {result}"
)
assert abs(result["width"] / result["height"] - XVFB_ASPECT) < 0.06, result
assert result["distinct"] >= 2, (
f"the watcher received a frozen frame: {result} — real desktop motion "
f"never made it across the peer connection"
)
# And the sharer agrees that someone is watching its desktop.
expect(share_page.locator("#viewer-count")).to_have_text("1", timeout=20_000)
# Stopping the share tears the mirror away for the watcher as well.
share_page.locator("#stop").click()
expect(watch_page.locator(".tile")).to_have_count(0, timeout=20_000)
watch_page.close()
share_page.close()
+385
View File
@@ -0,0 +1,385 @@
"""HTTP surface + signaling socket tests. No browser required."""
from __future__ import annotations
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
STATIC = Path(__file__).resolve().parent.parent / "src/mirror/static"
EXTERNAL_REF = re.compile(r"""(?:src|href)\s*=\s*["']https?://""", re.IGNORECASE)
def handshake(socket, role, name=""):
socket.send_json({"type": "hello", "role": role, "name": name})
message = socket.receive_json()
assert message["type"] == "welcome", message
return message["peerId"]
def wait_for(socket, kind):
"""Read frames until one has ``type == kind``.
The roster legitimately pushes ``stream_updated`` frames whenever the
audience changes, so tests must not assume a one-message-per-action cadence.
Any ``error`` frame seen on the way is a failure worth surfacing.
"""
seen = []
while True:
message = socket.receive_json()
if message.get("type") == kind:
return message
assert message.get("type") != "error", (
f"unexpected error while waiting for {kind}: {message}"
)
seen.append(message)
assert len(seen) < 25, f"never saw {kind!r}; got {seen}"
class TestPages:
@pytest.mark.parametrize(
("path", "marker"),
[
("/", "Watch everything"),
("/mirror", "Share my screen"),
("/share", "Share my screen"),
("/watch", "tile-template"),
],
)
def test_pages_render(self, client, path, marker):
response = client.get(path)
assert response.status_code == 200
assert response.headers["content-type"].startswith("text/html")
assert marker in response.text
def test_pages_are_not_cacheable(self, client):
assert client.get("/watch").headers["cache-control"] == "no-store"
def test_the_two_pages_use_the_media_apis_they_claim(self):
share = (STATIC / "share.js").read_text()
watch = (STATIC / "watch.js").read_text()
assert "getDisplayMedia" in share
assert "RTCPeerConnection" in share and "RTCPeerConnection" in watch
assert "new WebSocket" in (STATIC / "net.js").read_text()
def test_nothing_is_loaded_from_a_cdn(self):
"""The no-CDN rule is a hard product requirement, so assert it."""
offenders = []
for file in sorted(STATIC.iterdir()):
text = file.read_text()
if EXTERNAL_REF.search(text):
offenders.append(file.name)
if "@import" in text or "fonts.googleapis" in text:
offenders.append(f"{file.name} (@import)")
assert offenders == []
@pytest.mark.parametrize("asset", ["app.css", "net.js", "share.js", "watch.js"])
def test_static_assets_are_served(self, client, asset):
assert client.get(f"/static/{asset}").status_code == 200
def test_unknown_page_is_a_404(self, client):
assert client.get("/nope").status_code == 404
class TestClientScripts:
"""Guards for the hand-written ES modules: they must parse, and the ids they
reach for must exist. Both of these have already bitten once."""
NODE = shutil.which("node")
@pytest.mark.skipif(NODE is None, reason="node is not installed")
@pytest.mark.parametrize("script", ["net.js", "share.js", "watch.js"])
def test_scripts_parse_as_es_modules(self, script, tmp_path):
copy = tmp_path / "subject.mjs"
copy.write_text((STATIC / script).read_text())
result = subprocess.run([self.NODE, "--check", str(copy)], capture_output=True, text=True)
assert result.returncode == 0, f"{script}: {result.stderr}"
@pytest.mark.parametrize(
("script", "page"),
[("share.js", "mirror.html"), ("watch.js", "watch.html")],
)
def test_every_element_the_script_reaches_for_exists(self, script, page):
html = (STATIC / page).read_text()
wanted = set(re.findall(r"""\$\(\"#([A-Za-z0-9_-]+)\"\)""", (STATIC / script).read_text()))
missing = {name for name in wanted if f'id="{name}"' not in html}
assert not missing, f"{script} looks up {sorted(missing)} which {page} does not render"
def test_both_pages_load_the_stylesheet_and_their_module(self):
for page, script in (("mirror.html", "share.js"), ("watch.html", "watch.js")):
html = (STATIC / page).read_text()
assert 'href="/static/app.css"' in html
assert f'src="/static/{script}"' in html
assert 'type="module"' in html
def test_the_two_pages_share_no_element_ids_but_the_shared_chrome(self):
"""Both pages consume one stylesheet, so a duplicated id means a colliding rule.
This is not hypothetical: a `#stage` on both pages once made the share
page's preview inherit `display: none` from the watch page's overlay.
"""
shared = {"net", "notice", "toast"}
ids = {
page: set(re.findall(r'id="([A-Za-z0-9_-]+)"', (STATIC / page).read_text()))
for page in ("mirror.html", "watch.html", "index.html")
}
assert ids["mirror.html"] & ids["watch.html"] == shared
assert ids["index.html"] & ids["mirror.html"] <= shared | {"livebar"}
for page, found in ids.items():
assert len(found) == len(set(found)), f"{page} declares an id twice"
def test_videos_are_muted_and_inline_so_autoplay_is_not_blocked(self):
for page in ("mirror.html", "watch.html"):
for tag in re.findall(r"<video[^>]*>", (STATIC / page).read_text()):
assert "muted" in tag, f"{page}: {tag} would be blocked by autoplay policy"
assert "playsinline" in tag, f"{page}: {tag} would hijack iOS into fullscreen"
class TestStylesheet:
def test_hidden_always_means_gone(self):
"""Every component that sets its own `display` outranks the UA's `[hidden]`.
Without this rule the share page shows the LIVE badge and the Stop button
while nothing is being shared, and every wall tile offers an Exit button
it is not on stage for.
"""
css = (STATIC / "app.css").read_text()
assert re.search(r"\[hidden\]\s*\{[^}]*display:\s*none\s*!important", css), (
"app.css must declare a global `[hidden] { display: none !important }`"
)
# Components that set `display` and are toggled with `hidden` in JS.
for selector in (".btn", ".livetag", ".icon-btn", ".toast", ".net"):
assert selector in css, f"{selector} is toggled by `hidden`; it lives in app.css"
def test_no_rule_reintroduces_a_conflicting_display_for_hidden(self):
"""Every rule whose selector mentions `[hidden]` must actually hide."""
css = re.sub(r"/\*.*?\*/", "", (STATIC / "app.css").read_text(), flags=re.S)
offenders = [
selector.strip()
for selector, body in re.findall(r"([^{}]+)\{([^{}]*)\}", css)
if "[hidden]" in selector and not re.search(r"display\s*:\s*none", body)
]
assert offenders == []
class TestJsonApi:
def test_config_advertises_the_media_defaults(self, client):
body = client.get("/api/config").json()
assert body["iceServers"] == []
assert body["maxBitrateBps"] == 2_000_000
assert "version" in body
def test_config_is_a_whitelist(self, client):
body = client.get("/api/config").json()
assert set(body) == {"version", "iceServers", "maxBitrateBps", "pingIntervalMs"}
def test_streams_starts_empty(self, client):
body = client.get("/api/streams").json()
assert body["mirrors"] == []
assert body["stats"]["mirrors"] == 0
def test_healthz(self, client):
assert client.get("/healthz").json()["status"] == "ok"
class TestSignalingSocket:
def test_hello_must_come_first(self, client):
with client.websocket_connect("/ws") as socket:
socket.send_json({"type": "publish", "label": "sneaky"})
message = socket.receive_json()
assert message["type"] == "error"
assert message["code"] == "hello_required"
assert message["fatal"] is True
def test_malformed_json_is_rejected(self, client):
with client.websocket_connect("/ws") as socket:
socket.send_text("{not json")
assert socket.receive_json()["code"] == "bad_json"
def test_a_non_object_frame_is_rejected(self, client):
with client.websocket_connect("/ws") as socket:
socket.send_text("[1, 2, 3]")
assert socket.receive_json()["code"] == "bad_json"
def test_an_unknown_role_is_refused(self, client):
with client.websocket_connect("/ws") as socket:
socket.send_json({"type": "hello", "role": "spectator"})
assert socket.receive_json()["code"] == "bad_role"
def test_unknown_message_type_is_reported_but_survivable(self, client):
with client.websocket_connect("/ws") as socket:
handshake(socket, "viewer", "V")
socket.send_json({"type": "telepathy"})
assert socket.receive_json()["code"] == "bad_message"
socket.send_json({"type": "ping"})
assert socket.receive_json()["type"] == "pong"
def test_ping_roundtrips(self, client):
with client.websocket_connect("/ws") as socket:
handshake(socket, "viewer")
socket.send_json({"type": "ping"})
assert socket.receive_json() == {"type": "pong"}
def test_an_oversized_frame_is_refused(self, client):
with client.websocket_connect("/ws") as socket:
handshake(socket, "viewer")
socket.send_text(json.dumps({"type": "ping", "padding": "x" * 70_000}))
assert socket.receive_json()["code"] == "too_large"
def test_roster_flows_from_sharer_to_viewer(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "Linux Box")
viewer_id = handshake(viewer, "viewer", "Phone")
sharer.send_json({"type": "publish", "label": "Terminal", "width": 1280, "height": 720})
added = wait_for(viewer, "stream_added")["stream"]
assert added["id"] == sharer_id
assert added["label"] == "Terminal"
assert added["name"] == "Linux Box"
assert added["width"] == 1280
viewer.send_json({"type": "subscribe", "streamId": sharer_id})
joined = wait_for(sharer, "peer_joined")
assert joined["viewerId"] == viewer_id
assert joined["name"] == "Phone"
sharer.send_json({"type": "signal", "to": viewer_id, "payload": {"sdp": {"s": "o"}}})
relayed = wait_for(viewer, "signal")
assert relayed["from"] == sharer_id
assert relayed["payload"] == {"sdp": {"s": "o"}}
viewer.send_json({"type": "signal", "to": sharer_id, "payload": {"c": "ice"}})
assert wait_for(sharer, "signal")["payload"] == {"c": "ice"}
def test_capture_metadata_survives_the_wire(self, client):
"""Regression: `displaySurface` once arrived as "" because the model expected
snake_case and unknown keys were being ignored."""
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
handshake(sharer, "sharer", "A")
handshake(viewer, "viewer")
sharer.send_json(
{
"type": "publish",
"label": "proj.gl — zsh",
"displaySurface": "window",
"width": 1920,
"height": 1080,
"audio": True,
}
)
stream = wait_for(viewer, "stream_added")["stream"]
assert stream["displaySurface"] == "window"
assert stream["audio"] is True
assert (stream["width"], stream["height"]) == (1920, 1080)
assert client.get("/api/streams").json()["mirrors"][0]["displaySurface"] == "window"
def test_both_viewers_of_one_mirror_see_their_own_leg(self, client):
with (
client.websocket_connect("/ws") as sharer,
client.websocket_connect("/ws") as one,
client.websocket_connect("/ws") as two,
):
sharer_id = handshake(sharer, "sharer", "A")
first = handshake(one, "viewer", "one")
second = handshake(two, "viewer", "two")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(one, "stream_added")
wait_for(two, "stream_added")
one.send_json({"type": "subscribe", "streamId": sharer_id})
two.send_json({"type": "subscribe", "streamId": sharer_id})
assert wait_for(sharer, "peer_joined")["viewerId"] == first
assert wait_for(sharer, "peer_joined")["viewerId"] == second
sharer.send_json({"type": "signal", "to": first, "payload": {"n": 1}})
sharer.send_json({"type": "signal", "to": second, "payload": {"n": 2}})
assert wait_for(one, "signal")["payload"] == {"n": 1}
assert wait_for(two, "signal")["payload"] == {"n": 2}
def test_a_mirror_disappears_for_viewers_when_the_sharer_leaves(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "A")
handshake(viewer, "viewer", "V")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
viewer.send_json({"type": "subscribe", "streamId": sharer_id})
wait_for(sharer, "peer_joined")
sharer.close()
assert wait_for(viewer, "stream_removed")["streamId"] == sharer_id
def test_unpublish_removes_the_mirror(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer")
handshake(viewer, "viewer")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
sharer.send_json({"type": "unpublish"})
assert wait_for(viewer, "stream_removed")["streamId"] == sharer_id
assert client.get("/api/streams").json()["mirrors"] == []
def test_signalling_a_peer_that_never_subscribed_is_refused(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer")
viewer_id = handshake(viewer, "viewer")
sharer.send_json({"type": "publish", "label": "Screen"})
assert wait_for(viewer, "stream_added")["stream"]["sharerId"] == sharer_id
# The viewer knows the mirror exists but has not subscribed to it.
sharer.send_json({"type": "signal", "to": viewer_id, "payload": {}})
assert wait_for(sharer, "error")["code"] == "not_subscribed"
def test_a_viewer_cannot_reach_a_sharer_it_never_subscribed_to(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "A")
handshake(viewer, "viewer")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
viewer.send_json({"type": "signal", "to": sharer_id, "payload": {}})
assert wait_for(viewer, "error")["code"] == "not_subscribed"
def test_subscribing_to_a_mirror_that_is_not_there_reports_cleanly(self, client):
with client.websocket_connect("/ws") as viewer:
handshake(viewer, "viewer")
viewer.send_json({"type": "subscribe", "streamId": "p404"})
message = wait_for(viewer, "error")
assert message["code"] == "unknown_stream"
assert message["fatal"] is False
def test_editing_the_display_name_reaches_viewers(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "Old name")
handshake(viewer, "viewer")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
sharer.send_json({"type": "hello", "role": "sharer", "name": "Design Laptop"})
assert wait_for(viewer, "stream_updated")["stream"]["name"] == "Design Laptop"
assert client.get("/api/streams").json()["mirrors"][0]["sharerId"] == sharer_id
def test_stats_track_the_fan_out(self, client):
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "A")
handshake(viewer, "viewer", "V")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
viewer.send_json({"type": "subscribe", "streamId": sharer_id})
wait_for(sharer, "peer_joined")
stats = client.get("/api/streams").json()["stats"]
assert stats["mirrors"] == 1
assert stats["watching"] == 1
assert stats["connections"] == 2
def test_one_peer_cannot_impersonate_another(self, client):
"""`to` is addressed by peer id; a viewer must not be able to spoof a sharer."""
with client.websocket_connect("/ws") as sharer, client.websocket_connect("/ws") as viewer:
sharer_id = handshake(sharer, "sharer", "A")
handshake(viewer, "viewer", "V")
sharer.send_json({"type": "publish", "label": "Screen"})
wait_for(viewer, "stream_added")
viewer.send_json({"type": "publish", "label": "forged"})
assert wait_for(viewer, "error")["code"] == "not_sharer"
assert client.get("/api/streams").json()["stats"]["mirrors"] == 1
assert sharer_id and viewer
+116
View File
@@ -0,0 +1,116 @@
"""Configuration parsing, the CLI surface, and the debugpy hook."""
from __future__ import annotations
import pytest
from mirror import __main__ as entry
from mirror.config import DEFAULT_PORT, Settings, lan_addresses, parse_ice_servers
from mirror.debug import maybe_enable_debugpy
class TestIceServers:
@pytest.mark.parametrize("raw", ["", " ", "[]", "none", "null", "not json", "{}", "[1,2]"])
def test_unusable_input_means_no_turn_stun(self, raw):
assert parse_ice_servers(raw) == []
def test_valid_servers_survive_and_junk_is_dropped(self):
raw = '[{"urls": "stun:router:3478"}, "junk", {"urls": ["turn:h:3478"]}]'
assert parse_ice_servers(raw) == [
{"urls": "stun:router:3478"},
{"urls": ["turn:h:3478"]},
]
class TestSettings:
def test_defaults(self, monkeypatch):
for name in (
"MIRROR_HOST",
"MIRROR_PORT",
"MIRROR_ICE_SERVERS",
"MIRROR_MAX_BITRATE",
"MIRROR_MAX_MIRRORS",
):
monkeypatch.delenv(name, raising=False)
settings = Settings.from_env()
assert settings.port == DEFAULT_PORT
assert settings.host == "0.0.0.0"
assert settings.ice_servers == []
assert settings.max_streams == 0
def test_environment_wins(self, monkeypatch):
monkeypatch.setenv("MIRROR_HOST", "127.0.0.1")
monkeypatch.setenv("MIRROR_PORT", "9123")
monkeypatch.setenv("MIRROR_MAX_BITRATE", "250000")
monkeypatch.setenv("MIRROR_MAX_MIRRORS", "3")
monkeypatch.setenv("MIRROR_ICE_SERVERS", '[{"urls": "stun:box:3478"}]')
settings = Settings.from_env()
assert (settings.host, settings.port) == ("127.0.0.1", 9123)
assert settings.max_bitrate_bps == 250_000
assert settings.max_streams == 3
assert settings.ice_servers == [{"urls": "stun:box:3478"}]
@pytest.mark.parametrize("value", ["abc", "", "80x"])
def test_a_bogus_number_falls_back_instead_of_crashing(self, monkeypatch, value):
monkeypatch.setenv("MIRROR_PORT", value)
assert Settings.from_env().port == DEFAULT_PORT
def test_client_config_is_a_whitelist(self, monkeypatch):
monkeypatch.setenv("MIRROR_ICE_SERVERS", '[{"urls": "stun:box:3478"}]')
config = Settings.from_env().client_config()
assert set(config) == {"iceServers", "maxBitrateBps", "pingIntervalMs"}
def test_lan_addresses_are_real_addresses(self):
for address in lan_addresses():
assert not address.startswith("127.")
assert address.count(".") == 3 or ":" in address
class TestCli:
def test_bare_invocation_means_serve(self):
parser = entry.build_parser()
argv = ["serve", "--port", "8123", "--host", "127.0.0.1"]
args = parser.parse_args(argv)
assert args.command == "serve"
assert args.port == 8123
def test_port_validation_rejects_nonsense(self):
parser = entry.build_parser()
with pytest.raises(SystemExit):
parser.parse_args(["serve", "--port", "nope"])
with pytest.raises(SystemExit):
parser.parse_args(["serve", "--port", "70000"])
def test_version_exits_cleanly(self, capsys):
with pytest.raises(SystemExit) as excinfo:
entry.build_parser().parse_args(["--version"])
assert excinfo.value.code == 0
assert "mirror" in capsys.readouterr().out
def test_flags_without_a_subcommand_still_serve(self, monkeypatch):
"""`mirror --port 8901` must work as well as `mirror serve --port 8901`."""
import sys
captured = {}
class StubUvicorn:
@staticmethod
def run(_app, **kwargs):
captured.update(kwargs)
raise KeyboardInterrupt
for name in ("MIRROR_HOST", "MIRROR_PORT", "MIRROR_ICE_SERVERS", "DEBUGPY"):
monkeypatch.delenv(name, raising=False)
monkeypatch.setitem(sys.modules, "uvicorn", StubUvicorn)
with pytest.raises(KeyboardInterrupt):
entry.main(["--port", "8901", "--host", "127.0.0.1"])
assert captured["port"] == 8901
assert captured["host"] == "127.0.0.1"
assert captured["access_log"] is False
class TestDebugpy:
@pytest.mark.parametrize("value", ["", "0", "off", "no", "FALSE"])
def test_stays_asleep_unless_explicitly_asked(self, monkeypatch, value):
monkeypatch.setenv("DEBUGPY", value)
assert maybe_enable_debugpy() is False
+122
View File
@@ -0,0 +1,122 @@
"""The wire contract, in both directions.
These exist because the client speaks camelCase and Python speaks snake_case,
and a silently-dropped field is the kind of bug that only shows up as a
"cosmetic" column being blank three features later.
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from mirror import protocol
class TestInbound:
def test_publish_accepts_the_camel_case_wire_format(self):
message = protocol.parse_client_message(
{
"type": "publish",
"label": "proj.gl — zsh",
"displaySurface": "window",
"width": 1920,
"height": 1080,
"audio": True,
}
)
assert message.display_surface == "window"
assert (message.width, message.height) == (1920, 1080)
assert message.audio is True
def test_snake_case_is_accepted_too_so_server_side_tools_can_replay_frames(self):
message = protocol.parse_client_message(
{"type": "publish", "display_surface": "monitor", "width": 800, "height": 600}
)
assert message.display_surface == "monitor"
def test_subscribe_carries_a_camel_case_stream_id(self):
assert (
protocol.parse_client_message({"type": "subscribe", "streamId": "p1"}).stream_id == "p1"
)
assert (
protocol.parse_client_message({"type": "unsubscribe", "streamId": "p1"}).stream_id
== "p1"
)
def test_a_missing_stream_id_is_a_validation_error_not_a_none(self):
with pytest.raises(ValidationError):
protocol.parse_client_message({"type": "subscribe"})
def test_dimensions_are_bounded(self):
for bad in (-1, 16385, "wide"):
with pytest.raises(ValidationError):
protocol.parse_client_message({"type": "publish", "width": bad})
def test_unknown_message_types_are_rejected(self):
with pytest.raises(ValidationError):
protocol.parse_client_message({"type": "teleport"})
def test_extra_keys_are_tolerated_for_forward_compatibility(self):
message = protocol.parse_client_message({"type": "ping", "someDayWeAddThis": True})
assert message.type == "ping"
def test_a_signal_payload_is_carried_verbatim(self):
payload = {"sdp": {"type": "offer", "sdp": "v=0\r\n"}}
message = protocol.parse_client_message({"type": "signal", "to": "p2", "payload": payload})
assert message.payload == payload
class TestOutbound:
def test_the_stream_dto_is_camel_case(self):
dto = protocol.stream_dto(
stream_id="p1",
sharer_id="p1",
name="Tower",
label="Terminal",
display_surface="window",
width=1280,
height=720,
audio=False,
viewers=2,
)
assert set(dto) == {
"id",
"sharerId",
"name",
"label",
"displaySurface",
"width",
"height",
"audio",
"viewers",
}
assert dto["displaySurface"] == "window"
def test_every_server_message_advertises_its_type(self):
messages = [
protocol.welcome(peer_id="p1", role="viewer", streams=[]),
protocol.stream_added(stream={"id": "p1"}),
protocol.stream_updated(stream={"id": "p1"}),
protocol.stream_removed(stream_id="p1"),
protocol.peer_joined(stream_id="p1", viewer_id="p2", name="V"),
protocol.peer_left(stream_id="p1", viewer_id="p2"),
protocol.relayed_signal(frm="p1", to="p2", payload={}),
protocol.error(code="x", message="y"),
protocol.pong(),
]
assert [m["type"] for m in messages] == [
"welcome",
"stream_added",
"stream_updated",
"stream_removed",
"peer_joined",
"peer_left",
"signal",
"error",
"pong",
]
def test_an_error_says_whether_the_connection_survives_it(self):
assert protocol.error(code="x", message="y")["fatal"] is False
assert protocol.error(code="x", message="y", fatal=True)["fatal"] is True
+345
View File
@@ -0,0 +1,345 @@
"""Unit tests for the roster. No sockets, no async — pure policy."""
from __future__ import annotations
import pytest
from mirror.registry import Registry, RegistryError
def by_target(outbox):
"""Group emitted envelopes by their destination peer id."""
grouped: dict[str, list[dict]] = {}
for peer_id, message in outbox:
grouped.setdefault(peer_id, []).append(message)
return grouped
def types_for(outbox, peer_id):
return [m["type"] for m in by_target(outbox).get(peer_id, [])]
def message_for(outbox, peer_id, kind):
for message in by_target(outbox).get(peer_id, []):
if message["type"] == kind:
return message
raise AssertionError(f"no {kind!r} for {peer_id!r} in {by_target(outbox)}")
@pytest.fixture
def mesh(registry):
"""One sharer, two viewers, and the sharer already mirroring."""
sharer, _ = registry.join(role="sharer", name="Linux Box")
v1, _ = registry.join(role="viewer", name="Phone")
v2, _ = registry.join(role="viewer", name="Tablet")
registry.publish(sharer, label="Terminal", width=1280, height=720)
return sharer, v1, v2, registry
class TestJoin:
def test_viewer_gets_an_empty_roster(self, registry):
peer_id, outbox = registry.join(role="viewer", name="Phone")
welcome = message_for(outbox, peer_id, "welcome")
assert welcome["peerId"] == peer_id
assert welcome["role"] == "viewer"
assert welcome["streams"] == []
def test_ids_are_unique_and_sequential_when_injected(self, registry):
first, _ = registry.join(role="viewer")
second, _ = registry.join(role="viewer")
assert (first, second) == ("p1", "p2")
def test_generated_ids_do_not_collide(self):
registry = Registry(id_factory=lambda: "same")
registry.join(role="viewer")
with pytest.raises(RegistryError, match="out of peer ids"):
registry.join(role="viewer")
def test_default_names_are_useful(self, registry):
sharer, _ = registry.join(role="sharer")
viewer, _ = registry.join(role="viewer")
assert registry.peers[sharer].name == "This device"
assert registry.peers[viewer].name == "Viewer"
def test_names_are_sanitised_and_capped(self, registry):
peer_id, _ = registry.join(role="sharer", name=" a\n\t b " + "x" * 80)
name = registry.peers[peer_id].name
assert name.startswith("a b ")
assert len(name) == 48
assert "\n" not in name and " " not in name
def test_unknown_role_is_fatal(self, registry):
with pytest.raises(RegistryError) as excinfo:
registry.join(role="spectator")
assert excinfo.value.code == "bad_role"
assert excinfo.value.fatal is True
def test_a_late_viewer_sees_existing_mirrors(self, mesh):
sharer, _v1, _v2, registry = mesh
late, outbox = registry.join(role="viewer", name="Late")
roster = message_for(outbox, late, "welcome")["streams"]
assert [s["id"] for s in roster] == [sharer]
assert roster[0]["label"] == "Terminal"
assert roster[0]["viewers"] == 0
class TestPublish:
def test_viewers_are_told_and_other_sharers_are_not(self, registry):
sharer, _ = registry.join(role="sharer", name="A")
other_sharer, _ = registry.join(role="sharer", name="B")
viewer, _ = registry.join(role="viewer", name="V")
outbox = registry.publish(sharer, label="Terminal", width=800, height=600)
assert types_for(outbox, viewer) == ["stream_added"]
assert by_target(outbox).get(other_sharer) is None
added = message_for(outbox, viewer, "stream_added")["stream"]
assert added == {
"id": sharer,
"sharerId": sharer,
"name": "A",
"label": "Terminal",
"displaySurface": "",
"width": 800,
"height": 600,
"audio": False,
"viewers": 0,
}
def test_a_viewer_cannot_publish(self, registry):
viewer, _ = registry.join(role="viewer")
with pytest.raises(RegistryError) as excinfo:
registry.publish(viewer)
assert excinfo.value.code == "not_sharer"
assert excinfo.value.fatal is True
def test_republish_updates_in_place(self, registry):
sharer, _ = registry.join(role="sharer", name="A")
registry.join(role="viewer")
registry.publish(sharer, label="First", width=800, height=600)
registry.publish(sharer, label="Second", width=1920, height=1080, audio=True)
assert len(registry.streams) == 1
assert registry.roster()[0]["label"] == "Second"
assert registry.roster()[0]["audio"] is True
def test_unchanged_republish_does_not_re_offer_to_viewers(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
outbox = registry.publish(sharer, label="Terminal", width=1280, height=720)
# The viewer already has a peer connection; nothing to renegotiate.
assert types_for(outbox, v1) == ["stream_updated"]
assert types_for(outbox, sharer) == ["stream_updated"]
def test_changed_republish_asks_for_a_new_offer_per_viewer(self, mesh):
sharer, v1, v2, registry = mesh
registry.subscribe(v1, sharer)
registry.subscribe(v2, sharer)
outbox = registry.publish(sharer, label="Terminal", width=1920, height=1080)
assert types_for(outbox, v1).count("peer_joined") == 1
assert types_for(outbox, v2).count("peer_joined") == 1
assert message_for(outbox, v1, "peer_joined")["streamId"] == sharer
def test_unpublish_clears_the_tile_for_every_viewer(self, mesh):
"""A tile is drawn on `stream_added`, so every viewer must be told it is gone —
including one that had not subscribed yet."""
sharer, v1, v2, registry = mesh
registry.subscribe(v1, sharer)
outbox = registry.unpublish(sharer)
assert types_for(outbox, v1) == ["stream_removed"]
assert types_for(outbox, v2) == ["stream_removed"]
assert registry.streams == {}
assert registry.watching(v1) == set()
def test_dropping_a_mirror_leaves_other_mirrors_alone(self, registry):
first, _ = registry.join(role="sharer", name="A")
second, _ = registry.join(role="sharer", name="B")
viewer, _ = registry.join(role="viewer")
registry.publish(first, label="one")
registry.publish(second, label="two")
registry.subscribe(viewer, second)
outbox = registry.unpublish(first)
assert [m["streamId"] for m in by_target(outbox)[viewer]] == [first]
assert [s["label"] for s in registry.roster()] == ["two"]
assert registry.watching(viewer) == {second}
def test_unpublish_without_a_stream_is_a_no_op(self, registry):
sharer, _ = registry.join(role="sharer")
assert registry.unpublish(sharer) == []
def test_capacity_blocks_a_new_mirror_but_not_an_existing_one(self):
registry = Registry(max_streams=1)
first, _ = registry.join(role="sharer", name="A")
second, _ = registry.join(role="sharer", name="B")
viewer, _ = registry.join(role="viewer")
registry.publish(first, label="one")
with pytest.raises(RegistryError) as excinfo:
registry.publish(second, label="two")
assert excinfo.value.code == "capacity"
assert excinfo.value.fatal is True
# The incumbent may keep refreshing its own mirror.
registry.publish(first, label="one", width=1920, height=1080)
assert [s["label"] for s in registry.roster()] == ["one"]
# Capacity counts mirrors; an audience never uses it up.
assert registry.stats()["viewers"] == 1
assert registry.watching(viewer) == set()
class TestSubscribe:
def test_sharer_is_told_to_negotiate(self, mesh):
sharer, v1, _v2, registry = mesh
outbox = registry.subscribe(v1, sharer)
joined = message_for(outbox, sharer, "peer_joined")
assert joined["viewerId"] == v1
assert joined["name"] == "Phone"
assert message_for(outbox, v1, "stream_updated")["stream"]["viewers"] == 1
def test_subscribing_twice_is_idempotent(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
assert registry.subscribe(v1, sharer) == []
assert registry.streams[sharer].viewers == {v1}
def test_unknown_stream(self, registry):
viewer, _ = registry.join(role="viewer")
with pytest.raises(RegistryError) as excinfo:
registry.subscribe(viewer, "nope")
assert excinfo.value.code == "unknown_stream"
def test_a_sharer_cannot_even_watch_itself(self, mesh):
sharer, _v1, _v2, registry = mesh
with pytest.raises(RegistryError) as excinfo:
registry.subscribe(sharer, sharer)
assert excinfo.value.code == "not_viewer"
def test_a_sharer_cannot_subscribe_like_a_viewer(self, mesh):
sharer, _v1, _v2, registry = mesh
other, _ = registry.join(role="sharer", name="B")
registry.publish(other, label="B's screen")
with pytest.raises(RegistryError) as excinfo:
registry.subscribe(sharer, other)
assert excinfo.value.code == "not_viewer"
def test_unsubscribe_closes_the_leg(self, mesh):
sharer, v1, v2, registry = mesh
registry.subscribe(v1, sharer)
registry.subscribe(v2, sharer)
outbox = registry.unsubscribe(v1, sharer)
assert message_for(outbox, sharer, "peer_left")["viewerId"] == v1
assert message_for(outbox, v2, "stream_updated")["stream"]["viewers"] == 1
assert registry.unsubscribe(v1, sharer) == []
def test_unsubscribing_from_an_unknown_stream_is_quiet(self, registry):
viewer, _ = registry.join(role="viewer")
assert registry.unsubscribe(viewer, "ghost") == []
class TestSignalAuthorization:
def test_sharer_may_reach_a_subscribed_viewer(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
outbox = registry.signal(sharer, v1, {"sdp": {"type": "offer"}})
relay = message_for(outbox, v1, "signal")
assert relay["from"] == sharer
assert relay["payload"] == {"sdp": {"type": "offer"}}
def test_viewer_may_answer_the_sharer_it_watches(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
assert registry.signal(v1, sharer, {"candidate": {"candidate": "x"}}) == [
(
sharer,
{
"type": "signal",
"from": v1,
"to": sharer,
"payload": {"candidate": {"candidate": "x"}},
},
)
]
def test_sharer_may_not_blindly_push_to_a_stranger(self, mesh):
sharer, _v1, v2, registry = mesh
with pytest.raises(RegistryError) as excinfo:
registry.signal(sharer, v2, {"sdp": {"type": "offer"}})
assert excinfo.value.code == "not_subscribed"
def test_viewer_may_not_reach_a_sharer_it_never_subscribed_to(self, mesh):
_sharer, v1, _v2, registry = mesh
other, _ = registry.join(role="sharer", name="C")
registry.publish(other, label="private")
with pytest.raises(RegistryError) as excinfo:
registry.signal(v1, other, {})
assert excinfo.value.code == "not_subscribed"
def test_peers_may_not_talk_to_each_other_directly(self, mesh):
_sharer, v1, v2, registry = mesh
with pytest.raises(RegistryError) as excinfo:
registry.signal(v1, v2, {})
assert excinfo.value.code == "peer_to_peer"
def test_unknown_endpoints_are_refused(self, mesh):
sharer, _v1, _v2, registry = mesh
with pytest.raises(RegistryError) as excinfo:
registry.signal(sharer, "ghost", {})
assert excinfo.value.code == "unknown_peer"
class TestTeardown:
def test_sharer_disconnect_removes_the_mirror(self, mesh):
sharer, v1, v2, registry = mesh
registry.subscribe(v1, sharer)
registry.subscribe(v2, sharer)
outbox = registry.leave(sharer)
assert types_for(outbox, v1) == ["stream_removed"]
assert types_for(outbox, v2) == ["stream_removed"]
assert registry.streams == {}
assert registry.watching(v1) == set()
def test_viewer_disconnect_releases_the_sharer(self, mesh):
sharer, v1, v2, registry = mesh
registry.subscribe(v1, sharer)
registry.subscribe(v2, sharer)
outbox = registry.leave(v1)
assert message_for(outbox, sharer, "peer_left")["viewerId"] == v1
assert registry.streams[sharer].viewers == {v2}
def test_leaving_twice_is_quiet(self, mesh):
_sharer, v1, _v2, registry = mesh
registry.leave(v1)
assert registry.leave(v1) == []
def test_sharer_disconnect_after_a_crash_still_cleans_subscriptions(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
registry.leave(sharer)
# A stale viewer must not be able to signal the dead sharer.
with pytest.raises(RegistryError):
registry.signal(v1, sharer, {})
class TestRenameAndStats:
def test_rename_refreshes_the_mirrors_that_show_it(self, mesh):
sharer, v1, _v2, registry = mesh
outbox = registry.rename(sharer, name="Meeting Room")
assert message_for(outbox, v1, "stream_updated")["stream"]["name"] == "Meeting Room"
assert registry.roster()[0]["name"] == "Meeting Room"
def test_rename_to_the_same_name_is_quiet(self, mesh):
sharer, _v1, _v2, registry = mesh
assert registry.rename(sharer, name="Linux Box") == []
def test_stats_count_every_role(self, mesh):
sharer, v1, _v2, registry = mesh
registry.subscribe(v1, sharer)
assert registry.stats() == {
"mirrors": 1,
"sharers": 1,
"viewers": 2,
"connections": 3,
"watching": 1,
}
def test_roster_is_oldest_first(self, registry):
first, _ = registry.join(role="sharer", name="first")
registry.publish(first, label="first")
second, _ = registry.join(role="sharer", name="second")
registry.publish(second, label="second")
assert [s["label"] for s in registry.roster()] == ["first", "second"]