3f449d3600
原先把 Docker Desktop.exe 写死在 %ProgramFiles%\Docker\Docker\,装在 D 盘就自动拉起 失败(明明 docker CLI 已在 PATH 上可用、只是 daemon 没启)。改为按可靠性排序的多路探测: DOCKER_DESKTOP_EXE 显式指定 → 从 PATH 上 docker CLI 反推安装目录(跟随实际盘符)→ 注册表 → 各 Program Files 变体兜底;找不到时提示 DOCKER_DESKTOP_EXE / sqlite 逃生舱。 .env.example 记一笔用法。 顺带修一个先前就红、与现行"sqlite→降级返 True"契约矛盾的测试 (test_ensure_rejects_sqlite → test_ensure_sqlite_escape_hatch)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
130 lines
4.1 KiB
Python
130 lines
4.1 KiB
Python
"""scripts/ensure_pg.py 纯函数单测(不需要 Docker/PG)。"""
|
|
from __future__ import annotations
|
|
|
|
import socket
|
|
|
|
from scripts.ensure_pg import (
|
|
_docker_desktop_cmd,
|
|
_docker_desktop_from_registry,
|
|
_is_sqlite,
|
|
_parse_host_port,
|
|
_port_open,
|
|
_win_docker_desktop_candidates,
|
|
ensure,
|
|
)
|
|
|
|
|
|
def test_is_sqlite():
|
|
assert _is_sqlite("sqlite:///./data/app.db")
|
|
assert _is_sqlite(" SQLite:///x ")
|
|
assert not _is_sqlite("postgresql+psycopg://u:p@localhost:5432/db")
|
|
|
|
|
|
def test_parse_host_port_full():
|
|
assert _parse_host_port(
|
|
"postgresql+psycopg://u:p@localhost:5432/shaguabijia"
|
|
) == ("localhost", 5432)
|
|
|
|
|
|
def test_parse_host_port_defaults():
|
|
# 缺端口 → 5432
|
|
assert _parse_host_port("postgresql+psycopg://u:p@db.example/x")[1] == 5432
|
|
# 缺 host → localhost
|
|
assert _parse_host_port("postgresql+psycopg:///x") == ("localhost", 5432)
|
|
|
|
|
|
def test_parse_host_port_testdb():
|
|
assert _parse_host_port(
|
|
"postgresql+psycopg://u:p@localhost:5432/shaguabijia_test"
|
|
) == ("localhost", 5432)
|
|
|
|
|
|
def test_port_open_true():
|
|
srv = socket.socket()
|
|
srv.bind(("127.0.0.1", 0))
|
|
srv.listen(1)
|
|
port = srv.getsockname()[1]
|
|
try:
|
|
assert _port_open("127.0.0.1", port, timeout=1.0)
|
|
finally:
|
|
srv.close()
|
|
|
|
|
|
def test_port_open_false():
|
|
s = socket.socket()
|
|
s.bind(("127.0.0.1", 0))
|
|
port = s.getsockname()[1]
|
|
s.close() # 释放端口,无人监听 → 连接应失败
|
|
assert not _port_open("127.0.0.1", port, timeout=0.3)
|
|
|
|
|
|
def test_win_candidates_override_wins():
|
|
env = {
|
|
"DOCKER_DESKTOP_EXE": r"X:\custom\Docker Desktop.exe",
|
|
"ProgramFiles": r"C:\Program Files",
|
|
}
|
|
cands = [str(p) for p in _win_docker_desktop_candidates(env, None)]
|
|
assert cands[0] == r"X:\custom\Docker Desktop.exe"
|
|
|
|
|
|
def test_win_candidates_follow_cli_drive():
|
|
# 核心:docker CLI 在 D 盘 → 反推出 D 盘的 Docker Desktop.exe(不再写死 C 盘)
|
|
env = {"ProgramFiles": r"C:\Program Files"}
|
|
cli = r"D:\Docker\Docker\resources\bin\docker.exe"
|
|
cands = [str(p) for p in _win_docker_desktop_candidates(env, cli)]
|
|
assert r"D:\Docker\Docker\Docker Desktop.exe" in cands
|
|
# 兜底的 C 盘常见路径也仍在
|
|
assert r"C:\Program Files\Docker\Docker\Docker Desktop.exe" in cands
|
|
|
|
|
|
def test_win_candidates_no_cli_uses_program_files():
|
|
env = {"ProgramFiles": r"C:\Program Files"}
|
|
cands = [str(p) for p in _win_docker_desktop_candidates(env, None)]
|
|
assert cands == [r"C:\Program Files\Docker\Docker\Docker Desktop.exe"]
|
|
|
|
|
|
def test_docker_desktop_cmd_windows_found(monkeypatch, tmp_path):
|
|
exe = tmp_path / "Docker Desktop.exe"
|
|
exe.write_text("") # 真实存在
|
|
monkeypatch.setattr("scripts.ensure_pg._find_docker_desktop_exe", lambda: exe)
|
|
assert _docker_desktop_cmd("win32") == [str(exe)]
|
|
|
|
|
|
def test_docker_desktop_cmd_windows_not_found(monkeypatch):
|
|
monkeypatch.setattr("scripts.ensure_pg._find_docker_desktop_exe", lambda: None)
|
|
assert _docker_desktop_cmd("win32") is None
|
|
|
|
|
|
def test_docker_desktop_cmd_darwin():
|
|
assert _docker_desktop_cmd("darwin") == ["open", "-a", "Docker"]
|
|
|
|
|
|
def test_docker_desktop_cmd_linux():
|
|
assert _docker_desktop_cmd("linux") is None
|
|
|
|
|
|
def test_registry_probe_never_raises():
|
|
# best-effort:无论平台/有无键,只返回 Path 或 None,绝不抛
|
|
r = _docker_desktop_from_registry()
|
|
assert r is None or hasattr(r, "exists")
|
|
|
|
|
|
def test_ensure_sqlite_escape_hatch(monkeypatch):
|
|
# sqlite 是【显式降级逃生舱】:打印横幅、返回 True,且绝不触碰 docker
|
|
def _boom():
|
|
raise AssertionError("sqlite 分支不应调用 docker")
|
|
|
|
monkeypatch.setattr("scripts.ensure_pg._docker_cli_ok", _boom)
|
|
assert ensure("sqlite:///./data/app.db") is True
|
|
|
|
|
|
def test_ensure_shortcircuits_when_pg_up(monkeypatch):
|
|
# 端口通 → 直接 True,绝不触碰 docker
|
|
monkeypatch.setattr("scripts.ensure_pg._port_open", lambda *a, **k: True)
|
|
|
|
def _boom():
|
|
raise AssertionError("端口通时不应调用 docker")
|
|
|
|
monkeypatch.setattr("scripts.ensure_pg._docker_cli_ok", _boom)
|
|
assert ensure("postgresql+psycopg://u:p@localhost:5432/shaguabijia") is True
|