UNPKG

hpdf

Version:

NodeJS library for generation PDF from HTML

304 lines (265 loc) 8.83 kB
import { Readable, Stream } from 'stream'; import { afterEach, beforeEach, describe, expect, jest, test, } from '@jest/globals'; import { createPool, Factory as PoolFactory } from 'generic-pool'; import { PdfGenerator } from './index'; const readStream = async (stream: Readable): Promise<Buffer> => { const chunks: Buffer[] = []; for await (const chunk of stream) { chunks.push(chunk); } return Buffer.concat(chunks); }; const getMockedPool = (poolFactory?: Partial<PoolFactory<any>>) => { const getDefaultStreamCallback = () => jest.fn(() => { const stream = new Stream.Readable({ read: () => undefined, }); setTimeout(async () => { stream.push(Buffer.from('abc', 'utf-8')); await stream.destroy(); }, 50); return stream; }); const getDefaultBufferCallback = () => jest.fn(() => { return new Promise((resolve) => setTimeout(() => resolve(Buffer.from('abc', 'utf-8')), 50), ); }); const mockedPage = { page: { setContent: jest.fn(() => undefined) as () => any, emulateMediaType: jest.fn(() => undefined) as () => any, pdf: getDefaultBufferCallback() as () => any, createPDFStream: getDefaultStreamCallback() as () => any, }, }; return { setPdfMethod: (fn?: () => any) => { mockedPage.page.pdf = fn || getDefaultBufferCallback(); }, setSetContentMethod: (fn?: () => any) => { mockedPage.page.setContent = fn || jest.fn(() => undefined); }, setCreatePDFStreamMethod: (fn?: () => any) => { mockedPage.page.createPDFStream = fn || getDefaultStreamCallback(); }, mockedPage, pool: createPool( { async create(): Promise<any> { await new Promise((resolve) => setTimeout(resolve, 50)); return Promise.resolve(mockedPage); }, destroy(): Promise<void> { return Promise.resolve(); }, validate(): Promise<boolean> { return Promise.resolve(true); }, ...poolFactory, }, { autostart: true, testOnBorrow: true, }, ), }; }; describe('PdfGenerator module', () => { describe('Positive scenario', () => { let generator = new PdfGenerator(); beforeEach(async () => { generator = new PdfGenerator({ max: 1, min: 1, }); await generator.awaitPool(); }); afterEach(async () => { await generator.stop(); }); test('Render pdf from html test', async () => { await generator.generatePDF('<html lang="html">Hello World!</html>'); }); test('Render pdf from html', async () => { const pdf = await generator.generatePDF( '<html lang="html">Hello World!</html>', ); const pdfFromStream = await readStream( await generator.generatePDF( '<html lang="html">Hello World!</html>', true, ), ); expect(pdfFromStream.length).toBe(pdf.length); }, 60000); test('Render pdf from url', async () => { const pdf = await generator.generatePDF( new URL('https://github.com/frimuchkov/hpdf'), ); expect(pdf.length).toBeGreaterThan(100); }, 60000); test('PDF have to be rendered in FIFO', async () => { const timestamps: number[] = []; await Promise.all( new Array(3).fill(0).map(async (_, i) => { await new Promise((resolve) => setTimeout(resolve, i * 50)); await generator.generatePDF('<html lang="html">Hello World!</html>'); timestamps.push(Date.now()); }), ); expect([...timestamps].sort()).toStrictEqual(timestamps); }, 60000); test('Stream have to lock and release resource', async () => { let bufferTimestamp = 0; let streamTimestamp = 0; const pdfStream = generator.generatePDF( '<html lang="html">Hello World!</html>', true, ); const pdfBuffer = generator.generatePDF( '<html lang="html">Hello World!</html>', ); await Promise.all([ (async () => { await readStream(await pdfStream); streamTimestamp = Date.now(); })(), pdfBuffer.then(() => { bufferTimestamp = Date.now(); }), ]); expect(bufferTimestamp - streamTimestamp).toBeGreaterThan(100); }, 60000); }); describe('Negative scenario', () => { let generator = new PdfGenerator(); beforeEach(async () => { generator = new PdfGenerator({ max: 1, min: 1, }); await generator.awaitPool(); }); afterEach(async () => { await generator.stop(); }); test('Pool have to destroy invalid instance and acquire the new one', async () => { const destroyPool = jest.fn(() => undefined); const validatePool = jest.fn(() => true); const pagesPool = getMockedPool({ destroy(): Promise<void> { destroyPool(); return Promise.resolve(); }, validate(): Promise<boolean> { return Promise.resolve(validatePool()); }, }); await generator.stop(); (generator as any).pagesPool = pagesPool.pool; validatePool.mockImplementation(() => false); const runner = generator.generatePDF( '<html lang="html">Hello World!</html>', ); await new Promise((resolve) => setTimeout(resolve, 240)); expect(validatePool.mock.calls.length).toBeGreaterThan(1); validatePool.mockReset(); expect(validatePool).toBeCalledTimes(0); validatePool.mockImplementation(() => true); await expect(runner).resolves.toBeDefined(); }, 60000); test('Generator have to release resource in case of exception', async () => { const pagesPool = getMockedPool(); await generator.stop(); (generator as any).pagesPool = pagesPool.pool; pagesPool.setPdfMethod(() => { return new Promise((_, reject) => setTimeout(() => reject(new Error('Error from generator')), 50), ); }); // Handle exception during rendering { await expect( generator.generatePDF('<html lang="html">Hello World!</html>'), ).rejects.toEqual(new Error('Error from generator')); pagesPool.setPdfMethod(); await generator.generatePDF('<html lang="html">Hello World!</html>'); expect(pagesPool.mockedPage.page.pdf).toBeCalled(); } // Handle exception during setting content { pagesPool.setSetContentMethod(() => { throw new Error('Error from setContent'); }); await expect( generator.generatePDF('<html lang="html">Hello World!</html>'), ).rejects.toEqual(new Error('Error from setContent')); pagesPool.setSetContentMethod(); pagesPool.setPdfMethod(); await generator.generatePDF('<html lang="html">Hello World!</html>'); expect(pagesPool.mockedPage.page.pdf).toBeCalled(); } }, 60000); test('Stream have to release resource in case of exception', async () => { const pagesPool = getMockedPool(); await generator.stop(); (generator as any).pagesPool = pagesPool.pool; // Handle exception from generator { pagesPool.setCreatePDFStreamMethod(() => { throw new Error('Error from generator'); }); await expect( generator.generatePDF('<html lang="html">Hello World!</html>', true), ).rejects.toEqual(new Error('Error from generator')); pagesPool.setCreatePDFStreamMethod(); await readStream( await generator.generatePDF( '<html lang="html">Hello World!</html>', true, ), ); expect(pagesPool.mockedPage.page.createPDFStream).toBeCalled(); } // Handle exception from stream { pagesPool.setCreatePDFStreamMethod(() => { const stream = new Stream.Readable({ read: () => undefined, }); setTimeout(() => { stream.push(Buffer.from('abc', 'utf-8')); stream.emit('error', new Error('Error from stream')); }, 50); return stream; }); await expect( readStream( await generator.generatePDF( '<html lang="html">Hello World!</html>', true, ), ), ).rejects.toEqual(new Error('Error from stream')); pagesPool.setCreatePDFStreamMethod(); await readStream( await generator.generatePDF( '<html lang="html">Hello World!</html>', true, ), ); expect(pagesPool.mockedPage.page.createPDFStream).toBeCalled(); } }, 60000); }); });