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
+71 -15
+71 -15
View File
@@ -13,11 +13,12 @@ import threading
import time
import uuid
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse
import httpx
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"
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 ───────────────────────────────────
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
container reaches it on 127.0.0.1.
Binds 0.0.0.0 and advertises this host's LAN IP so the deployment host's
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):
def do_GET(self):
@@ -57,10 +60,19 @@ def _serve_fake_audio(payload: bytes):
def log_message(self, *args):
pass
server = HTTPServer(("127.0.0.1", 0), Handler)
server = HTTPServer(("0.0.0.0", 0), Handler)
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()
return server, f"http://127.0.0.1:{port}/audio.mp3"
return server, f"http://{lan_ip}:{port}/audio.mp3"
# ── health + basic API shape ────────────────────────────────────────────────
@@ -171,27 +183,60 @@ def test_media_player_state_flow(client):
def test_media_stream_proxy_and_range(client):
"""External audio URLs must be reachable same-origin (HTTPS) and support
Range passthrough so the browser can seek progressive streams."""
payload = b"0123456789" * 100
upstream, url = _serve_fake_audio(payload)
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
upstream, url = _serve_fake_audio(payload)
track_id = None
try:
track_id = client.post(
f"{MEDIA_API}/streams", json={"name": "Range FM", "url": url}
).json()["track"]["id"]
full = client.get(f"{MEDIA_API}/stream/{track_id}")
assert full.status_code == 200
assert full.content == payload
ranged = client.get(
f"{MEDIA_API}/stream/{track_id}", headers={"Range": "bytes=100-199"}
)
assert ranged.status_code == 206
assert ranged.content == payload[100:200]
assert ranged.headers["content-range"] == f"bytes 100-199/{len(payload)}"
finally:
upstream.shutdown()
if track_id:
try:
client.delete(f"{MEDIA_API}/tracks/{track_id}")
except httpx.HTTPError:
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", "url": url}
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.content == payload
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=100-199"}
f"{MEDIA_API}/stream/{track_id}", headers={"Range": "bytes=0-1023"}
)
assert ranged.status_code == 206
assert ranged.content == payload[100:200]
assert ranged.headers["content-range"] == f"bytes 100-199/{len(payload)}"
assert len(ranged.content) == 1024
assert (ranged.headers.get("content-range") or "").startswith("bytes 0-1023/")
finally:
upstream.shutdown()
if track_id:
try:
client.delete(f"{MEDIA_API}/tracks/{track_id}")
@@ -286,13 +331,22 @@ def test_token_response_includes_room():
# ── 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_SERVER_URL": "http://127.0.0.1:8093",
"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):
text, is_error = mcp_client_factory(
"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()
@needs_local_container
def test_mcp_media_add_stream_and_now_playing(mcp_client_factory, client):
upstream, url = _serve_fake_audio(b"\x00" * 16)
try:
@@ -335,6 +390,7 @@ def test_mcp_media_add_stream_and_now_playing(mcp_client_factory, client):
upstream.shutdown()
@needs_local_container
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) so Hope can explain them naturally."""