feat(cli): dynamic default range, --version, --verbose, --debug, --url, --api-key, --config
- #17: default date range falls back to current month when .env dates are missing - #18: add --version, --verbose, --debug flags - #20: add --url, --api-key, --config CLI overrides - Config supports CLI overrides for URL/API key and explicit config file loading - Update README with new CLI options - Add tests for new flags and config overrides Closes #17, closes #18, closes #20
This commit is contained in:
@@ -11,6 +11,18 @@ VALID_ENV = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_config_overrides():
|
||||
"""Сбрасывает CLI-переопределения Config перед каждым тестом."""
|
||||
from redmine_reporter.config import Config
|
||||
|
||||
Config.set_redmine_url("")
|
||||
Config.set_redmine_api_key("")
|
||||
yield
|
||||
Config.set_redmine_url("")
|
||||
Config.set_redmine_api_key("")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"date_arg, expected",
|
||||
[
|
||||
@@ -93,6 +105,28 @@ def test_cli_odt_missing_odfpy_message(mock_gf, mock_fetch, tmp_path, capsys):
|
||||
assert "odfpy" in captured.err
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
def test_cli_version_flag(capsys):
|
||||
"""--version выводит версию и завершается с 0."""
|
||||
with pytest.raises(SystemExit) as exc_info:
|
||||
main(["--version"])
|
||||
assert exc_info.value.code == 0
|
||||
captured = capsys.readouterr()
|
||||
assert "redmine-reporter" in captured.out
|
||||
assert "1.5.0" in captured.out
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_cli_verbose_and_debug_flags_accepted(mock_fetch):
|
||||
"""--verbose и --debug не ломают запуск."""
|
||||
mock_fetch.return_value = None
|
||||
code = main(["--date", "2026-01-01--2026-01-31", "--verbose"])
|
||||
assert code == 0
|
||||
code = main(["--date", "2026-01-01--2026-01-31", "--debug"])
|
||||
assert code == 0
|
||||
|
||||
|
||||
class _MockIssue:
|
||||
"""Простой mock Redmine Issue для CLI-тестов."""
|
||||
|
||||
@@ -104,6 +138,38 @@ class _MockIssue:
|
||||
self.fixed_version = None
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_cli_url_and_api_key_override_env(mock_fetch):
|
||||
"""--url и --api-key переопределяют отсутствующие/другие env-переменные."""
|
||||
mock_fetch.return_value = None
|
||||
code = main(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--url",
|
||||
"https://other.redmine.loc",
|
||||
"--api-key",
|
||||
"cli-token",
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_cli_config_file_loading(mock_fetch, tmp_path):
|
||||
"""--config загружает переменные из указанного .env-файла."""
|
||||
config_path = tmp_path / "custom.env"
|
||||
config_path.write_text(
|
||||
"REDMINE_URL=https://config.redmine.loc\n" "REDMINE_API_KEY=config-token\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
mock_fetch.return_value = None
|
||||
code = main(["--date", "2026-01-01--2026-01-31", "--config", str(config_path)])
|
||||
assert code == 0
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_total_issues_message_goes_to_stderr(mock_fetch, capsys):
|
||||
|
||||
@@ -74,9 +74,19 @@ def test_get_default_date_range_from_env():
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_default_date_range_fallback():
|
||||
"""Если даты не заданы -- используется хардкод-заглушка."""
|
||||
"""Если даты не заданы -- используется текущий месяц."""
|
||||
from datetime import date, timedelta
|
||||
|
||||
today = date.today()
|
||||
start = today.replace(day=1)
|
||||
if today.month == 12:
|
||||
next_month = today.replace(year=today.year + 1, month=1, day=1)
|
||||
else:
|
||||
next_month = today.replace(month=today.month + 1, day=1)
|
||||
end = next_month - timedelta(days=1)
|
||||
|
||||
result = Config.get_default_date_range()
|
||||
assert "--" in result # формат YYYY-MM-DD--YYYY-MM-DD
|
||||
assert result == f"{start.isoformat()}--{end.isoformat()}"
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
|
||||
Reference in New Issue
Block a user