Files
atm/tests/test_env_loader.py
Marius Mutu 9c44eb6e31 feat: mută secretele Discord/Telegram din TOML în .env
TOML-urile din configs/ rămân 100% calibrare — safe to commit. Secretele
(ATM_DISCORD_URL, ATM_TG_TOKEN, ATM_TG_CHAT) trăiesc în .env la rădăcină
(ignored), cu loader stdlib (shell wins peste file). Validare fail-fast
pentru env lipsă, placeholder REPLACE_ME, chat_id non-numeric.

Include .env.example + secţiune README §Secrets. Tests: 19 noi (env loader +
missing-env + placeholder + chat_id + regression post-migrate snapshot).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:37:24 +03:00

105 lines
3.1 KiB
Python

"""Tests for the minimal .env loader (stdlib, no python-dotenv)."""
from __future__ import annotations
from pathlib import Path
import pytest
from atm.config import _find_env_file, _load_env_file
def test_no_file_returns_none(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.chdir(tmp_path)
assert _find_env_file() is None
def test_finds_env_in_root(tmp_path: Path) -> None:
(tmp_path / "pyproject.toml").write_text("", encoding="utf-8")
(tmp_path / ".env").write_text("X=1\n", encoding="utf-8")
sub = tmp_path / "sub" / "deeper"
sub.mkdir(parents=True)
found = _find_env_file(sub)
assert found == (tmp_path / ".env").resolve()
def test_pyproject_sentinel_stops_walk(tmp_path: Path) -> None:
(tmp_path / "pyproject.toml").write_text("", encoding="utf-8")
sub = tmp_path / "sub"
sub.mkdir()
assert _find_env_file(sub) is None
def test_parses_simple_kv(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
for k in ("A", "B", "C"):
monkeypatch.delenv(k, raising=False)
env = tmp_path / ".env"
env.write_text(
"# comment\n"
"\n"
"A=1\n"
"B=hello world\n"
" # indented comment\n"
"C=with=equals=in=value\n",
encoding="utf-8",
)
loaded, overridden = _load_env_file(env)
assert loaded == 3
assert overridden == 0
import os
assert os.environ["A"] == "1"
assert os.environ["B"] == "hello world"
assert os.environ["C"] == "with=equals=in=value"
def test_parses_quoted_values(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
for k in ("SQ", "DQ"):
monkeypatch.delenv(k, raising=False)
env = tmp_path / ".env"
env.write_text("SQ='abc'\nDQ=\"def\"\n", encoding="utf-8")
_load_env_file(env)
import os
assert os.environ["SQ"] == "abc"
assert os.environ["DQ"] == "def"
def test_handles_crlf_and_whitespace(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
for k in ("K1", "K2"):
monkeypatch.delenv(k, raising=False)
env = tmp_path / ".env"
env.write_bytes(b"K1=v1\r\n K2 = v2 \r\n")
_load_env_file(env)
import os
assert os.environ["K1"] == "v1"
assert os.environ["K2"] == "v2"
def test_shell_env_wins(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("SHELLWINS", "from_shell")
env = tmp_path / ".env"
env.write_text("SHELLWINS=from_file\nOTHER=x\n", encoding="utf-8")
monkeypatch.delenv("OTHER", raising=False)
loaded, overridden = _load_env_file(env)
import os
assert os.environ["SHELLWINS"] == "from_shell"
assert os.environ["OTHER"] == "x"
assert loaded == 1
assert overridden == 1
def test_malformed_line_raises_with_lineno(tmp_path: Path) -> None:
env = tmp_path / ".env"
env.write_text("A=1\nOOPSNOEQUALS\n", encoding="utf-8")
with pytest.raises(ValueError, match=":2:"):
_load_env_file(env)
def test_missing_path_is_noop() -> None:
assert _load_env_file(None) == (0, 0)
assert _load_env_file(Path("/nonexistent/does-not-exist-xyz")) == (0, 0)