@@ -6,7 +6,58 @@ import {Octokit} from '@octokit/core'
66import { throttling } from '@octokit/plugin-throttling'
77const OctokitWithThrottling = Octokit . plugin ( throttling )
88
9- describe . skip ( 'site-with-errors' , ( ) => {
9+ const WAIT_FOR_PULL_REQUESTS = ! ! process . env . WAIT_FOR_PULL_REQUESTS
10+ const POLL_INTERVAL_MS = 30_000 // 30 seconds
11+ const POLL_TIMEOUT_MS = 15 * 60 * 1000 // 15 minutes
12+
13+ /**
14+ * Repeatedly calls `fn` until `predicate(result)` returns `true`, or until the timeout is exceeded.
15+ * Errors thrown by `fn` (e.g. HTTP 404) are swallowed while polling continues.
16+ */
17+ async function pollUntil < T > (
18+ fn : ( ) => Promise < T > ,
19+ predicate : ( result : T ) => boolean ,
20+ { intervalMs, timeoutMs} : { intervalMs : number ; timeoutMs : number } ,
21+ ) : Promise < T > {
22+ const deadline = Date . now ( ) + timeoutMs
23+ let lastError : unknown
24+ while ( true ) {
25+ try {
26+ const result = await fn ( )
27+ if ( predicate ( result ) ) return result
28+ } catch ( error ) {
29+ lastError = error
30+ }
31+ if ( Date . now ( ) >= deadline ) {
32+ throw lastError ?? new Error ( `Timed out after ${ timeoutMs } ms waiting for condition` )
33+ }
34+ await new Promise ( resolve => setTimeout ( resolve , intervalMs ) )
35+ }
36+ }
37+
38+ function createOctokit ( ) : InstanceType < typeof OctokitWithThrottling > {
39+ return new OctokitWithThrottling ( {
40+ auth : process . env . GITHUB_TOKEN ,
41+ throttle : {
42+ onRateLimit : ( retryAfter , options , octokit , retryCount ) => {
43+ octokit . log . warn ( `Request quota exhausted for request ${ options . method } ${ options . url } ` )
44+ if ( retryCount < 3 ) {
45+ octokit . log . info ( `Retrying after ${ retryAfter } seconds!` )
46+ return true
47+ }
48+ } ,
49+ onSecondaryRateLimit : ( retryAfter , options , octokit , retryCount ) => {
50+ octokit . log . warn ( `Secondary rate limit hit for request ${ options . method } ${ options . url } ` )
51+ if ( retryCount < 3 ) {
52+ octokit . log . info ( `Retrying after ${ retryAfter } seconds!` )
53+ return true
54+ }
55+ } ,
56+ } ,
57+ } )
58+ }
59+
60+ describe ( 'site-with-errors' , ( ) => {
1061 let results : Result [ ]
1162
1263 beforeAll ( ( ) => {
@@ -100,32 +151,11 @@ describe.skip('site-with-errors', () => {
100151 expect ( process . env . GITHUB_TOKEN ) . toBeDefined ( )
101152 } )
102153
103- describe . runIf ( ! ! process . env . GITHUB_TOKEN ) ( '—' , ( ) => {
104- let octokit : Octokit
154+ describe . runIf ( ! ! process . env . GITHUB_TOKEN ) ( 'issues' , ( ) => {
105155 let issues : Endpoints [ 'GET /repos/{owner}/{repo}/issues/{issue_number}' ] [ 'response' ] [ 'data' ] [ ]
106- let pullRequests : Endpoints [ 'GET /repos/{owner}/{repo}/pulls/{pull_number}' ] [ 'response' ] [ 'data' ] [ ]
107156
108157 beforeAll ( async ( ) => {
109- octokit = new OctokitWithThrottling ( {
110- auth : process . env . GITHUB_TOKEN ,
111- throttle : {
112- onRateLimit : ( retryAfter , options , octokit , retryCount ) => {
113- octokit . log . warn ( `Request quota exhausted for request ${ options . method } ${ options . url } ` )
114- if ( retryCount < 3 ) {
115- octokit . log . info ( `Retrying after ${ retryAfter } seconds!` )
116- return true
117- }
118- } ,
119- onSecondaryRateLimit : ( retryAfter , options , octokit , retryCount ) => {
120- octokit . log . warn ( `Secondary rate limit hit for request ${ options . method } ${ options . url } ` )
121- if ( retryCount < 3 ) {
122- octokit . log . info ( `Retrying after ${ retryAfter } seconds!` )
123- return true
124- }
125- } ,
126- } ,
127- } )
128- // Fetch issues referenced in the cache file
158+ const octokit = createOctokit ( )
129159 issues = await Promise . all (
130160 results . map ( async ( { issue : { url : issueUrl } } ) => {
131161 expect ( issueUrl ) . toBeDefined ( )
@@ -142,23 +172,6 @@ describe.skip('site-with-errors', () => {
142172 return issue
143173 } ) ,
144174 )
145- // Fetch pull requests referenced in the findings file
146- pullRequests = await Promise . all (
147- results . map ( async ( { pullRequest : { url : pullRequestUrl } } ) => {
148- expect ( pullRequestUrl ) . toBeDefined ( )
149- const { owner, repo, pullNumber} =
150- / h t t p s : \/ \/ g i t h u b \. c o m \/ (?< owner > [ ^ / ] + ) \/ (?< repo > [ ^ / ] + ) \/ p u l l \/ (?< pullNumber > \d + ) / . exec (
151- pullRequestUrl ! ,
152- ) ! . groups !
153- const { data : pullRequest } = await octokit . request ( 'GET /repos/{owner}/{repo}/pulls/{pull_number}' , {
154- owner,
155- repo,
156- pull_number : parseInt ( pullNumber , 10 ) ,
157- } )
158- expect ( pullRequest ) . toBeDefined ( )
159- return pullRequest
160- } ) ,
161- )
162175 } )
163176
164177 it ( 'issues exist and have expected title, state, and assignee' , async ( ) => {
@@ -179,6 +192,35 @@ describe.skip('site-with-errors', () => {
179192 expect ( issue . assignees ! . some ( a => a . login === 'Copilot' ) ) . toBe ( true )
180193 }
181194 } )
195+ } )
196+
197+ describe . runIf ( ! ! process . env . GITHUB_TOKEN && WAIT_FOR_PULL_REQUESTS ) ( 'pull requests' , ( ) => {
198+ let pullRequests : Endpoints [ 'GET /repos/{owner}/{repo}/pulls/{pull_number}' ] [ 'response' ] [ 'data' ] [ ]
199+
200+ beforeAll ( async ( ) => {
201+ const octokit = createOctokit ( )
202+ pullRequests = await Promise . all (
203+ results . map ( async ( { pullRequest : { url : pullRequestUrl } } ) => {
204+ expect ( pullRequestUrl ) . toBeDefined ( )
205+ const { owner, repo, pullNumber} =
206+ / h t t p s : \/ \/ g i t h u b \. c o m \/ (?< owner > [ ^ / ] + ) \/ (?< repo > [ ^ / ] + ) \/ p u l l \/ (?< pullNumber > \d + ) / . exec (
207+ pullRequestUrl ! ,
208+ ) ! . groups !
209+ return pollUntil (
210+ async ( ) => {
211+ const { data} = await octokit . request ( 'GET /repos/{owner}/{repo}/pulls/{pull_number}' , {
212+ owner,
213+ repo,
214+ pull_number : parseInt ( pullNumber , 10 ) ,
215+ } )
216+ return data
217+ } ,
218+ pr => pr . state === 'open' ,
219+ { intervalMs : POLL_INTERVAL_MS , timeoutMs : POLL_TIMEOUT_MS } ,
220+ )
221+ } ) ,
222+ )
223+ } , POLL_TIMEOUT_MS + 60_000 )
182224
183225 it ( 'pull requests exist and have expected author, state, and assignee' , async ( ) => {
184226 for ( const pullRequest of pullRequests ) {
0 commit comments