@@ -97,7 +97,13 @@ function fixture() {
9797 return { data : { permission : state . permission } } ;
9898 } ,
9999 } ,
100- pulls : { list ( ) { } , get : async ( ) => ( { data : pr } ) } ,
100+ pulls : {
101+ list ( ) { } ,
102+ get : async ( params ) => {
103+ state . requests . push ( params ) ;
104+ return { data : structuredClone ( pr ) } ;
105+ } ,
106+ } ,
101107 actions : {
102108 listWorkflowRunArtifacts ( ) { } ,
103109 getWorkflowRun : async ( params ) => {
@@ -118,7 +124,7 @@ function fixture() {
118124 paginate : async ( method , params ) => {
119125 state . requests . push ( params ) ;
120126 if ( method === github . rest . pulls . list ) {
121- return state . pulls ;
127+ return structuredClone ( state . pulls ) ;
122128 }
123129 if ( method === github . rest . actions . listWorkflowRunArtifacts ) {
124130 return state . artifacts ;
@@ -297,6 +303,17 @@ await test('isolates build concurrency by PR and SHA, including delayed old runs
297303 assert . equal ( group ( 2684 , 'b' . repeat ( 40 ) ) , newer ) ;
298304} ) ;
299305
306+ await test ( 'queues pending deployments without replacing them when an old run arrives late' , async ( ) => {
307+ const yaml = await readFile (
308+ new URL ( '../../workflows/deploy-docs-fork-preview.yml' , import . meta. url ) ,
309+ 'utf8' ,
310+ ) ;
311+ assert . match (
312+ yaml ,
313+ / c o n c u r r e n c y : \n \s + g r o u p : d e p l o y - d o c s - f o r k - p r e v i e w - \$ \{ \{ n e e d s \. a u t h o r i z e \. o u t p u t s \. p r \} \} \n \s + q u e u e : m a x \n \s + c a n c e l - i n - p r o g r e s s : f a l s e / ,
314+ ) ;
315+ } ) ;
316+
300317await test ( 'authorizes a fork with an empty workflow_run PR list and pins its artifact' , async ( ) => {
301318 const f = fixture ( ) ;
302319 await authorizePreview ( f ) ;
@@ -312,6 +329,10 @@ await test('authorizes a fork with an empty workflow_run PR list and pins its ar
312329 { owner : 'voidzero-dev' , repo : 'vite-plus' , run_id : 123 } ,
313330 { owner : 'voidzero-dev' , repo : 'vite-plus' , ref : 'a' . repeat ( 40 ) , per_page : 100 } ,
314331 { owner : 'voidzero-dev' , repo : 'vite-plus' , run_id : 987 } ,
332+ { owner : 'voidzero-dev' , repo : 'vite-plus' , pull_number : 2684 } ,
333+ { owner : 'voidzero-dev' , repo : 'vite-plus' , username : 'maintainer' } ,
334+ { owner : 'voidzero-dev' , repo : 'vite-plus' , ref : 'a' . repeat ( 40 ) , per_page : 100 } ,
335+ { owner : 'voidzero-dev' , repo : 'vite-plus' , run_id : 987 } ,
315336 ] ) ;
316337 assert . deepEqual ( f . state . outputs , {
317338 pr : 2684 ,
@@ -452,6 +473,66 @@ for (const pending of ['missing status', 'running workflow']) {
452473 } ) ;
453474}
454475
476+ await test ( 'does not enqueue an older authorization that finishes after a newer preview' , async ( ) => {
477+ const older = fixture ( ) ;
478+ older . approval . status = 'in_progress' ;
479+ older . approval . conclusion = null ;
480+ const newerOutputs = { } ;
481+ const newer = {
482+ ...older ,
483+ context : structuredClone ( older . context ) ,
484+ core : {
485+ info ( ) { } ,
486+ setOutput : ( key , value ) => ( newerOutputs [ key ] = value ) ,
487+ } ,
488+ } ;
489+ newer . context . payload . workflow_run . id = 124 ;
490+ newer . context . payload . workflow_run . head_sha = 'b' . repeat ( 40 ) ;
491+ older . sleep = async ( ms ) => {
492+ older . state . sleeps . push ( ms ) ;
493+ older . pr . head . sha = newer . context . payload . workflow_run . head_sha ;
494+ grantApproval ( older . state , older . pr . head . sha , older . pr . number , 988 ) ;
495+ await authorizePreview ( newer ) ;
496+ older . approval . status = 'completed' ;
497+ older . approval . conclusion = 'success' ;
498+ } ;
499+
500+ await authorizePreview ( older ) ;
501+
502+ assert . equal ( newerOutputs . pr , 2684 ) ;
503+ assert . deepEqual ( older . state . sleeps , [ 5000 ] ) ;
504+ assert . deepEqual ( older . state . outputs , { } ) ;
505+ assert . equal ( await isCurrentPreview ( newer , 2684 ) , true ) ;
506+ } ) ;
507+
508+ for ( const { name, mutate } of [
509+ { name : 'label removal' , mutate : ( f ) => ( f . pr . labels = [ ] ) } ,
510+ { name : 'PR closure' , mutate : ( f ) => ( f . pr . state = 'closed' ) } ,
511+ { name : 'a base change' , mutate : ( f ) => ( f . pr . base . ref = 'release' ) } ,
512+ { name : 'requester permission removal' , mutate : ( f ) => ( f . state . permission = 'read' ) } ,
513+ ] ) {
514+ await test ( `rechecks ${ name } after approval polling and before queueing` , async ( ) => {
515+ const f = fixture ( ) ;
516+ f . approval . status = 'in_progress' ;
517+ f . approval . conclusion = null ;
518+ f . sleep = async ( ) => {
519+ grantApproval ( f . state ) ;
520+ mutate ( f ) ;
521+ } ;
522+ await authorizePreview ( f ) ;
523+ assert . deepEqual ( f . state . outputs , { } ) ;
524+ } ) ;
525+ }
526+
527+ await test ( 'fails closed when the PR recheck before queueing fails' , async ( ) => {
528+ const f = fixture ( ) ;
529+ f . github . rest . pulls . get = async ( ) => {
530+ throw new Error ( 'GitHub PR lookup failed' ) ;
531+ } ;
532+ await assert . rejects ( authorizePreview ( f ) , / G i t H u b P R l o o k u p f a i l e d / ) ;
533+ assert . deepEqual ( f . state . outputs , { } ) ;
534+ } ) ;
535+
455536for ( const api of [ 'statuses' , 'workflow proof' ] ) {
456537 await test ( `fails closed when the ${ api } lookup fails` , async ( ) => {
457538 const f = fixture ( ) ;
0 commit comments