diff --git a/src/benchmark_radar/sources.py b/src/benchmark_radar/sources.py index 709ed224..ab98cd8b 100644 --- a/src/benchmark_radar/sources.py +++ b/src/benchmark_radar/sources.py @@ -689,6 +689,9 @@ def fetch_github_organizations( for page in range(1, max_pages + 1): if requests_made >= budget or len(found) + len(organization_found) >= limit: break + # An outage still spends a request: count before calling so + # failed organizations cannot bypass the shared quota cap. + requests_made += 1 payload = get_json( f"https://api.github.com/orgs/{login}/repos", params={ @@ -701,7 +704,6 @@ def fetch_github_organizations( headers=headers, **_request_options(config), ) - requests_made += 1 if not isinstance(payload, list) or not all( isinstance(row, dict) for row in payload ): diff --git a/tests/test_sources.py b/tests/test_sources.py index 9dba4b53..d2d005cb 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -3375,3 +3375,24 @@ def test_collection_method_falls_back_to_a_static_default_without_items(): assert collection_method("brave", []) == "API" assert collection_method("datacite", []) == "API" assert collection_method("openaire", []) == "API" + + +def test_github_organization_failures_consume_request_budget(monkeypatch): + organizations = [ + {"login": name, "display_name": name, "tier": "frontier"} + for name in ("first", "second", "third") + ] + calls = [] + monkeypatch.setattr( + "benchmark_radar.sources.load_priority_github_organizations", lambda path: organizations + ) + + def fake_get_json(url, **kwargs): + calls.append(url) + raise RequestError("temporary outage") + + monkeypatch.setattr("benchmark_radar.sources.get_json", fake_get_json) + # Failed requests still spend quota; max_requests used to count successes only. + with pytest.raises(RequestError, match="temporary outage"): + fetch_github_organizations({"max_requests": 1}, datetime(2026, 8, 8, tzinfo=UTC), 10) + assert calls == ["https://api.github.com/orgs/first/repos"]