deer-flow/backend/tests/test_setup_script_encoding.py
luckiest-draw 3f3cd23965
fix(config): avoid Unicode failure after generating configuration (#7102)
* fix(config): avoid Unicode failure after generating configuration

* fix(setup): handle legacy stdout encodings in setup commands

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-10-21 16:03:39 +08:00

147 lines
5.4 KiB
Python

"""Exercise setup command output through real, strictly encoded stdout streams."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
import textwrap
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
ENCODINGS = ["ascii", "cp1252", "cp936", "utf-8"]
@pytest.fixture
def setup_checkout(tmp_path: Path) -> Path:
scripts = tmp_path / "scripts"
scripts.mkdir()
for name in ("doctor.py", "setup_wizard.py"):
shutil.copyfile(REPO_ROOT / "scripts" / name, scripts / name)
shutil.copytree(REPO_ROOT / "scripts" / "wizard", scripts / "wizard")
(tmp_path / ".env.example").write_text("# test environment\n", encoding="utf-8")
(tmp_path / "frontend").mkdir()
(tmp_path / "frontend" / ".env.example").write_text("# frontend environment\n", encoding="utf-8")
return tmp_path
def run_setup_script(checkout: Path, encoding: str, code: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, "-c", textwrap.dedent(code)],
cwd=checkout,
env={**os.environ, "PYTHONPATH": str(checkout / "scripts"), "PYTHONIOENCODING": f"{encoding}:strict"},
capture_output=True,
text=True,
encoding=encoding,
check=False,
timeout=30,
)
@pytest.mark.parametrize("encoding", ENCODINGS)
@pytest.mark.parametrize("is_tty", [False, True], ids=["pipe", "tty"])
@pytest.mark.parametrize("has_failure", [False, True], ids=["ready", "failed"])
def test_doctor_report_survives_restricted_stdout(setup_checkout: Path, encoding: str, is_tty: bool, has_failure: bool):
result = run_setup_script(
setup_checkout,
encoding,
f"""
import sys
from unittest.mock import patch
import doctor
sys.stdout.isatty = lambda: {is_tty!r}
checks = {{}}
for name in vars(doctor):
if name.startswith("check_"):
value = doctor.CheckResult("Example check", "ok", "Unicode detail: 检查")
if name in ("check_llm_api_key", "check_llm_auth", "check_llm_package", "check_sandbox"):
value = [value]
checks[name] = lambda *args, value=value: value
checks["check_node"] = lambda: doctor.CheckResult("Warning check", "warn")
checks["check_pnpm"] = lambda: doctor.CheckResult("Skipped check", "skip")
checks["check_nginx"] = lambda: doctor.CheckResult(
"Required check", "fail" if {has_failure!r} else "ok", fix="Run make setup"
)
with patch.multiple(doctor, **checks), patch.object(doctor, "resolve_config_path", return_value=(None, None)):
sys.exit(doctor.main())
""",
)
assert result.returncode == int(has_failure), result.stderr
assert result.stderr == ""
assert "DeerFlow Health Check" in result.stdout
assert "Skipped check" in result.stdout
assert "Run make setup" in result.stdout
assert "Status: " in result.stdout
assert ("error(s)" if has_failure else "Ready") in result.stdout
if encoding == "utf-8":
assert "✓" in result.stdout
assert "检查" in result.stdout
@pytest.mark.parametrize("encoding", ENCODINGS)
def test_interactive_setup_completes_with_restricted_stdout(setup_checkout: Path, encoding: str):
result = run_setup_script(
setup_checkout,
encoding,
"""
import builtins
import getpass
import sys
import setup_wizard
from wizard.providers import LLM_PROVIDERS, SEARCH_PROVIDERS, WEB_FETCH_PROVIDERS
# Model a legacy-locale terminal while keeping this test portable.
sys.stdin.isatty = lambda: True
sys.stdout.isatty = lambda: True
choices = iter([
str(next(i for i, provider in enumerate(LLM_PROVIDERS, 1) if provider.name == "openai")),
"", # default model
str(len(SEARCH_PROVIDERS) + 1), # skip web search
str(len(WEB_FETCH_PROVIDERS) + 1), # skip web fetch
"", # local sandbox
"", # bash disabled
"", # file writes enabled
"", # no IM channels
])
builtins.input = lambda prompt: next(choices)
getpass.getpass = lambda prompt: "test-api-key"
sys.exit(setup_wizard.main())
""",
)
assert result.returncode == 0, result.stderr
assert result.stderr == ""
assert "Writing configuration" in result.stdout
assert "Setup complete!" in result.stdout
assert "make doctor" in result.stdout
assert (setup_checkout / "config.yaml").is_file()
assert "OPENAI_API_KEY=test-api-key" in (setup_checkout / ".env").read_text(encoding="utf-8")
assert (setup_checkout / "frontend" / ".env").read_bytes() == (setup_checkout / "frontend" / ".env.example").read_bytes()
if encoding == "utf-8":
assert "✓" in result.stdout
assert "· Writing configuration" in result.stdout
@pytest.mark.parametrize("encoding", ENCODINGS)
def test_setup_still_requires_an_interactive_terminal(setup_checkout: Path, encoding: str):
result = run_setup_script(
setup_checkout,
encoding,
"""
import sys
import setup_wizard
sys.exit(setup_wizard.main())
""",
)
assert result.returncode == 1
assert result.stderr == ""
assert "Non-interactive environment detected" in result.stdout
assert not (setup_checkout / "config.yaml").exists()
assert not (setup_checkout / ".env").exists()