test: stream-proxy test works against remote deployments
Rebuild & Deploy / rebuild (push) Successful in 14s

The fake audio upstream bound the test host's loopback, which the Unraid
media server cannot reach (and its LAN route is firewalled). Local runs
keep the controlled upstream; remote runs use a public reference mp3 and
assert Range semantics without byte equality. docker-exec MCP probes
skip on remote targets (they necessarily exercise the local container).
This commit is contained in:
snowspeeder committed 2026-08-29 16:09:42 -04:00
1 parent ce8fd1e046
commit d89918fd2a
1 file changed
+63 -7
+63 -7
View File
@@ -13,11 +13,12 @@ import threading
import time import time
import uuid import uuid
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
import httpx import httpx
import pytest import pytest
from conftest import TOKEN_URL, docker_exec, IS_LOCAL_DEPLOYMENT from conftest import TOKEN_URL, docker_exec, IS_LOCAL_DEPLOYMENT, WEB_BASE_URL
MEDIA_API = "/media/api" MEDIA_API = "/media/api"
TEST_ROOM = f"media-test-{uuid.uuid4().hex[:8]}" TEST_ROOM = f"media-test-{uuid.uuid4().hex[:8]}"
@@ -26,11 +27,13 @@ TEST_ROOM = f"media-test-{uuid.uuid4().hex[:8]}"
# ── local upstream for stream-proxy tests ─────────────────────────────────── # ── local upstream for stream-proxy tests ───────────────────────────────────
def _serve_fake_audio(payload: bytes): def _serve_fake_audio(payload: bytes):
"""Tiny Range-capable HTTP server on an ephemeral host port. """Tiny Range-capable HTTP server on an ephemeral port.
The container uses host networking, so the media server inside the Binds 0.0.0.0 and advertises this host's LAN IP so the deployment host's
container reaches it on 127.0.0.1. media server (which may be a different machine — Unraid) can reach it
over the LAN when fetching external stream URLs.
""" """
import socket
class Handler(BaseHTTPRequestHandler): class Handler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
@@ -57,10 +60,19 @@ def _serve_fake_audio(payload: bytes):
def log_message(self, *args): def log_message(self, *args):
pass pass
server = HTTPServer(("127.0.0.1", 0), Handler) server = HTTPServer(("0.0.0.0", 0), Handler)
port = server.server_address[1] port = server.server_address[1]
# The local IP toward the deployment host (works across the LAN).
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
probe.connect((urlparse(WEB_BASE_URL).hostname or "192.168.86.2", 9178))
lan_ip = probe.getsockname()[0]
except OSError:
lan_ip = "127.0.0.1"
finally:
probe.close()
threading.Thread(target=server.serve_forever, daemon=True).start() threading.Thread(target=server.serve_forever, daemon=True).start()
return server, f"http://127.0.0.1:{port}/audio.mp3" return server, f"http://{lan_ip}:{port}/audio.mp3"
# ── health + basic API shape ──────────────────────────────────────────────── # ── health + basic API shape ────────────────────────────────────────────────
@@ -171,7 +183,13 @@ def test_media_player_state_flow(client):
def test_media_stream_proxy_and_range(client): def test_media_stream_proxy_and_range(client):
"""External audio URLs must be reachable same-origin (HTTPS) and support """External audio URLs must be reachable same-origin (HTTPS) and support
Range passthrough so the browser can seek progressive streams.""" Range passthrough so the browser can seek progressive streams.
Local deployments test against a controlled fake upstream; remote
deployments (Unraid) cannot reach the test host's ephemeral ports, so a
public reference mp3 is used and Range semantics are asserted without
byte equality."""
if IS_LOCAL_DEPLOYMENT:
payload = b"0123456789" * 100 payload = b"0123456789" * 100
upstream, url = _serve_fake_audio(payload) upstream, url = _serve_fake_audio(payload)
track_id = None track_id = None
@@ -197,6 +215,33 @@ def test_media_stream_proxy_and_range(client):
client.delete(f"{MEDIA_API}/tracks/{track_id}") client.delete(f"{MEDIA_API}/tracks/{track_id}")
except httpx.HTTPError: except httpx.HTTPError:
pass pass
return
# Remote deployment: public reference audio (SoundHelix).
url = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
track_id = None
try:
track_id = client.post(
f"{MEDIA_API}/streams", json={"name": "Range FM public", "url": url}
).json()["track"]["id"]
full = client.get(f"{MEDIA_API}/stream/{track_id}")
assert full.status_code == 200
assert full.headers.get("content-type", "").startswith("audio/")
assert len(full.content) > 100_000
ranged = client.get(
f"{MEDIA_API}/stream/{track_id}", headers={"Range": "bytes=0-1023"}
)
assert ranged.status_code == 206
assert len(ranged.content) == 1024
assert (ranged.headers.get("content-range") or "").startswith("bytes 0-1023/")
finally:
if track_id:
try:
client.delete(f"{MEDIA_API}/tracks/{track_id}")
except httpx.HTTPError:
pass
def test_media_files_static_range_support(client): def test_media_files_static_range_support(client):
@@ -286,13 +331,22 @@ def test_token_response_includes_room():
# ── Hope's media MCP toolset ──────────────────────────────────────────────── # ── Hope's media MCP toolset ────────────────────────────────────────────────
# These spawn the MCP server inside the LOCAL dev container (docker exec),
# whose media server is the local one — they can only be consistent when
# the test target IS the local deployment.
MEDIA_MCP_ENV = { MEDIA_MCP_ENV = {
"MEDIA_SERVER_URL": "http://127.0.0.1:8093", "MEDIA_SERVER_URL": "http://127.0.0.1:8093",
"MEDIA_ROOM": "mcp-test-room", "MEDIA_ROOM": "mcp-test-room",
} }
needs_local_container = pytest.mark.skipif(
not IS_LOCAL_DEPLOYMENT,
reason="docker-exec MCP probes run against the local dev container",
)
@needs_local_container
def test_mcp_media_list_library(mcp_client_factory): def test_mcp_media_list_library(mcp_client_factory):
text, is_error = mcp_client_factory( text, is_error = mcp_client_factory(
"media_mcp.py", "list_library", {}, env=MEDIA_MCP_ENV "media_mcp.py", "list_library", {}, env=MEDIA_MCP_ENV
@@ -301,6 +355,7 @@ def test_mcp_media_list_library(mcp_client_factory):
assert "library" in text.lower() assert "library" in text.lower()
@needs_local_container
def test_mcp_media_add_stream_and_now_playing(mcp_client_factory, client): def test_mcp_media_add_stream_and_now_playing(mcp_client_factory, client):
upstream, url = _serve_fake_audio(b"\x00" * 16) upstream, url = _serve_fake_audio(b"\x00" * 16)
try: try:
@@ -335,6 +390,7 @@ def test_mcp_media_add_stream_and_now_playing(mcp_client_factory, client):
upstream.shutdown() upstream.shutdown()
@needs_local_container
def test_mcp_media_resume_without_load(mcp_client_factory, client): def test_mcp_media_resume_without_load(mcp_client_factory, client):
"""Errors from the media server reach the LLM as tool text (not MCP """Errors from the media server reach the LLM as tool text (not MCP
errors) so Hope can explain them naturally.""" errors) so Hope can explain them naturally."""