fix: sliding period window for datetime precision
After --commit with precision=datetime, last_used held from==to==now, so the next default period degenerated to a single second and showed no new time entries. Now --commit stores the real report window start and get_default_date_range() returns last_used.to..now for datetime+dynamic mode. Closes #70
This commit is contained in:
@@ -715,7 +715,9 @@ class TestCommitFlag:
|
||||
def test_commit_with_precision_datetime_passes_datetime_strings(
|
||||
self, mock_save, mock_fetch, tmp_path
|
||||
):
|
||||
"""При precision=datetime --commit сохраняет timestamps."""
|
||||
"""При precision=datetime --commit сохраняет from окна и to в naive UTC."""
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
issue = _MockIssue()
|
||||
@@ -732,7 +734,7 @@ class TestCommitFlag:
|
||||
code = main(
|
||||
[
|
||||
"--date",
|
||||
"2026-06-30--2026-06-30",
|
||||
"2026-09-10T09:00:00--2026-09-12T18:00:00",
|
||||
"--commit",
|
||||
"--output",
|
||||
str(tmp_path / "report.xlsx"),
|
||||
@@ -745,17 +747,19 @@ class TestCommitFlag:
|
||||
call_args = mock_save.call_args
|
||||
assert call_args is not None
|
||||
saved_from, saved_to = call_args.args[1], call_args.args[2]
|
||||
assert "T" in saved_from
|
||||
assert "T" in saved_to
|
||||
assert saved_from == "2026-09-10T09:00:00"
|
||||
assert datetime.strptime(saved_to, "%Y-%m-%dT%H:%M:%S")
|
||||
assert call_args.args[3] == "datetime"
|
||||
assert call_args.args[4] is True
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
@mock.patch("redmine_reporter.cli.save_period_to_config")
|
||||
def test_commit_with_precision_datetime_saves_aware_utc(
|
||||
def test_commit_with_precision_datetime_saves_naive_utc_to(
|
||||
self, mock_save, mock_fetch, tmp_path
|
||||
):
|
||||
"""При precision=datetime --commit сохраняет last_used как aware UTC (#58)."""
|
||||
from datetime import datetime, timezone
|
||||
"""При precision=datetime --commit сохраняет to как naive UTC (#70)."""
|
||||
from datetime import datetime
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -773,7 +777,7 @@ class TestCommitFlag:
|
||||
code = main(
|
||||
[
|
||||
"--date",
|
||||
"2026-06-30--2026-06-30",
|
||||
"2026-09-10T09:00:00--2026-09-12T18:00:00",
|
||||
"--commit",
|
||||
"--output",
|
||||
str(tmp_path / "report.xlsx"),
|
||||
@@ -786,10 +790,60 @@ class TestCommitFlag:
|
||||
call_args = mock_save.call_args
|
||||
assert call_args is not None
|
||||
saved_from, saved_to = call_args.args[1], call_args.args[2]
|
||||
for saved in (saved_from, saved_to):
|
||||
parsed = datetime.fromisoformat(saved)
|
||||
assert parsed.tzinfo is not None, f"{saved} must be timezone-aware"
|
||||
assert parsed.utcoffset() == timezone.utc.utcoffset(None)
|
||||
assert saved_from == "2026-09-10T09:00:00"
|
||||
parsed = datetime.fromisoformat(saved_to)
|
||||
assert parsed.tzinfo is None, f"{saved_to} must be naive UTC"
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
@mock.patch("redmine_reporter.cli.save_period_to_config")
|
||||
def test_commit_datetime_persists_window_start_and_now(
|
||||
self, mock_save, mock_fetch, tmp_path
|
||||
):
|
||||
"""--commit при datetime сохраняет начало окна и текущий naive UTC (#70)."""
|
||||
from datetime import datetime as real_datetime
|
||||
|
||||
import yaml
|
||||
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0, None)]
|
||||
|
||||
config_path = tmp_path / "config.yml"
|
||||
config_path.write_text(
|
||||
yaml.dump({"period": {"precision": "datetime", "dynamic": True}})
|
||||
)
|
||||
|
||||
fake_datetime = mock.MagicMock()
|
||||
fake_datetime.fromisoformat = real_datetime.fromisoformat
|
||||
fake_datetime.strptime = real_datetime.strptime
|
||||
fake_datetime.now.return_value = real_datetime(2026, 9, 15, 12, 0, 0)
|
||||
|
||||
with mock.patch("redmine_reporter.cli.datetime", fake_datetime):
|
||||
with mock.patch(
|
||||
"redmine_reporter.cli.get_formatter_by_extension"
|
||||
) as mock_get:
|
||||
mock_formatter = mock.MagicMock()
|
||||
mock_get.return_value = mock_formatter
|
||||
code = main(
|
||||
[
|
||||
"--commit",
|
||||
"--date",
|
||||
"2026-09-10T09:00:00--2026-09-12T18:00:00",
|
||||
"--output",
|
||||
str(tmp_path / "report.xlsx"),
|
||||
"--config-path",
|
||||
str(config_path),
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
|
||||
mock_save.assert_called_once_with(
|
||||
str(config_path),
|
||||
"2026-09-10T09:00:00",
|
||||
"2026-09-15T12:00:00",
|
||||
"datetime",
|
||||
True,
|
||||
)
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
@@ -1535,11 +1589,10 @@ class TestReportNoTimeIntegration:
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_cli_dynamic_datetime_period_without_date(mock_fetch, tmp_path):
|
||||
"""dynamic+datetime без --date: datetime-диапазон из last_used принимается.
|
||||
"""dynamic+datetime без --date: скользящее окно от last_used.to до now (#70).
|
||||
|
||||
Ожидаемое поведение после фикса #59: parse_date_range принимает диапазон
|
||||
с временем (YYYY-MM-DDTHH:MM:SS), следующий период вычисляется от last_used,
|
||||
отчёт строится без ошибки.
|
||||
Ожидаемое поведение после фикса #70: дефолтный диапазон начинается с
|
||||
last_used.to и заканчивается текущим моментом, а не сдвинутым периодом.
|
||||
"""
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0, None)]
|
||||
@@ -1554,14 +1607,17 @@ def test_cli_dynamic_datetime_period_without_date(mock_fetch, tmp_path):
|
||||
" to: '2026-06-30T23:59:59'\n"
|
||||
)
|
||||
|
||||
code = main(["--config-path", str(config_path)])
|
||||
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, 7, 15, 10, 0, 0)
|
||||
code = main(["--config-path", str(config_path)])
|
||||
|
||||
assert code == 0
|
||||
args, _ = mock_fetch.call_args
|
||||
from_date, to_date = args[0], args[1]
|
||||
# Следующий период после 2026-06-01T00:00:00--2026-06-30T23:59:59
|
||||
assert from_date == "2026-07-01T00:00:00"
|
||||
assert to_date == "2026-07-30T23:59:59"
|
||||
assert from_date == "2026-06-30T23:59:59"
|
||||
assert to_date == "2026-07-15T10:00:00"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user