Skip to content

Commit cfefc9a

Browse files
fix(automation): page workflow list to exhaustion, not a fixed page cap
_list_all_workflows now loops until a short/empty page or total_count is reached, instead of stopping at page 10. Added tests for pagination past ten pages and for stopping at total_count without an extra request.
1 parent 80838da commit cfefc9a

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

apps/api/src/routers/automation.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,21 @@ def _list_all_workflows(client: GitHubClient, owner: str, repo: str) -> list[dic
164164
``{total_count, workflows}`` object (not a bare array), so request_paginated's
165165
Link-following can't be reused -- page through it explicitly instead."""
166166
workflows: list[dict] = []
167-
for page in range(1, 11): # hard stop at 1000 workflows -- far past any real repo
167+
page = 1
168+
while True:
168169
data = client.request(
169170
"GET",
170171
f"/repos/{owner}/{repo}/actions/workflows",
171172
params={"per_page": 100, "page": page},
172173
)
173174
batch = data.get("workflows", [])
174175
workflows.extend(batch)
175-
if len(batch) < 100:
176+
total = data.get("total_count")
177+
# Stop on an empty page (guards against a misbehaving API and infinite loops),
178+
# a short page, or once total_count says we've seen everything.
179+
if not batch or len(batch) < 100 or (total is not None and len(workflows) >= total):
176180
break
181+
page += 1
177182
return workflows
178183

179184

apps/api/tests/test_automation.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,54 @@ def test_personal_dispatch_all_paginates_past_first_page(automation_client, db):
545545
assert {log.target for log in logs} == {"acme/demo#100", "acme/demo#101"}
546546

547547

548+
def test_personal_dispatch_all_paginates_beyond_ten_pages(automation_client, db):
549+
# 11 pages of 100 inactive workflows, then a final short page holding the only
550+
# two active ones -- the loop must not stop early at an arbitrary page cap.
551+
db.add(User(id=_USER.id, email=_USER.email, name=None, password_hash=None, is_workspace_admin=False))
552+
db.commit()
553+
full_page = {
554+
"total_count": 1102,
555+
"workflows": [{"id": i, "name": f"w{i}", "path": "p", "state": "disabled_manually"} for i in range(100)],
556+
}
557+
last_page = {
558+
"total_count": 1102,
559+
"workflows": [
560+
{"id": 1100, "name": "CI", "path": "p", "state": "active"},
561+
{"id": 1101, "name": "Release", "path": "p", "state": "active"},
562+
],
563+
}
564+
with patch("src.routers.automation.GitHubClient") as mock_client:
565+
mock_client.return_value.request.side_effect = [full_page] * 11 + [last_page, {}, {}]
566+
resp = automation_client.post(
567+
"/me/repos/acme/demo/workflows/dispatch-all",
568+
json={"token": "ghp_testtoken123456789012345678901234", "ref": "main"},
569+
)
570+
assert resp.status_code == 200
571+
assert resp.json()["dispatched_count"] == 2
572+
573+
574+
def test_personal_dispatch_all_stops_at_total_count(automation_client, db):
575+
# A page that is exactly per_page long but total_count says it's the last:
576+
# the loop must not fetch another (non-existent) page.
577+
db.add(User(id=_USER.id, email=_USER.email, name=None, password_hash=None, is_workspace_admin=False))
578+
db.commit()
579+
only_page = {
580+
"total_count": 100,
581+
"workflows": [{"id": i, "name": f"w{i}", "path": "p", "state": "disabled_manually"} for i in range(100)],
582+
}
583+
with patch("src.routers.automation.GitHubClient") as mock_client:
584+
mock_client.return_value.request.side_effect = [only_page] # exactly one GET, no POSTs
585+
resp = automation_client.post(
586+
"/me/repos/acme/demo/workflows/dispatch-all",
587+
json={"token": "ghp_testtoken123456789012345678901234", "ref": "main"},
588+
)
589+
assert resp.status_code == 200
590+
assert resp.json() == {
591+
"ref": "main", "results": [], "dispatched_count": 0, "skipped_count": 0, "failed_count": 0,
592+
}
593+
assert mock_client.return_value.request.call_count == 1
594+
595+
548596
def test_org_dispatch_all_no_token_returns_400(db, acme_org):
549597
client = _org_client(db, acme_org["admin"].id, email=acme_org["admin"].email)
550598
resp = client.post("/orgs/acme/repos/acme/demo/workflows/dispatch-all", json={"ref": "main"})

0 commit comments

Comments
 (0)