Skip to content

Commit 2d209f7

Browse files
authored
Merge pull request #13 from RECETOX/copilot/fix-github-action-workflow-issue
Fix: oldest report rows dropped on every weekly Action run
2 parents 47f5d05 + 311b5ac commit 2d209f7

2 files changed

Lines changed: 123 additions & 2 deletions

File tree

src/reports/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ def create_report(self, year: Optional[int] = None) -> None:
107107
entity: self.aggregate_data(path) for entity, path in files.items()
108108
}
109109

110-
# Get all periods and filter
111-
all_periods = {p for data in entity_data.values() for p in data.keys()}
110+
# Get all periods and filter (include existing periods to prevent data loss)
111+
all_periods = {p for data in entity_data.values() for p in data.keys()} | set(existing_data.keys())
112112
periods = self.filter_periods(all_periods, year)
113113

114114
if not periods:

tests/test_report.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,132 @@
1+
import csv
2+
import json
3+
import os
4+
import shutil
5+
import tempfile
16
import unittest
7+
from pathlib import Path
28
from unittest.mock import mock_open, patch
39

410
import orjson
511

12+
from src.reports.github import GitHubReportGenerator
613
from src.utils import write_json
714

815

16+
class TestReportPreservesExistingPeriods(unittest.TestCase):
17+
"""Regression tests for https://github.com/RECETOX/specdatri_reporting/issues/12.
18+
19+
Each weekly GitHub Action run only retrieves the last 14 days of traffic data.
20+
Older periods that are no longer returned by the API must be preserved from the
21+
existing report rather than silently dropped.
22+
"""
23+
24+
def setUp(self):
25+
self.tmp_dir = tempfile.mkdtemp()
26+
self.output_dir = tempfile.mkdtemp()
27+
self.output_file = Path(self.output_dir) / "github_clones.tsv"
28+
29+
def tearDown(self):
30+
shutil.rmtree(self.tmp_dir)
31+
shutil.rmtree(self.output_dir)
32+
33+
def _write_github_json(self, filename, stat_type, entries):
34+
"""Write a GitHub API traffic response JSON file into tmp_dir."""
35+
data = {stat_type: entries}
36+
with open(os.path.join(self.tmp_dir, filename), "w") as f:
37+
json.dump(data, f)
38+
39+
def _write_existing_report(self, periods_data):
40+
"""Write a TSV report with the given {week: {package: count}} data."""
41+
packages = sorted({p for row in periods_data.values() for p in row})
42+
with open(self.output_file, "w", newline="", encoding="utf-8") as f:
43+
writer = csv.writer(f, delimiter="\t")
44+
writer.writerow(["week"] + packages)
45+
for period, row in sorted(periods_data.items()):
46+
writer.writerow([period] + [row.get(p, "") for p in packages])
47+
48+
def _read_report_weeks(self):
49+
"""Return the list of week values from the written report."""
50+
with open(self.output_file, "r", newline="", encoding="utf-8") as f:
51+
reader = csv.DictReader(f, delimiter="\t")
52+
return [row["week"] for row in reader]
53+
54+
def test_oldest_period_preserved_after_api_window_moves(self):
55+
"""The oldest week in the report must not be dropped when the GitHub API
56+
no longer returns it (14-day rolling window moved forward).
57+
58+
Scenario (mimics the observed weekly Action behaviour):
59+
- Existing report contains W07 and W08.
60+
- New API response only covers W08 and W09 (W07 has aged out of the window).
61+
- After regeneration W07 must still be present in the report.
62+
"""
63+
year = 2026
64+
stat_type = "clones"
65+
package = "mypkg"
66+
67+
# Existing report: W07 and W08 are already recorded.
68+
self._write_existing_report(
69+
{
70+
"2026-W07": {package: 10},
71+
"2026-W08": {package: 8},
72+
}
73+
)
74+
75+
# New API file (dated 2026-03-02): coverage window is Feb 16 – Mar 02.
76+
# W07 (Feb 9-15) has fallen outside the window; only W08 and W09 data present.
77+
filename = f"2026-03-02_00-00-00__myproject__{package}__github__{stat_type}.json"
78+
self._write_github_json(
79+
filename,
80+
stat_type,
81+
[
82+
# W08 days (within coverage window)
83+
{"timestamp": "2026-02-16T00:00:00Z", "count": 5, "uniques": 5},
84+
{"timestamp": "2026-02-17T00:00:00Z", "count": 3, "uniques": 3},
85+
# W09 days (within coverage window)
86+
{"timestamp": "2026-02-23T00:00:00Z", "count": 7, "uniques": 7},
87+
{"timestamp": "2026-02-24T00:00:00Z", "count": 2, "uniques": 2},
88+
],
89+
)
90+
91+
generator = GitHubReportGenerator(
92+
Path(self.tmp_dir), self.output_file, year, stat_type
93+
)
94+
generator.create_report(year=year)
95+
96+
weeks = self._read_report_weeks()
97+
98+
self.assertIn("2026-W07", weeks, "Oldest existing week must be preserved")
99+
self.assertIn("2026-W08", weeks)
100+
self.assertIn("2026-W09", weeks)
101+
102+
def test_oldest_period_value_preserved(self):
103+
"""The data value for the oldest week must remain correct after regeneration."""
104+
year = 2026
105+
stat_type = "clones"
106+
package = "mypkg"
107+
108+
self._write_existing_report({"2026-W07": {package: 42}})
109+
110+
filename = f"2026-03-02_00-00-00__myproject__{package}__github__{stat_type}.json"
111+
self._write_github_json(
112+
filename,
113+
stat_type,
114+
[{"timestamp": "2026-02-23T00:00:00Z", "count": 5, "uniques": 5}],
115+
)
116+
117+
generator = GitHubReportGenerator(
118+
Path(self.tmp_dir), self.output_file, year, stat_type
119+
)
120+
generator.create_report(year=year)
121+
122+
with open(self.output_file, "r", newline="", encoding="utf-8") as f:
123+
reader = csv.DictReader(f, delimiter="\t")
124+
rows = {row["week"]: row for row in reader}
125+
126+
self.assertIn("2026-W07", rows)
127+
self.assertEqual(rows["2026-W07"][package], "42")
128+
129+
9130
class TestReports(unittest.TestCase):
10131
@patch("builtins.open", new_callable=mock_open)
11132
@patch("src.utils.orjson.dumps")

0 commit comments

Comments
 (0)