Files
redmine-reporter/tests/test_yaml_config.py
Кокос Артем Николаевич 47152f8f04 feat: output path defaults (#43) + datetime precision & dedup (#47)
#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
2026-07-07 10:16:46 +07:00

209 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import logging
import os
import tempfile
from pathlib import Path
from unittest import mock
from redmine_reporter.yaml_config import (
check_file_permissions,
ensure_config_dir,
expand_filename_template,
resolve_env_vars,
)
class TestResolveEnvVars:
"""Tests for ${VAR} resolution."""
@mock.patch.dict(os.environ, {"MY_VAR": "hello"}, clear=True)
def test_replaces_var_with_env_value(self):
assert resolve_env_vars("prefix_${MY_VAR}_suffix") == "prefix_hello_suffix"
@mock.patch.dict(os.environ, {}, clear=True)
def test_missing_var_returns_empty_and_warns(self, caplog):
with caplog.at_level(logging.WARNING):
result = resolve_env_vars("${MISSING_VAR}")
assert result == ""
assert "MISSING_VAR" in caplog.text
def test_no_braces_returns_unchanged(self):
assert resolve_env_vars("plain text no vars") == "plain text no vars"
@mock.patch.dict(os.environ, {"A": "${B}", "B": "final"}, clear=True)
def test_non_recursive_no_double_resolution(self):
# A → literal "${B}", B → "final". Non-recursive means we get "${B}", not "final".
result = resolve_env_vars("${A}")
assert result == "${B}"
class TestEnsureConfigDir:
"""Tests for config directory creation."""
def test_creates_dir_with_0700(self):
with tempfile.TemporaryDirectory() as tmp:
config_dir = Path(tmp) / "test_config"
result = ensure_config_dir(config_dir)
assert result == config_dir
assert result.exists()
assert result.is_dir()
mode = result.stat().st_mode & 0o777
assert mode == 0o700
def test_noop_if_dir_exists(self):
with tempfile.TemporaryDirectory() as tmp:
config_dir = Path(tmp) / "existing"
config_dir.mkdir(mode=0o700)
mtime_before = config_dir.stat().st_mtime
result = ensure_config_dir(config_dir)
assert result == config_dir
assert config_dir.stat().st_mtime == mtime_before
class TestCheckFilePermissions:
"""Tests for config file permission checks."""
def test_warns_on_permissive_file(self):
with tempfile.TemporaryDirectory() as tmp:
f = Path(tmp) / "open.yml"
f.write_text("key: value")
f.chmod(0o644)
warnings = check_file_permissions(f)
assert len(warnings) >= 1
assert "0644" in warnings[0] or "permissions" in warnings[0].lower()
def test_silent_on_0600(self):
with tempfile.TemporaryDirectory() as tmp:
f = Path(tmp) / "secure.yml"
f.write_text("key: value")
f.chmod(0o600)
warnings = check_file_permissions(f)
assert warnings == []
class TestExpandFilenameTemplate:
"""Tests for filename template expansion."""
def test_expands_all_placeholders(self):
result = expand_filename_template(
"report_{author}_{from}_{to}.{ext}",
author="Кокос А.А.",
from_date="2026-06-01",
to_date="2026-06-30",
ext="xlsx",
)
assert result == "report_Кокос_А.А._2026-06-01_2026-06-30.xlsx"
def test_date_placeholder_dd_mm_yyyy(self):
result = expand_filename_template(
"отчёт_{date}.{ext}",
author="Кокос А.А.",
from_date="2026-03-01",
to_date="2026-03-31",
ext="odt",
)
assert result == "отчёт_31_03_2026.odt"
def test_no_placeholders_returns_unchanged(self):
result = expand_filename_template(
"report.odt",
author="Кокос А.А.",
from_date="2026-01-01",
to_date="2026-01-31",
ext="odt",
)
assert result == "report.odt"
def test_unknown_placeholder_left_as_is(self):
result = expand_filename_template(
"{author}_{unknown}.{ext}",
author="Кокос А.А.",
from_date="2026-01-01",
to_date="2026-01-31",
ext="xlsx",
)
assert result == "Кокос_А.А._{unknown}.xlsx"
class TestResolveOutputPath:
"""Tests for resolve_output_path()."""
def test_none_returns_none(self):
from redmine_reporter.yaml_config import resolve_output_path
assert resolve_output_path(None) is None
def test_explicit_path_with_known_extension_is_unchanged(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path("/tmp/report.xlsx")
assert result == "/tmp/report.xlsx"
def test_explicit_path_with_unknown_extension_is_unchanged(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path("report.odt")
assert result == "report.odt"
def test_no_extension_appends_default_format(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path("report", default_format="xlsx")
assert result == "report.xlsx"
def test_no_extension_appends_custom_default_format(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path("file", default_format="csv")
assert result == "file.csv"
def test_bare_format_name_resolves_to_template_path(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path(
"xlsx",
output_dir="/tmp/reports",
filename_template="{date}.{ext}",
author="Кокос А.А.",
from_date="2026-06-01",
to_date="2026-06-30",
default_format="xlsx",
)
assert result == "/tmp/reports/30_06_2026.xlsx"
def test_bare_format_name_odt_resolves_to_template_path(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path(
"odt",
output_dir="~/reports",
filename_template="{author}_{from}_{to}.{ext}",
author="Кокос А.А.",
from_date="2026-06-01",
to_date="2026-06-30",
default_format="xlsx",
)
assert result.startswith("/") and result.endswith(
"/reports/Кокос_А.А._2026-06-01_2026-06-30.odt"
)
assert "~" not in result
def test_bare_format_name_with_empty_output_dir_uses_cwd(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path(
"csv",
output_dir="",
filename_template="report.{ext}",
author="A",
from_date="2026-01-01",
to_date="2026-01-31",
)
assert not result.startswith("/")
assert "report.csv" in result
def test_unknown_bare_format_is_treated_as_path(self):
from redmine_reporter.yaml_config import resolve_output_path
result = resolve_output_path("unknown_format")
assert result == "unknown_format.xlsx"