e4dcfc49aa
Tests / Lint and Format (push) Waiting to run
Tests / Web Node Tests (push) Waiting to run
Tests / Import Check (Python 3.11) (push) Waiting to run
Tests / Import Check (Python 3.12) (push) Waiting to run
Tests / Import Check (Python 3.13) (push) Waiting to run
Tests / Import Check (Python 3.14) (push) Waiting to run
Tests / Python Tests (Python 3.11) (push) Blocked by required conditions
Tests / Python Tests (Python 3.12) (push) Blocked by required conditions
Tests / Python Tests (Python 3.13) (push) Blocked by required conditions
Tests / Python Tests (Python 3.14) (push) Blocked by required conditions
Tests / Test Summary (push) Blocked by required conditions
188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import builtins
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from deeptutor.runtime import launcher
|
|
|
|
|
|
class _FakeTty:
|
|
def isatty(self) -> bool:
|
|
return True
|
|
|
|
|
|
def test_packaged_web_cache_replaces_next_public_placeholders(tmp_path: Path) -> None:
|
|
packaged = tmp_path / "pkg"
|
|
(packaged / ".next" / "static").mkdir(parents=True)
|
|
(packaged / "server.js").write_text(
|
|
"const api='__NEXT_PUBLIC_API_BASE_PLACEHOLDER__';",
|
|
encoding="utf-8",
|
|
)
|
|
(packaged / ".next" / "static" / "app.js").write_text(
|
|
"auth='__NEXT_PUBLIC_AUTH_ENABLED_PLACEHOLDER__'",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
runtime = launcher._copy_packaged_web_if_needed(
|
|
packaged,
|
|
home=tmp_path / "home",
|
|
api_base="http://localhost:8001",
|
|
auth_enabled=True,
|
|
)
|
|
|
|
assert (runtime / "server.js").read_text(encoding="utf-8") == (
|
|
"const api='http://localhost:8001';"
|
|
)
|
|
assert "auth='true'" in (runtime / ".next" / "static" / "app.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_packaged_web_cache_refreshes_when_public_settings_change(tmp_path: Path) -> None:
|
|
packaged = tmp_path / "pkg"
|
|
(packaged / ".next").mkdir(parents=True)
|
|
(packaged / "server.js").write_text(
|
|
"const api='__NEXT_PUBLIC_API_BASE_PLACEHOLDER__';",
|
|
encoding="utf-8",
|
|
)
|
|
home = tmp_path / "home"
|
|
|
|
first = launcher._copy_packaged_web_if_needed(
|
|
packaged,
|
|
home=home,
|
|
api_base="http://localhost:8001",
|
|
auth_enabled=False,
|
|
)
|
|
second = launcher._copy_packaged_web_if_needed(
|
|
packaged,
|
|
home=home,
|
|
api_base="https://api.example",
|
|
auth_enabled=False,
|
|
)
|
|
|
|
assert first == second
|
|
assert "https://api.example" in (second / "server.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def test_detect_existing_source_frontend_from_next_dev_lock(tmp_path: Path, monkeypatch) -> None:
|
|
source = tmp_path / "web"
|
|
lock = source / ".next" / "dev" / "lock"
|
|
lock.parent.mkdir(parents=True)
|
|
lock.write_text(
|
|
'{"pid":12345,"port":3999,"appUrl":"http://localhost:3999"}',
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setattr(launcher, "_is_pid_alive", lambda pid: pid == 12345)
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: False)
|
|
|
|
existing = launcher._detect_existing_source_frontend(
|
|
launcher.FrontendRuntime("source", [], source)
|
|
)
|
|
|
|
assert existing is not None
|
|
assert existing.url == "http://localhost:3999"
|
|
assert existing.port == 3999
|
|
assert existing.pid == 12345
|
|
assert existing.lock_path == lock
|
|
|
|
|
|
def test_detect_existing_source_frontend_ignores_stale_lock(tmp_path: Path, monkeypatch) -> None:
|
|
source = tmp_path / "web"
|
|
lock = source / ".next" / "dev" / "lock"
|
|
lock.parent.mkdir(parents=True)
|
|
lock.write_text(
|
|
'{"pid":12345,"port":3999,"appUrl":"http://localhost:3999"}',
|
|
encoding="utf-8",
|
|
)
|
|
monkeypatch.setattr(launcher, "_is_pid_alive", lambda pid: False)
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: False)
|
|
|
|
existing = launcher._detect_existing_source_frontend(
|
|
launcher.FrontendRuntime("source", [], source)
|
|
)
|
|
|
|
assert existing is None
|
|
|
|
|
|
def test_resolve_port_conflicts_passthrough_when_free(tmp_path: Path, monkeypatch) -> None:
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: False)
|
|
|
|
result = launcher._resolve_port_conflicts(
|
|
backend_port=8000,
|
|
frontend_port=3784,
|
|
check_frontend=True,
|
|
settings_dir=tmp_path,
|
|
)
|
|
|
|
assert result == (8000, 3784)
|
|
|
|
|
|
def test_resolve_port_conflicts_non_tty_exits_with_message(tmp_path: Path, monkeypatch) -> None:
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: port == 8000)
|
|
monkeypatch.setattr(launcher, "_port_listeners", lambda port: [(123, "python uvicorn")])
|
|
monkeypatch.setattr(launcher.sys, "stdin", None)
|
|
|
|
with pytest.raises(SystemExit) as excinfo:
|
|
launcher._resolve_port_conflicts(
|
|
backend_port=8000,
|
|
frontend_port=3784,
|
|
check_frontend=True,
|
|
settings_dir=tmp_path,
|
|
)
|
|
|
|
assert "8000" in str(excinfo.value)
|
|
|
|
|
|
def test_resolve_port_conflicts_kill_option_frees_port(tmp_path: Path, monkeypatch) -> None:
|
|
occupied = {8000}
|
|
killed: list[int] = []
|
|
|
|
def fake_kill(pid, pgid, sig):
|
|
killed.append(pid)
|
|
occupied.discard(8000)
|
|
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: port in occupied)
|
|
monkeypatch.setattr(launcher, "_port_listeners", lambda port: [(123, "python uvicorn")])
|
|
monkeypatch.setattr(launcher, "_send_tree_signal", fake_kill)
|
|
monkeypatch.setattr(launcher.sys, "stdin", _FakeTty())
|
|
monkeypatch.setattr(builtins, "input", lambda prompt="": "2")
|
|
|
|
result = launcher._resolve_port_conflicts(
|
|
backend_port=8000,
|
|
frontend_port=3784,
|
|
check_frontend=True,
|
|
settings_dir=tmp_path,
|
|
)
|
|
|
|
assert result == (8000, 3784)
|
|
assert killed == [123]
|
|
|
|
|
|
def test_resolve_port_conflicts_change_option_prompts_and_persists(
|
|
tmp_path: Path, monkeypatch
|
|
) -> None:
|
|
saved: dict[str, int] = {}
|
|
|
|
def fake_persist(settings_dir, backend_port, frontend_port):
|
|
saved["backend"] = backend_port
|
|
saved["frontend"] = frontend_port
|
|
return settings_dir / "system.json"
|
|
|
|
answers = iter(["1", "8002", "3785"])
|
|
|
|
monkeypatch.setattr(launcher, "_port_accepts_connection", lambda port: port == 8000)
|
|
monkeypatch.setattr(launcher, "_port_listeners", lambda port: [(123, "python uvicorn")])
|
|
monkeypatch.setattr(launcher, "_persist_ports", fake_persist)
|
|
monkeypatch.setattr(launcher.sys, "stdin", _FakeTty())
|
|
monkeypatch.setattr(builtins, "input", lambda prompt="": next(answers))
|
|
|
|
result = launcher._resolve_port_conflicts(
|
|
backend_port=8000,
|
|
frontend_port=3784,
|
|
check_frontend=True,
|
|
settings_dir=tmp_path,
|
|
)
|
|
|
|
assert result == (8002, 3785)
|
|
assert saved == {"backend": 8002, "frontend": 3785}
|