#43 — Имя файла и пути по умолчанию: - resolve_output_path() в yaml_config.py: резолвит --output с учётом output.dir, filename_template, default_format из YAML-конфига - Bare format (xlsx/odt/...) → путь по шаблону - Без расширения → автодописывается default_format (.xlsx) - Config.get_output_dir/filename/default_format() #47 — datetime precision и дедупликация: - period.last_used.from/to в YAML-конфиге и AppConfig - period.precision: date|datetime - _compute_dedup_cutoff() в cli.py: при precision=datetime вычисляет cutoff из period.last_used.to - _parse_datetime() + AND-логика дедупликации в client.py: запись исключается если created_on И updated_on < cutoff - Пропуск issues без часов после дедупликации Closes #43 Closes #47
This commit is contained in:
@@ -97,13 +97,27 @@ def test_cli_unknown_output_extension(mock_fetch, tmp_path):
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_cli_output_without_extension(mock_fetch, tmp_path):
|
||||
"""Файл без расширения -- выход 1 с подсказкой."""
|
||||
@mock.patch("redmine_reporter.cli.get_formatter_by_extension")
|
||||
def test_cli_output_without_extension(mock_get, mock_fetch, tmp_path):
|
||||
"""Файл без расширения — приложение расширяет default_format (#43)."""
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0)]
|
||||
mock_formatter = mock.MagicMock()
|
||||
mock_get.return_value = mock_formatter
|
||||
output = str(tmp_path / "report")
|
||||
code = main(["--date", "2026-01-01--2026-01-31", "--output", output])
|
||||
assert code == 1
|
||||
code = main(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--output",
|
||||
output,
|
||||
"--config-path",
|
||||
str(tmp_path / "nonexistent.yml"),
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
saved_path = mock_formatter.save.call_args.args[1]
|
||||
assert saved_path.endswith(".xlsx")
|
||||
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@@ -407,3 +421,120 @@ def test_cli_autoloads_yaml_config(mock_fetch, tmp_path):
|
||||
mock_fetch.return_value = None
|
||||
code = main(["--date", "2026-01-01--2026-01-31", "--config-path", str(config_path)])
|
||||
assert code == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# #43: --output path resolution tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestOutputPathResolution:
|
||||
"""Tests for --output flag with path defaults."""
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_bare_format_name_xlsx_uses_template_path(self, mock_fetch, tmp_path):
|
||||
"""--output xlsx без пути → использует шаблон из конфига."""
|
||||
import yaml
|
||||
|
||||
config_path = tmp_path / "config.yml"
|
||||
config_path.write_text(
|
||||
yaml.dump(
|
||||
{
|
||||
"output": {
|
||||
"dir": str(tmp_path / "reports"),
|
||||
"filename": "report_{date}.{ext}",
|
||||
"default_format": "xlsx",
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
mock_fetch.return_value = None
|
||||
code = main(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--output",
|
||||
"xlsx",
|
||||
"--config-path",
|
||||
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_output_without_extension_appends_default_format(self, mock_fetch, tmp_path):
|
||||
"""--output report без расширения → добавляет .xlsx."""
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0)]
|
||||
|
||||
output = str(tmp_path / "report")
|
||||
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(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--output",
|
||||
output,
|
||||
"--config-path",
|
||||
str(tmp_path / "nonexistent.yml"),
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
mock_formatter.save.assert_called_once()
|
||||
saved_path = mock_formatter.save.call_args.args[1]
|
||||
assert saved_path.endswith(".xlsx")
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_output_with_known_extension_unchanged(self, mock_fetch, tmp_path):
|
||||
"""--output report.csv с явным расширением передаётся как есть."""
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0)]
|
||||
|
||||
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(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--output",
|
||||
str(tmp_path / "report.csv"),
|
||||
"--config-path",
|
||||
str(tmp_path / "nonexistent.yml"),
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
mock_formatter.save.assert_called_once()
|
||||
saved_path = mock_formatter.save.call_args.args[1]
|
||||
assert saved_path.endswith(".csv")
|
||||
|
||||
@mock.patch.dict(os.environ, VALID_ENV, clear=True)
|
||||
@mock.patch("redmine_reporter.cli.fetch_issues_with_spent_time")
|
||||
def test_output_bare_format_invalid_is_treated_as_path(self, mock_fetch, tmp_path):
|
||||
"""--output notanxlsx (no path, not known format) → treated as path + .xlsx appended."""
|
||||
issue = _MockIssue()
|
||||
mock_fetch.return_value = [(issue, 1.0)]
|
||||
|
||||
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(
|
||||
[
|
||||
"--date",
|
||||
"2026-01-01--2026-01-31",
|
||||
"--output",
|
||||
"myreport",
|
||||
"--config-path",
|
||||
str(tmp_path / "nonexistent.yml"),
|
||||
]
|
||||
)
|
||||
assert code == 0
|
||||
mock_formatter.save.assert_called_once()
|
||||
saved_path = mock_formatter.save.call_args.args[1]
|
||||
assert saved_path == "myreport.xlsx"
|
||||
|
||||
Reference in New Issue
Block a user