@@ -25,6 +25,7 @@ import {
2525} from 'graphql' ;
2626
2727import { graphqlHTTP } from '../index' ;
28+ import { isAsyncIterable } from '../isAsyncIterable' ;
2829
2930type Middleware = ( req : any , res : any , next : ( ) => void ) => unknown ;
3031type Server = ( ) => {
@@ -1027,6 +1028,60 @@ function runTests(server: Server) {
10271028 errors : [ { message : 'Must provide query string.' } ] ,
10281029 } ) ;
10291030 } ) ;
1031+
1032+ it ( 'allows for streaming results with @defer' , async ( ) => {
1033+ const app = server ( ) ;
1034+ const fakeFlush = sinon . fake ( ) ;
1035+
1036+ app . use ( ( _ , res , next ) => {
1037+ res . flush = fakeFlush ;
1038+ next ( ) ;
1039+ } ) ;
1040+ app . post (
1041+ urlString ( ) ,
1042+ graphqlHTTP ( {
1043+ schema : TestSchema ,
1044+ } ) ,
1045+ ) ;
1046+
1047+ const req = app
1048+ . request ( )
1049+ . post ( urlString ( ) )
1050+ . send ( {
1051+ query :
1052+ '{ ...frag @defer(label: "deferLabel") } fragment frag on QueryRoot { test(who: "World") }' ,
1053+ } )
1054+ . parse ( ( res , cb ) => {
1055+ res . on ( 'data' , ( data ) => {
1056+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
1057+ } ) ;
1058+ res . on ( 'end' , ( err ) => {
1059+ cb ( err , null ) ;
1060+ } ) ;
1061+ } ) ;
1062+
1063+ const response = await req ;
1064+ expect ( fakeFlush . callCount ) . to . equal ( 2 ) ;
1065+ expect ( response . text ) . to . equal (
1066+ [
1067+ '' ,
1068+ '---' ,
1069+ 'Content-Type: application/json; charset=utf-8' ,
1070+ 'Content-Length: 26' ,
1071+ '' ,
1072+ '{"data":{},"hasNext":true}' ,
1073+ '' ,
1074+ '---' ,
1075+ 'Content-Type: application/json; charset=utf-8' ,
1076+ 'Content-Length: 78' ,
1077+ '' ,
1078+ '{"data":{"test":"Hello World"},"path":[],"label":"deferLabel","hasNext":false}' ,
1079+ '' ,
1080+ '-----' ,
1081+ '' ,
1082+ ] . join ( '\r\n' ) ,
1083+ ) ;
1084+ } ) ;
10301085 } ) ;
10311086
10321087 describe ( 'Pretty printing' , ( ) => {
@@ -1109,6 +1164,62 @@ function runTests(server: Server) {
11091164
11101165 expect ( unprettyResponse . text ) . to . equal ( '{"data":{"test":"Hello World"}}' ) ;
11111166 } ) ;
1167+ it ( 'supports pretty printing async iterable requests' , async ( ) => {
1168+ const app = server ( ) ;
1169+
1170+ app . post (
1171+ urlString ( ) ,
1172+ graphqlHTTP ( {
1173+ schema : TestSchema ,
1174+ pretty : true ,
1175+ } ) ,
1176+ ) ;
1177+
1178+ const req = app
1179+ . request ( )
1180+ . post ( urlString ( ) )
1181+ . send ( {
1182+ query :
1183+ '{ ...frag @defer } fragment frag on QueryRoot { test(who: "World") }' ,
1184+ } )
1185+ . parse ( ( res , cb ) => {
1186+ res . on ( 'data' , ( data ) => {
1187+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
1188+ } ) ;
1189+ res . on ( 'end' , ( err ) => {
1190+ cb ( err , null ) ;
1191+ } ) ;
1192+ } ) ;
1193+
1194+ const response = await req ;
1195+ expect ( response . text ) . to . equal (
1196+ [
1197+ '' ,
1198+ '---' ,
1199+ 'Content-Type: application/json; charset=utf-8' ,
1200+ 'Content-Length: 35' ,
1201+ '' ,
1202+ [ '{' , ' "data": {},' , ' "hasNext": true' , '}' ] . join ( '\n' ) ,
1203+ '' ,
1204+ '---' ,
1205+ 'Content-Type: application/json; charset=utf-8' ,
1206+ 'Content-Length: 79' ,
1207+ '' ,
1208+ [
1209+ '{' ,
1210+ ' "data": {' ,
1211+ ' "test": "Hello World"' ,
1212+ ' },' ,
1213+ ' "path": [],' ,
1214+ ' "hasNext": false' ,
1215+ '}' ,
1216+ ] . join ( '\n' ) ,
1217+ '' ,
1218+ '-----' ,
1219+ '' ,
1220+ ] . join ( '\r\n' ) ,
1221+ ) ;
1222+ } ) ;
11121223 } ) ;
11131224
11141225 it ( 'will send request and response when using thunk' , async ( ) => {
@@ -1260,6 +1371,108 @@ function runTests(server: Server) {
12601371 } ) ;
12611372 } ) ;
12621373
1374+ it ( 'allows for custom error formatting in initial payload of async iterator' , async ( ) => {
1375+ const app = server ( ) ;
1376+
1377+ app . post (
1378+ urlString ( ) ,
1379+ graphqlHTTP ( {
1380+ schema : TestSchema ,
1381+ customFormatErrorFn ( error ) {
1382+ return { message : 'Custom error format: ' + error . message } ;
1383+ } ,
1384+ } ) ,
1385+ ) ;
1386+
1387+ const req = app
1388+ . request ( )
1389+ . post ( urlString ( ) )
1390+ . send ( {
1391+ query :
1392+ '{ thrower, ...frag @defer } fragment frag on QueryRoot { test(who: "World") }' ,
1393+ } )
1394+ . parse ( ( res , cb ) => {
1395+ res . on ( 'data' , ( data ) => {
1396+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
1397+ } ) ;
1398+ res . on ( 'end' , ( err ) => {
1399+ cb ( err , null ) ;
1400+ } ) ;
1401+ } ) ;
1402+
1403+ const response = await req ;
1404+ expect ( response . text ) . to . equal (
1405+ [
1406+ '' ,
1407+ '---' ,
1408+ 'Content-Type: application/json; charset=utf-8' ,
1409+ 'Content-Length: 94' ,
1410+ '' ,
1411+ '{"errors":[{"message":"Custom error format: Throws!"}],"data":{"thrower":null},"hasNext":true}' ,
1412+ '' ,
1413+ '---' ,
1414+ 'Content-Type: application/json; charset=utf-8' ,
1415+ 'Content-Length: 57' ,
1416+ '' ,
1417+ '{"data":{"test":"Hello World"},"path":[],"hasNext":false}' ,
1418+ '' ,
1419+ '-----' ,
1420+ '' ,
1421+ ] . join ( '\r\n' ) ,
1422+ ) ;
1423+ } ) ;
1424+
1425+ it ( 'allows for custom error formatting in subsequent payloads of async iterator' , async ( ) => {
1426+ const app = server ( ) ;
1427+
1428+ app . post (
1429+ urlString ( ) ,
1430+ graphqlHTTP ( {
1431+ schema : TestSchema ,
1432+ customFormatErrorFn ( error ) {
1433+ return { message : 'Custom error format: ' + error . message } ;
1434+ } ,
1435+ } ) ,
1436+ ) ;
1437+
1438+ const req = app
1439+ . request ( )
1440+ . post ( urlString ( ) )
1441+ . send ( {
1442+ query :
1443+ '{ test(who: "World"), ...frag @defer } fragment frag on QueryRoot { thrower }' ,
1444+ } )
1445+ . parse ( ( res , cb ) => {
1446+ res . on ( 'data' , ( data ) => {
1447+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
1448+ } ) ;
1449+ res . on ( 'end' , ( err ) => {
1450+ cb ( err , null ) ;
1451+ } ) ;
1452+ } ) ;
1453+
1454+ const response = await req ;
1455+ expect ( response . text ) . to . equal (
1456+ [
1457+ '' ,
1458+ '---' ,
1459+ 'Content-Type: application/json; charset=utf-8' ,
1460+ 'Content-Length: 46' ,
1461+ '' ,
1462+ '{"data":{"test":"Hello World"},"hasNext":true}' ,
1463+ '' ,
1464+ '---' ,
1465+ 'Content-Type: application/json; charset=utf-8' ,
1466+ 'Content-Length: 105' ,
1467+ '' ,
1468+ '{"data":{"thrower":null},"path":[],"errors":[{"message":"Custom error format: Throws!"}],"hasNext":false}' ,
1469+ '' ,
1470+ '-----' ,
1471+ '' ,
1472+ ] . join ( '\r\n' ) ,
1473+ ) ;
1474+ } ) ;
1475+
12631476 it ( 'allows for custom error formatting to elaborate' , async ( ) => {
12641477 const app = server ( ) ;
12651478
@@ -2100,6 +2313,10 @@ function runTests(server: Server) {
21002313 async customExecuteFn ( args ) {
21012314 seenExecuteArgs = args ;
21022315 const result = await Promise . resolve ( execute ( args ) ) ;
2316+ // istanbul ignore if this test query will never return an async iterable
2317+ if ( isAsyncIterable ( result ) ) {
2318+ return result ;
2319+ }
21032320 return {
21042321 ...result ,
21052322 data : {
@@ -2253,6 +2470,57 @@ function runTests(server: Server) {
22532470 } ) ;
22542471 } ) ;
22552472
2473+ it ( 'allows for custom extensions in initial and subsequent payloads of async iterator' , async ( ) => {
2474+ const app = server ( ) ;
2475+
2476+ app . post (
2477+ urlString ( ) ,
2478+ graphqlHTTP ( {
2479+ schema : TestSchema ,
2480+ extensions ( { result } ) {
2481+ return { preservedResult : { ...result } } ;
2482+ } ,
2483+ } ) ,
2484+ ) ;
2485+
2486+ const req = app
2487+ . request ( )
2488+ . post ( urlString ( ) )
2489+ . send ( {
2490+ query :
2491+ '{ hello: test(who: "Rob"), ...frag @defer } fragment frag on QueryRoot { test(who: "World") }' ,
2492+ } )
2493+ . parse ( ( res , cb ) => {
2494+ res . on ( 'data' , ( data ) => {
2495+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
2496+ } ) ;
2497+ res . on ( 'end' , ( err ) => {
2498+ cb ( err , null ) ;
2499+ } ) ;
2500+ } ) ;
2501+
2502+ const response = await req ;
2503+ expect ( response . text ) . to . equal (
2504+ [
2505+ '' ,
2506+ '---' ,
2507+ 'Content-Type: application/json; charset=utf-8' ,
2508+ 'Content-Length: 124' ,
2509+ '' ,
2510+ '{"data":{"hello":"Hello Rob"},"hasNext":true,"extensions":{"preservedResult":{"data":{"hello":"Hello Rob"},"hasNext":true}}}' ,
2511+ '' ,
2512+ '---' ,
2513+ 'Content-Type: application/json; charset=utf-8' ,
2514+ 'Content-Length: 148' ,
2515+ '' ,
2516+ '{"data":{"test":"Hello World"},"path":[],"hasNext":false,"extensions":{"preservedResult":{"data":{"test":"Hello World"},"path":[],"hasNext":false}}}' ,
2517+ '' ,
2518+ '-----' ,
2519+ '' ,
2520+ ] . join ( '\r\n' ) ,
2521+ ) ;
2522+ } ) ;
2523+
22562524 it ( 'extension function may be async' , async ( ) => {
22572525 const app = server ( ) ;
22582526
@@ -2293,12 +2561,44 @@ function runTests(server: Server) {
22932561
22942562 const response = await app
22952563 . request ( )
2296- . get ( urlString ( { query : '{test}' , raw : '' } ) )
2297- . set ( 'Accept' , 'text/html' ) ;
2564+ . get (
2565+ urlString ( {
2566+ query :
2567+ '{ hello: test(who: "Rob"), ...frag @defer } fragment frag on QueryRoot { test(who: "World") }' ,
2568+ raw : '' ,
2569+ } ) ,
2570+ )
2571+ . set ( 'Accept' , 'text/html' )
2572+ . parse ( ( res , cb ) => {
2573+ res . on ( 'data' , ( data ) => {
2574+ res . text = `${ res . text || '' } ${ data . toString ( 'utf8' ) as string } ` ;
2575+ } ) ;
2576+ res . on ( 'end' , ( err ) => {
2577+ cb ( err , null ) ;
2578+ } ) ;
2579+ } ) ;
22982580
22992581 expect ( response . status ) . to . equal ( 200 ) ;
2300- expect ( response . type ) . to . equal ( 'application/json' ) ;
2301- expect ( response . text ) . to . equal ( '{"data":{"test":"Hello World"}}' ) ;
2582+ expect ( response . type ) . to . equal ( 'multipart/mixed' ) ;
2583+ expect ( response . text ) . to . equal (
2584+ [
2585+ '' ,
2586+ '---' ,
2587+ 'Content-Type: application/json; charset=utf-8' ,
2588+ 'Content-Length: 45' ,
2589+ '' ,
2590+ '{"data":{"hello":"Hello Rob"},"hasNext":true}' ,
2591+ '' ,
2592+ '---' ,
2593+ 'Content-Type: application/json; charset=utf-8' ,
2594+ 'Content-Length: 57' ,
2595+ '' ,
2596+ '{"data":{"test":"Hello World"},"path":[],"hasNext":false}' ,
2597+ '' ,
2598+ '-----' ,
2599+ '' ,
2600+ ] . join ( '\r\n' ) ,
2601+ ) ;
23022602 } ) ;
23032603 } ) ;
23042604}
0 commit comments