fix(api): prefer anonymous gitea weekly stats
This commit is contained in:
@@ -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:
|
||||
"""
|
||||
生成週報
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user