mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
165 lines
6.4 KiB
TypeScript
165 lines
6.4 KiB
TypeScript
import { Readable } from 'stream';
|
|
import type { Logger } from '../../../src/logging/Logger';
|
|
import { getLoggerFor } from '../../../src/logging/LogUtil';
|
|
import { guardStream, isGuarded } from '../../../src/util/GuardedStream';
|
|
import { readableToString } from '../../../src/util/StreamUtil';
|
|
|
|
jest.mock('../../../src/logging/LogUtil', (): any => {
|
|
const logger: Logger = { error: jest.fn() } as any;
|
|
return { getLoggerFor: (): Logger => logger };
|
|
});
|
|
const logger: jest.Mocked<Logger> = getLoggerFor('GuardedStream') as any;
|
|
|
|
jest.useFakeTimers();
|
|
|
|
describe('GuardedStream', (): void => {
|
|
beforeEach((): void => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('#guardStream', (): void => {
|
|
it('has no effect if no error is thrown.', async(): Promise<void> => {
|
|
const stream = Readable.from([ 'data' ]);
|
|
expect(isGuarded(stream)).toBe(false);
|
|
const guarded = guardStream(stream);
|
|
expect(guarded).toBe(stream);
|
|
expect(isGuarded(stream)).toBe(true);
|
|
expect(isGuarded(guarded)).toBe(true);
|
|
|
|
await expect(readableToString(guarded)).resolves.toBe('data');
|
|
});
|
|
|
|
it('returns the stream if it is already guarded.', async(): Promise<void> => {
|
|
const stream = Readable.from([ 'data' ]);
|
|
expect(isGuarded(stream)).toBe(false);
|
|
const guarded = guardStream(stream);
|
|
expect(guarded).toBe(stream);
|
|
expect(isGuarded(stream)).toBe(true);
|
|
expect(isGuarded(guarded)).toBe(true);
|
|
expect(guardStream(guarded)).toBe(stream);
|
|
expect(isGuarded(stream)).toBe(true);
|
|
expect(isGuarded(guarded)).toBe(true);
|
|
|
|
expect(guarded.listenerCount('error')).toBe(1);
|
|
expect(guarded.listenerCount('newListener')).toBe(1);
|
|
expect(guarded.listenerCount('removeListener')).toBe(0);
|
|
|
|
await expect(readableToString(guarded)).resolves.toBe('data');
|
|
});
|
|
|
|
it('emits errors when listeners are currently attached.', async(): Promise<void> => {
|
|
const stream = guardStream(Readable.from([ 'data' ]));
|
|
const listener = jest.fn();
|
|
stream.on('error', listener);
|
|
const error = new Error('error');
|
|
stream.emit('error', error);
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(listener).toHaveBeenLastCalledWith(error);
|
|
});
|
|
|
|
it('emits guarded errors when new listeners are attached.', async(): Promise<void> => {
|
|
const errors = [ new Error('0'), new Error('1') ];
|
|
const stream = guardStream(Readable.from([ 'data' ]));
|
|
stream.emit('error', errors[0]);
|
|
stream.emit('error', errors[1]);
|
|
|
|
const errorListeners = [ jest.fn(), jest.fn(), jest.fn() ];
|
|
stream.addListener('error', errorListeners[0]);
|
|
stream.addListener('error', errorListeners[1]);
|
|
stream.addListener('error', errorListeners[2]);
|
|
const endListener = jest.fn();
|
|
stream.addListener('end', endListener);
|
|
|
|
expect(errorListeners[0]).toHaveBeenCalledTimes(0);
|
|
expect(errorListeners[1]).toHaveBeenCalledTimes(0);
|
|
expect(errorListeners[2]).toHaveBeenCalledTimes(0);
|
|
expect(endListener).toHaveBeenCalledTimes(0);
|
|
|
|
await new Promise((resolve): any => setImmediate(resolve));
|
|
|
|
expect(errorListeners[0]).toHaveBeenCalledTimes(2);
|
|
expect(errorListeners[0]).toHaveBeenNthCalledWith(1, errors[0]);
|
|
expect(errorListeners[0]).toHaveBeenNthCalledWith(2, errors[1]);
|
|
expect(errorListeners[1]).toHaveBeenCalledTimes(2);
|
|
expect(errorListeners[1]).toHaveBeenNthCalledWith(1, errors[0]);
|
|
expect(errorListeners[1]).toHaveBeenNthCalledWith(2, errors[1]);
|
|
expect(errorListeners[2]).toHaveBeenCalledTimes(2);
|
|
expect(errorListeners[1]).toHaveBeenNthCalledWith(1, errors[0]);
|
|
expect(errorListeners[1]).toHaveBeenNthCalledWith(2, errors[1]);
|
|
expect(endListener).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
it('does not time out when a listener was already attached.', async(): Promise<void> => {
|
|
const stream = Readable.from([ 'data' ]);
|
|
stream.addListener('error', jest.fn());
|
|
guardStream(stream);
|
|
|
|
stream.emit('error', new Error('error'));
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
expect(logger.error).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
it('still works if error listeners get removed and added again.', async(): Promise<void> => {
|
|
const stream = guardStream(Readable.from([ 'data' ]));
|
|
|
|
// Make sure no unneeded listeners stay attached
|
|
const errorCb = jest.fn();
|
|
const errorCb2 = jest.fn();
|
|
stream.on('error', errorCb);
|
|
stream.on('error', errorCb2);
|
|
expect(stream.listenerCount('error')).toBe(3);
|
|
expect(stream.listenerCount('newListener')).toBe(1);
|
|
stream.removeListener('error', errorCb2);
|
|
expect(stream.listenerCount('error')).toBe(2);
|
|
expect(stream.listenerCount('newListener')).toBe(1);
|
|
stream.removeListener('error', errorCb);
|
|
expect(stream.listenerCount('error')).toBe(1);
|
|
expect(stream.listenerCount('newListener')).toBe(1);
|
|
|
|
const error = new Error('error');
|
|
stream.emit('error', error);
|
|
|
|
const errorCb3 = jest.fn();
|
|
stream.on('error', errorCb3);
|
|
|
|
await new Promise((resolve): any => setImmediate(resolve));
|
|
|
|
expect(errorCb).toHaveBeenCalledTimes(0);
|
|
expect(errorCb2).toHaveBeenCalledTimes(0);
|
|
expect(errorCb3).toHaveBeenCalledTimes(1);
|
|
expect(errorCb3).toHaveBeenLastCalledWith(error);
|
|
});
|
|
|
|
it('logs an error if nobody listens to the error.', async(): Promise<void> => {
|
|
const errors = [ new Error('0'), new Error('1'), new Error('2') ];
|
|
const stream = guardStream(Readable.from([ 'data' ]));
|
|
stream.emit('error', errors[0]);
|
|
|
|
jest.advanceTimersByTime(100);
|
|
stream.emit('error', errors[1]);
|
|
stream.emit('error', errors[2]);
|
|
|
|
// Only the first error gets logged
|
|
jest.advanceTimersByTime(900);
|
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
'No error listener was attached but error was thrown: 0', { error: errors[0] },
|
|
);
|
|
|
|
jest.advanceTimersByTime(1000);
|
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
|
|
const errorCb = jest.fn();
|
|
stream.on('error', errorCb);
|
|
|
|
await new Promise((resolve): any => setImmediate(resolve));
|
|
|
|
expect(errorCb).toHaveBeenCalledTimes(3);
|
|
expect(errorCb).toHaveBeenNthCalledWith(1, errors[0]);
|
|
expect(errorCb).toHaveBeenNthCalledWith(2, errors[1]);
|
|
expect(errorCb).toHaveBeenNthCalledWith(3, errors[2]);
|
|
});
|
|
});
|
|
});
|