tests/insist.test.ts   A
last analyzed

Complexity

Total Complexity 16
Complexity/F 0

Size

Lines of Code 236
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 16
eloc 174
mnd 16
bc 16
fnc 0
dl 0
loc 236
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import { expect } from 'chai'
2
import { CustomError } from 'ts-custom-error'
3
4
import { InsistPolicy, insist } from '../src/insist'
5
import { mockSetTimeout, restoreSetTimeout } from './mocks'
6
7
// We'll use some Bluebird features for our tests
8
import Bluebird from 'bluebird'
9
10
// Custom Errors
11
class RecoverableError extends CustomError {}
12
class ProblematicError extends CustomError {}
13
class WaitForItError extends CustomError {
14
  constructor(message: string, public retryAfter: number) {
15
    super(message)
16
  }
17
}
18
19
describe('When the passed function does not fail', function() {
20
  it('`insist` behaves as the passed regular function', async function() {
21
    const testFn = () => 42
22
    const result = await insist(testFn)
23
    expect(result).to.equal(42)
24
  })
25
26
  it('`insist` behaves as the passed async function', async function() {
27
    const testFn = async () => 42
28
    const result = await insist(testFn)
29
    expect(result).to.equal(42)
30
  })
31
})
32
33
describe('When the passed function does fail', function() {
34
  it("`insist` retries for fn failures, and succeeds if fn doesn't fail too many times", async function() {
35
    let failuresCounter = 0
36
    const testFn = async () => {
37
      if (failuresCounter < 3) {
38
        failuresCounter += 1
39
        throw new Error('Fake Error')
40
      }
41
      return 42
42
    }
43
    const result = await insist(testFn, {
44
      // These two parameters are needed to make the tests fast
45
      retryInterval: 0,
46
      intervalIncrements: 0,
47
48
      maxRetries: 3,
49
      insistPolicy: InsistPolicy.RETRY,
50
    })
51
    expect(result).to.equal(42)
52
  })
53
54
  it('`insist` raises if the number of retries exceeds the configured threshold', async function() {
55
    let failuresCounter = 0
56
    const testFn = async () => {
57
      if (failuresCounter < 5) {
58
        failuresCounter += 1
59
        throw new Error('Fake Error')
60
      }
61
      return 42
62
    }
63
    try {
64
      await insist(testFn, {
65
        // These two parameters are needed to make the tests fast
66
        retryInterval: 0,
67
        intervalIncrements: 0,
68
69
        maxRetries: 3,
70
        insistPolicy: InsistPolicy.RETRY,
71
      })
72
      expect.fail('The code should not reach this point')
73
    } catch (e) {
74
      expect(e).to.be.instanceOf(Error)
75
      expect(e.message).to.equal('Fake Error')
76
    }
77
  })
78
79
  it('`insist` raises if the policy is THROW and the error is not in alwaysRetryOn', async function() {
80
    let failureToggle = false
81
    const testFn = async () => {
82
      if (!failureToggle) {
83
        failureToggle = true
84
        throw new ProblematicError('Problematic Error')
85
      }
86
      return 42
87
    }
88
    try {
89
      await insist(testFn, {
90
        alwaysRetryOn: [RecoverableError],
91
        insistPolicy: InsistPolicy.THROW,
92
      })
93
      expect.fail('The code should not reach this point')
94
    } catch (e) {
95
      expect(e).to.be.instanceOf(ProblematicError)
96
      expect(e.message).to.equal('Problematic Error')
97
    }
98
  })
99
100
  it('`insist` retries & succeeds if the policy is THROW & error is in alwaysRetryOn', async function() {
101
    let failureToggle = false
102
    const testFn = async () => {
103
      if (!failureToggle) {
104
        failureToggle = true
105
        throw new RecoverableError('Recoverable Error')
106
      }
107
      return 42
108
    }
109
110
    const result = await insist(testFn, {
111
      // These two parameters are needed to make the tests fast
112
      retryInterval: 0,
113
      intervalIncrements: 0,
114
115
      alwaysRetryOn: [RecoverableError],
116
      insistPolicy: InsistPolicy.THROW,
117
    })
118
    expect(result).to.be.equal(42)
119
  })
120
121
  it('`insist` does not retry, and throws, if error is in alwaysThrowOn', async function() {
122
    let failureToggle = false
123
    const testFn = async () => {
124
      if (!failureToggle) {
125
        failureToggle = true
126
        throw new ProblematicError('Problematic Error')
127
      }
128
      return 42
129
    }
130
    try {
131
      await insist(testFn, {
132
        alwaysThrowOn: [ProblematicError],
133
        insistPolicy: InsistPolicy.RETRY,
134
      })
135
      expect.fail('The code should not reach this point')
136
    } catch (e) {
137
      expect(e).to.be.instanceOf(ProblematicError)
138
      expect(e.message).to.equal('Problematic Error')
139
    }
140
  })
141
142
  it('`insist` retries if errorClassifiers tells to do so', async function() {
143
    let failureToggle = false
144
    const testFn = async () => {
145
      if (!failureToggle) {
146
        failureToggle = true
147
        throw new RecoverableError('Recoverable Error')
148
      }
149
      return 42
150
    }
151
152
    const result = await insist(testFn, {
153
      // These two parameters are needed to make the tests fast
154
      retryInterval: 0,
155
      intervalIncrements: 0,
156
157
      insistPolicy: InsistPolicy.THROW,
158
159
      // We use errorClassifier instead of relying on alwaysRetryOn
160
      errorClassifier: (e: Error) => {
161
        return e instanceof RecoverableError ? InsistPolicy.RETRY : null
162
      },
163
    })
164
    expect(result).to.be.equal(42)
165
  })
166
167
  it('`insist` does not retry, and throws, if errorClassifiers tells to do so', async function() {
168
    let failureToggle = false
169
    const testFn = async () => {
170
      if (!failureToggle) {
171
        failureToggle = true
172
        throw new ProblematicError('Problematic Error')
173
      }
174
      return 42
175
    }
176
    try {
177
      await insist(testFn, {
178
        insistPolicy: InsistPolicy.RETRY,
179
180
        // We use errorClassifier instead of relying on alwaysThrowOn
181
        errorClassifier: (e: Error) => {
182
          return e instanceof ProblematicError ? InsistPolicy.THROW : null
183
        },
184
      })
185
      expect.fail('The code should not reach this point')
186
    } catch (e) {
187
      expect(e).to.be.instanceOf(ProblematicError)
188
      expect(e.message).to.equal('Problematic Error')
189
    }
190
  })
191
192
  it('`insist` is able to set the time between retries dynamically, based on the thrown error', async function() {
193
    const MAGIC_TIME_WAIT = 5432
194
    let failureToggle = false
195
    const testFn = async () => {
196
      if (!failureToggle) {
197
        failureToggle = true
198
        throw new WaitForItError('Recoverable Error', MAGIC_TIME_WAIT)
199
      }
200
      return 42
201
    }
202
203
    const timeMockState = mockSetTimeout(MAGIC_TIME_WAIT)
204
    const resultPromise = insist(testFn, {
205
      // These two parameters are needed to make the tests fast
206
      retryInterval: 0,
207
      intervalIncrements: 0,
208
209
      insistPolicy: InsistPolicy.THROW,
210
211
      // We use errorClassifier instead of relying on alwaysRetryOn
212
      errorClassifier: (e: Error, setRetryAfter) => {
213
        if (e instanceof WaitForItError) {
214
          setRetryAfter(e.retryAfter)
215
          return InsistPolicy.RETRY
216
        }
217
        return null
218
      },
219
    })
220
221
    expect((resultPromise as Bluebird<number>).isFulfilled()).to.be.false
222
223
    // Our dirty way to "advance time"
224
    while (timeMockState.numTimeoutCalls < 1) {
225
      await (async () => null)()
226
    }
227
    timeMockState.pendingCallback(...timeMockState.pendingCallbackArgs)
228
229
    restoreSetTimeout()
230
231
    expect(await resultPromise).to.be.equal(42)
232
    expect(timeMockState.passedTimeout).to.equal(MAGIC_TIME_WAIT) // We check that insist "waited" for 5.432s
233
    expect(timeMockState.numTimeoutCalls).to.equal(1) // And that we only called setTimeOut once
234
  })
235
})
236