UNPKG

7.45 kBPlain TextView Raw
1import {expect} from 'chai';
2import * as sinon from 'sinon';
3import {delayedPromise, retryPromise, RetryPromiseOptions, timeoutPromise} from '../src/promise-utils';
4
5const accuracyFactor = 0.9;
6describe('Promise utilities', () => {
7 describe('delayedPromise', () => {
8 it('resolves after provided the ms', async () => {
9 const startTime = Date.now(), delay = 50;
10
11 await delayedPromise(delay);
12
13 expect(Date.now(), 'verify delay').to.be.gte(startTime + (delay * accuracyFactor));
14 })
15 });
16
17 describe('timeoutPromise', () => {
18 it('resolves with original value if original promise resolves within time frame', async () => {
19 await expect(timeoutPromise(Promise.resolve('test'), 100)).to.eventually.become('test');
20 });
21
22 it('rejects with original value if original promise rejects within time frame', async () => {
23 await expect(timeoutPromise(Promise.reject('an error'), 100)).to.eventually.be.rejectedWith('an error');
24 });
25
26 it('rejects with a timeout message if time is up and original promise is pending', async () => {
27 await expect(timeoutPromise(delayedPromise(200), 50)).to.eventually.be.rejectedWith('timed out after 50ms');
28 });
29
30 it('allows providing a custom timeout message', async () => {
31 await expect(timeoutPromise(delayedPromise(200), 50, 'FAILED!')).to.eventually.be.rejectedWith('FAILED!');
32 });
33 });
34
35 describe('retryPromise', () => {
36 async function verifyCallCount(spy: sinon.SinonSpy, count: number, noExtraEventsGrace: number): Promise<void> {
37 expect(spy).to.have.callCount(count);
38 await delayedPromise(noExtraEventsGrace); // to catch unwanted calls to provider post fullfillment
39 expect(spy).to.have.callCount(count);
40 }
41
42 it('resolves if first run was a success', async () => {
43 const retryOptions: RetryPromiseOptions = {retries: 2, interval: 5};
44 const promiseProvider = sinon.stub().resolves('value');
45
46 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.become('value');
47 await verifyCallCount(promiseProvider, 1, retryOptions.interval + 1);
48 });
49
50 it('rejects if first run failed, and no retries', async () => {
51 const retryOptions: RetryPromiseOptions = {retries: 0, interval: 10};
52 const promiseProvider = sinon.stub().rejects(new Error('failed'));
53
54 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.rejectedWith('failed');
55 await verifyCallCount(promiseProvider, 1, retryOptions.interval + 1);
56 });
57
58 it('resolves if a success run was achieved during a retry', async () => {
59 const retryOptions: RetryPromiseOptions = {retries: 2, interval: 10};
60 const promiseProvider = sinon.stub()
61 .onFirstCall().rejects(new Error('first failure'))
62 .onSecondCall().rejects(new Error('second failure'))
63 .resolves('success');
64
65 const startTime = Date.now();
66 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.become('success');
67 expect(Date.now(), 'verify interval').to.be.gte(startTime + (retryOptions.interval * 2 * accuracyFactor));
68 await verifyCallCount(promiseProvider, 3, retryOptions.interval + 1);
69 });
70
71 it('rejects if all tries failed', async () => {
72 const retryOptions: RetryPromiseOptions = {retries: 5, interval: 5};
73 const promiseProvider = sinon.stub().rejects(new Error('failed'));
74
75 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.rejectedWith('failed');
76 await verifyCallCount(promiseProvider, 6, retryOptions.interval + 1);
77 });
78
79 it('rejects with error of last failed attempt', async () => {
80 const retryOptions: RetryPromiseOptions = {retries: 1, interval: 5};
81 const promiseProvider = sinon.stub()
82 .onFirstCall().rejects(new Error('first failure'))
83 .onSecondCall().rejects(new Error('second failure'))
84 .rejects(new Error('other failures'));
85
86 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.rejectedWith('second failure');
87 await verifyCallCount(promiseProvider, 2, retryOptions.interval + 1);
88
89 });
90
91 describe('when provided with a timeout', () => {
92 it('verifies timeout is greater than retries*interval', async () => {
93 const retryOptions: RetryPromiseOptions = {retries: 10, interval: 10, timeout: 90};
94 const promiseProvider = sinon.stub();
95
96 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually
97 .be.rejectedWith('timeout (90ms) must be greater than retries (10) times interval (10ms)');
98 await verifyCallCount(promiseProvider, 0, retryOptions.interval + 1);
99 });
100
101 it('resolves if a success run was achieved during timeout', async () => {
102 const retryOptions: RetryPromiseOptions = {retries: 1, interval: 5, timeout: 1500};
103 const promiseProvider = sinon.stub()
104 .onFirstCall().rejects(new Error('first failure'))
105 .resolves('success');
106
107 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.become('success');
108 await verifyCallCount(promiseProvider, 2, retryOptions.interval + 1);
109 });
110
111 it('rejects with error of last failed attempt if timeout expires', async () => {
112 const retryOptions: RetryPromiseOptions = {retries: 1, interval: 10, timeout: 400};
113 const promiseProvider = sinon.stub()
114 .onFirstCall().rejects(new Error('first failure'))
115 .onSecondCall().returns(delayedPromise(2000));
116
117 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.be.rejectedWith('first failure');
118 await verifyCallCount(promiseProvider, 2, retryOptions.interval + 1);
119
120 });
121
122 it('rejects with default timeout message, if no last failed attempt and timeout expires', async () => {
123 const retryOptions: RetryPromiseOptions = {retries: 0, interval: 20, timeout: 50};
124 const promiseProvider = sinon.stub().returns(delayedPromise(1000));
125
126 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.be.rejectedWith('timed out after 50ms');
127 await verifyCallCount(promiseProvider, 1, retryOptions.interval + 1);
128 });
129
130 it('rejects with provided timeout message, if no last failed attempt and timeout expires', async () => {
131 const retryOptions: RetryPromiseOptions = {
132 retries: 0,
133 interval: 20,
134 timeout: 50,
135 timeoutMessage: 'FAILED'
136 };
137 const promiseProvider = sinon.stub().returns(delayedPromise(1000));
138
139 await expect(retryPromise(promiseProvider, retryOptions)).to.eventually.be.rejectedWith('FAILED');
140 await verifyCallCount(promiseProvider, 1, retryOptions.interval + 1);
141 });
142 });
143 });
144});