77} from '../../__testUtils__/diagnosticsTestUtils.js' ;
88
99import { isAsyncIterable } from '../../jsutils/isAsyncIterable.js' ;
10+ import { isPromise } from '../../jsutils/isPromise.js' ;
1011
1112import { parse } from '../../language/parser.js' ;
1213
@@ -23,18 +24,22 @@ const schema = buildSchema(`
2324 type Query {
2425 sync: String
2526 async: String
27+ dummy: String
2628 }
27- ` ) ;
28-
29- const rootValue = {
30- sync : ( ) => 'hello' ,
31- async : ( ) => Promise . resolve ( 'hello-async' ) ,
32- } ;
3329
34- const executeChannel = getTracingChannel ( 'graphql:execute' ) ;
30+ type Subscription {
31+ tick: String
32+ }
33+ ` ) ;
3534
3635describe ( 'execute diagnostics channel' , ( ) => {
3736 let active : ReturnType < typeof collectEvents > | undefined ;
37+ const executeChannel = getTracingChannel ( 'graphql:execute' ) ;
38+
39+ const rootValue = {
40+ sync : ( ) => 'hello' ,
41+ async : ( ) => Promise . resolve ( 'hello-async' ) ,
42+ } ;
3843
3944 afterEach ( ( ) => {
4045 active ?. unsubscribe ( ) ;
@@ -110,16 +115,6 @@ describe('execute diagnostics channel', () => {
110115 } ) ;
111116
112117 it ( 'emits for each subscription event with resolved operation ctx' , async ( ) => {
113- const subscriptionSchema = buildSchema ( `
114- type Query {
115- dummy: String
116- }
117-
118- type Subscription {
119- tick: String
120- }
121- ` ) ;
122-
123118 async function * tickGenerator ( ) {
124119 await Promise . resolve ( ) ;
125120 yield { tick : 'one' } ;
@@ -131,7 +126,7 @@ describe('execute diagnostics channel', () => {
131126 active = collectEvents ( executeChannel ) ;
132127
133128 const subscription = await subscribe ( {
134- schema : subscriptionSchema ,
129+ schema,
135130 document,
136131 rootValue : { tick : tickGenerator } ,
137132 } ) ;
@@ -152,7 +147,7 @@ describe('execute diagnostics channel', () => {
152147 expect ( ev . ctx . operationType ) . to . equal ( 'subscription' ) ;
153148 expect ( ev . ctx . operationName ) . to . equal ( 'S' ) ;
154149 expect ( ev . ctx . operation ) . to . equal ( document . definitions [ 0 ] ) ;
155- expect ( ev . ctx . schema ) . to . equal ( subscriptionSchema ) ;
150+ expect ( ev . ctx . schema ) . to . equal ( schema ) ;
156151 }
157152 } ) ;
158153
@@ -162,3 +157,91 @@ describe('execute diagnostics channel', () => {
162157 expect ( result ) . to . deep . equal ( { data : { sync : 'hello' } } ) ;
163158 } ) ;
164159} ) ;
160+
161+ describe ( 'subscribe diagnostics channel' , ( ) => {
162+ let active : ReturnType < typeof collectEvents > | undefined ;
163+ const subscribeChannel = getTracingChannel ( 'graphql:subscribe' ) ;
164+
165+ async function * twoTicks ( ) : AsyncIterable < { tick : string } > {
166+ await Promise . resolve ( ) ;
167+ yield { tick : 'one' } ;
168+ yield { tick : 'two' } ;
169+ }
170+
171+ afterEach ( ( ) => {
172+ active ?. unsubscribe ( ) ;
173+ active = undefined ;
174+ } ) ;
175+
176+ it ( 'emits start and end for a synchronous subscription setup' , async ( ) => {
177+ active = collectEvents ( subscribeChannel ) ;
178+
179+ const document = parse ( 'subscription S { tick }' ) ;
180+
181+ const result = subscribe ( {
182+ schema,
183+ document,
184+ rootValue : { tick : twoTicks } ,
185+ } ) ;
186+ const resolved = isPromise ( result ) ? await result : result ;
187+ assert ( isAsyncIterable ( resolved ) ) ;
188+ await resolved . return ?.( ) ;
189+
190+ expect ( active . events . map ( ( e ) => e . kind ) ) . to . deep . equal ( [ 'start' , 'end' ] ) ;
191+ expect ( active . events [ 0 ] . ctx . operationType ) . to . equal ( 'subscription' ) ;
192+ expect ( active . events [ 0 ] . ctx . operationName ) . to . equal ( 'S' ) ;
193+ expect ( active . events [ 0 ] . ctx . document ) . to . equal ( document ) ;
194+ expect ( active . events [ 0 ] . ctx . schema ) . to . equal ( schema ) ;
195+ } ) ;
196+
197+ it ( 'emits the full async lifecycle when subscribe resolver returns a promise' , async ( ) => {
198+ active = collectEvents ( subscribeChannel ) ;
199+
200+ const document = parse ( 'subscription { tick }' ) ;
201+
202+ const result = subscribe ( {
203+ schema,
204+ document,
205+ rootValue : {
206+ tick : ( ) : Promise < AsyncIterable < { tick : string } > > =>
207+ Promise . resolve ( twoTicks ( ) ) ,
208+ } ,
209+ } ) ;
210+ const resolved = isPromise ( result ) ? await result : result ;
211+ assert ( isAsyncIterable ( resolved ) ) ;
212+ await resolved . return ?.( ) ;
213+
214+ expect ( active . events . map ( ( e ) => e . kind ) ) . to . deep . equal ( [
215+ 'start' ,
216+ 'end' ,
217+ 'asyncStart' ,
218+ 'asyncEnd' ,
219+ ] ) ;
220+ } ) ;
221+
222+ it ( 'emits only start and end for a synchronous validation failure' , ( ) => {
223+ active = collectEvents ( subscribeChannel ) ;
224+
225+ // Invalid: no operation.
226+ const document = parse ( 'fragment F on Subscription { tick }' ) ;
227+
228+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
229+ subscribe ( { schema, document, rootValue : { tick : twoTicks } } ) ;
230+
231+ expect ( active . events . map ( ( e ) => e . kind ) ) . to . deep . equal ( [ 'start' , 'end' ] ) ;
232+ } ) ;
233+
234+ it ( 'does nothing when no subscribers are attached' , async ( ) => {
235+ const document = parse ( 'subscription { tick }' ) ;
236+
237+ const result = subscribe ( {
238+ schema,
239+ document,
240+ rootValue : { tick : twoTicks } ,
241+ } ) ;
242+ const resolved = isPromise ( result ) ? await result : result ;
243+ if ( isAsyncIterable ( resolved ) ) {
244+ await resolved . return ?.( ) ;
245+ }
246+ } ) ;
247+ } ) ;
0 commit comments