fix: restore .env loading for --init-config
Регрессия от 1df1194 (#64): load_dotenv переехал с module-level в
Config.load_yaml(), но --init-config выходит до этого вызова, поэтому
_run_init_config больше не видел значения из .env — пользователь с
настройками только в .env получал пустой YAML, хотя docs/CONFIG.md
обещает чтение текущих значений из .env.
В начале _run_init_config добавлен load_dotenv(find_dotenv(usecwd=True),
override=False): поиск .env идёт от текущей директории (plain-вызов без
usecwd стартует от директории cli.py и .env пользователя не находит),
переменные окружения не перебиваются. Импорт модуля по-прежнему без
side effects — вызов внутри функции.
Регрессионный тест: test_init_config_reads_dotenv_file (реальный .env
в tmp_path + chdir, без мока os.environ на целевые переменные).
Refs #64
This commit is contained in:
@@ -8,6 +8,7 @@ from pathlib import Path
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
from dotenv import find_dotenv, load_dotenv
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .client import RedmineAPIError, fetch_issues_with_spent_time
|
from .client import RedmineAPIError, fetch_issues_with_spent_time
|
||||||
@@ -70,7 +71,16 @@ def parse_date_range(date_arg: str) -> tuple[str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def _run_init_config(config_path: str, force: bool) -> int:
|
def _run_init_config(config_path: str, force: bool) -> int:
|
||||||
"""Создаёт YAML-конфиг из текущих настроек окружения."""
|
"""Создаёт YAML-конфиг из текущих настроек окружения.
|
||||||
|
|
||||||
|
Подгружает .env из текущей директории (override=False: реальные
|
||||||
|
переменные окружения не перебиваются), чтобы --init-config видел
|
||||||
|
значения, заданные только в .env (#64). Поиск идёт от cwd
|
||||||
|
(usecwd=True): find_dotenv() без него стартует от директории
|
||||||
|
cli.py и .env пользователя не находит.
|
||||||
|
"""
|
||||||
|
load_dotenv(find_dotenv(usecwd=True), override=False)
|
||||||
|
|
||||||
path = Path(config_path)
|
path = Path(config_path)
|
||||||
|
|
||||||
if path.exists() and not force:
|
if path.exists() and not force:
|
||||||
|
|||||||
@@ -425,6 +425,49 @@ class TestInitConfig:
|
|||||||
assert data["redmine"]["url"] == ""
|
assert data["redmine"]["url"] == ""
|
||||||
assert data["redmine"]["api_key"] == ""
|
assert data["redmine"]["api_key"] == ""
|
||||||
|
|
||||||
|
def test_init_config_reads_dotenv_file(self, tmp_path, monkeypatch):
|
||||||
|
"""#64: --init-config подхватывает значения из реального .env в cwd.
|
||||||
|
|
||||||
|
Регрессионный тест: после 1df1194 load_dotenv перестал выполняться
|
||||||
|
до _run_init_config, и пользователь с настройками только в .env
|
||||||
|
получал пустой YAML.
|
||||||
|
"""
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
(tmp_path / ".env").write_text(
|
||||||
|
"REDMINE_URL=https://redmine.example.com\n"
|
||||||
|
"REDMINE_API_KEY=secret-from-dotenv\n"
|
||||||
|
"REDMINE_AUTHOR=ivan.ivanov\n"
|
||||||
|
"DEFAULT_FROM_DATE=2026-01-01\n"
|
||||||
|
"DEFAULT_TO_DATE=2026-01-31\n"
|
||||||
|
"SMTP_PASSWORD=smtp-secret\n"
|
||||||
|
)
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
# Целевые переменные не мокаются: значения должны прийти только из .env.
|
||||||
|
for var in (
|
||||||
|
"REDMINE_URL",
|
||||||
|
"REDMINE_API_KEY",
|
||||||
|
"REDMINE_AUTHOR",
|
||||||
|
"DEFAULT_FROM_DATE",
|
||||||
|
"DEFAULT_TO_DATE",
|
||||||
|
"SMTP_PASSWORD",
|
||||||
|
):
|
||||||
|
monkeypatch.delenv(var, raising=False)
|
||||||
|
|
||||||
|
config_path = tmp_path / "config.yml"
|
||||||
|
code = main(["--init-config", "--config-path", str(config_path)])
|
||||||
|
assert code == 0
|
||||||
|
|
||||||
|
with open(config_path) as fh:
|
||||||
|
data = yaml.safe_load(fh)
|
||||||
|
|
||||||
|
assert data["redmine"]["url"] == "https://redmine.example.com"
|
||||||
|
assert data["redmine"]["api_key"] == "${REDMINE_API_KEY}"
|
||||||
|
assert data["redmine"]["author"] == "ivan.ivanov"
|
||||||
|
assert data["period"]["default_from"] == "2026-01-01"
|
||||||
|
assert data["period"]["default_to"] == "2026-01-31"
|
||||||
|
assert data["email"]["smtp"]["password"] == "${SMTP_PASSWORD}"
|
||||||
|
|
||||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||||
def test_init_config_includes_report_section(self, tmp_path):
|
def test_init_config_includes_report_section(self, tmp_path):
|
||||||
"""--init-config генерирует секцию report с no_time: false."""
|
"""--init-config генерирует секцию report с no_time: false."""
|
||||||
|
|||||||
Reference in New Issue
Block a user