fix(api): prefer anonymous gitea weekly stats
Some checks failed
Code Review / ai-code-review (push) Successful in 15s
CD Pipeline / tests (push) Successful in 1m43s
CD Pipeline / build-and-deploy (push) Successful in 5m12s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-27 16:39:38 +08:00
parent 41681b7015
commit cb8bc9463c
2 changed files with 93 additions and 49 deletions

View File

@@ -154,9 +154,7 @@ class WeeklyReportService:
owner = quote(str(settings.GITEA_REPO_OWNER), safe="")
repo = quote(str(settings.GITEA_REPO_NAME), safe="")
since_utc = since.astimezone(ZoneInfo("UTC")).isoformat().replace("+00:00", "Z")
headers = {"Accept": "application/json", "User-Agent": "awoooi-weekly-report/1.0"}
if settings.GITEA_API_TOKEN:
headers["Authorization"] = f"token {settings.GITEA_API_TOKEN}"
header_candidates = self._gitea_read_headers()
commits = 0
deploys = 0
@@ -165,52 +163,54 @@ class WeeklyReportService:
max_pages = 4
try:
for api_url in self._gitea_api_candidates():
commits = deploys = 0
page = 1
total_commits: int | None = None
try:
while page <= max_pages:
query = urlencode({
"sha": "main",
"since": since_utc,
"page": page,
"limit": limit,
})
url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
request = Request(url, headers=headers)
with urlopen(request, timeout=3) as response:
total_header = response.headers.get("X-Total-Count") or response.headers.get("X-Total")
if total_header and total_header.isdigit():
total_commits = int(total_header)
payload = json.loads(response.read().decode("utf-8"))
if not isinstance(payload, list):
logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
break
commits += len(payload)
for item in payload:
commit = item.get("commit") if isinstance(item, dict) else {}
message = str((commit or {}).get("message") or "")
subject = message.splitlines()[0].lower()
if "deploy" in subject or subject.startswith("chore(cd):"):
deploys += 1
if len(payload) < limit:
return total_commits or commits, deploys, True
page += 1
if total_commits is not None and total_commits > 0:
logger.info(
"gitea_git_stats_sampled",
total_commits=total_commits,
sampled_pages=max_pages,
sampled_deploys=deploys,
for headers in header_candidates:
commits = deploys = 0
page = 1
total_commits: int | None = None
try:
while page <= max_pages:
query = urlencode({
"sha": "main",
"since": since_utc,
"page": page,
"limit": limit,
})
url = f"{api_url}/api/v1/repos/{owner}/{repo}/commits?{query}"
request = Request(url, headers=headers)
with urlopen(request, timeout=2) as response:
total_header = response.headers.get("X-Total-Count") or response.headers.get("X-Total")
if total_header and total_header.isdigit():
total_commits = int(total_header)
payload = json.loads(response.read().decode("utf-8"))
if not isinstance(payload, list):
logger.warning("gitea_git_stats_unexpected_payload", payload_type=type(payload).__name__)
break
commits += len(payload)
for item in payload:
commit = item.get("commit") if isinstance(item, dict) else {}
message = str((commit or {}).get("message") or "")
subject = message.splitlines()[0].lower()
if "deploy" in subject or subject.startswith("chore(cd):"):
deploys += 1
if len(payload) < limit:
return total_commits or commits, deploys, True
page += 1
if total_commits is not None and total_commits > 0:
logger.info(
"gitea_git_stats_sampled",
total_commits=total_commits,
sampled_pages=max_pages,
sampled_deploys=deploys,
)
return total_commits, deploys, True
except Exception as candidate_exc:
logger.warning(
"gitea_git_stats_candidate_failed",
api_url=api_url,
auth_mode="token" if "Authorization" in headers else "anonymous",
error=str(candidate_exc),
)
return total_commits, deploys, True
except Exception as candidate_exc:
logger.warning(
"gitea_git_stats_candidate_failed",
api_url=api_url,
error=str(candidate_exc),
)
continue
continue
except Exception as exc:
logger.warning("gitea_git_stats_failed", error=str(exc))
return 0, 0, False
@@ -224,6 +224,14 @@ class WeeklyReportService:
deduped.append(candidate)
return deduped
def _gitea_read_headers(self) -> list[dict[str, str]]:
"""取得 Gitea read-only 統計用 headers匿名讀優先。"""
base = {"Accept": "application/json", "User-Agent": "awoooi-weekly-report/1.0"}
if not settings.GITEA_API_TOKEN:
return [base]
token_headers = {**base, "Authorization": f"token {settings.GITEA_API_TOKEN}"}
return [base, token_headers]
async def generate_report(self) -> WeeklyReportMessage:
"""
生成週報

View File

@@ -151,7 +151,8 @@ class TestWeeklyReportGitStats:
def fake_urlopen(request, timeout):
assert "/api/v1/repos/wooo/awoooi/commits?" in request.full_url
assert timeout == 3
assert timeout == 2
assert "Authorization" not in request.headers
return Response()
monkeypatch.setattr(weekly_report_module, "urlopen", fake_urlopen)
@@ -167,6 +168,41 @@ class TestWeeklyReportGitStats:
assert deploys == 2
assert source_ok is True
def test_gitea_commit_stats_prefers_anonymous_read_before_token(self, monkeypatch):
payload = [{"commit": {"message": "fix(api): one\n"}}]
seen_authorization = []
class Response:
headers = {"X-Total-Count": "1"}
def __enter__(self):
return self
def __exit__(self, *_args):
return None
def read(self):
return weekly_report_module.json.dumps(payload).encode("utf-8")
def fake_urlopen(request, timeout):
seen_authorization.append(request.headers.get("Authorization"))
return Response()
monkeypatch.setattr(weekly_report_module, "urlopen", fake_urlopen)
monkeypatch.setattr(weekly_report_module.settings, "GITEA_API_URL", "https://gitea.example.test")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_REPO_OWNER", "wooo")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_REPO_NAME", "awoooi")
monkeypatch.setattr(weekly_report_module.settings, "GITEA_API_TOKEN", "redacted-token")
svc = WeeklyReportService(stats_service=object(), k3s_monitor=object())
monkeypatch.setattr(svc, "_gitea_api_candidates", lambda: ["https://gitea.example.test"])
commits, deploys, source_ok = svc._get_gitea_commit_stats(datetime.now(_TZ_TAIPEI))
assert commits == 1
assert deploys == 0
assert source_ok is True
assert seen_authorization == [None]
def test_gitea_commit_stats_uses_total_header_with_limited_scan(self, monkeypatch):
payload = [{"commit": {"message": "fix(api): one\n"}} for _ in range(50)]
calls = []