@@ -6,6 +6,7 @@ const statuses = require('statuses')
66const  assert  =  require ( 'node:assert/strict' ) 
77const  Koa  =  require ( '../..' ) 
88const  fs  =  require ( 'fs' ) 
9+ const  http  =  require ( 'http' ) 
910
1011describe ( 'app.respond' ,  ( )  =>  { 
1112  describe ( 'when ctx.respond === false' ,  ( )  =>  { 
@@ -916,4 +917,378 @@ describe('app.respond', () => {
916917      assert . equal ( res . headers [ 'content-length' ] ,  '0' ) 
917918    } ) 
918919  } ) 
920+ 
921+   describe ( 'when setupStreamAbortHandling is called' ,  ( )  =>  { 
922+     it ( 'should handle ReadableStream bodies' ,  async  ( )  =>  { 
923+       const  app  =  new  Koa ( ) 
924+ 
925+       app . use ( ( ctx )  =>  { 
926+         const  readable  =  new  ReadableStream ( { 
927+           start  ( controller )  { 
928+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'test data' ) ) 
929+             controller . close ( ) 
930+           } 
931+         } ) 
932+         ctx . body  =  readable 
933+       } ) 
934+ 
935+       await  request ( app . callback ( ) ) 
936+         . get ( '/' ) 
937+         . expect ( 200 ) 
938+         . expect ( Buffer . from ( 'test data' ) ) 
939+     } ) 
940+ 
941+     it ( 'should handle Response objects with streams' ,  async  ( )  =>  { 
942+       const  app  =  new  Koa ( ) 
943+ 
944+       app . use ( ( ctx )  =>  { 
945+         const  readable  =  new  ReadableStream ( { 
946+           start  ( controller )  { 
947+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'response data' ) ) 
948+             controller . close ( ) 
949+           } 
950+         } ) 
951+         ctx . body  =  new  Response ( readable ) 
952+       } ) 
953+ 
954+       await  request ( app . callback ( ) ) 
955+         . get ( '/' ) 
956+         . expect ( 200 ) 
957+         . expect ( Buffer . from ( 'response data' ) ) 
958+     } ) 
959+ 
960+     it ( 'should handle Response objects without streams' ,  async  ( )  =>  { 
961+       const  app  =  new  Koa ( ) 
962+ 
963+       app . use ( ( ctx )  =>  { 
964+         ctx . body  =  new  Response ( null ) 
965+       } ) 
966+ 
967+       await  request ( app . callback ( ) ) 
968+         . get ( '/' ) 
969+         . expect ( 200 ) 
970+         . expect ( Buffer . from ( [ ] ) ) 
971+     } ) 
972+ 
973+     it ( 'should handle Response objects with non-ReadableStream body' ,  async  ( )  =>  { 
974+       const  app  =  new  Koa ( ) 
975+ 
976+       app . use ( ( ctx )  =>  { 
977+         const  response  =  new  Response ( 'text body' ) 
978+         Object . defineProperty ( response ,  'body' ,  { 
979+           value : 'not a stream' , 
980+           writable : false 
981+         } ) 
982+         ctx . body  =  response 
983+       } ) 
984+ 
985+       await  request ( app . callback ( ) ) 
986+         . get ( '/' ) 
987+         . expect ( 200 ) 
988+     } ) 
989+ 
990+     it ( 'should handle ReadableStream destruction' ,  async  ( )  =>  { 
991+       const  app  =  new  Koa ( ) 
992+       let  destroyed  =  false 
993+       let  cancelPromiseResolve 
994+       const  cancelPromise  =  new  Promise ( ( resolve )  =>  { 
995+         cancelPromiseResolve  =  resolve 
996+       } ) 
997+ 
998+       app . use ( ( ctx )  =>  { 
999+         const  readable  =  new  ReadableStream ( { 
1000+           start  ( controller )  { 
1001+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'destroy test' ) ) 
1002+             setTimeout ( ( )  =>  { 
1003+               if  ( ! destroyed )  { 
1004+                 controller . enqueue ( new  TextEncoder ( ) . encode ( ' more data' ) ) 
1005+               } 
1006+             } ,  10 ) 
1007+           } , 
1008+           cancel  ( )  { 
1009+             destroyed  =  true 
1010+             cancelPromiseResolve ( ) 
1011+           } 
1012+         } ) 
1013+         ctx . body  =  readable 
1014+       } ) 
1015+ 
1016+       const  server  =  app . listen ( ) 
1017+       return  new  Promise ( ( resolve ,  reject )  =>  { 
1018+         const  timeout  =  setTimeout ( ( )  =>  { 
1019+           server . close ( ) 
1020+           reject ( new  Error ( 'Test timed out - cancel was not called' ) ) 
1021+         } ,  1000 ) 
1022+ 
1023+         const  req  =  http . request ( { 
1024+           port : server . address ( ) . port , 
1025+           path : '/' 
1026+         } ) 
1027+ 
1028+         req . on ( 'response' ,  ( res )  =>  { 
1029+           res . on ( 'data' ,  ( chunk )  =>  { 
1030+             setImmediate ( ( )  =>  req . destroy ( ) ) 
1031+           } ) 
1032+ 
1033+           res . on ( 'error' ,  ( )  =>  { 
1034+           } ) 
1035+         } ) 
1036+ 
1037+         req . on ( 'error' ,  ( )  =>  { 
1038+         } ) 
1039+ 
1040+         cancelPromise . then ( ( )  =>  { 
1041+           clearTimeout ( timeout ) 
1042+           server . close ( ) 
1043+           assert . strictEqual ( destroyed ,  true ,  'ReadableStream should be destroyed' ) 
1044+           server . on ( 'close' ,  resolve ) 
1045+         } ) 
1046+ 
1047+         req . end ( ) 
1048+       } ) 
1049+     } ) 
1050+ 
1051+     it ( 'should handle locked ReadableStream' ,  async  ( )  =>  { 
1052+       const  app  =  new  Koa ( ) 
1053+       let  cleanupCalled  =  false 
1054+ 
1055+       app . use ( ( ctx )  =>  { 
1056+         const  readable  =  new  ReadableStream ( { 
1057+           start  ( controller )  { 
1058+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'locked stream' ) ) 
1059+             controller . close ( ) 
1060+           } , 
1061+           cancel  ( )  { 
1062+             cleanupCalled  =  true 
1063+           } 
1064+         } ) 
1065+ 
1066+         // Lock the stream by getting a reader 
1067+         const  reader  =  readable . getReader ( ) 
1068+         reader . releaseLock ( ) 
1069+ 
1070+         ctx . body  =  readable 
1071+       } ) 
1072+ 
1073+       const  server  =  app . listen ( ) 
1074+       const  req  =  http . request ( { 
1075+         port : server . address ( ) . port , 
1076+         path : '/' 
1077+       } ) 
1078+ 
1079+       req . on ( 'response' ,  ( res )  =>  { 
1080+         req . destroy ( ) 
1081+         setTimeout ( ( )  =>  { 
1082+           server . close ( ) 
1083+         } ,  50 ) 
1084+       } ) 
1085+ 
1086+       req . end ( ) 
1087+ 
1088+       return  new  Promise ( ( resolve )  =>  { 
1089+         server . on ( 'close' ,  ( )  =>  { 
1090+           assert . strictEqual ( cleanupCalled ,  false ,  'Cancel should not be called for locked stream' ) 
1091+           resolve ( ) 
1092+         } ) 
1093+       } ) 
1094+     } ) 
1095+ 
1096+     it ( 'should handle ReadableStream without cancel method' ,  async  ( )  =>  { 
1097+       const  app  =  new  Koa ( ) 
1098+ 
1099+       app . use ( ( ctx )  =>  { 
1100+         const  readable  =  new  ReadableStream ( { 
1101+           start  ( controller )  { 
1102+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'no cancel' ) ) 
1103+             controller . close ( ) 
1104+           } 
1105+         } ) 
1106+ 
1107+         // Remove the cancel method 
1108+         delete  readable . cancel 
1109+ 
1110+         ctx . body  =  readable 
1111+       } ) 
1112+ 
1113+       const  server  =  app . listen ( ) 
1114+       const  req  =  http . request ( { 
1115+         port : server . address ( ) . port , 
1116+         path : '/' 
1117+       } ) 
1118+ 
1119+       req . on ( 'response' ,  ( res )  =>  { 
1120+         req . destroy ( ) 
1121+         setTimeout ( ( )  =>  { 
1122+           server . close ( ) 
1123+         } ,  50 ) 
1124+       } ) 
1125+ 
1126+       req . end ( ) 
1127+ 
1128+       return  new  Promise ( ( resolve )  =>  { 
1129+         server . on ( 'close' ,  resolve ) 
1130+       } ) 
1131+     } ) 
1132+ 
1133+     it ( 'should handle Response with locked ReadableStream body' ,  async  ( )  =>  { 
1134+       const  app  =  new  Koa ( ) 
1135+       let  cleanupCalled  =  false 
1136+ 
1137+       app . use ( ( ctx )  =>  { 
1138+         const  readable  =  new  ReadableStream ( { 
1139+           start  ( controller )  { 
1140+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'response locked' ) ) 
1141+             controller . close ( ) 
1142+           } , 
1143+           cancel  ( )  { 
1144+             cleanupCalled  =  true 
1145+           } 
1146+         } ) 
1147+ 
1148+         // Lock the stream 
1149+         const  reader  =  readable . getReader ( ) 
1150+         reader . releaseLock ( ) 
1151+ 
1152+         ctx . body  =  new  Response ( readable ) 
1153+       } ) 
1154+ 
1155+       const  server  =  app . listen ( ) 
1156+       const  req  =  http . request ( { 
1157+         port : server . address ( ) . port , 
1158+         path : '/' 
1159+       } ) 
1160+ 
1161+       req . on ( 'response' ,  ( res )  =>  { 
1162+         req . destroy ( ) 
1163+         setTimeout ( ( )  =>  { 
1164+           server . close ( ) 
1165+         } ,  50 ) 
1166+       } ) 
1167+ 
1168+       req . end ( ) 
1169+ 
1170+       return  new  Promise ( ( resolve )  =>  { 
1171+         server . on ( 'close' ,  ( )  =>  { 
1172+           assert . strictEqual ( cleanupCalled ,  false ,  'Cancel should not be called for locked Response body' ) 
1173+           resolve ( ) 
1174+         } ) 
1175+       } ) 
1176+     } ) 
1177+ 
1178+     it ( 'should handle Response with body without cancel method' ,  async  ( )  =>  { 
1179+       const  app  =  new  Koa ( ) 
1180+ 
1181+       app . use ( ( ctx )  =>  { 
1182+         const  readable  =  new  ReadableStream ( { 
1183+           start  ( controller )  { 
1184+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'no cancel method' ) ) 
1185+             controller . close ( ) 
1186+           } 
1187+         } ) 
1188+ 
1189+         // Remove the cancel method 
1190+         delete  readable . cancel 
1191+ 
1192+         ctx . body  =  new  Response ( readable ) 
1193+       } ) 
1194+ 
1195+       const  server  =  app . listen ( ) 
1196+       const  req  =  http . request ( { 
1197+         port : server . address ( ) . port , 
1198+         path : '/' 
1199+       } ) 
1200+ 
1201+       req . on ( 'response' ,  ( res )  =>  { 
1202+         req . destroy ( ) 
1203+         setTimeout ( ( )  =>  { 
1204+           server . close ( ) 
1205+         } ,  50 ) 
1206+       } ) 
1207+ 
1208+       req . end ( ) 
1209+ 
1210+       return  new  Promise ( ( resolve )  =>  { 
1211+         server . on ( 'close' ,  resolve ) 
1212+       } ) 
1213+     } ) 
1214+ 
1215+     it ( 'should handle non-stream original parameter' ,  async  ( )  =>  { 
1216+       const  app  =  new  Koa ( ) 
1217+ 
1218+       app . use ( ( ctx )  =>  { 
1219+         const  readable  =  new  ReadableStream ( { 
1220+           start  ( controller )  { 
1221+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'non-stream original' ) ) 
1222+             controller . close ( ) 
1223+           } 
1224+         } ) 
1225+ 
1226+         // We'll test this indirectly by ensuring the stream works normally 
1227+         ctx . body  =  readable 
1228+       } ) 
1229+ 
1230+       const  server  =  app . listen ( ) 
1231+       const  req  =  http . request ( { 
1232+         port : server . address ( ) . port , 
1233+         path : '/' 
1234+       } ) 
1235+ 
1236+       req . on ( 'response' ,  ( res )  =>  { 
1237+         req . destroy ( ) 
1238+         setTimeout ( ( )  =>  { 
1239+           server . close ( ) 
1240+         } ,  50 ) 
1241+       } ) 
1242+ 
1243+       req . end ( ) 
1244+ 
1245+       return  new  Promise ( ( resolve )  =>  { 
1246+         server . on ( 'close' ,  resolve ) 
1247+       } ) 
1248+     } ) 
1249+ 
1250+     it ( 'should exercise setupStreamAbortHandling code paths' ,  ( )  =>  { 
1251+       const  app  =  new  Koa ( ) 
1252+       let  setupCalled  =  false 
1253+ 
1254+       app . use ( ( ctx )  =>  { 
1255+         const  readable  =  new  ReadableStream ( { 
1256+           start  ( controller )  { 
1257+             setupCalled  =  true 
1258+             controller . enqueue ( new  TextEncoder ( ) . encode ( 'data' ) ) 
1259+             controller . close ( ) 
1260+           } 
1261+         } ) 
1262+         ctx . body  =  readable 
1263+       } ) 
1264+ 
1265+       return  request ( app . callback ( ) ) 
1266+         . get ( '/' ) 
1267+         . expect ( 200 ) 
1268+         . then ( ( )  =>  { 
1269+           assert . strictEqual ( setupCalled ,  true ,  'Stream setup should be called' ) 
1270+         } ) 
1271+     } ) 
1272+   } ) 
1273+ 
1274+   describe ( 'when accessing static default property' ,  ( )  =>  { 
1275+     it ( 'should return Application constructor' ,  ( )  =>  { 
1276+       assert . strictEqual ( Koa . default ,  Koa ) 
1277+     } ) 
1278+   } ) 
1279+ 
1280+   describe ( 'when using test helpers' ,  ( )  =>  { 
1281+     it ( 'should exercise test helper stream functions' ,  ( )  =>  { 
1282+       const  {  Readable }  =  require ( '../../test-helpers/stream' ) 
1283+       const  stream  =  new  Readable ( ) 
1284+ 
1285+       stream . pipe ( ) 
1286+       stream . read ( ) 
1287+       stream . destroy ( ) 
1288+ 
1289+       assert . strictEqual ( stream . readable ,  true ) 
1290+       assert . strictEqual ( stream . readableObjectMode ,  false ) 
1291+       assert . strictEqual ( stream . destroyed ,  false ) 
1292+     } ) 
1293+   } ) 
9191294} ) 
0 commit comments