Files
Octop/tests/unit/cli/test_main_encoding.py
XiaoChen afd1857154 fix(cli): force utf-8 stdio so octop init does not crash on GBK consoles (#348)
Chinese Windows (GBK/cp936) encode piped/redirected CLI stdout with the
ANSI code page, so the checkmark success message in `octop init` raised
UnicodeEncodeError after the DB and admin user were already created.
Re-encode stdout/stderr to utf-8 at the CLI entry to cover the whole bug
class (init, stub, QR art, future emoji).

Add 3 unit tests (tests/unit/cli/test_main_encoding.py): two for the
guard helper and an end-to-end `octop init` run under
PYTHONIOENCODING=gbk. pytest -m "not live" passes locally; the 12
failures are pre-existing Windows/db-migration environment issues,
unchanged by this patch.
2026-08-20 16:30:54 +08:00

67 lines
2.1 KiB
Python

"""CLI stdio encoding guard — emoji output must not crash on legacy code pages.
Reported on Chinese Windows (GBK / cp936): ``octop init`` bootstraps the
database and then raises ``UnicodeEncodeError`` while printing its ``✅``
success message, because piped/redirected stdout is encoded with the ANSI
code page. The CLI entry point now re-encodes stdout/stderr to UTF-8.
"""
from __future__ import annotations
import io
import os
import subprocess
import sys
from pathlib import Path
from octop.cli.main import _ensure_utf8_stdio
def test_ensure_utf8_stdio_reconfigures_gbk_streams() -> None:
out = io.TextIOWrapper(io.BytesIO(), encoding="gbk")
err = io.TextIOWrapper(io.BytesIO(), encoding="gbk")
_ensure_utf8_stdio(streams=(out, err))
assert out.encoding.lower() == "utf-8"
assert err.encoding.lower() == "utf-8"
# U+2705 (✅) is not representable in GBK; must encode without raising.
out.write("\u2705 Octop bootstrapped")
out.flush()
def test_ensure_utf8_stdio_leaves_utf8_streams_alone() -> None:
out = io.TextIOWrapper(io.BytesIO(), encoding="utf-8")
_ensure_utf8_stdio(streams=(out, None))
assert out.encoding.lower() == "utf-8"
def test_init_completes_with_gbk_stdout(tmp_path: Path) -> None:
"""``octop init`` still bootstraps when stdout is forced to GBK. The crash
previously happened at the final success message, after the DB and admin
user were already created."""
home = tmp_path / "octop-home"
env = dict(os.environ)
env["OCTOP_HOME"] = str(home)
env["PYTHONIOENCODING"] = "gbk"
env.pop("PYTHONUTF8", None)
proc = subprocess.run(
[
sys.executable,
"-m",
"octop.cli.main",
"init",
"--yes",
"--admin-username",
"alice",
"--admin-password",
"Wonderland1",
],
env=env,
capture_output=True,
check=False,
timeout=120,
)
assert proc.returncode == 0, proc.stdout.decode("utf-8", "replace") + proc.stderr.decode(
"utf-8", "replace"
)
assert (home / "octop.db").is_file()