diff options
| author | historia <historiavg@proton.me> | 2026-09-12 15:25:06 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-12 15:25:06 -0400 |
| commit | 464edbc4fd658728f06d3a7d98340aa28ff3e3ec (patch) | |
| tree | a182f62ef0365f2c2143f88436de78a03d752367 /lib/project/tests/test_gui_launcher.py | |
| parent | 1bbe59f78be9a8355bb0bee2bc8ab14ca42ac71c (diff) | |
| download | producer-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.py | 113 |
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) |
