-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathpoller.test.ts
224 lines (173 loc) · 6.11 KB
/
poller.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import Poller from '../poller'
import type { FetchFlagsResult, Options } from '../types'
import { getRandom } from '../utils'
import { Event } from '../types'
import type { Emitter } from 'mitt'
jest.useFakeTimers()
jest.mock('../utils.ts', () => ({
getRandom: jest.fn()
}))
const mockEventBus: Emitter = {
emit: jest.fn(),
on: jest.fn(),
off: jest.fn(),
all: new Map()
}
interface PollerArgs {
fetchFlags: jest.MockedFunction<() => Promise<FetchFlagsResult>>
eventBus: typeof mockEventBus
configurations: Partial<Options>
}
interface TestArgs {
delayFunction: jest.MockedFunction<() => number>
logSpy: jest.SpyInstance
mockError: Error
delayMs: number
}
const logError = jest.fn()
const logDebug = jest.fn()
let currentPoller: Poller
const getPoller = (overrides: Partial<PollerArgs> = {}): Poller => {
const args: PollerArgs = {
fetchFlags: jest.fn(),
configurations: {},
eventBus: mockEventBus,
...overrides
}
currentPoller = new Poller(args.fetchFlags, args.configurations, args.eventBus, logDebug, logError)
return currentPoller
}
const getTestArgs = (overrides: Partial<TestArgs> = {}): TestArgs => {
const { delayMs = 5000 } = overrides // Extract delayMs or set a default of 5000
return {
delayMs,
delayFunction: (getRandom as jest.Mock).mockReturnValue(delayMs),
logSpy: logDebug,
mockError: new Error('Fetch Error'),
...overrides
}
}
describe('Poller', () => {
afterEach(() => {
currentPoller.stop()
jest.clearAllMocks()
})
it('should not start polling if it is already polling', () => {
getPoller({ configurations: { debug: true } })
const testArgs = getTestArgs()
currentPoller.start()
currentPoller.start()
expect(mockEventBus.emit).toHaveBeenNthCalledWith(1, Event.POLLING)
expect(testArgs.logSpy).toHaveBeenCalledTimes(2)
expect(mockEventBus.emit).toHaveBeenCalled()
})
it('should retry fetching if there is an error', async () => {
const testArgs = getTestArgs()
let attemptCount = 0
const fetchFlagsMock: jest.Mock<Promise<FetchFlagsResult>> = jest
.fn()
.mockImplementation((): Promise<FetchFlagsResult> => {
attemptCount++
return Promise.resolve(
attemptCount === 2
? {
type: 'success',
data: [{ flag: 'flag1', kind: 'boolean', value: true, identifier: 'true' }]
}
: ({ type: 'error', error: testArgs.mockError } as FetchFlagsResult)
)
})
const pollInterval = 60000
getPoller({
fetchFlags: fetchFlagsMock,
configurations: { pollingInterval: pollInterval, debug: true }
})
currentPoller.start()
jest.advanceTimersByTime(pollInterval)
// Allow first attempt to resolve
await Promise.resolve()
// Advance past the first delay
jest.advanceTimersByTime(testArgs.delayMs)
// Allow successful attempt to resolve
await Promise.resolve()
expect(fetchFlagsMock).toHaveBeenCalledTimes(2)
expect(testArgs.logSpy).toHaveBeenCalledTimes(2)
})
it('should not retry after max attempts are exceeded', async () => {
const maxAttempts = 5
let attemptCount = 0
const fetchFlagsMock: jest.Mock<Promise<FetchFlagsResult>> = jest
.fn()
.mockImplementation((): Promise<FetchFlagsResult> => {
attemptCount++
return Promise.resolve(
attemptCount === maxAttempts
? {
type: 'success',
data: [{ flag: 'flag1', kind: 'boolean', value: true, identifier: 'true' }]
}
: ({ type: 'error', error: testArgs.mockError } as FetchFlagsResult)
)
})
const pollInterval = 60000
getPoller({
fetchFlags: fetchFlagsMock,
configurations: { pollingInterval: pollInterval, debug: true }
})
const testArgs = getTestArgs()
currentPoller.start()
jest.advanceTimersByTime(pollInterval)
for (let i = 0; i < maxAttempts; i++) {
jest.advanceTimersByTime(testArgs.delayMs)
// We need to wait for the fetchFlags promise and the timeout promise to resolve
await Promise.resolve()
await Promise.resolve()
}
expect(fetchFlagsMock).toHaveBeenCalledTimes(5)
expect(testArgs.logSpy).toHaveBeenCalledTimes(6)
})
it('should successfully fetch flags without retrying on success', async () => {
const pollInterval = 60000
const fetchFlagsMock: jest.Mock<Promise<FetchFlagsResult>> = jest
.fn()
.mockImplementation((): Promise<FetchFlagsResult> => {
return Promise.resolve({
type: 'success',
data: [{ flag: 'flag1', kind: 'boolean', value: true, identifier: 'true' }]
})
})
getPoller({
fetchFlags: fetchFlagsMock,
configurations: { pollingInterval: pollInterval, debug: true }
})
const testArgs = getTestArgs()
currentPoller.start()
jest.advanceTimersByTime(pollInterval)
await Promise.resolve()
expect(fetchFlagsMock).toHaveBeenCalledTimes(1)
expect(testArgs.logSpy).toHaveBeenCalledTimes(2)
})
it('should stop polling when stop is called', () => {
const pollInterval = 60000
const fetchFlagsMock = jest.fn().mockResolvedValue(null)
getPoller({
fetchFlags: fetchFlagsMock,
configurations: { pollingInterval: pollInterval, debug: true }
})
// Use this just to mock the calls to console.debug
getTestArgs()
// Start the poller
currentPoller.start()
expect(mockEventBus.emit).toHaveBeenCalledWith(Event.POLLING)
// At this point, the poller will be scheduled to run after the pollingInterval.
// Before advancing the timers, we stop the poller.
currentPoller.stop()
// Now we'll check that eventBus.emit was called with POLLING_STOPPED.
expect(mockEventBus.emit).toHaveBeenCalledWith(Event.POLLING_STOPPED)
// Ensure that fetchFlags isn't called after the poller has been stopped
expect(fetchFlagsMock).not.toHaveBeenCalled()
// As a final check, advance timers to ensure that the poller doesn't poll after an elapsed interval.
jest.advanceTimersByTime(pollInterval)
expect(fetchFlagsMock).not.toHaveBeenCalled()
})
})