Add unit-tests

This commit is contained in:
Артём Кокос
2026-01-25 12:40:23 +07:00
parent ea90fe79d0
commit d839be8776
5 changed files with 155 additions and 0 deletions

23
tests/test_cli.py Normal file
View File

@@ -0,0 +1,23 @@
import sys
from io import StringIO
from unittest import mock
from redmine_reporter.cli import main
@mock.patch.dict("os.environ", {
"REDMINE_URL": "https://red.eltex.loc/",
"REDMINE_USER": "x",
"REDMINE_PASSWORD": "y"
})
@mock.patch("redmine_reporter.client.fetch_issues_with_spent_time")
def test_cli_smoke(mock_fetch):
mock_fetch.return_value = []
old_stdout = sys.stdout
sys.stdout = captured = StringIO()
try:
code = main(["--date", "2026-01-01--2026-01-31"])
assert code == 0
output = captured.getvalue()
assert "Total issues: 0" in output
finally:
sys.stdout = old_stdout

36
tests/test_client.py Normal file
View File

@@ -0,0 +1,36 @@
import pytest
from unittest import mock
from redmine_reporter.client import fetch_issues_with_spent_time
@mock.patch("redmine_reporter.client.Redmine")
def test_fetch_issues_with_spent_time(mock_redmine_class):
# Подготовка моков
mock_redmine = mock_redmine_class.return_value
mock_user = mock.MagicMock()
mock_user.id = 123
mock_redmine.user.get.return_value = mock_user
# Два time entry на одну задачу
mock_entry1 = mock.MagicMock()
mock_entry1.issue.id = 101
mock_entry1.hours = 2.0
mock_entry2 = mock.MagicMock()
mock_entry2.issue.id = 101
mock_entry2.hours = 1.5
mock_redmine.time_entry.filter.return_value = [mock_entry1, mock_entry2]
# Мок задачи
mock_issue = mock.MagicMock()
mock_issue.id = 101
mock_issue.project = "Проект X"
mock_issue.subject = "Тестовая задача"
mock_issue.status = "New"
mock_redmine.issue.filter.return_value = [mock_issue]
result = fetch_issues_with_spent_time("2026-01-01", "2026-01-31")
assert result is not None
assert len(result) == 1
issue, total_hours = result[0]
assert total_hours == 3.5

25
tests/test_config.py Normal file
View File

@@ -0,0 +1,25 @@
import os
import pytest
from unittest import mock
from redmine_reporter.config import Config
@mock.patch.dict(os.environ, {
"REDMINE_URL": "https://red.eltex.loc/",
"REDMINE_USER": "test",
"REDMINE_PASSWORD": "secret"
})
def test_config_valid():
Config.validate() # не должно быть исключения
@mock.patch.dict(os.environ, {}, clear=True)
def test_config_missing():
with pytest.raises(ValueError, match="REDMINE_URL"):
Config.validate()
@mock.patch.dict(os.environ, {"REDMINE_AUTHOR": "Иванов И.И."})
def test_get_author():
assert Config.get_author("") == "Иванов И.И."
assert Config.get_author("Петров П.П.") == "Петров П.П."

View File

@@ -0,0 +1,44 @@
import pytest
from redmine_reporter.report_builder import build_grouped_report, STATUS_TRANSLATION
from redmine_reporter.utils import get_version
class MockIssue:
def __init__(self, project, subject, status, fixed_version=None):
self.project = project
self.subject = subject
self.status = status
if fixed_version is not None:
self.fixed_version = fixed_version
def test_status_translation():
assert STATUS_TRANSLATION["Closed"] == "Закрыто"
assert STATUS_TRANSLATION["New"] == "В работе"
assert STATUS_TRANSLATION["Resolved"] == "Решена"
def test_build_grouped_report():
issues = [
(MockIssue("Камеры", "Фича A", "New", "v2.5.0"), 2.0),
(MockIssue("Камеры", "Баг B", "Resolved", "v2.5.0"), 1.5),
(MockIssue("ПО", "Доки", "Pending", None), 4.0),
]
rows = build_grouped_report(issues)
assert len(rows) == 3
# Первая строка — полное название проекта и версии
assert rows[0]["display_project"] == "Камеры"
assert rows[0]["display_version"] == "v2.5.0"
# Вторая — пустые display_* из-за совпадения
assert rows[1]["display_project"] == ""
assert rows[1]["display_version"] == ""
# Третья — новый проект
assert rows[2]["display_project"] == "ПО"
assert rows[2]["display_version"] == "<N/A>"
# Проверка перевода и времени
assert rows[0]["status_ru"] == "В работе"
assert rows[0]["time_text"] == ""
assert rows[1]["time_text"] == "1ч 30м"

27
tests/test_utils.py Normal file
View File

@@ -0,0 +1,27 @@
import pytest
from redmine_reporter.utils import hours_to_human, get_month_name_from_range, get_version
def test_hours_to_human():
assert hours_to_human(0) == ""
assert hours_to_human(1.0) == ""
assert hours_to_human(2.5) == "2ч 30м"
assert hours_to_human(0.75) == "45м"
assert hours_to_human(3.1666) == "3ч 10м" # ≈ 3ч 10м
def test_get_month_name_from_range():
assert get_month_name_from_range("2026-01-01", "2026-01-31") == "Январь"
assert get_month_name_from_range("2025-12-01", "2026-02-15") == "Февраль" # берётся to_date
assert get_month_name_from_range("invalid", "also_invalid") == "Январь" # fallback
def test_get_version():
class MockIssue:
pass
issue_with = MockIssue()
issue_with.fixed_version = "v2.5.0"
assert get_version(issue_with) == "v2.5.0"
issue_without = MockIssue()
assert get_version(issue_without) == "<N/A>"