"""Tests for the optional MJPEG web viewer (stdlib only -- no cv2 / hardware). Binds an ephemeral localhost port, hits each route, and checks the framing. """ from __future__ import annotations import time import urllib.request import pytest from gowelcome.web import MjpegServer # A minimal valid JPEG (SOI ... EOI) used as the stub frame. _FAKE_JPEG = bytes.fromhex("ffd8ffe000104a46494600010100000100010000ffd9") @pytest.fixture() def server(): srv = MjpegServer(lambda: _FAKE_JPEG, host="127.0.0.1", port=0, fps=30) srv.start() time.sleep(0.2) try: yield srv finally: srv.stop() def _get(port: int, path: str, n: int | None = None): resp = urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=3) return resp.status, resp.headers.get("Content-Type"), (resp.read(n) if n else resp.read()) def test_healthz(server): status, ctype, body = _get(server.port, "/healthz") assert status == 200 assert body == b"ok" def test_index_embeds_stream(server): status, ctype, body = _get(server.port, "/") assert status == 200 assert "text/html" in ctype assert b"/stream.mjpg" in body def test_snapshot_returns_jpeg(server): status, ctype, body = _get(server.port, "/snapshot.jpg") assert status == 200 assert ctype == "image/jpeg" assert body[:2] == b"\xff\xd8" # JPEG SOI def test_stream_is_multipart_mjpeg(server): resp = urllib.request.urlopen(f"http://127.0.0.1:{server.port}/stream.mjpg", timeout=3) assert "multipart/x-mixed-replace" in resp.headers.get("Content-Type", "") chunk = resp.read(400) resp.close() assert b"--gowelcomeframe" in chunk assert b"image/jpeg" in chunk def test_unknown_path_404(server): with pytest.raises(urllib.error.HTTPError) as exc: _get(server.port, "/nope") assert exc.value.code == 404 def test_stop_is_idempotent(server): server.stop() server.stop() # must not raise