11import zlib from 'zlib' ;
2- import type http from 'http' ;
2+ import http from 'http' ;
3+ import type { AddressInfo } from 'net' ;
34
45import type { Server as Restify } from 'restify' ;
56import connect from 'connect' ;
67import express from 'express' ;
78import supertest from 'supertest' ;
89import bodyParser from 'body-parser' ;
910
10- import type { ASTVisitor , ValidationContext } from 'graphql' ;
11+ import type {
12+ ASTVisitor ,
13+ ValidationContext ,
14+ AsyncExecutionResult ,
15+ } from 'graphql' ;
1116import sinon from 'sinon' ;
1217import multer from 'multer' ; // cSpell:words mimetype originalname
1318import { expect } from 'chai' ;
@@ -28,8 +33,11 @@ import {
2833import { graphqlHTTP } from '../index' ;
2934import { isAsyncIterable } from '../isAsyncIterable' ;
3035
36+ import SimplePubSub from './simplePubSub' ;
37+
3138type Middleware = ( req : any , res : any , next : ( ) => void ) => unknown ;
3239type Server = ( ) => {
40+ listener : http . Server | http . RequestListener ;
3341 request : ( ) => supertest . SuperTest < supertest . Test > ;
3442 use : ( middleware : Middleware ) => unknown ;
3543 get : ( path : string , middleware : Middleware ) => unknown ;
@@ -82,10 +90,51 @@ function urlString(urlParams?: { [param: string]: string }): string {
8290 return string ;
8391}
8492
85- function sleep ( ms = 1 ) {
86- return new Promise ( ( r ) => {
87- setTimeout ( r , ms ) ;
93+ async function streamRequest (
94+ server : Server ,
95+ customExecuteFn ?: ( ) => AsyncIterable < AsyncExecutionResult > ,
96+ ) : Promise < {
97+ req : http . ClientRequest ;
98+ responseStream : http . IncomingMessage ;
99+ } > {
100+ const app = server ( ) ;
101+ app . get (
102+ urlString ( ) ,
103+ graphqlHTTP ( ( ) => ( {
104+ schema : TestSchema ,
105+ customExecuteFn,
106+ } ) ) ,
107+ ) ;
108+
109+ let httpServer : http . Server ;
110+ if ( typeof app . listener === 'function' ) {
111+ httpServer = http . createServer ( app . listener ) ;
112+ } else {
113+ httpServer = app . listener ;
114+ }
115+ await new Promise ( ( resolve ) => {
116+ httpServer . listen ( resolve ) ;
117+ } ) ;
118+
119+ const addr = httpServer . address ( ) as AddressInfo ;
120+ const req = http . get ( {
121+ method : 'GET' ,
122+ path : urlString ( { query : '{test}' } ) ,
123+ port : addr . port ,
88124 } ) ;
125+
126+ const res : http . IncomingMessage = await new Promise ( ( resolve ) => {
127+ req . on ( 'response' , resolve ) ;
128+ } ) ;
129+
130+ req . on ( 'close' , ( ) => {
131+ httpServer . close ( ) ;
132+ } ) ;
133+
134+ return {
135+ req,
136+ responseStream : res ,
137+ } ;
89138}
90139
91140describe ( 'GraphQL-HTTP tests for connect' , ( ) => {
@@ -99,6 +148,7 @@ describe('GraphQL-HTTP tests for connect', () => {
99148 } ) ;
100149
101150 return {
151+ listener : app ,
102152 request : ( ) => supertest ( app ) ,
103153 use : app . use . bind ( app ) ,
104154 // Connect only likes using app.use.
@@ -123,6 +173,7 @@ describe('GraphQL-HTTP tests for express', () => {
123173 } ) ;
124174
125175 return {
176+ listener : app ,
126177 request : ( ) => supertest ( app ) ,
127178 use : app . use . bind ( app ) ,
128179 get : app . get . bind ( app ) ,
@@ -148,6 +199,7 @@ describe('GraphQL-HTTP tests for restify', () => {
148199 } ) ;
149200
150201 return {
202+ listener : app . server ,
151203 request : ( ) => supertest ( app ) ,
152204 use : app . use . bind ( app ) ,
153205 get : app . get . bind ( app ) ,
@@ -2406,8 +2458,8 @@ function runTests(server: Server) {
24062458 urlString ( ) ,
24072459 graphqlHTTP ( ( ) => ( {
24082460 schema : TestSchema ,
2461+ // eslint-disable-next-line @typescript-eslint/require-await
24092462 async * customExecuteFn ( ) {
2410- await sleep ( ) ;
24112463 yield {
24122464 data : {
24132465 test2 : 'Modification' ,
@@ -2450,132 +2502,117 @@ function runTests(server: Server) {
24502502 } ) ;
24512503
24522504 it ( 'calls return on underlying async iterable when connection is closed' , async ( ) => {
2453- const app = server ( ) ;
2454- const fakeReturn = sinon . fake ( ) ;
2505+ const executePubSub = new SimplePubSub < AsyncExecutionResult > ( ) ;
2506+ const executeIterable = executePubSub . getSubscriber ( ) ;
2507+ const spy = sinon . spy ( executeIterable [ Symbol . asyncIterator ] ( ) , 'return' ) ;
2508+ expect (
2509+ executePubSub . emit ( { data : { test : 'Hello, World 1' } , hasNext : true } ) ,
2510+ ) . to . equal ( true ) ;
24552511
2456- app . get (
2457- urlString ( ) ,
2458- graphqlHTTP ( ( ) => ( {
2459- schema : TestSchema ,
2460- // custom iterable keeps yielding until return is called
2461- customExecuteFn ( ) {
2462- let returned = false ;
2463- return {
2464- [ Symbol . asyncIterator ] : ( ) => ( {
2465- next : async ( ) => {
2466- await sleep ( ) ;
2467- if ( returned ) {
2468- return { value : undefined , done : true } ;
2469- }
2470- return {
2471- value : { data : { test : 'Hello, World' } , hasNext : true } ,
2472- done : false ,
2473- } ;
2474- } ,
2475- return : ( ) => {
2476- returned = true ;
2477- fakeReturn ( ) ;
2478- return Promise . resolve ( { value : undefined , done : true } ) ;
2479- } ,
2480- } ) ,
2481- } ;
2482- } ,
2483- } ) ) ,
2512+ const { req, responseStream } = await streamRequest (
2513+ server ,
2514+ ( ) => executeIterable ,
24842515 ) ;
2516+ const iterator = responseStream [ Symbol . asyncIterator ] ( ) ;
24852517
2486- let text = '' ;
2487- const request = app
2488- . request ( )
2489- . get ( urlString ( { query : '{test}' } ) )
2490- . parse ( ( res , cb ) => {
2491- res . on ( 'data' , ( data ) => {
2492- text = `${ text } ${ data . toString ( 'utf8' ) as string } ` ;
2493- ( ( res as unknown ) as http . IncomingMessage ) . destroy ( ) ;
2494- cb ( new Error ( 'Aborted connection' ) , null ) ;
2495- } ) ;
2496- } ) ;
2497-
2498- try {
2499- await request ;
2500- } catch ( e : unknown ) {
2501- // ignore aborted error
2502- }
2503- // sleep to allow time for return function to be called
2504- await sleep ( 2 ) ;
2505- expect ( text ) . to . equal (
2518+ const { value : firstResponse } = await iterator . next ( ) ;
2519+ expect ( firstResponse . toString ( 'utf8' ) ) . to . equal (
25062520 [
2521+ '\r\n---' ,
2522+ 'Content-Type: application/json; charset=utf-8' ,
25072523 '' ,
2508- '---' ,
2524+ '{"data":{"test":"Hello, World 1"},"hasNext":true}' ,
2525+ '---\r\n' ,
2526+ ] . join ( '\r\n' ) ,
2527+ ) ;
2528+
2529+ expect (
2530+ executePubSub . emit ( { data : { test : 'Hello, World 2' } , hasNext : true } ) ,
2531+ ) . to . equal ( true ) ;
2532+ const { value : secondResponse } = await iterator . next ( ) ;
2533+ expect ( secondResponse . toString ( 'utf8' ) ) . to . equal (
2534+ [
25092535 'Content-Type: application/json; charset=utf-8' ,
25102536 '' ,
2511- '{"data":{"test":"Hello, World"},"hasNext":true}' ,
2537+ '{"data":{"test":"Hello, World 2 "},"hasNext":true}' ,
25122538 '---\r\n' ,
25132539 ] . join ( '\r\n' ) ,
25142540 ) ;
2515- expect ( fakeReturn . callCount ) . to . equal ( 1 ) ;
2541+
2542+ req . destroy ( ) ;
2543+
2544+ // wait for server to call return on underlying iterable
2545+ await executeIterable . next ( ) ;
2546+
2547+ expect ( spy . calledOnce ) . to . equal ( true ) ;
2548+ // emit returns false because `return` cleaned up subscribers
2549+ expect (
2550+ executePubSub . emit ( { data : { test : 'Hello, World 3' } , hasNext : true } ) ,
2551+ ) . to . equal ( false ) ;
25162552 } ) ;
25172553
25182554 it ( 'handles return function on async iterable that throws' , async ( ) => {
2519- const app = server ( ) ;
2555+ const executePubSub = new SimplePubSub < AsyncExecutionResult > ( ) ;
2556+ const executeIterable = executePubSub . getSubscriber ( ) ;
2557+ const executeIterator = executeIterable [ Symbol . asyncIterator ] ( ) ;
2558+ const originalReturn = executeIterator . return . bind ( executeIterator ) ;
2559+ const fake = sinon . fake ( async ( ) => {
2560+ await originalReturn ( ) ;
2561+ throw new Error ( 'Throws!' ) ;
2562+ } ) ;
2563+ sinon . replace ( executeIterator , 'return' , fake ) ;
2564+ expect (
2565+ executePubSub . emit ( {
2566+ data : { test : 'Hello, World 1' } ,
2567+ hasNext : true ,
2568+ } ) ,
2569+ ) . to . equal ( true ) ;
25202570
2521- app . get (
2522- urlString ( ) ,
2523- graphqlHTTP ( ( ) => ( {
2524- schema : TestSchema ,
2525- // custom iterable keeps yielding until return is called
2526- customExecuteFn ( ) {
2527- let returned = false ;
2528- return {
2529- [ Symbol . asyncIterator ] : ( ) => ( {
2530- next : async ( ) => {
2531- await sleep ( ) ;
2532- if ( returned ) {
2533- return { value : undefined , done : true } ;
2534- }
2535- return {
2536- value : { data : { test : 'Hello, World' } , hasNext : true } ,
2537- done : false ,
2538- } ;
2539- } ,
2540- return : ( ) => {
2541- returned = true ;
2542- return Promise . reject ( new Error ( 'Throws!' ) ) ;
2543- } ,
2544- } ) ,
2545- } ;
2546- } ,
2547- } ) ) ,
2571+ const { req, responseStream } = await streamRequest (
2572+ server ,
2573+ ( ) => executeIterable ,
25482574 ) ;
2575+ const iterator = responseStream [ Symbol . asyncIterator ] ( ) ;
25492576
2550- let text = '' ;
2551- const request = app
2552- . request ( )
2553- . get ( urlString ( { query : '{test}' } ) )
2554- . parse ( ( res , cb ) => {
2555- res . on ( 'data' , ( data ) => {
2556- text = `${ text } ${ data . toString ( 'utf8' ) as string } ` ;
2557- ( ( res as unknown ) as http . IncomingMessage ) . destroy ( ) ;
2558- cb ( new Error ( 'Aborted connection' ) , null ) ;
2559- } ) ;
2560- } ) ;
2561-
2562- try {
2563- await request ;
2564- } catch ( e : unknown ) {
2565- // ignore aborted error
2566- }
2567- // sleep to allow return function to be called
2568- await sleep ( 2 ) ;
2569- expect ( text ) . to . equal (
2577+ const { value : firstResponse } = await iterator . next ( ) ;
2578+ expect ( firstResponse . toString ( 'utf8' ) ) . to . equal (
25702579 [
2580+ '\r\n---' ,
2581+ 'Content-Type: application/json; charset=utf-8' ,
25712582 '' ,
2572- '---' ,
2583+ '{"data":{"test":"Hello, World 1"},"hasNext":true}' ,
2584+ '---\r\n' ,
2585+ ] . join ( '\r\n' ) ,
2586+ ) ;
2587+
2588+ expect (
2589+ executePubSub . emit ( {
2590+ data : { test : 'Hello, World 2' } ,
2591+ hasNext : true ,
2592+ } ) ,
2593+ ) . to . equal ( true ) ;
2594+ const { value : secondResponse } = await iterator . next ( ) ;
2595+ expect ( secondResponse . toString ( 'utf8' ) ) . to . equal (
2596+ [
25732597 'Content-Type: application/json; charset=utf-8' ,
25742598 '' ,
2575- '{"data":{"test":"Hello, World"},"hasNext":true}' ,
2599+ '{"data":{"test":"Hello, World 2 "},"hasNext":true}' ,
25762600 '---\r\n' ,
25772601 ] . join ( '\r\n' ) ,
25782602 ) ;
2603+ req . destroy ( ) ;
2604+
2605+ // wait for server to call return on underlying iterable
2606+ await executeIterable . next ( ) ;
2607+
2608+ expect ( fake . calledOnce ) . to . equal ( true ) ;
2609+ // emit returns false because `return` cleaned up subscribers
2610+ expect (
2611+ executePubSub . emit ( {
2612+ data : { test : 'Hello, World 3' } ,
2613+ hasNext : true ,
2614+ } ) ,
2615+ ) . to . equal ( false ) ;
25792616 } ) ;
25802617 } ) ;
25812618
0 commit comments