UNPKG

promises-to-retry

Version:

Simple utility library that provides retry/reflect mechanism for a list of promises

281 lines (213 loc) 9.86 kB
// node core modules // 3rd party modules const test = require('ava'); const sinon = require('sinon'); // local modules const methods = require('./lib'); const { reflectAllPromises, retryAllRejectedPromises, reflectAndRetryAllRejectedPromises, delay, batchPromises, racePromisesWithTime } = methods; test.beforeEach((t) => { const logger = { debug: value => value, error: value => value }; Object.assign(t.context, { logger }); }); test('Verify that reflectAllPromises does not throw an error', async (t) => { const errorPromise = () => Promise.reject(new Error('Throw error')); const { logger } = t.context; const loggerErrorSpy = sinon.spy(logger, 'error'); const [{ status, error, rejectedPromise }] = await reflectAllPromises([errorPromise], logger); t.is(status, 'rejected'); t.is(typeof rejectedPromise, 'function'); t.is(error.message, 'Throw error'); t.true(loggerErrorSpy.calledWith('Reflect promise error: ')); t.true(loggerErrorSpy.calledOnce); }); test('Verify that reflectAllPromises does not throw an error when non-function is provided as promise', async (t) => { const { logger } = t.context; const loggerErrorSpy = sinon.spy(logger, 'error'); const [{ status, error, rejectedPromise }] = await reflectAllPromises([Promise.reject(new Error('Throw error'))], logger); t.is(status, 'rejected'); t.is(rejectedPromise, undefined, 'rejectedPromise is not provided in this case'); t.is(error.message, 'Throw error'); t.true(loggerErrorSpy.calledWith('Reflect promise error: ')); t.true(loggerErrorSpy.calledOnce); }); test('Verify that reflectAllPromises returns resolved promises', async (t) => { const { logger } = t.context; const mockResponse = { a: 'b' }; const loggerErrorSpy = sinon.spy(logger, 'error'); const validResponse = () => Promise.resolve(mockResponse); const [{ status, data }] = await reflectAllPromises([validResponse], logger); t.is(status, 'resolved'); t.is(typeof data, 'object'); t.deepEqual(data, mockResponse); t.false(loggerErrorSpy.calledOnce); }); test('Verify that reflectAllPromises returns resolved promises when non-function is provided', async (t) => { const { logger } = t.context; const mockResponse = { a: 'b' }; const loggerErrorSpy = sinon.spy(logger, 'error'); const [{ status, data }] = await reflectAllPromises([Promise.resolve(mockResponse)], logger); t.is(status, 'resolved'); t.is(typeof data, 'object'); t.deepEqual(data, mockResponse); t.false(loggerErrorSpy.calledOnce); }); test('Verify that retryAllPromises works without retryParams', async (t) => { const { logger } = t.context; const mockResponse = { a: 'b' }; const loggerErrorSpy = sinon.spy(logger, 'error'); const validResponse = () => Promise.resolve(mockResponse); await retryAllRejectedPromises([validResponse], logger); t.false(loggerErrorSpy.calledOnce); }); test('Verify that retryAllPromises retries for the number of provided attempts before failing', async (t) => { const { logger } = t.context; const errorPromise = () => Promise.reject(new Error('Throw error')); const loggerErrorSpy = sinon.spy(logger, 'error'); const maxAttempts = 2; const retryParams = { maxAttempts, delay: 10 }; const result = await (retryAllRejectedPromises([errorPromise], retryParams, logger)); t.is(result.length, 1); t.is(loggerErrorSpy.callCount, maxAttempts + 1); }); test('Verify that retryAllPromises retries only failed promises', async (t) => { const { logger } = t.context; const errorPromise = () => Promise.reject(new Error('Throw error')); const validResponse = () => Promise.resolve({}); const maxAttempts = 1; const retryParams = { maxAttempts, delay: 10 }; const result = await (retryAllRejectedPromises([errorPromise, validResponse, errorPromise], retryParams, logger)); t.is(result.length, 2); }); test('Verify that reflectAndRetryAllRejectedPromises never throws an error', async (t) => { const { logger } = t.context; const errorPromise = () => Promise.reject(new Error('Throw error')); const validResponse = () => Promise.resolve({}); const loggerErrorSpy = sinon.spy(logger, 'error'); const maxAttempts = 2; const retryParams = { maxAttempts, delay: 10 }; await reflectAndRetryAllRejectedPromises([errorPromise, validResponse, errorPromise], retryParams, logger); t.is(loggerErrorSpy.callCount, 2, 'For initial execution there should be 2 error log calls'); await delay(300); // we try to execute failed promises after in next event loops t.is(loggerErrorSpy.callCount, 6, 'For each retry attempt there should be more logs per execution'); }); test('Verify that batchPromises returns only resolved promises', async (t) => { const errorPromise = () => Promise.reject(new Error('Throw error')); const response1 = 1; const response2 = 2; const response3 = 3; const validResponse1 = () => Promise.resolve(response1); const validResponse2 = () => Promise.resolve(response2); const validResponse3 = () => Promise.resolve(response3); const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100, responseMode: 'ONLY_RESOLVED' }; const promises = [errorPromise, validResponse1, validResponse2, validResponse3]; const response = await batchPromises(batchParams)(promises); t.deepEqual(response, [response1, response2, response3]); }); test('Verify that batchPromises returns only rejected promises', async (t) => { const errorPromise = () => Promise.reject(new Error('Some error')); const validResponse1 = () => Promise.resolve(1); const validResponse2 = () => Promise.resolve(2); const validResponse3 = () => Promise.resolve(3); const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100, responseMode: 'ONLY_REJECTED' }; const promises = [errorPromise, validResponse1, validResponse2, validResponse3]; const [errorResponse] = await batchPromises(batchParams)(promises); t.deepEqual(errorResponse.message, 'Some error'); }); test('Verify that batchPromises returns all promises. Order must be preserved', async (t) => { const errorPromise = () => Promise.reject(new Error('Some error')); const response1 = 1; const response2 = 2; const response3 = 3; const validResponse1 = () => Promise.resolve(response1); const validResponse2 = () => Promise.resolve(response2); const validResponse3 = () => Promise.resolve(response3); const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100 }; const promises = [errorPromise, validResponse1, validResponse2, validResponse3]; const [error, ...resolved] = await batchPromises(batchParams)(promises); t.deepEqual(resolved, [response1, response2, response3]); t.is(error.message, 'Some error'); }); test('Verify that batchPromises returns all promises, with split of resolved/rejected', async (t) => { const errorPromise = () => Promise.reject(new Error('Some error')); const response1 = 1; const response2 = 2; const response3 = 3; const validResponse1 = () => Promise.resolve(response1); const validResponse2 = () => Promise.resolve(response2); const validResponse3 = () => Promise.resolve(response3); const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100, responseMode: 'ALL_SPLIT' }; const promises = [errorPromise, validResponse2, validResponse1, validResponse3]; const [resolved, [rejected]] = await batchPromises(batchParams)(promises); t.deepEqual(resolved, [response2, response1, response3]); t.is(rejected.message, 'Some error'); }); test('Verify that batchPromises returns [] when no promises array is provided', async (t) => { const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100 }; const response = await batchPromises(batchParams)([]); t.deepEqual(response, []); }); test('Verify that batchPromises throws error for invalid responseMode', async (t) => { const maxBatchSize = 2; const batchParams = { maxBatchSize, delay: 100, responseMode: 'Invalid' }; const errorResponse = await t.throwsAsync(batchPromises(batchParams)([])); t.is(errorResponse.message, 'Invalid responseMode provided'); }); test('Test race promises with all passing', async t => { const res1 = delay(1000, 1); const res2 = delay(2000, 2); const res3 = delay(4000, 3); const listOfPromises = [res1, res2, res3]; const response = await racePromisesWithTime({ listOfPromises, raceTimeoutInMs: 5000 }); t.is(response.length, 3); t.is(response[0], 1); t.is(response[1], 2); t.is(response[2], 3); }); test('Test race promises with 0 passing', async t => { const res1 = delay(1000, 1); const res2 = delay(2000, 2); const res3 = delay(5000, 3); const listOfPromises = [res1, res2, res3]; const response = await racePromisesWithTime({ listOfPromises, raceTimeoutInMs: 700 }); t.is(response.length, 0); }); test('Test race promises with some passing', async t => { const res1 = delay(1000, 1); const res2 = delay(2000, 2); const res3 = delay(5000, 3); const listOfPromises = [res2, res1, res3]; const response = await racePromisesWithTime({ listOfPromises, raceTimeoutInMs: 1500 }); t.is(response.length, 1); t.is(response[0], 1); }); test('Test race promises and verify all results', async t => { const res1 = delay(1000, 1); const res2 = delay(2000, 2); const res3 = delay(5000, 3); const listOfPromises = [res1, res2, res3]; const raceTimeoutMessage = 'My error message'; const params = { listOfPromises, raceTimeoutMessage, raceTimeoutInMs: 1500, responseMode: 'ONLY_RESOLVED' }; const [valid, invalid1, invalid2] = await racePromisesWithTime(params); t.is(valid, 1); [invalid1, invalid2].forEach(({ raceTimeoutMessage, timeoutPromise }) => { t.is(raceTimeoutMessage, raceTimeoutMessage); t.true(timeoutPromise instanceof Promise); }); });