import os from unittest import mock import pytest from redmine_reporter.config import DEFAULT_REDMINE_VERIFY, Config @mock.patch.dict( os.environ, { "REDMINE_URL": "https://red.eltex.loc/", "REDMINE_USER": "test", "REDMINE_PASSWORD": "secret", }, clear=True, ) def test_config_valid_with_password_fallback(): Config.validate() # не должно быть исключения @mock.patch.dict( os.environ, { "REDMINE_URL": "https://red.eltex.loc/", "REDMINE_API_KEY": "token", }, clear=True, ) def test_config_valid_with_api_key(): Config.validate() # не должно быть исключения @mock.patch.dict(os.environ, {}, clear=True) def test_config_missing_url(): with pytest.raises(ValueError, match="REDMINE_URL"): Config.validate() @mock.patch.dict(os.environ, {"REDMINE_URL": "https://red.eltex.loc/"}, clear=True) def test_config_missing_auth(): with pytest.raises(ValueError, match="REDMINE_API_KEY"): Config.validate() @mock.patch.dict(os.environ, {"REDMINE_URL": " https://red.eltex.loc/ "}, clear=True) def test_get_redmine_url_strips_spaces_and_trailing_slash(): assert Config.get_redmine_url() == "https://red.eltex.loc" @mock.patch.dict(os.environ, {"REDMINE_AUTHOR": " Иванов И.И. "}, clear=True) def test_get_author(): assert Config.get_author("") == "Иванов И.И." assert Config.get_author("Петров П.П.") == "Петров П.П." @mock.patch.dict(os.environ, {}, clear=True) def test_get_author_fallback(): """Если ни CLI, ни .env не задали автора -- возвращается пустая строка.""" assert Config.get_author("") == "" @mock.patch.dict( os.environ, { "DEFAULT_FROM_DATE": "2026-01-01", "DEFAULT_TO_DATE": "2026-01-31", }, clear=True, ) def test_get_default_date_range_from_env(): assert Config.get_default_date_range() == "2026-01-01--2026-01-31" @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 result == f"{start.isoformat()}--{end.isoformat()}" @mock.patch.dict(os.environ, {}, clear=True) def test_get_redmine_verify_default(): assert Config.get_redmine_verify() == DEFAULT_REDMINE_VERIFY @pytest.mark.parametrize("value", ["0", "false", "False", "no", "off"]) def test_get_redmine_verify_false_values(value): with mock.patch.dict(os.environ, {"REDMINE_VERIFY": value}, clear=True): assert Config.get_redmine_verify() is False @pytest.mark.parametrize("value", ["1", "true", "True", "yes", "on"]) def test_get_redmine_verify_true_values(value): with mock.patch.dict(os.environ, {"REDMINE_VERIFY": value}, clear=True): assert Config.get_redmine_verify() is True @mock.patch.dict(os.environ, {"REDMINE_VERIFY": "/tmp/redmine-ca.pem"}, clear=True) def test_get_redmine_verify_custom_path(): assert Config.get_redmine_verify() == "/tmp/redmine-ca.pem" # -- #15: .env не должен переопределять переменные окружения -- @mock.patch("dotenv.load_dotenv") def test_env_var_takes_priority_over_dotenv(mock_load): """load_dotenv вызывается с override=False — env vars не перебиваются .env.""" import importlib from redmine_reporter import config as cfg_mod importlib.reload(cfg_mod) mock_load.assert_called_once_with(override=False) # -- #35: get_redmine_password должен делать .strip() -- @mock.patch.dict(os.environ, {"REDMINE_PASSWORD": " secret123 "}, clear=True) def test_get_redmine_password_strips_whitespace(): """Пароль обрезается от whitespace, как и все остальные геттеры.""" assert Config.get_redmine_password() == "secret123"