**Phase 02 final verification pass — complete, 100% green.** **Verified (all 4 tasks in `complete/02_sharp_capture/`):** - Task 1: `pickSurface()` requests `screen × devicePixelRatio` (capped 3840×2160) instead of window size - Task 2: `degradationPreference: "maintain-resolution"` + per-leg `maxBitrate`, re-applied after `setLocalDescription` *and* on `connected` (both call sites asserted in `test_app.py`) - Task 3: default cap raised to 25 Mbit/s — `config.py` constant, `net.js` offline fallback, `.env.example`, README, and the drift-guard test all agree - Task 4: realdisplay sharpness floor (viewer decoded width ≥ 1152px = 90% of Xvfb 1280px) in `test_sharp_picture_reaches_the_wall` **Gates (all green):** - `uv run ruff check . && uv run ruff format --check .` → clean - `uv run pyright` → 0 errors - `uv run pytest` → 231 passed, 3 skipped (realdisplay), coverage 95% - `MIRROR_REALDISPLAY=1 uv run pytest -m realdisplay` → 3 passed on a real Xvfb + headed Chromium **Completion criteria:** (1) 90% width floor asserted on viewer's decoded pixels — PASS · (2) `/api/config` `maxBitrateBps: 25000000` default, net.js fallback agrees — PASS · (3) `#stat-resolution` matches viewer-decoded size incl. documented window downscale (1100×620, 1280×720 fake-device tests) — PASS · (4) both suites green, aspect-ratio guard (`1280/800` ± 0.06 + `≠ 1280×720`) still distinguishes real from fake capture — PASS · (5) README Configuration documents `25000000` — PASS; `PLAN.md` §6 "default 12 Mbit/s" left untouched as instructed — **flag for the user to amend**. **Docs:** README — fixed suite timing `~20s → ~50s` (measured 49s); all other claims verified (CLI flags, dev extra, config table, kiosk key table vs. `watch.js`/`share.js` routers, kiosk commands vs. `docs/kiosk-silverblue.md`). AGENTS.md — same stale timing fixed, nothing else stale (all referenced test names/files exist). **Notable:** `docs/kiosk-silverblue.md` + its scripts use `mirror.reeselink.com` while README/SECURITY.md/Containerfile use `mirror.reeseapps.com` — a pre-existing cross-file mismatch I could not resolve from the repo (both in same-day commits; live-infra fact). Left untouched; user should confirm the live kiosk URL. No live changes made. **Next pending phase:** `03_audio_share`.
203 lines
8.1 KiB
Python
203 lines
8.1 KiB
Python
"""Configuration parsing, the CLI surface, and the debugpy hook."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from mirror import __main__ as entry
|
|
from mirror.access import ConfigError, parse_cidrs
|
|
from mirror.config import (
|
|
DEFAULT_MAX_BITRATE_BPS,
|
|
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",
|
|
"MIRROR_ALLOW_CIDRS",
|
|
"MIRROR_TRUSTED_PROXIES",
|
|
):
|
|
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_bitrate_bps == DEFAULT_MAX_BITRATE_BPS
|
|
assert settings.max_streams == 0
|
|
# Unrestricted until told otherwise, and only loopback may speak for a client.
|
|
assert settings.allow_cidrs == ()
|
|
assert [str(network) for network in settings.trusted_proxies] == [
|
|
"127.0.0.0/8",
|
|
"::1/128",
|
|
]
|
|
assert settings.gate().enabled is False
|
|
|
|
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"}]')
|
|
monkeypatch.setenv("MIRROR_ALLOW_CIDRS", "10.2.0.0/22, fd00::/64")
|
|
monkeypatch.setenv("MIRROR_TRUSTED_PROXIES", "10.1.1.27")
|
|
settings = Settings.from_env()
|
|
assert (settings.host, settings.port) == ("127.0.0.1", 9123)
|
|
assert [str(network) for network in settings.allow_cidrs] == ["10.2.0.0/22", "fd00::/64"]
|
|
assert [str(network) for network in settings.trusted_proxies] == ["10.1.1.27/32"]
|
|
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_a_bad_cidr_refuses_to_start_rather_than_failing_open(self, monkeypatch):
|
|
monkeypatch.setenv("MIRROR_ALLOW_CIDRS", "office")
|
|
with pytest.raises(ConfigError, match="MIRROR_ALLOW_CIDRS"):
|
|
Settings.from_env()
|
|
|
|
def test_the_gate_carries_both_halves_of_the_setting(self):
|
|
settings = Settings(
|
|
allow_cidrs=parse_cidrs("10.2.0.0/22"), trusted_proxies=parse_cidrs("127.0.0.1")
|
|
)
|
|
gate = settings.gate()
|
|
assert gate.enabled is True
|
|
assert gate.check("127.0.0.1", "10.2.1.5") == (True, "10.2.1.5")
|
|
|
|
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_allow_cidr_is_repeatable_and_replaces_the_environment(self, monkeypatch):
|
|
monkeypatch.setenv("MIRROR_ALLOW_CIDRS", "192.168.0.0/16")
|
|
args = entry.build_parser().parse_args(
|
|
["serve", "--allow-cidr", "10.2.0.0/22", "--allow-cidr", "fd00::/64"]
|
|
)
|
|
assert args.allow_cidr == ["10.2.0.0/22", "fd00::/64"]
|
|
# cmd_serve replaces rather than merges, so the environment cannot leave a
|
|
# second, wider network open by accident.
|
|
assert [str(network) for network in parse_cidrs(",".join(args.allow_cidr))] == [
|
|
"10.2.0.0/22",
|
|
"fd00::/64",
|
|
]
|
|
|
|
@pytest.mark.parametrize(
|
|
("given", "expected"),
|
|
[
|
|
([], ["serve"]),
|
|
(["--port", "9999"], ["serve", "--port", "9999"]),
|
|
(["--allow-cidr", "10.2.0.0/22"], ["serve", "--allow-cidr", "10.2.0.0/22"]),
|
|
(["serve"], ["serve"]),
|
|
(["serve", "--version"], ["serve", "--version"]),
|
|
(["--version"], ["--version"]),
|
|
(["--help"], ["--help"]),
|
|
(["-h"], ["-h"]),
|
|
],
|
|
)
|
|
def test_only_informational_flags_escape_the_implicit_serve(self, given, expected):
|
|
assert entry.with_serve(given) == expected
|
|
|
|
@pytest.mark.parametrize("argv", [["--version"], ["serve", "--version"]])
|
|
def test_version_prints_and_exits_cleanly(self, argv, capsys):
|
|
with pytest.raises(SystemExit) as caught:
|
|
entry.main(argv)
|
|
assert caught.value.code == 0
|
|
assert f"mirror {entry.__version__}" in capsys.readouterr().out
|
|
|
|
def test_help_lists_the_subcommand_it_is_not_being_rewritten_into(self, capsys):
|
|
with pytest.raises(SystemExit):
|
|
entry.main(["--help"])
|
|
out = capsys.readouterr().out
|
|
assert "usage: mirror" in out
|
|
assert "serve" in out
|
|
|
|
def test_a_bad_allow_cidr_exits_with_a_message_not_a_traceback(self, capsys):
|
|
# Config is parsed before anything binds or imports, so this cannot stall
|
|
# on a stray DEBUGPY in the operator's shell.
|
|
code = entry.cmd_serve(
|
|
entry.build_parser().parse_args(["serve", "--allow-cidr", "nonsense"])
|
|
)
|
|
assert code == 2
|
|
assert "--allow-cidr" in capsys.readouterr().err
|
|
|
|
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
|