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