@@ -7,6 +7,7 @@ import type {
77 IDaemonPhasedOperationSelection ,
88 IDaemonPhasedRequest
99} from '@rushstack/rush-daemon-protocol' ;
10+ import { RUSHD_OPERATION_STREAM_CLOSED } from '@rushstack/rush-daemon-protocol' ;
1011import { OperationStatus } from '@microsoft/rush-lib' ;
1112
1213import { PhasedRequestRouter } from '../PhasedRequestRouter' ;
@@ -143,6 +144,23 @@ describe(PhasedRequestRouter.name, () => {
143144 expect ( ignoredDependencyFixture . operations . get ( OPERATION_A ) ?. enabled ) . toBe (
144145 'ignore-dependency-changes'
145146 ) ;
147+
148+ const mixedFixture : ITestRoutingFixture = createThreeOperationFixture ( ) ;
149+ await new PhasedRequestRouter ( mixedFixture . session ) . executeAsync (
150+ createRequest ( [
151+ {
152+ enabledState : 'ignore-dependency-changes' ,
153+ operationId : OPERATION_A
154+ } ,
155+ select ( OPERATION_B )
156+ ] ) ,
157+ new TestPhasedRequestClient ( )
158+ ) ;
159+
160+ expect ( mixedFixture . operations . get ( OPERATION_A ) ?. enabled ) . toBe (
161+ 'ignore-dependency-changes'
162+ ) ;
163+ expect ( mixedFixture . operations . get ( OPERATION_B ) ?. enabled ) . toBe ( true ) ;
146164 } ) ;
147165
148166 it ( 'reconciles invalidations, applies the safe dependency closure, and runs one iteration' , async ( ) => {
@@ -220,6 +238,50 @@ describe(PhasedRequestRouter.name, () => {
220238 . map ( getEventOperationId )
221239 . filter ( ( operationId : string | undefined ) : operationId is string => ! ! operationId ) ;
222240 expect ( new Set ( eventOperationIds ) ) . toEqual ( new Set ( [ OPERATION_A ] ) ) ;
241+ const streamClosedEvent : IDaemonEventEnvelope | undefined = client . writes
242+ . map ( ( write : ITestClientWrite ) => write . event )
243+ . find (
244+ ( event : IDaemonEventEnvelope | undefined ) =>
245+ ( event ?. payload as { name ?: unknown } | undefined ) ?. name ===
246+ RUSHD_OPERATION_STREAM_CLOSED
247+ ) ;
248+ expect ( streamClosedEvent ?. required ) . toBe ( true ) ;
249+ } ) ;
250+
251+ it ( 'allocates event sequences when queued writes are invoked' , async ( ) => {
252+ let releaseFirstEvent : ( ( ) => void ) | undefined ;
253+ let markFirstEventStarted : ( ( ) => void ) | undefined ;
254+ const firstEventStarted : Promise < void > = new Promise < void > ( ( resolve ) => {
255+ markFirstEventStarted = resolve ;
256+ } ) ;
257+ const fixture : ITestRoutingFixture = createThreeOperationFixture ( ) ;
258+ const client : TestPhasedRequestClient = new TestPhasedRequestClient ( ) ;
259+ let hasBlockedEvent : boolean = false ;
260+ client . onWriteAsync = async ( write : ITestClientWrite ) : Promise < void > => {
261+ if ( write . event && ! hasBlockedEvent ) {
262+ hasBlockedEvent = true ;
263+ markFirstEventStarted ?.( ) ;
264+ await new Promise < void > ( ( resolve ) => {
265+ releaseFirstEvent = resolve ;
266+ } ) ;
267+ }
268+ } ;
269+
270+ const requestPromise = new PhasedRequestRouter ( fixture . session ) . executeAsync (
271+ createRequest ( [ select ( OPERATION_A ) ] ) ,
272+ client
273+ ) ;
274+ await firstEventStarted ;
275+ const interleavedSequence : number = client . getNextEventSequence ( ) ;
276+ releaseFirstEvent ?.( ) ;
277+ await requestPromise ;
278+
279+ const routedSequences : number [ ] = client . writes
280+ . map ( ( { event } ) => event ?. sequence )
281+ . filter ( ( sequence : number | undefined ) : sequence is number => sequence !== undefined ) ;
282+ expect ( routedSequences [ 0 ] ) . toBe ( 1 ) ;
283+ expect ( interleavedSequence ) . toBe ( 2 ) ;
284+ expect ( routedSequences . slice ( 1 ) . every ( ( sequence ) => sequence > interleavedSequence ) ) . toBe ( true ) ;
223285 } ) ;
224286
225287 it ( 'returns client-scoped failures without converting them to routing errors' , async ( ) => {
@@ -289,6 +351,48 @@ describe(PhasedRequestRouter.name, () => {
289351 expect ( followUp . operationResults [ 0 ] ?. status ) . toBe ( OperationStatus . Success ) ;
290352 } ) ;
291353
354+ it ( 'does not combine an observed result with an error retained from a prior iteration' , async ( ) => {
355+ let invocation : number = 0 ;
356+ let releaseOperationA : ( ( ) => void ) | undefined ;
357+ let markOperationAStarted : ( ( ) => void ) | undefined ;
358+ const operationAStarted : Promise < void > = new Promise < void > ( ( resolve ) => {
359+ markOperationAStarted = resolve ;
360+ } ) ;
361+ const fixture : ITestRoutingFixture = createThreeOperationFixture ( {
362+ actionAAsync : async ( ) : Promise < void > => {
363+ if ( invocation ++ === 0 ) {
364+ throw new Error ( 'first iteration failure' ) ;
365+ }
366+ markOperationAStarted ?.( ) ;
367+ await new Promise < void > ( ( resolve ) => {
368+ releaseOperationA = resolve ;
369+ } ) ;
370+ }
371+ } ) ;
372+ const router : PhasedRequestRouter = new PhasedRequestRouter ( fixture . session ) ;
373+ const first = await router . executeAsync (
374+ createRequest ( [ select ( OPERATION_B ) ] ) ,
375+ new TestPhasedRequestClient ( )
376+ ) ;
377+ expect (
378+ first . operationResults . find ( ( { operationId } ) => operationId === OPERATION_A ) ?. errorMessage
379+ ) . toBe ( 'first iteration failure' ) ;
380+
381+ const secondClient : TestPhasedRequestClient = new TestPhasedRequestClient ( ) ;
382+ const secondPromise = router . executeAsync (
383+ { ...createRequest ( [ select ( OPERATION_B ) ] ) , requestId : 'request-2' } ,
384+ secondClient
385+ ) ;
386+ await operationAStarted ;
387+ secondClient . abortController . abort ( ) ;
388+ releaseOperationA ?.( ) ;
389+ const second = await secondPromise ;
390+
391+ expect (
392+ second . operationResults . find ( ( { operationId } ) => operationId === OPERATION_A ) ?. errorMessage
393+ ) . toBeUndefined ( ) ;
394+ } ) ;
395+
292396 it ( 'aborts and unsubscribes when a disconnected client rejects a write' , async ( ) => {
293397 let releaseOperationA : ( ( ) => void ) | undefined ;
294398 const fixture : ITestRoutingFixture = createThreeOperationFixture ( {
@@ -398,8 +502,9 @@ describe(PhasedRequestRouter.name, () => {
398502 }
399503 } ) ;
400504 const router : PhasedRequestRouter = new PhasedRequestRouter ( fixture . session ) ;
401- const firstClient : TestPhasedRequestClient = new TestPhasedRequestClient ( ) ;
402- const secondClient : TestPhasedRequestClient = new TestPhasedRequestClient ( ) ;
505+ const sequenceState : { next : number } = { next : 1 } ;
506+ const firstClient : TestPhasedRequestClient = new TestPhasedRequestClient ( sequenceState ) ;
507+ const secondClient : TestPhasedRequestClient = new TestPhasedRequestClient ( sequenceState ) ;
403508
404509 const first = await router . executeAsync (
405510 createRequest ( [ select ( OPERATION_A ) ] ) ,
0 commit comments