UNPKG

poll-until-promise

Version:

Try repeatedly for a promise to be resolved

462 lines (400 loc) 13.1 kB
import { AbortError, IWaitForOptions, PollUntil, waitFor, } from '../src'; describe('Unit: Wait Until Factory', () => { let options: IWaitForOptions = { interval: 30, timeout: 100, }; let promiseTimeout = 10; let tryingAttemptsRemaining = 0; let shouldHaltPromiseResolve = false; let shouldRejectAfterHalt = false; const someRandPromise = (timeout = promiseTimeout) => new Promise((resolve, reject) => { setTimeout(() => { if (shouldHaltPromiseResolve && tryingAttemptsRemaining > 0) { resolve(false); tryingAttemptsRemaining -= 1; } else if (shouldRejectAfterHalt) { reject(new Error('rejected')); } else { resolve(true); } }, timeout); }); beforeEach(() => { promiseTimeout = 10; tryingAttemptsRemaining = 2; shouldHaltPromiseResolve = false; shouldRejectAfterHalt = false; options = { interval: 30, timeout: 100, }; }); it('should create the default wait params', () => { const pollUntil = new PollUntil(); expect(pollUntil._interval).toEqual(100); expect(pollUntil._timeout).toEqual(1000); }); it('should apply options with pre defined option object', () => { const pollUntil = new PollUntil(options); expect(pollUntil._interval).toEqual(options.interval); expect(pollUntil._timeout).toEqual(options.timeout); }); it('should apply options by functional insert', () => { const pollUntil = new PollUntil() .tryEvery(options.interval!) .stopAfter(options.timeout!); expect(pollUntil._interval).toEqual(options.interval); expect(pollUntil._timeout).toEqual(options.timeout); }); it('should execute runFunctions', () => { const pollUntil = new PollUntil(); jest.spyOn(pollUntil, '_runFunction'); pollUntil .tryEvery(options.interval!) .stopAfter(options.timeout!) .execute(someRandPromise); expect(pollUntil._runFunction).toHaveBeenCalled(); }); it('should resolve the promise', (done) => { const pollUntil = new PollUntil(); pollUntil .tryEvery(options.interval!) .stopAfter(options.timeout!) .execute(someRandPromise) .then((value) => { expect(value).toEqual(true); done(); }); }); it('should resolve the promise with waitFor', (done) => { waitFor(someRandPromise, options) .then((value) => { expect(value).toEqual(true); done(); }); }); it('should resolve the promise', (done) => { const pollUntil = new PollUntil(); pollUntil .tryEvery(options.interval!) .stopAfter(options.timeout!) .execute(someRandPromise) .then((value) => { expect(value).toEqual(true); done(); }); }); it('should get the promise', (done) => { const pollUntil = new PollUntil(); pollUntil .tryEvery(options.interval!) .stopAfter(options.timeout!) .execute(someRandPromise); pollUntil .getPromise() .then((value) => { expect(value).toEqual(true); done(); }); }); it('should resolve a stubborn promise after few attempts', (done) => { const pollUntil = new PollUntil({ verbose: true }); shouldHaltPromiseResolve = true; pollUntil .tryEvery(1) .stopAfter(options.timeout!) .execute(someRandPromise) .then((value) => { expect(value).toEqual(true); done(); }); }); it('should reject a failed promise after timeout', (done) => { const pollUntil = new PollUntil(); shouldHaltPromiseResolve = true; jest.spyOn(pollUntil, '_shouldStopTrying').mockReturnValue(true); pollUntil .tryEvery(1) .stopAfter(5) .execute(someRandPromise) .catch((error) => { expect(error.message).toContain('Failed to wait'); done(); }); }); it('should reject a failed promise when stopOnFailure is true', (done) => { const pollUntil = new PollUntil(); pollUntil .tryEvery(options.interval!) .stopAfter(options.timeout!) .stopOnFailure(true) .execute(() => new Promise((resolve, reject) => { reject(new Error('wow')); })) .catch((error) => { expect(error.message).toContain('wow'); done(); }); }); it('should try again until rejected for a failed promise when stopOnFailure is true', (done) => { const pollUntil = new PollUntil(); shouldHaltPromiseResolve = true; shouldRejectAfterHalt = true; pollUntil .tryEvery(1) .stopAfter(options.timeout!) .stopOnFailure(true) .execute(someRandPromise) .catch((error) => { expect(error.message).toContain('rejected'); done(); }); }); it('should fail wait after timeout when stopOnFailure is false', async () => { const pollUntil = new PollUntil(); const errorContent = 'error abcdefg'; const specificFailedError = new Error(errorContent); const mockPromise = jest.fn(() => Promise.reject(specificFailedError)); expect.assertions(3); try { await pollUntil .tryEvery(1) .stopAfter(50) .stopOnFailure(false) .execute(mockPromise); } catch (err) { const error = err as Error; expect(error.message).toContain('Failed to wait'); expect(error.message).toContain(errorContent); expect(mockPromise.mock.calls.length).toBeGreaterThan(1); } }); it('should fail immediately for AbortError when stopOnFailure is false -- passing Error arg', async () => { const pollUntil = new PollUntil(); const errorContent = 'error abcdefg'; const specificFailedError = new Error(errorContent); const abortError = new AbortError(specificFailedError); const mockPromise = jest.fn(() => Promise.reject(abortError)); expect.assertions(3); try { await pollUntil .tryEvery(1) .stopAfter(50) .stopOnFailure(false) .execute(mockPromise); } catch (err) { const error = err as Error; expect(error.message).not.toContain('Failed to wait'); expect(error.message).toContain(errorContent); expect(mockPromise.mock.calls.length).toBe(1); } }); it('should fail immediately for AbortError when stopOnFailure is false -- passing string arg', async () => { const pollUntil = new PollUntil(); const errorContent = 'error abcdefg'; const abortError = new AbortError(errorContent); const mockPromise = jest.fn(() => Promise.reject(abortError)); expect.assertions(3); try { await pollUntil .tryEvery(1) .stopAfter(50) .stopOnFailure(false) .execute(mockPromise); } catch (err) { const error = err as Error; expect(error.message).not.toContain('Failed to wait'); expect(error.message).toContain(errorContent); expect(mockPromise.mock.calls.length).toBe(1); } }); it('should execute a second waiting when waiting is done (exceeded timeout) but not resolved', (done) => { const pollUntil = new PollUntil(); pollUntil .tryEvery(5) .stopAfter(10) .execute(() => Promise.resolve(false)) .catch(() => { expect(pollUntil.isWaiting()).toEqual(false); expect(pollUntil.isResolved()).toEqual(false); }); pollUntil .tryEvery(5) .stopAfter(10) .execute(() => Promise.resolve(true)) .then((value) => { expect(value).toEqual(true); expect(pollUntil.isWaiting()).toEqual(false); expect(pollUntil.isResolved()).toEqual(true); done(); }); }); it('should throw an error if the execute function is not a function', (done) => { const pollUntil = new PollUntil(); try { pollUntil .execute(5); } catch (e: Error | any) { expect(e.message).toContain('executor is not a function.'); done(); } }); it('should convert a static function to a promise', (done) => { const pollUntil = new PollUntil(); pollUntil .execute(() => 5) .then((value) => { expect(value).toEqual(5); done(); }); }); it('should convert a static function that sometimes return undefined to a promise', (done) => { const pollUntil = new PollUntil(); let counter = 0; pollUntil .tryEvery(2) .stopAfter(10) .execute(() => { if (counter > 0) { return 5; } counter += 1; return false; }) .then((value) => { expect(value).toEqual(5); done(); }); }); it('wait for within wait for should throw a single error', async () => { const options1 = { ...options, message: 'waiting for something', }; const options2 = { ...options, message: 'waiting for another thing', }; try { await waitFor(() => waitFor(async () => { function alon() { throw new Error('some error message'); } alon(); }, options2), options1); } catch (e: Error | any) { expect(e.message).toMatch(/Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something\nFailed to wait after \d+ms \(total of \d+ attempts\): waiting for another thing/); expect(e.stack).toMatch(/alon/); } }); it('wait for should show the user message on failure', async () => { options.message = 'waiting for something'; let error: Error | any = null; try { await waitFor(async () => { throw new Error('some error message'); }, options); } catch (e) { error = e; } expect(error?.message).toMatch(/^Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something\nsome error message$/); }); it('wait for should save the original stacktrace', async () => { options.message = 'waiting for something'; let error: Error | any = null; async function customFunction() { await waitFor(() => false, options); } try { await customFunction(); } catch (e) { error = e; } expect(error?.message).toMatch(/^Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something$/); expect(error?.stack).toMatch(/customFunction/); }); it('should show stack if thrown inside a function', async () => { let counter = 100; let error: Error | any = null; function functionA() { return waitFor(async () => { if (counter !== 0) { counter -= 1; throw new Error('try again'); } }, { timeout: 20, interval: 2, verbose: true }); } async function functionB() { await functionA(); } try { await functionB(); } catch (e) { error = e; } expect(error?.message).toMatch(/try again/); expect(error?.stack).toMatch(/functionA/); expect(error?.stack).toMatch(/functionB/); }); it('should backoff if factor defined', async () => { const baseInterval = 100; const backoffFactor = 2; shouldHaltPromiseResolve = true; tryingAttemptsRemaining = 1; const pollUntil = new PollUntil({ backoffFactor }); const mockPromise = jest.fn(() => someRandPromise(0)); return pollUntil .tryEvery(baseInterval) .execute(mockPromise) .then(() => { expect(pollUntil._interval).toEqual(baseInterval * backoffFactor); }); }); it('should respect max interval during backoff if defined', async () => { const baseInterval = 10; const backoffFactor = 2; const backoffMaxInterval = 24; shouldHaltPromiseResolve = true; tryingAttemptsRemaining = 4; const pollUntil = new PollUntil({ backoffFactor, backoffMaxInterval }); const mockPromise = jest.fn(() => someRandPromise(0)); return pollUntil .tryEvery(baseInterval) .execute(mockPromise) .then(() => { expect(pollUntil._interval).toEqual(backoffMaxInterval); }); }); it('wait for should retry in sync function that throws errors', async () => { let counter = 0; let error: Error | any = null; try { await waitFor(() => { counter += 1; throw new Error('some error message'); }, options); } catch (e) { error = e; } expect(counter).toBeGreaterThan(1); expect(error).not.toBeNull(); }); it('should fail if max attempts exceeded', async () => { const pollUntil = new PollUntil({ maxAttempts: 3, interval: 1 }); const error = new Error('whoops'); const mockPromise = jest.fn().mockRejectedValue(error); await expect(pollUntil.execute(mockPromise)).rejects.toThrow(/Operation unsuccessful after 3 attempts \(total of \d+ms\)\nwhoops/); }); it('should not fail if max attempts not exceeded', async () => { const pollUntil = new PollUntil({ maxAttempts: 3, interval: 1 }); const error = new Error('whoops'); const mockPromise = jest.fn() .mockRejectedValueOnce(error) .mockRejectedValueOnce(error) .mockResolvedValue({ fake: 'result' }); expect(await pollUntil.execute(mockPromise)).toEqual({ fake: 'result' }); }); });