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>
This commit is contained in:
104
tests/test_env_loader.py
Normal file
104
tests/test_env_loader.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user