11import { assert } from 'chai' ;
2- import { ReadableWebToNodeStream as NodeReadableWebToNodeStream } from '../lib/node.js' ;
2+ import { ReadableWebToNodeStream as NodeReadableWebToNodeStream , type ReadableWebToNodeStreamOptions } from '../lib/node.js' ;
33import { ReadableWebToNodeStream as DefaultReadableWebToNodeStream } from '../lib/default.js' ;
44import { fileTypeFromStream } from 'file-type' ;
55import { parseStream } from 'music-metadata' ;
6+ import { MockedReadableStream , NodeReadableListener } from "./util.js" ;
7+ import type { Readable } from "node:stream" ;
68
79const jpegDataBase64 = '/9j/4AAQSkZJRgABAQEAAAAAAAD/2wCEAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE' ;
810
@@ -17,14 +19,14 @@ async function httpGetByUrl(url: string): Promise<Response> {
1719 return response ;
1820}
1921
20- type makeReadableWebToNodeStream = ( stream : ReadableStream | ReadableStream < Uint8Array > ) => NodeReadableWebToNodeStream | DefaultReadableWebToNodeStream ;
22+ type makeReadableWebToNodeStream = ( stream : ReadableStream | ReadableStream < Uint8Array > , options ?: ReadableWebToNodeStreamOptions ) => Readable ;
2123
2224const entryPoints : { label : string , makeNodeStream : makeReadableWebToNodeStream } [ ] = [ {
2325 label : 'Node.js' ,
24- makeNodeStream : stream => new NodeReadableWebToNodeStream ( stream )
26+ makeNodeStream : ( stream , options ) => new NodeReadableWebToNodeStream ( stream , options )
2527} , {
2628 label : 'default (userland Readable)' ,
27- makeNodeStream : stream => new DefaultReadableWebToNodeStream ( stream ) ,
29+ makeNodeStream : ( stream , options ) => new DefaultReadableWebToNodeStream ( stream , options )
2830}
2931] ;
3032
@@ -41,7 +43,7 @@ entryPoints.forEach(entryPoint => {
4143 assert . isDefined ( fileType , 'Detected file-type' ) ;
4244 assert . strictEqual ( fileType . mime , 'image/jpeg' , 'fileType.mime' ) ;
4345 } finally {
44- await nodeReadable . close ( ) ;
46+ nodeReadable . destroy ( )
4547 }
4648 } finally {
4749 await webReadableStream . cancel ( ) ;
@@ -57,21 +59,60 @@ entryPoints.forEach(entryPoint => {
5759 const url = `https://raw.githubusercontent.com/Borewit/test-audio/958e057${ trackPath } ` ;
5860 const response = await httpGetByUrl ( url ) ;
5961 const contentLength = response . headers . get ( 'Content-Length' ) ;
60- if ( ! response . body ) {
62+ const webStream = response . body ;
63+ if ( ! webStream ) {
6164 assert . fail ( 'response.body (Web API ReadableStream) not defined' )
6265 }
63- const nodeReadable = entryPoint . makeNodeStream ( response . body ) ;
6466 try {
65- const metadata = await parseStream ( nodeReadable , {
66- size : contentLength ? Number . parseInt ( contentLength , 10 ) : undefined ,
67- mimeType : response . headers . get ( 'Content-Type' ) ?? undefined
68- } ) ;
69- assert . isDefined ( metadata , 'metadata' ) ;
70- assert . strictEqual ( metadata . common . artist , 'Diablo Swing Orchestra' , 'metadata.common.artist' ) ;
71- assert . strictEqual ( metadata . common . title , 'Heroines' , 'metadata.common.title' ) ;
67+ const nodeReadable = entryPoint . makeNodeStream ( webStream ) ;
68+ try {
69+ const metadata = await parseStream ( nodeReadable , {
70+ size : contentLength ? Number . parseInt ( contentLength , 10 ) : undefined ,
71+ mimeType : response . headers . get ( 'Content-Type' ) ?? undefined
72+ } ) ;
73+ assert . isDefined ( metadata , 'metadata' ) ;
74+ assert . strictEqual ( metadata . common . artist , 'Diablo Swing Orchestra' , 'metadata.common.artist' ) ;
75+ assert . strictEqual ( metadata . common . title , 'Heroines' , 'metadata.common.title' ) ;
76+ } finally {
77+ nodeReadable . destroy ( ) ;
78+ }
7279 } finally {
73- await nodeReadable . close ( ) ;
80+ await webStream . cancel ( ) ;
81+ }
82+ } ) ;
83+
84+ describe ( 'destroying Node.js stream.Readable' , ( ) => {
85+
86+ async function destroyReadable ( propagateDestroy : boolean ) : Promise < void > {
87+ const mockedWebReadableStream = new MockedReadableStream ( ) ;
88+
89+ const nodeReadable = entryPoint . makeNodeStream ( mockedWebReadableStream . stream , { propagateDestroy} ) ;
90+ const nodeReadableListener = new NodeReadableListener ( nodeReadable ) ;
91+
92+ assert . isFalse ( mockedWebReadableStream . cancelled , 'Web API ReadableStream is not cancelled' ) ;
93+ assert . strictEqual ( nodeReadableListener . closed , 0 , 'Node ReadableStream is not ended' ) ;
94+ assert . strictEqual ( nodeReadableListener . errors . length , 0 , 'Node ReadableStream did not receive any error' ) ;
95+ assert . isFalse ( mockedWebReadableStream . cancelled , 'Web API ReadableStream is not cancelled yet' ) ;
96+ nodeReadable . destroy ( ) ;
97+ await nodeReadableListener . waitUntilClosed ( ) ;
98+ assert . strictEqual ( nodeReadableListener . errors . length , 0 , 'Node ReadableStream did not receive any error' ) ;
99+ assert . strictEqual ( nodeReadableListener . closed , 1 , 'Node ReadableStream ended, once' ) ;
100+
101+ assert . strictEqual ( mockedWebReadableStream . cancelled , propagateDestroy , 'Web API ReadableStream is cancelled' ) ;
102+ if ( ! propagateDestroy ) {
103+ await mockedWebReadableStream . stream . cancel ( ) ;
104+ assert . isTrue ( mockedWebReadableStream . cancelled , 'Web API ReadableStream is cancelled' ) ;
105+ }
106+
74107 }
108+
109+ it ( 'should be able to abort a pending read' , async ( ) => {
110+ await destroyReadable ( false ) ;
111+ } ) ;
112+
113+ it ( 'should be able to abort a pending read with cancellation propagation' , async ( ) => {
114+ await destroyReadable ( true ) ;
115+ } ) ;
75116 } ) ;
76117 } ) ;
77118} ) ;
0 commit comments