refactor: период через period.from вместо last_used; дедуп удалён
Удалены last_used, precision, dynamic и дедупликация по created_on/updated_on. Период задаётся одним полем period.from (env FROM_DATE), окно -- от этой даты до сегодня. После успешного --commit/--send начало окна сдвигается на конец отчёта; datetime-границы --date приводятся к полным дням. Схема конфига ломающая: старые ключи игнорируются, миграция описана в документации.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import contextlib
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
@@ -8,6 +9,18 @@ import pytest
|
||||
from redmine_reporter.config import AppConfig, Config, EmailConfig
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock_today(fake_today):
|
||||
"""Подменяет date.today() в config, сохраняя реальные date() и fromisoformat()."""
|
||||
from datetime import date as real_date
|
||||
|
||||
with mock.patch("redmine_reporter.config.date") as mock_date:
|
||||
mock_date.today.return_value = fake_today
|
||||
mock_date.side_effect = lambda *a, **kw: real_date(*a, **kw)
|
||||
mock_date.fromisoformat.side_effect = real_date.fromisoformat
|
||||
yield
|
||||
|
||||
|
||||
@mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
@@ -62,25 +75,9 @@ def test_get_author_fallback():
|
||||
assert Config.get_author("") == ""
|
||||
|
||||
|
||||
@mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"DEFAULT_FROM_DATE": "2026-01-01",
|
||||
"DEFAULT_TO_DATE": "2026-01-31",
|
||||
},
|
||||
clear=True,
|
||||
)
|
||||
@mock.patch.dict(os.environ, {"FROM_DATE": "2026-01-01"}, 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,
|
||||
{"DEFAULT_FROM_DATE": "2026-01-01"},
|
||||
clear=True,
|
||||
)
|
||||
def test_get_default_date_range_from_env_without_to():
|
||||
"""Если DEFAULT_TO_DATE не задан, конец периода — сегодня."""
|
||||
"""FROM_DATE задаёт начало периода, конец всегда -- сегодня."""
|
||||
from datetime import date
|
||||
|
||||
today = date.today().isoformat()
|
||||
@@ -99,25 +96,6 @@ def test_get_default_date_range_fallback():
|
||||
assert result == f"{start.isoformat()}--{today.isoformat()}"
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_default_date_range_datetime_dynamic_sliding_window():
|
||||
app = AppConfig(
|
||||
period_precision="datetime",
|
||||
period_dynamic=True,
|
||||
period_last_used_to="2026-09-10T15:00:00+00:00",
|
||||
)
|
||||
Config._app = app
|
||||
try:
|
||||
real_dt = __import__("datetime").datetime
|
||||
with mock.patch("redmine_reporter.config.datetime") as dt:
|
||||
dt.fromisoformat = real_dt.fromisoformat
|
||||
dt.now.return_value = real_dt(2026, 9, 15, 10, 0, 0)
|
||||
rng = Config.get_default_date_range()
|
||||
finally:
|
||||
Config._app = None
|
||||
assert rng == "2026-09-10T15:00:00--2026-09-15T10:00:00"
|
||||
|
||||
|
||||
# -- #56: дефолтный период — текущий месяц (детерминированные тесты) --
|
||||
|
||||
|
||||
@@ -145,31 +123,98 @@ class TestDefaultPeriodIsCurrentMonth:
|
||||
assert Config.get_default_date_range() == "2026-05-01--2026-05-01"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_yaml_default_from_falls_back_to_today(self):
|
||||
"""default_from задан, default_to нет → конец периода = сегодня."""
|
||||
def test_yaml_period_from_runs_to_today(self):
|
||||
"""period.from задан -- конец периода всегда сегодня."""
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n default_from: '2026-04-01'\n")
|
||||
yaml_path.write_text("period:\n from: '2026-04-01'\n")
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with mock.patch("redmine_reporter.config.date") as mock_date:
|
||||
mock_date.today.return_value = date(2026, 4, 20)
|
||||
mock_date.side_effect = lambda *a, **kw: date(*a, **kw)
|
||||
with mock_today(date(2026, 4, 20)):
|
||||
assert Config.get_default_date_range() == "2026-04-01--2026-04-20"
|
||||
|
||||
@mock.patch.dict(os.environ, {"DEFAULT_FROM_DATE": "2026-02-01"}, clear=True)
|
||||
def test_env_from_without_to_falls_back_to_today(self):
|
||||
"""DEFAULT_FROM_DATE задана, DEFAULT_TO_DATE нет → конец = сегодня."""
|
||||
|
||||
class TestDefaultDateRangePeriodFrom:
|
||||
"""Приоритет окна: env FROM_DATE → YAML period.from → текущий месяц."""
|
||||
|
||||
@mock.patch.dict(os.environ, {"FROM_DATE": "2026-02-01"}, clear=True)
|
||||
def test_env_from_runs_to_today(self):
|
||||
"""FROM_DATE задаёт начало, конец всегда сегодня."""
|
||||
from datetime import date
|
||||
|
||||
Config._app = None
|
||||
with mock.patch("redmine_reporter.config.date") as mock_date:
|
||||
mock_date.today.return_value = date(2026, 2, 10)
|
||||
mock_date.side_effect = lambda *a, **kw: date(*a, **kw)
|
||||
with mock_today(date(2026, 2, 10)):
|
||||
assert Config.get_default_date_range() == "2026-02-01--2026-02-10"
|
||||
|
||||
@mock.patch.dict(os.environ, {"FROM_DATE": "2026-07-01"}, clear=True)
|
||||
def test_env_from_wins_over_yaml_period_from(self):
|
||||
"""env FROM_DATE имеет приоритет над YAML period.from."""
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n from: '2026-03-01'\n")
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with mock_today(date(2026, 7, 15)):
|
||||
assert Config.get_default_date_range() == "2026-07-01--2026-07-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {"FROM_DATE": "2026-12-31"}, clear=True)
|
||||
def test_env_from_in_future_clamps_to_today(self):
|
||||
"""from позже сегодня -- окно 'сегодня--сегодня', без инверсии."""
|
||||
from datetime import date
|
||||
|
||||
Config._app = None
|
||||
with mock_today(date(2026, 9, 15)):
|
||||
assert Config.get_default_date_range() == "2026-09-15--2026-09-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_yaml_period_from_in_future_clamps_to_today(self):
|
||||
"""YAML period.from позже сегодня -- окно 'сегодня--сегодня'."""
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n from: '2026-12-31'\n")
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with mock_today(date(2026, 9, 15)):
|
||||
assert Config.get_default_date_range() == "2026-09-15--2026-09-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_malformed_yaml_period_from_warns_and_uses_current_month(self, caplog):
|
||||
"""period.from не дата -- warning в лог, окно = текущий месяц."""
|
||||
import logging
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n from: 'not-a-date'\n")
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with caplog.at_level(logging.WARNING):
|
||||
with mock_today(date(2026, 9, 15)):
|
||||
rng = Config.get_default_date_range()
|
||||
|
||||
assert rng == "2026-09-01--2026-09-15"
|
||||
assert "not-a-date" in caplog.text
|
||||
|
||||
@mock.patch.dict(os.environ, {"FROM_DATE": "garbage"}, clear=True)
|
||||
def test_malformed_env_from_warns_and_uses_current_month(self, caplog):
|
||||
"""FROM_DATE не дата -- warning в лог, окно = текущий месяц."""
|
||||
import logging
|
||||
from datetime import date
|
||||
|
||||
Config._app = None
|
||||
with caplog.at_level(logging.WARNING):
|
||||
with mock_today(date(2026, 9, 15)):
|
||||
rng = Config.get_default_date_range()
|
||||
|
||||
assert rng == "2026-09-01--2026-09-15"
|
||||
assert "garbage" in caplog.text
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_redmine_verify_default():
|
||||
@@ -318,13 +363,7 @@ redmine:
|
||||
verify_ssl: false
|
||||
|
||||
period:
|
||||
precision: datetime
|
||||
default_from: "2026-06-01"
|
||||
default_to: "2026-06-30"
|
||||
dynamic: true
|
||||
last_used:
|
||||
from: "2026-06-30T09:00:00"
|
||||
to: "2026-06-30T12:00:00"
|
||||
from: "2026-06-01"
|
||||
|
||||
output:
|
||||
dir: ~/reports
|
||||
@@ -363,12 +402,7 @@ class TestAppConfigFromYaml:
|
||||
assert cfg.redmine_api_key == "" # ${MISSING_API_KEY_FOR_TEST} not set
|
||||
assert cfg.redmine_author == "Тестовый Автор"
|
||||
assert cfg.redmine_verify is False
|
||||
assert cfg.period_precision == "datetime"
|
||||
assert cfg.period_default_from == "2026-06-01"
|
||||
assert cfg.period_default_to == "2026-06-30"
|
||||
assert cfg.period_dynamic is True
|
||||
assert cfg.period_last_used_from == "2026-06-30T09:00:00"
|
||||
assert cfg.period_last_used_to == "2026-06-30T12:00:00"
|
||||
assert cfg.period_from == "2026-06-01"
|
||||
assert cfg.output_dir == "~/reports"
|
||||
assert cfg.output_filename == "{author}_{from}_{to}.{ext}"
|
||||
assert cfg.output_default_format == "xlsx"
|
||||
@@ -391,7 +425,7 @@ class TestAppConfigFromYaml:
|
||||
assert cfg.redmine_url == "https://x.com/"
|
||||
# defaults for everything else
|
||||
assert cfg.redmine_author == ""
|
||||
assert cfg.period_precision == "date"
|
||||
assert cfg.period_from == ""
|
||||
assert cfg.output_dir == ""
|
||||
assert cfg.email.to == []
|
||||
|
||||
@@ -404,7 +438,36 @@ class TestAppConfigFromYaml:
|
||||
|
||||
assert cfg.redmine_url == ""
|
||||
assert cfg.redmine_api_key == ""
|
||||
assert cfg.period_precision == "date"
|
||||
assert cfg.period_from == ""
|
||||
|
||||
def test_period_from_loaded_from_yaml(self):
|
||||
"""period.from читается из YAML как единственный ключ периода."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n from: '2026-06-01'\n")
|
||||
|
||||
cfg = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
assert cfg.period_from == "2026-06-01"
|
||||
|
||||
def test_old_period_keys_are_ignored(self):
|
||||
"""Устаревшие ключи period молча игнорируются -- читается только from."""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n"
|
||||
" precision: datetime\n"
|
||||
" dynamic: true\n"
|
||||
" default_from: '2026-01-01'\n"
|
||||
" default_to: '2026-01-31'\n"
|
||||
" last_used:\n"
|
||||
" from: '2026-01-31T09:00:00'\n"
|
||||
" to: '2026-01-31T12:00:00'\n"
|
||||
)
|
||||
|
||||
cfg = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
assert cfg.period_from == ""
|
||||
|
||||
def test_file_not_found_returns_all_defaults(self):
|
||||
cfg = AppConfig.from_yaml("/nonexistent/path/config.yml")
|
||||
@@ -552,14 +615,15 @@ class TestConfigYamlFallback:
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_date_range_from_yaml(self):
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n default_from: '2026-03-01'\n default_to: '2026-03-15'\n"
|
||||
)
|
||||
yaml_path.write_text("period:\n from: '2026-03-01'\n")
|
||||
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with mock_today(date(2026, 3, 15)):
|
||||
assert Config.get_default_date_range() == "2026-03-01--2026-03-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
@@ -589,205 +653,17 @@ class TestConfigYamlFallback:
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_default_date_range_env_beats_yaml(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n default_from: '2026-03-01'\n default_to: '2026-03-15'\n"
|
||||
)
|
||||
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{"DEFAULT_FROM_DATE": "2026-07-01", "DEFAULT_TO_DATE": "2026-07-15"},
|
||||
clear=True,
|
||||
):
|
||||
assert Config.get_default_date_range() == "2026-07-01--2026-07-15"
|
||||
|
||||
|
||||
class TestConfigPeriodPrecision:
|
||||
"""Tests for period.precision and period.last_used."""
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_period_precision_defaults_to_date(self):
|
||||
"""Без YAML-конфига precision == 'date'."""
|
||||
Config._app = None
|
||||
assert Config.get_period_precision() == "date"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_period_precision_from_yaml(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n precision: datetime\n")
|
||||
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
assert Config.get_period_precision() == "datetime"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_last_used_from_yaml(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n last_used:\n from: '2026-06-30T09:00:00'\n to: '2026-06-30T12:00:00'\n"
|
||||
)
|
||||
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
assert Config.get_last_used_from() == "2026-06-30T09:00:00"
|
||||
assert Config.get_last_used_to() == "2026-06-30T12:00:00"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_get_last_used_returns_empty_when_not_set(self):
|
||||
Config._app = AppConfig()
|
||||
assert Config.get_last_used_from() == ""
|
||||
assert Config.get_last_used_to() == ""
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_yaml_loads_last_used_field(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n last_used:\n from: '2026-07-01T08:00:00'\n to: '2026-07-01T18:00:00'\n"
|
||||
)
|
||||
|
||||
cfg = AppConfig.from_yaml(yaml_path)
|
||||
assert cfg.period_last_used_from == "2026-07-01T08:00:00"
|
||||
assert cfg.period_last_used_to == "2026-07-01T18:00:00"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_yaml_without_last_used_has_empty_fields(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text("period:\n precision: date\n")
|
||||
|
||||
cfg = AppConfig.from_yaml(yaml_path)
|
||||
assert cfg.period_last_used_from == ""
|
||||
assert cfg.period_last_used_to == ""
|
||||
|
||||
|
||||
class TestComputeNextPeriod:
|
||||
"""Tests for compute_next_period()."""
|
||||
|
||||
def test_full_month_goes_to_next_full_month(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2026-06-01", "2026-06-30", "date")
|
||||
assert nf == "2026-07-01"
|
||||
assert nt == "2026-07-31"
|
||||
|
||||
def test_december_to_next_year_january(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2025-12-01", "2025-12-31", "date")
|
||||
assert nf == "2026-01-01"
|
||||
assert nt == "2026-01-31"
|
||||
|
||||
def test_february_2026_non_leap_to_march(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2026-02-01", "2026-02-28", "date")
|
||||
assert nf == "2026-03-01"
|
||||
assert nt == "2026-03-31"
|
||||
|
||||
def test_arbitrary_range_same_length_from_to_plus_one(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2026-06-15", "2026-06-20", "date")
|
||||
assert nf == "2026-06-21"
|
||||
assert nt == "2026-06-26"
|
||||
|
||||
def test_single_day_range_moves_one_day(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2026-06-15", "2026-06-15", "date")
|
||||
assert nf == "2026-06-16"
|
||||
assert nt == "2026-06-16"
|
||||
|
||||
def test_cross_month_range(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period("2026-06-25", "2026-07-05", "date")
|
||||
assert nf == "2026-07-06"
|
||||
assert nt == "2026-07-16"
|
||||
|
||||
def test_datetime_precision_moves_by_seconds(self):
|
||||
from redmine_reporter.config import compute_next_period
|
||||
|
||||
nf, nt = compute_next_period(
|
||||
"2026-06-30T09:00:00", "2026-06-30T12:00:00", "datetime"
|
||||
)
|
||||
assert nf == "2026-06-30T12:00:01"
|
||||
assert nt == "2026-06-30T15:00:01"
|
||||
|
||||
|
||||
class TestDefaultDateRangeWithLastUsed:
|
||||
"""Tests that get_default_date_range() uses last_used when dynamic=True."""
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_uses_last_used_when_dynamic_true(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n"
|
||||
" precision: date\n"
|
||||
" dynamic: true\n"
|
||||
" last_used:\n"
|
||||
" from: '2026-05-01'\n"
|
||||
" to: '2026-05-31'\n"
|
||||
)
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
result = Config.get_default_date_range()
|
||||
assert result == "2026-06-01--2026-06-30"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_ignores_last_used_when_dynamic_false(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n"
|
||||
" precision: date\n"
|
||||
" dynamic: false\n"
|
||||
" default_from: '2026-03-01'\n"
|
||||
" default_to: '2026-03-15'\n"
|
||||
" last_used:\n"
|
||||
" from: '2026-06-01'\n"
|
||||
" to: '2026-06-30'\n"
|
||||
)
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
result = Config.get_default_date_range()
|
||||
assert result == "2026-03-01--2026-03-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_falls_back_when_no_last_used_even_with_dynamic(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n"
|
||||
" precision: date\n"
|
||||
" dynamic: true\n"
|
||||
" default_from: '2026-04-01'\n"
|
||||
" default_to: '2026-04-15'\n"
|
||||
)
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
result = Config.get_default_date_range()
|
||||
assert result == "2026-04-01--2026-04-15"
|
||||
|
||||
@mock.patch.dict(os.environ, {}, clear=True)
|
||||
def test_falls_back_to_today_when_default_to_missing(self):
|
||||
"""Если default_from задан, а default_to нет — конец периода сегодня."""
|
||||
from datetime import date
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
yaml_path = Path(tmp) / "config.yml"
|
||||
yaml_path.write_text(
|
||||
"period:\n"
|
||||
" precision: date\n"
|
||||
" dynamic: false\n"
|
||||
" default_from: '2026-04-01'\n"
|
||||
)
|
||||
yaml_path.write_text("period:\n from: '2026-03-01'\n")
|
||||
|
||||
Config._app = AppConfig.from_yaml(yaml_path)
|
||||
today = date.today().isoformat()
|
||||
result = Config.get_default_date_range()
|
||||
assert result == f"2026-04-01--{today}"
|
||||
|
||||
with mock.patch.dict(os.environ, {"FROM_DATE": "2026-07-01"}, clear=True):
|
||||
with mock_today(date(2026, 7, 15)):
|
||||
assert Config.get_default_date_range() == "2026-07-01--2026-07-15"
|
||||
|
||||
|
||||
class TestGetEmailConfig:
|
||||
|
||||
Reference in New Issue
Block a user