aboutsummaryrefslogtreecommitdiff
path: root/lib/project/tests/test_gui_launcher.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-12 15:25:06 -0400
committerhistoria <historiavg@proton.me>2026-09-12 15:25:06 -0400
commit464edbc4fd658728f06d3a7d98340aa28ff3e3ec (patch)
treea182f62ef0365f2c2143f88436de78a03d752367 /lib/project/tests/test_gui_launcher.py
parent1bbe59f78be9a8355bb0bee2bc8ab14ca42ac71c (diff)
downloadproducer-464edbc4fd658728f06d3a7d98340aa28ff3e3ec.tar.gz
feat: slop web gui
Diffstat (limited to 'lib/project/tests/test_gui_launcher.py')
-rw-r--r--lib/project/tests/test_gui_launcher.py113
1 files changed, 113 insertions, 0 deletions
diff --git a/lib/project/tests/test_gui_launcher.py b/lib/project/tests/test_gui_launcher.py
new file mode 100644
index 0000000..8111c67
--- /dev/null
+++ b/lib/project/tests/test_gui_launcher.py
@@ -0,0 +1,113 @@
+"""Exercise bootstrap/config routing without downloading or changing the real runtime."""
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+
+import pytest
+
+from producer import cli
+
+LAUNCHER = Path(__file__).resolve().parents[3] / "producer.sh"
+
+
+def test_cli_gui_dispatch_preserves_configuration_flags(tmp_path, monkeypatch):
+ from producer import gui
+ config = tmp_path / "config.toml"
+ config.write_text('profile = "narrator"\n')
+ received = []
+ monkeypatch.setattr(gui, "serve", lambda *args, **kwargs: received.append((args, kwargs)))
+ monkeypatch.setattr(sys, "argv", ["producer", "gui", "--root", str(tmp_path), "--port", "9001",
+ "--config", str(config), "--profile", "radio", "--target-lufs", "-21", "--set", "target_lufs=-18"])
+ cli.main()
+ assert received == [((tmp_path, 9001), {"config": config, "profile": "radio", "overrides": {"target_lufs": -18}})]
+
+
+def test_launcher_only_gui_installs_extra_and_preserves_config_injection(tmp_path):
+ checkout = tmp_path / "checkout"
+ checkout.mkdir()
+ launcher = checkout / "producer.sh"
+ launcher.write_text(LAUNCHER.read_text())
+ launcher.chmod(0o755)
+ (checkout / "config.toml").write_text('profile = "narrator"\n')
+ (checkout / "lib/project").mkdir(parents=True)
+ tools = tmp_path / "tools"
+ tools.mkdir()
+ log, forwarded = tmp_path / "install.jsonl", tmp_path / "forwarded.json"
+ fake_python = (f"#!{sys.executable}\nimport json,sys\nfrom pathlib import Path\n"
+ f"if '-m' in sys.argv: Path({str(forwarded)!r}).write_text(json.dumps(sys.argv[1:]))\n")
+ uv = tools / "uv"
+ uv.write_text(f"#!{sys.executable}\n"
+ "import json,sys\nfrom pathlib import Path\n"
+ f"with Path({str(log)!r}).open('a') as out: out.write(json.dumps(sys.argv[1:])+'\\n')\n"
+ "if sys.argv[1] == 'venv':\n"
+ " python = Path(sys.argv[-1]) / 'bin/python'\n"
+ " python.parent.mkdir(parents=True, exist_ok=True)\n"
+ f" python.write_text({fake_python!r})\n"
+ " python.chmod(0o755)\n")
+ uv.chmod(0o755)
+ env = os.environ.copy() | {"PATH": f"{tools}:{os.environ['PATH']}"}
+ def launch(*args):
+ result = subprocess.run([str(launcher), *args], cwd=checkout, env=env,
+ capture_output=True, text=True, timeout=15)
+ assert result.returncode == 0, result.stderr
+ return json.loads(forwarded.read_text())
+ args = launch("config", "--json")
+ assert args == ["-I", "-m", "producer", "config", f"--config={checkout / 'config.toml'}", "--json"]
+ calls = [json.loads(line) for line in log.read_text().splitlines()]
+ installs = [call for call in calls if call[0] == "pip"]
+ assert len(installs) == 1 and installs[0][-1].endswith("[bundled-ffmpeg]")
+ args = launch("gui", "--root", str(checkout), "--port", "8766")
+ assert args[3:5] == ["gui", f"--config={checkout / 'config.toml'}"]
+ calls = [json.loads(line) for line in log.read_text().splitlines()]
+ installs = [call for call in calls if call[0] == "pip"]
+ assert len(installs) == 2 and installs[1][-1].endswith("[bundled-ffmpeg,gui]")
+ before = log.read_text()
+ args = launch("gui", "--config=custom.toml")
+ assert args[-2:] == ["gui", "--config=custom.toml"]
+ launch("config")
+ assert log.read_text() == before # GUI/core marker reuse, no extra installs.
+
+
+def test_cli_commands_import_without_optional_web_dependencies():
+ # A fresh interpreter explicitly denies optional imports, even if installed.
+ code = """
+import sys
+class Deny:
+ def find_spec(self, fullname, *args):
+ if fullname.split('.')[0] in ('fastapi', 'uvicorn', 'starlette'):
+ raise AssertionError('CLI imported optional GUI dependency: ' + fullname)
+sys.meta_path.insert(0, Deny())
+from producer.cli import parser
+assert parser().parse_args(['config']).command == 'config'
+"""
+ result = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True)
+ assert result.returncode == 0, result.stderr
+
+
+def test_server_binds_only_loopback_and_prints_fragment_token(tmp_path, monkeypatch, capsys):
+ pytest.importorskip("fastapi")
+ uvicorn = pytest.importorskip("uvicorn")
+ import socket
+ from producer import gui
+ with socket.socket() as reservation:
+ reservation.bind(("127.0.0.1", 0))
+ port = reservation.getsockname()[1]
+ seen = []
+ class Server:
+ def __init__(self, config):
+ assert config.host == "127.0.0.1"
+ assert config.proxy_headers is False
+ assert config.access_log is False
+ def run(self, sockets):
+ seen.append(sockets[0].getsockname())
+ monkeypatch.setattr(uvicorn, "Server", Server)
+ gui.serve(tmp_path, port)
+ assert seen == [("127.0.0.1", port)]
+ output = capsys.readouterr().out
+ assert f"http://127.0.0.1:{port}/#token=" in output
+ assert len(output.split("#token=")[1].splitlines()[0]) >= 43
+ for invalid in (0, -1, 65536):
+ with pytest.raises(ValueError):
+ gui.serve(tmp_path, invalid)