Extract grouping logic into shared function (#19)
Add group_rows_by_project_and_version() to report_builder.py and replace the duplicated inline grouping in both html.py and odt.py with a single call to the shared function. This ensures consistent grouping behavior across formatters and removes a maintenance burden — any change to grouping logic now happens in one place. Closes #19
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
from redmine_reporter.report_builder import STATUS_TRANSLATION, build_grouped_report
|
||||
from redmine_reporter.report_builder import (
|
||||
STATUS_TRANSLATION,
|
||||
build_grouped_report,
|
||||
group_rows_by_project_and_version,
|
||||
)
|
||||
|
||||
|
||||
class MockIssue:
|
||||
@@ -117,3 +121,104 @@ def test_build_grouped_report_deterministic_order_within_group():
|
||||
]
|
||||
rows = build_grouped_report(issues)
|
||||
assert [r["issue_id"] for r in rows] == [101, 102, 103]
|
||||
|
||||
|
||||
# -- #19: Общая функция группировки --
|
||||
|
||||
|
||||
def test_group_rows_basic():
|
||||
"""Два проекта, в одном две версии — структура корректна."""
|
||||
rows = [
|
||||
{
|
||||
"project": "A",
|
||||
"version": "v1",
|
||||
"issue_id": 1,
|
||||
"subject": "T1",
|
||||
"status_ru": "S",
|
||||
"time_text": "1ч",
|
||||
},
|
||||
{
|
||||
"project": "A",
|
||||
"version": "v2",
|
||||
"issue_id": 2,
|
||||
"subject": "T2",
|
||||
"status_ru": "S",
|
||||
"time_text": "2ч",
|
||||
},
|
||||
{
|
||||
"project": "B",
|
||||
"version": "v1",
|
||||
"issue_id": 3,
|
||||
"subject": "T3",
|
||||
"status_ru": "S",
|
||||
"time_text": "3ч",
|
||||
},
|
||||
]
|
||||
grouped = group_rows_by_project_and_version(rows)
|
||||
|
||||
assert list(grouped.keys()) == ["A", "B"]
|
||||
assert list(grouped["A"].keys()) == ["v1", "v2"]
|
||||
assert list(grouped["B"].keys()) == ["v1"]
|
||||
assert len(grouped["A"]["v1"]) == 1
|
||||
assert len(grouped["A"]["v2"]) == 1
|
||||
assert len(grouped["B"]["v1"]) == 1
|
||||
|
||||
|
||||
def test_group_rows_multiple_tasks_per_version():
|
||||
"""Несколько задач в одной версии — все попадают в список."""
|
||||
rows = [
|
||||
{
|
||||
"project": "A",
|
||||
"version": "v1",
|
||||
"issue_id": 1,
|
||||
"subject": "T1",
|
||||
"status_ru": "S",
|
||||
"time_text": "1ч",
|
||||
},
|
||||
{
|
||||
"project": "A",
|
||||
"version": "v1",
|
||||
"issue_id": 2,
|
||||
"subject": "T2",
|
||||
"status_ru": "S",
|
||||
"time_text": "2ч",
|
||||
},
|
||||
{
|
||||
"project": "A",
|
||||
"version": "v1",
|
||||
"issue_id": 3,
|
||||
"subject": "T3",
|
||||
"status_ru": "S",
|
||||
"time_text": "3ч",
|
||||
},
|
||||
]
|
||||
grouped = group_rows_by_project_and_version(rows)
|
||||
|
||||
assert len(grouped["A"]["v1"]) == 3
|
||||
assert [r["issue_id"] for r in grouped["A"]["v1"]] == [1, 2, 3]
|
||||
|
||||
|
||||
def test_group_rows_preserves_row_data():
|
||||
"""Строки в группировке — те же объекты, без потери данных."""
|
||||
rows = [
|
||||
{
|
||||
"project": "P",
|
||||
"version": "v1",
|
||||
"issue_id": 42,
|
||||
"subject": "Task",
|
||||
"status_ru": "Готово",
|
||||
"time_text": "5ч",
|
||||
},
|
||||
]
|
||||
grouped = group_rows_by_project_and_version(rows)
|
||||
|
||||
row = grouped["P"]["v1"][0]
|
||||
assert row["issue_id"] == 42
|
||||
assert row["subject"] == "Task"
|
||||
assert row["status_ru"] == "Готово"
|
||||
assert row["time_text"] == "5ч"
|
||||
|
||||
|
||||
def test_group_rows_empty():
|
||||
"""Пустой список — пустой словарь."""
|
||||
assert group_rows_by_project_and_version([]) == {}
|
||||
|
||||
Reference in New Issue
Block a user