113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
"""Loading resilience for .env files.
|
|
|
|
``load_env`` runs at CLI import time, so a broken ``~/.opensquilla/.env``
|
|
(one latin-1 byte is enough) used to kill every command — including the
|
|
onboarding wizard that could repair the file — with a raw
|
|
``UnicodeDecodeError`` before Typer even started. These tests pin the
|
|
recovery contract: a bad file is skipped with a single stderr warning while
|
|
every valid file keeps loading exactly as before, and **stdout stays byte
|
|
clean** — the warning used to also go through an unconfigured structlog
|
|
logger whose PrintLogger writes to stdout, corrupting machine-readable
|
|
output such as ``onboard status --json``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from opensquilla import env as env_mod
|
|
|
|
|
|
def _isolate_global_env(monkeypatch: pytest.MonkeyPatch, home: Path) -> None:
|
|
home.mkdir()
|
|
monkeypatch.setattr(env_mod, "default_opensquilla_home", lambda: home)
|
|
|
|
|
|
def test_load_env_survives_non_utf8_home_env(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
home = tmp_path / "home"
|
|
_isolate_global_env(monkeypatch, home)
|
|
monkeypatch.delenv("OPENSQUILLA_ENV_TEST_BAD_KEY", raising=False)
|
|
monkeypatch.delenv("OPENSQUILLA_ENV_TEST_GOOD_KEY", raising=False)
|
|
|
|
# \xe9 is latin-1 "e-acute" — a single byte that is invalid UTF-8.
|
|
(home / ".env").write_bytes(b"OPENSQUILLA_ENV_TEST_BAD_KEY=caf\xe9\n")
|
|
(tmp_path / ".env").write_text(
|
|
"OPENSQUILLA_ENV_TEST_GOOD_KEY=ok\n", encoding="utf-8"
|
|
)
|
|
|
|
injected = env_mod.load_env(tmp_path)
|
|
|
|
assert injected == 1
|
|
assert os.environ["OPENSQUILLA_ENV_TEST_GOOD_KEY"] == "ok"
|
|
assert "OPENSQUILLA_ENV_TEST_BAD_KEY" not in os.environ
|
|
|
|
captured = capsys.readouterr()
|
|
err = captured.err
|
|
assert str(home / ".env") in err
|
|
assert "UTF-8" in err
|
|
assert err.count("\n") == 1, f"expected a single concise stderr line, got: {err!r}"
|
|
# Machine-readable stdout must stay byte clean: the warning must never be
|
|
# emitted through an unconfigured structlog PrintLogger on stdout.
|
|
assert captured.out == "", f"env warning leaked to stdout: {captured.out!r}"
|
|
|
|
# The CLI loads env twice (import time and app callback); the warning
|
|
# must not repeat for the same file within one process.
|
|
env_mod.load_env(tmp_path)
|
|
repeat = capsys.readouterr()
|
|
assert repeat.err == ""
|
|
assert repeat.out == ""
|
|
|
|
|
|
@pytest.mark.skipif(os.name == "nt", reason="POSIX permission bits are meaningless on Windows")
|
|
@pytest.mark.skipif(
|
|
hasattr(os, "geteuid") and os.geteuid() == 0, reason="root bypasses file modes"
|
|
)
|
|
def test_load_env_survives_unreadable_home_env(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
home = tmp_path / "home"
|
|
_isolate_global_env(monkeypatch, home)
|
|
monkeypatch.delenv("OPENSQUILLA_ENV_TEST_BAD_KEY", raising=False)
|
|
|
|
env_file = home / ".env"
|
|
env_file.write_text("OPENSQUILLA_ENV_TEST_BAD_KEY=nope\n", encoding="utf-8")
|
|
os.chmod(env_file, 0)
|
|
try:
|
|
injected = env_mod.load_env(tmp_path)
|
|
finally:
|
|
os.chmod(env_file, 0o600)
|
|
|
|
assert injected == 0
|
|
assert "OPENSQUILLA_ENV_TEST_BAD_KEY" not in os.environ
|
|
captured = capsys.readouterr()
|
|
assert str(env_file) in captured.err
|
|
assert captured.out == "", f"env warning leaked to stdout: {captured.out!r}"
|
|
|
|
|
|
def test_load_env_valid_files_still_load_the_same_values(
|
|
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
home = tmp_path / "home"
|
|
_isolate_global_env(monkeypatch, home)
|
|
monkeypatch.delenv("OPENSQUILLA_ENV_TEST_KEY", raising=False)
|
|
monkeypatch.delenv("OPENSQUILLA_ENV_TEST_QUOTED", raising=False)
|
|
|
|
(home / ".env").write_text(
|
|
"# comment\n"
|
|
"\n"
|
|
"OPENSQUILLA_ENV_TEST_KEY=plain-value\n"
|
|
"OPENSQUILLA_ENV_TEST_QUOTED='quoted value'\n"
|
|
"not-an-assignment\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
assert env_mod.load_env(tmp_path) == 2
|
|
assert os.environ["OPENSQUILLA_ENV_TEST_KEY"] == "plain-value"
|
|
assert os.environ["OPENSQUILLA_ENV_TEST_QUOTED"] == "quoted value"
|
|
assert capsys.readouterr().err == ""
|