From a82be05b83a91c7a9c814a0d2e30e1a0c3d2636c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D0=BA=D0=BE=D1=81=20=D0=90=D1=80=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=9D=D0=B8=D0=BA=D0=BE=D0=BB=D0=B0=D0=B5=D0=B2=D0=B8?= =?UTF-8?q?=D1=87?= Date: Mon, 29 Jun 2026 12:21:44 +0700 Subject: [PATCH] fix: handle empty time entries and add missing mypy override for openpyxl - Treat empty list from fetch_issues_with_spent_time as 'no entries' and exit 0 instead of crashing later in the pipeline. - Update CLI output-extension tests to use non-empty mock data so they actually reach the extension validation code path. - Add openpyxl to mypy ignore_missing_imports overrides. Closes #36 --- pyproject.toml | 2 +- redmine_reporter/cli.py | 4 ++++ tests/test_cli.py | 20 +++++++++++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index af862ba..adf4c8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,5 +57,5 @@ multi_line_output = 3 warn_unused_configs = true [[tool.mypy.overrides]] -module = ["odf.*", "redminelib.*", "tabulate"] +module = ["odf.*", "redminelib.*", "tabulate", "openpyxl.*"] ignore_missing_imports = true diff --git a/redmine_reporter/cli.py b/redmine_reporter/cli.py index 4b8688a..81e8a5c 100644 --- a/redmine_reporter/cli.py +++ b/redmine_reporter/cli.py @@ -114,6 +114,10 @@ def main(argv: Optional[List[str]] = None) -> int: print("ℹ️ No time entries found in the given period.", file=sys.stderr) return 0 + if not issue_hours: + print("ℹ️ No time entries found in the given period.", file=sys.stderr) + return 0 + print(f"✅ Total issues: {len(issue_hours)} [{args.date}]", file=sys.stderr) rows = build_grouped_report(issue_hours, fill_time=not args.no_time) diff --git a/tests/test_cli.py b/tests/test_cli.py index cfa1fa0..0be2e5b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -71,11 +71,23 @@ def test_cli_invalid_date_format(): assert code == 1 +class _MockIssue: + """Простой mock Redmine Issue для CLI-тестов.""" + + def __init__(self, issue_id=1, subject="Task", project="Project", status="New"): + self.id = issue_id + self.subject = subject + self.project = project + self.status = status + self.fixed_version = None + + @mock.patch.dict(os.environ, VALID_ENV, clear=True) @mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time") def test_cli_unknown_output_extension(mock_fetch, tmp_path): """Неизвестное расширение файла -- выход 1.""" - mock_fetch.return_value = [] + issue = _MockIssue() + mock_fetch.return_value = [(issue, 1.0)] output = str(tmp_path / "report.xyz") code = main(["--date", "2026-01-01--2026-01-31", "--output", output]) assert code == 1 @@ -85,7 +97,8 @@ def test_cli_unknown_output_extension(mock_fetch, tmp_path): @mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time") def test_cli_output_without_extension(mock_fetch, tmp_path): """Файл без расширения -- выход 1 с подсказкой.""" - mock_fetch.return_value = [] + issue = _MockIssue() + mock_fetch.return_value = [(issue, 1.0)] output = str(tmp_path / "report") code = main(["--date", "2026-01-01--2026-01-31", "--output", output]) assert code == 1 @@ -96,7 +109,8 @@ def test_cli_output_without_extension(mock_fetch, tmp_path): @mock.patch("redmine_reporter.cli.get_formatter_by_extension") def test_cli_odt_missing_odfpy_message(mock_gf, mock_fetch, tmp_path, capsys): """При запросе .odt без odfpy — выход 1, понятное сообщение про odfpy.""" - mock_fetch.return_value = [] + issue = _MockIssue() + mock_fetch.return_value = [(issue, 1.0)] mock_gf.return_value = None output = str(tmp_path / "report.odt") code = main(["--date", "2026-01-01--2026-01-31", "--output", output])