Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue-425 - streamToPromise error propagation and test cases #426

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions lib/sitemap-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,23 @@ export class SitemapStream extends Transform {
}

/**
* Takes a stream returns a promise that resolves when stream emits finish
* @param stream what you want wrapped in a promise
* Converts a readable stream into a promise that resolves with the concatenated data from the stream.
*
* The function listens for 'data' events from the stream, and when the stream ends, it resolves the promise with the concatenated data. If an error occurs while reading from the stream, the promise is rejected with the error.
*
* ⚠️ CAUTION: This function should not generally be used in production / when writing to files as it holds a copy of the entire file contents in memory until finished.
*
* @param {Readable} stream - The readable stream to convert to a promise.
* @returns {Promise<Buffer>} A promise that resolves with the concatenated data from the stream as a Buffer, or rejects with an error if one occurred while reading from the stream. If the stream is empty, the promise is rejected with an EmptyStream error.
* @throws {EmptyStream} If the stream is empty.
*/
export function streamToPromise(stream: Readable): Promise<Buffer> {
return new Promise((resolve, reject): void => {
const drain: Buffer[] = [];
stream
// Error propagation is not automatic
// Bubble up errors on the read stream
.on('error', reject)
.pipe(
new Writable({
write(chunk, enc, next): void {
Expand All @@ -156,6 +166,8 @@ export function streamToPromise(stream: Readable): Promise<Buffer> {
},
})
)
// This bubbles up errors when writing to the internal buffer
// This is unlikely to happen, but we have this for completeness
.on('error', reject)
.on('finish', () => {
if (!drain.length) {
Expand Down
33 changes: 33 additions & 0 deletions tests/sitemap-stream.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { createReadStream } from 'fs';
import { tmpdir } from 'os';
import { resolve } from 'path';
import { Readable } from 'stream';
import { EmptyStream } from '../lib/errors';
import {
SitemapStream,
closetag,
Expand Down Expand Up @@ -92,4 +97,32 @@ describe('sitemap stream', () => {
closetag
);
});

it('streamToPromise propagates error on read stream', async () => {
await expect(
streamToPromise(
createReadStream(resolve(tmpdir(), `./does-not-exist-sitemap.xml`))
)
).rejects.toThrow('ENOENT');
});

it('streamToPromise throws EmptyStream error on empty stream', async () => {
const emptyStream = new Readable();
emptyStream.push(null); // This makes the stream "empty"

await expect(streamToPromise(emptyStream)).rejects.toThrow(EmptyStream);
});

it('streamToPromise returns concatenated data', async () => {
const stream = new Readable();
stream.push('Hello');
stream.push(' ');
stream.push('World');
stream.push('!');
stream.push(null); // Close the stream

await expect(streamToPromise(stream)).resolves.toEqual(
Buffer.from('Hello World!', 'utf-8')
);
});
});
Loading