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, issue_id=999): self.id = issue_id 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"] == "Решена" assert STATUS_TRANSLATION["Pending"] == "Ожидание" def test_status_translation_unknown_passthrough(): """Неизвестный статус возвращается как есть.""" from redmine_reporter.report_builder import STATUS_TRANSLATION assert "SomeNewStatus" not in STATUS_TRANSLATION # build_grouped_report вернёт оригинальное значение issue = MockIssue("P", "S", "SomeNewStatus", "v1.0", 1) rows = build_grouped_report([(issue, 1.0)]) assert rows[0]["status_ru"] == "SomeNewStatus" # -- Основная логика группировки -- def test_build_grouped_report_grouping(): issues = [ (MockIssue("Камеры", "Фича A", "New", "v2.5.0", 101), 2.0), (MockIssue("Камеры", "Баг B", "Resolved", "v2.5.0", 102), 1.5), (MockIssue("ПО", "Доки", "Pending", None, 201), 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"] == "" assert rows[0]["status_ru"] == "В работе" assert rows[0]["time_text"] == "2ч" assert rows[1]["time_text"] == "1ч 30м" def test_build_grouped_report_new_version_same_project(): """Смена версии внутри одного проекта -- display_project пустой, display_version новая.""" issues = [ (MockIssue("Камеры", "Задача 1", "New", "v1.0", 1), 1.0), (MockIssue("Камеры", "Задача 2", "New", "v2.0", 2), 1.0), ] rows = build_grouped_report(issues) assert rows[0]["display_project"] == "Камеры" assert rows[0]["display_version"] == "v1.0" assert rows[1]["display_project"] == "" assert rows[1]["display_version"] == "v2.0" def test_build_grouped_report_sorts_input(): """Несортированный вход -- результат всё равно корректно сгруппирован.""" issues = [ (MockIssue("ПО", "Задача B", "New", "v1.0", 2), 1.0), (MockIssue("Камеры", "Задача A", "New", "v1.0", 1), 2.0), ] rows = build_grouped_report(issues) # После сортировки "Камеры" < "ПО" (лексикографически по кириллице) assert rows[0]["project"] == "Камеры" assert rows[1]["project"] == "ПО" # Оба display_project непустые -- разные проекты assert rows[0]["display_project"] == "Камеры" assert rows[1]["display_project"] == "ПО" def test_build_grouped_report_empty(): """Пустой вход -- пустой результат, без исключений.""" rows = build_grouped_report([]) assert rows == [] def test_build_grouped_report_no_time(): """fill_time=False -- time_text пустой для всех строк.""" issues = [(MockIssue("P", "S", "New", "v1.0", 1), 3.5)] rows = build_grouped_report(issues, fill_time=False) assert rows[0]["time_text"] == "" def test_build_grouped_report_preserves_issue_id_and_subject(): issues = [(MockIssue("P", "Моя задача", "Closed", "v1.0", 42), 0.5)] rows = build_grouped_report(issues) assert rows[0]["issue_id"] == 42 assert rows[0]["subject"] == "Моя задача"