fix: five from the full review

- doctor printed OPENAI_BASE_URL raw, the third place a credential-bearing URL
  reached the terminal; it goes through _endpoint like the others.
- dev.run left its output readers unjoined, so a child's last lines could be
  lost on exit. The threads are kept and joined during teardown.
- logs read each file and then reopened it to follow, so anything written in
  between appeared in neither. One handle now serves both.
- dev took --port straight from argparse: 0 would have served on an ephemeral
  port while printing 0, and oversized values reach socket.bind. It goes
  through _port_number first.
- doctor --redis-url overrode only the broker, so a stale result backend or
  cache was still pinged and the flag looked broken. All three endpoints now
  come from the URL given.
This commit is contained in:
Alex committed 2026-09-17 13:15:56 +01:00
1 parent d0a0b352e0
commit da58c072a0
4 files changed
+131 -18

No files matched your search

+11
View File
@@ -123,6 +123,17 @@ class TestRun:
assert "api | hello" in out.getvalue()
assert code == 1, "a child that ends by itself ends the session, however it exited"
def test_output_in_flight_is_not_lost_when_run_returns(self, tmp_path):
"""The reader is a thread: without joining it, a child's last lines can never be printed."""
out = io.StringIO()
lines = [f"line {number}\n" for number in range(200)]
children = [dev.Child(name="api", command=["x"], cwd=tmp_path)]
code = dev.run(children, out=out, spawn=self._spawn([FakeProcess(lines, code=0)]),
sleep=lambda _: None, colour=False)
assert code == 1
printed = out.getvalue()
assert "line 0" in printed and "line 199" in printed, "every line the child wrote is printed"
def test_a_failing_child_returns_its_code(self, tmp_path):
out = io.StringIO()
children = [dev.Child(name="api", command=["false"], cwd=tmp_path)]
+71
View File
@@ -1,5 +1,6 @@
"""`docsgpt doctor`, `restart`, following native logs, and settings that apply themselves."""
import argparse
import socket
import pytest
@@ -49,6 +50,18 @@ class TestDevCommand:
_run(["dev", "--port", str(port)], _context())
assert started == [], "nothing is spawned when the port is taken"
@pytest.mark.parametrize("port", ["0", "-1", "70000"])
def test_a_port_argparse_accepts_but_a_socket_cannot_use(self, monkeypatch, tmp_path, port):
"""argparse takes any integer: 0 would serve on an ephemeral port while printing 0."""
from docsgpt.deploy import dev as dev_module
self._checkout(monkeypatch, tmp_path)
started = []
monkeypatch.setattr(dev_module, "run", lambda children, **kwargs: started.append(children) or 0)
with pytest.raises(DeployError, match="not a port number"):
_run(["dev", "--port", port], _context())
assert started == []
def test_a_busy_mock_llm_port_is_refused_before_anything_starts(self, monkeypatch, tmp_path):
"""It starts first and the rest are pointed at it, so a clash cannot wait until spawn time."""
from docsgpt.deploy import dev as dev_module
@@ -184,6 +197,33 @@ class TestFollowLogs:
assert "old line" not in printed, "it starts at the end, like tail -f"
class TestLogsTransition:
def test_a_line_written_between_the_read_and_the_follow_is_not_lost(self, tmp_path, capsys, monkeypatch):
"""The read and the follow used to open the file twice, and whatever landed between was gone."""
services = FakeServices()
argv = ["up", "--native", "--dir", str(tmp_path), "--yes", "--postgres-uri", "postgresql://localhost/d"]
assert _run(argv, _native_context(services)) == 0
logs = tmp_path / "logs"
(logs / "api.log").write_text("first\n", encoding="utf-8")
def sleep(_):
raise KeyboardInterrupt
original = commands._follow
def follow(logs_dir, wanted, handles=None):
# Written after the read, before following starts.
(logs / "api.log").open("a", encoding="utf-8").write("during\n")
monkeypatch.setattr(commands.time, "sleep", sleep)
return original(logs_dir, wanted, handles)
monkeypatch.setattr(commands, "_follow", follow)
assert _run(["logs", "-f", "api", "--dir", str(tmp_path)], _native_context(services)) == 0
printed = capsys.readouterr().out
assert "first" in printed
assert "during" in printed, "the line written during the handover has to appear"
class TestChecks:
def test_the_public_api_needs_no_key(self):
check = commands._check_provider({"LLM_PROVIDER": "docsgpt"})
@@ -201,6 +241,17 @@ class TestChecks:
assert check.level == "ok"
assert "8090" in check.detail
def test_a_provider_base_url_is_printed_without_its_credentials(self):
"""An OpenAI-compatible endpoint can carry userinfo, and doctor prints its detail."""
check = commands._check_provider(
{"LLM_PROVIDER": "openai", "API_KEY": "x",
"OPENAI_BASE_URL": "https://someone:sEcReTtOkEn@models.example.com/v1"}
)
assert check.level == "ok"
assert "sEcReTtOkEn" not in check.detail
assert "someone" not in check.detail
assert "models.example.com" in check.detail
def test_services_are_named_for_the_install(self, tmp_path):
names = _names(tmp_path)
assert commands._chosen_services(names, []) == list(names)
@@ -400,6 +451,26 @@ class TestMigrationHead:
assert head[0].isdigit(), head
class TestRedisSelection:
def test_redis_url_replaces_every_endpoint(self):
"""Overriding only the broker would still ping a stale backend, and one failure fails all."""
args = argparse.Namespace(redis_url="redis://given:6379/5")
stale = {
"CELERY_BROKER_URL": "redis://old:6379/0",
"CELERY_RESULT_BACKEND": "redis://old:6379/1",
"CACHE_REDIS_URL": "redis://old:6379/2",
}
chosen = commands._redis_to_check(args, stale)
assert set(chosen) == {"broker", "results", "cache"}
assert all("old" not in url for url in chosen.values()), chosen
assert chosen["broker"].endswith("/5") and chosen["cache"].endswith("/7")
def test_without_the_flag_the_settings_are_used(self):
args = argparse.Namespace(redis_url=None)
chosen = commands._redis_to_check(args, {"CELERY_BROKER_URL": "redis://localhost:6379/0"})
assert chosen == {"broker": "redis://localhost:6379/0"}
class TestDoctor:
def _only(self, monkeypatch, postgres, redis):
monkeypatch.setattr(commands, "_check_postgres", lambda uri: postgres)