@@ -29,8 +29,12 @@ function json(value: unknown, status = 200): Response {
2929 } ) ;
3030}
3131
32- function api ( workflowRuns : WorkflowRun [ ] ) {
32+ function api ( workflowRuns : WorkflowRun [ ] | WorkflowRun [ ] [ ] ) {
3333 const requests : Array < { method : string ; url : string } > = [ ] ;
34+ const responses = Array . isArray ( workflowRuns [ 0 ] )
35+ ? ( workflowRuns as WorkflowRun [ ] [ ] )
36+ : [ workflowRuns as WorkflowRun [ ] ] ;
37+ let poll = 0 ;
3438 const fetchImpl = vi . fn ( async ( input : string | URL | Request , init ?: RequestInit ) => {
3539 const url = String ( input ) ;
3640 const method = init ?. method ?? "GET" ;
@@ -39,7 +43,9 @@ function api(workflowRuns: WorkflowRun[]) {
3943 return json ( { head : { ref : headBranch , sha : headSha , repo : { full_name : repository } } } ) ;
4044 }
4145 if ( url . includes ( "/actions/workflows/ci.yml/runs?" ) ) {
42- return json ( { workflow_runs : workflowRuns } ) ;
46+ const response = responses [ Math . min ( poll , responses . length - 1 ) ] ?? [ ] ;
47+ poll += 1 ;
48+ return json ( { workflow_runs : response } ) ;
4349 }
4450 if ( url . endsWith ( "/actions/runs/42/approve" ) && method === "POST" ) {
4551 return new Response ( null , { status : 201 } ) ;
@@ -80,6 +86,32 @@ describe("release PR held-run approval", () => {
8086 expect ( runsRequest ?. url ) . toContain ( "status=action_required" ) ;
8187 } ) ;
8288
89+ it ( "approves a held run that materializes late in the widened polling window" , async ( ) => {
90+ const delayedResponses : WorkflowRun [ ] [ ] = Array . from ( { length : 29 } , ( ) => [ ] ) ;
91+ delayedResponses . push ( [ run ( ) ] ) ;
92+ const client = api ( delayedResponses ) ;
93+ const sleep = vi . fn ( async ( ) => { } ) ;
94+
95+ const result = await approveReleasePullRequestRun ( {
96+ releasePr,
97+ repository,
98+ token : "test-token" ,
99+ apiUrl : "https://api.github.test" ,
100+ fetchImpl : client . fetchImpl ,
101+ sleep,
102+ } ) ;
103+
104+ expect ( result ) . toEqual ( { runId : 42 , headBranch, headSha } ) ;
105+ expect ( sleep ) . toHaveBeenCalledTimes ( 29 ) ;
106+ expect ( sleep ) . toHaveBeenCalledWith ( 10_000 ) ;
107+ expect (
108+ client . requests . filter (
109+ ( { method, url } ) => method === "GET" && url . includes ( "/actions/workflows/ci.yml/runs?" ) ,
110+ ) ,
111+ ) . toHaveLength ( 30 ) ;
112+ expect ( client . requests . filter ( ( { method } ) => method === "POST" ) ) . toHaveLength ( 1 ) ;
113+ } ) ;
114+
83115 it ( "fails the planted dispatch-only negative control" , async ( ) => {
84116 const client = api ( [ run ( { id : 7 , event : "workflow_dispatch" } ) ] ) ;
85117
0 commit comments