update dependency, fixes for logger update
[websub-hub] / test / src / worker.js
1 /* eslint-env mocha */
2 'use strict';
3
4 const assert = require('assert');
5 const sinon = require('sinon'); // eslint-disable-line node/no-unpublished-require
6
7 const Worker = require('../../src/worker');
8 const Config = require('../../config');
9
10 const stubLogger = require('../stub-logger');
11 const stubDb = require('../stub-db');
12
13 const noExpectedException = 'did not get expected exception';
14
15 describe('Worker', function () {
16 let config;
17 let worker;
18 let promiseGiver;
19
20 beforeEach(function () {
21 config = new Config('test');
22 promiseGiver = sinon.stub();
23 worker = new Worker(stubLogger, stubDb, promiseGiver, config);
24 stubDb._reset();
25 });
26
27 afterEach(function () {
28 sinon.restore();
29 });
30
31 describe('constructor', function () {
32 it('instantiates', function () {
33 assert(worker);
34 });
35
36 it('requires a promiseGiver function', function () {
37 try {
38 worker = new Worker(stubLogger, stubDb, undefined, config);
39 assert.fail('should require function argument');
40 } catch (e) {
41 assert(e instanceof TypeError);
42 }
43 });
44 }); // constructor
45
46 describe('start', function () {
47 it('starts without polling', function () {
48 config.worker.pollingEnabled = false;
49 worker = new Worker(stubLogger, stubDb, promiseGiver, config);
50 worker.start();
51 assert.strictEqual(worker.running, false);
52 });
53 it('starts with polling', function () {
54 config.worker.pollingEnabled = true;
55 worker = new Worker(stubLogger, stubDb, promiseGiver, config);
56 sinon.stub(worker, '_recurr');
57 worker.start();
58 clearTimeout(worker.nextTimeout);
59 assert.strictEqual(worker.running, true);
60 });
61 }); // start
62
63 describe('stop', function () {
64 it('stops', function () {
65 worker = new Worker(stubLogger, stubDb, promiseGiver, config);
66 worker.start();
67 worker.stop();
68 assert.strictEqual(worker.running, false);
69 assert.strictEqual(worker.nextTimeout, undefined);
70 });
71 }); // stop
72
73 describe('watchedPromise', function () {
74 let promise;
75 it('watches a resolvable promise', async function () {
76 const res = 'yay';
77 promise = Promise.resolve(res);
78 const watched = Worker.watchedPromise(promise);
79 const result = await watched;
80 assert.strictEqual(result, res);
81 assert.strictEqual(watched.resolved, res);
82 assert(watched.isSettled);
83 });
84 it('watches a rejectable promise', async function () {
85 const rej = new Error('boo');
86 promise = Promise.reject(rej);
87 const watched = Worker.watchedPromise(promise);
88 try {
89 await watched;
90 assert.fail(noExpectedException);
91 } catch (e) {
92 assert.deepStrictEqual(e, rej);
93 assert.deepStrictEqual(watched.rejected, rej);
94 assert(watched.isSettled);
95 }
96 });
97 it('covers wrapped promise', async function () {
98 const res = 'yay';
99 promise = Promise.resolve(res);
100 const watched = Worker.watchedPromise(promise);
101 const rewatched = Worker.watchedPromise(watched);
102 const result = await rewatched;
103 assert.strictEqual(result, res);
104 assert.strictEqual(rewatched.resolved, res);
105 assert(rewatched.isSettled);
106 });
107 }); // watchedPromise
108
109 describe('_handleWatchedList', function () {
110 let handler;
111 beforeEach(function () {
112 handler = sinon.stub();
113 });
114 it('handled resolveds', function () {
115 worker.inFlight = [
116 { isSettled: false, resolved: undefined, rejected: undefined },
117 { isSettled: true, resolved: 'value', rejected: undefined },
118 { isSettled: true, resolved: undefined, rejected: 'error' },
119 { isSettled: false, resolved: undefined, rejected: undefined },
120 ];
121 const result = worker._handleWatchedList(handler);
122 assert.strictEqual(result, 2);
123 assert.strictEqual(worker.inFlight.length, 2);
124 assert.strictEqual(handler.callCount, 2);
125 });
126 }); // _handleWatchedList
127
128 describe('_getWork', function () {
129 let stubCtx;
130 beforeEach(function () {
131 stubCtx = {};
132 });
133 it('gets tasks', async function () {
134 /**
135 * In older versions, could just deepStrictEqual un-awaited promises for equality,
136 * but post 14 or so, async_id symbol properties are included in comparison, and
137 * in some executions of test suites these are somehow different in value so the test
138 * was failing. So now we settle everything prior to comparison.
139 */
140 const expected = [
141 Promise.resolve('first'),
142 Promise.reject('bad'),
143 Promise.resolve('second'),
144 ];
145 worker.promiseGiver.resolves(expected);
146 const result = await worker._getWork(stubCtx);
147
148 const expectedResolved = await Promise.allSettled(expected);
149 const resultResolved = await Promise.allSettled(result);
150 assert.deepStrictEqual(resultResolved, expectedResolved);
151
152 assert.strictEqual(worker.inFlight.length, expected.length);
153 });
154 it('covers none wanted', async function () {
155 worker.concurrency = 3;
156 worker.inFlight = [
157 Promise.resolve('first'),
158 Promise.reject('bad'),
159 Promise.resolve('second'),
160 ];
161 const result = await worker._getWork(stubCtx);
162 assert(!worker.promiseGiver.called);
163 assert.deepStrictEqual(result, []);
164 });
165 }); // _getWork
166
167 describe('_watchedHandler', function () {
168 it('covers resolved', function () {
169 worker._watchedHandler('resolved', undefined);
170 });
171 it('covers rejected', function () {
172 worker._watchedHandler(undefined, 'rejected');
173 });
174 }); // _watchedHandler
175
176 describe('_recurr', function () {
177 it('covers', function (done) {
178 worker.recurrSleepMs = 10;
179 this.slow(worker.recurrSleepMs * 3);
180 sinon.stub(worker, 'process').callsFake(done);
181 worker.running = true;
182 worker._recurr();
183 });
184 it('covers not running', function () {
185 worker.running = false;
186 worker._recurr();
187 });
188 }); // _recurr
189
190 describe('process', function () {
191 beforeEach(function () {
192 sinon.stub(worker, '_getWork');
193 sinon.stub(worker, '_recurr');
194 });
195 it('covers', async function () {
196 worker.inFlight = [
197 Worker.watchedPromise(Promise.resolve('one')),
198 Worker.watchedPromise(Promise.reject('foo')),
199 ];
200 await worker.process();
201 assert.strictEqual(worker._getWork.callCount, 2);
202 assert.strictEqual(worker._recurr.callCount, 1);
203 });
204 it('covers no work', async function () {
205 await worker.process();
206 assert.strictEqual(worker._getWork.callCount, 1);
207 assert.strictEqual(worker._recurr.callCount, 1);
208 });
209 it('covers error', async function () {
210 const expected = new Error('blah');
211 stubDb.context.restore();
212 sinon.stub(stubDb, 'context').rejects(expected);
213 await worker.process();
214 assert.strictEqual(worker._getWork.callCount, 0);
215 assert.strictEqual(worker._recurr.callCount, 1);
216 });
217 it('covers double invocation', async function () {
218 const snooze = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
219
220 this.slow(300);
221 worker.inFlight = [
222 Worker.watchedPromise(snooze(100)),
223 ];
224
225 await Promise.all([worker.process(), worker.process()]);
226 assert.strictEqual(worker._getWork.callCount, 2);
227 assert.strictEqual(worker._recurr.callCount, 1);
228 });
229 }); // process
230
231 }); // Worker