aboutsummaryrefslogtreecommitdiff
path: root/lib/project/tests/test_gui_launcher.py
blob: 758d034fa6edda9c150f5f11ffda7b7c69309f07 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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", "--port", "9001",
        "--config", str(config), "--profile", "radio", "--target-lufs", "-21", "--set", "target_lufs=-18"])
    cli.main()
    assert received == [((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", "--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_plain_url(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(port)
    assert seen == [("127.0.0.1", port)]
    output = capsys.readouterr().out
    assert f"http://127.0.0.1:{port}/\n" in output
    assert "#token" not in output
    for invalid in (0, -1, 65536):
        with pytest.raises(ValueError):
            gui.serve(invalid)