update dependencies, fixes to support new authentication features
[websub-hub] / test / src / db / sqlite.js
1 'use strict';
2
3 /* This provides implementation coverage, stubbing parts of better-sqlite3. */
4
5 const assert = require('node:assert');
6 const sinon = require('sinon');
7 const DBStub = require('../../stub-db');
8 const stubLogger = require('../../stub-logger');
9 const DB = require('../../../src/db/sqlite');
10 const DBErrors = require('../../../src/db/errors');
11 const common = require('../../../src/common');
12 const Config = require('../../../config');
13
14 const noExpectedException = 'did not receive expected exception';
15
16 describe('DatabaseSQLite', function () {
17 let db, options;
18 let dbCtx, claimant, claimTimeoutSeconds, callback, subscriptionId, topicId, verificationId;
19 let topicUrl, leaseSeconds, secret, httpRemoteAddr, httpFrom, retryDelays, wanted;
20 before(function () {
21 options = new Config('test');
22 options.db.connectionString = 'sqlite://:memory:';
23 db = new DB(stubLogger, options);
24 });
25 beforeEach(function () {
26 stubLogger._reset();
27 dbCtx = db.db;
28 claimant = '19af19b8-6be3-4a6f-8946-65f5f1ccc5d7';
29 claimTimeoutSeconds = 300;
30 subscriptionId = 'fbaf8f19-ed9c-4a21-89ae-98b7005e3bf6';
31 topicUrl = 'https://example.com/blog';
32 callback = 'https://example.com/callback?id=123';
33 topicId = 'c59d4bda-10ad-41d9-99df-4ce8bc331424';
34 verificationId = '55cd7748-d2d5-11eb-b355-0025905f714a';
35 retryDelays = [60];
36 leaseSeconds = 86400;
37 secret = 'secret';
38 httpRemoteAddr = '127.0.0.1';
39 httpFrom = 'user@example.com';
40 wanted = 5;
41 });
42 afterEach(function () {
43 sinon.restore();
44 });
45
46 it('covers options', function () {
47 const xoptions = new Config('test');
48 delete xoptions.db.connectionString;
49 db = new DB(stubLogger, xoptions);
50 });
51
52 // Ensure all interface methods are implemented
53 describe('Implementation', function () {
54 it('implements interface', async function () {
55 const results = await Promise.allSettled(DBStub._implementation.map(async (fn) => {
56 try {
57 // eslint-disable-next-line security/detect-object-injection
58 await db[fn](db.db);
59 } catch (e) {
60 assert(!(e instanceof DBErrors.NotImplemented), `${fn} not implemented`);
61 }
62 }));
63 const failures = results.filter((x) => x.status === 'rejected');
64 assert(!failures.length, failures.map((x) => {
65 x = x.reason.toString();
66 return x.slice(x.indexOf(': '));
67 }));
68 });
69 }); // Implementation
70
71 describe('_initTables', function () {
72 let preparedGet;
73 beforeEach(function () {
74 preparedGet = sinon.stub();
75 sinon.stub(db.db, 'prepare').returns({
76 pluck: () => ({
77 bind: () => ({
78 get: preparedGet,
79 }),
80 }),
81 });
82 sinon.stub(db, '_currentSchema').returns(db.schemaVersionsSupported.min);
83 sinon.stub(db.db, 'exec');
84 });
85 it('covers migration', async function() {
86 preparedGet.returns({});
87 await db._initTables();
88 assert(db.db.exec.called);
89 });
90 it('covers migration failure', async function() {
91 const expected = new Error('oh no');
92 preparedGet.returns({});
93 db.db.exec.throws(expected);
94 try {
95 await db._initTables();
96 assert.fail(noExpectedException);
97 } catch (e) {
98 assert.deepStrictEqual(e, expected);
99 }
100 });
101 }); // _initTables
102
103 describe('_currentSchema', function () {
104 it('covers', async function () {
105 const version = { major: 1, minor: 0, patch: 0 };
106 sinon.stub(db.db, 'prepare').returns({
107 get: () => version,
108 });
109 const result = await db._currentSchema();
110 assert.deepStrictEqual(result, version);
111 });
112 }); // _currentSchema
113
114 describe('_closeConnection', function () {
115 it('success', async function () {
116 sinon.stub(db.db, 'close');
117 await db._closeConnection();
118 assert(db.db.close.called);
119 });
120 it('failure', async function () {
121 const expected = new Error();
122 sinon.stub(db.db, 'close').throws(expected);
123 try {
124 await db._closeConnection();
125 assert.fail(noExpectedException);
126 } catch (e) {
127 assert.deepStrictEqual(e, expected);
128 }
129 });
130 }); // _closeConnection
131
132 describe('_purgeTables', function () {
133 beforeEach(function () {
134 sinon.stub(db.db, 'prepare').returns({
135 run: sinon.stub(),
136 });
137 });
138 it('covers not really', async function () {
139 await db._purgeTables(false);
140 assert(!db.db.prepare.called);
141 });
142 it('success', async function () {
143 await db._purgeTables(true);
144 assert(db.db.prepare.called);
145 });
146 it('failure', async function () {
147 const expected = new Error();
148 db.db.prepare.restore();
149 sinon.stub(db.db, 'prepare').throws(expected);
150 try {
151 await db._purgeTables(true);
152 assert.fail(noExpectedException);
153 } catch (e) {
154 assert.deepStrictEqual(e, expected);
155 }
156 });
157 }); // _purgeTables
158
159 describe('_optimize', function () {
160 let origOAC;
161 beforeEach(function () {
162 origOAC = db.optimizeAfterChanges;
163 sinon.stub(db.statement._optimize, 'all');
164 sinon.stub(db.db, 'pragma');
165 });
166 this.afterEach(function () {
167 db.optimizeAfterChanges = origOAC;
168 });
169 it('covers', async function () {
170 db.optimizeAfterChanges = 10;
171 db.changesSinceLastOptimize = BigInt(20);
172 await db._optimize();
173 assert(db.db.pragma.called);
174 });
175 it('covers none', async function () {
176 db.optimizeAfterChanges = 0;
177 await db._optimize();
178 assert(!db.db.pragma.called);
179 });
180 it('covers not enough changes', async function () {
181 db.optimizeAfterChanges = 10;
182 db.changesSinceLastOptimize = BigInt(5);
183 await db._optimize();
184 assert(!db.db.pragma.called);
185 });
186 }); // _optimize
187
188 describe('_deOphidiate', function () {
189 it('covers non-array', function () {
190 const obj = {
191 'snake_case': 1,
192 };
193 const expected = {
194 snakeCase: 1,
195 };
196 const result = DB._deOphidiate(obj);
197 assert.deepStrictEqual(result, expected);
198 });
199 it('covers array', function () {
200 const rows = [
201 {
202 'snek_field': 'foo',
203 },
204 {
205 'snek_field': 'bar',
206 },
207 ];
208 const expected = [
209 {
210 snekField: 'foo',
211 },
212 {
213 snekField: 'bar',
214 },
215 ];
216 const result = DB._deOphidiate(rows);
217 assert.deepStrictEqual(result, expected);
218 });
219 }); // _deOphidiate
220
221 describe('_topicDataToNative', function () {
222 it('covers', function () {
223 const now = new Date();
224 const nowEpoch = now.getTime() / 1000;
225 const topic = {
226 isActive: 1,
227 isDeleted: 0,
228 created: nowEpoch,
229 lastPublish: nowEpoch,
230 contentFetchNextAttempt: nowEpoch,
231 contentUpdated: nowEpoch,
232 url: 'https://example.com/',
233 };
234 const expected = {
235 isActive: true,
236 isDeleted: false,
237 created: now,
238 lastPublish: now,
239 contentFetchNextAttempt: now,
240 contentUpdated: now,
241 url: topic.url,
242 };
243 const result = DB._topicDataToNative(topic);
244 assert.deepStrictEqual(result, expected);
245 });
246 it('covers empty', function () {
247 const topic = undefined;
248 const result = DB._topicDataToNative(topic);
249 assert.deepStrictEqual(result, topic);
250 });
251 }); // _topicDataToNative
252
253 describe('healthCheck', function () {
254 let origDb;
255 beforeEach(function () {
256 origDb = db.db;
257 });
258 afterEach(function () {
259 db.db = origDb;
260 });
261 it('covers', function () {
262 db.healthCheck();
263 });
264 it('covers failure', function () {
265 db.db = { open: false };
266 try {
267 db.healthCheck();
268 assert.fail(noExpectedException);
269 } catch (e) {
270 assert(e instanceof DBErrors.UnexpectedResult);
271 }
272 });
273 }); // healthCheck
274
275 describe('context', function () {
276 it('covers', async function () {
277 await db.context(common.nop);
278 });
279 }); // context
280
281 describe('transaction', function () {
282 it('covers', async function () {
283 await db.transaction(db.db, common.nop);
284 });
285 it('covers no context', async function () {
286 await db.transaction(undefined, common.nop);
287 });
288 }); // transaction
289
290 describe('authenticationSuccess', function () {
291 let identifier;
292 beforeEach(function () {
293 identifier = 'username';
294 });
295 it('success', async function() {
296 const dbResult = {
297 changes: 1,
298 lastInsertRowid: undefined,
299 };
300 sinon.stub(db.statement.authenticationSuccess, 'run').returns(dbResult);
301 await db.authenticationSuccess(dbCtx, identifier);
302 });
303 it('failure', async function () {
304 const dbResult = {
305 changes: 0,
306 lastInsertRowid: undefined,
307 };
308 sinon.stub(db.statement.authenticationSuccess, 'run').returns(dbResult);
309 try {
310 await db.authenticationSuccess(dbCtx, identifier);
311 assert.fail(noExpectedException);
312 } catch (e) {
313 assert(e instanceof DBErrors.UnexpectedResult);
314 }
315 });
316 }); // authenticationSuccess
317
318 describe('authenticationGet', function () {
319 let identifier, credential;
320 beforeEach(function () {
321 identifier = 'username';
322 credential = '$z$foo';
323 });
324 it('success', async function() {
325 const expected = {
326 identifier,
327 credential,
328 };
329 sinon.stub(db.statement.authenticationGet, 'get').returns(expected);
330 const result = await db.authenticationGet(dbCtx, identifier);
331 assert.deepStrictEqual(result, expected);
332 });
333 it('failure', async function () {
334 const expected = new Error();
335 sinon.stub(db.statement.authenticationGet, 'get').throws(expected);
336 try {
337 await db.authenticationGet(dbCtx, identifier);
338 assert.fail(noExpectedException);
339 } catch (e) {
340 assert.deepStrictEqual(e, expected);
341 }
342 });
343 }); // authenticationGet
344
345 describe('authenticationUpsert', function () {
346 let identifier, credential, otpKey;
347 beforeEach(function () {
348 identifier = 'username';
349 credential = '$z$foo';
350 otpKey = '12345678901234567890123456789012';
351 });
352 it('success', async function() {
353 const dbResult = {
354 changes: 1,
355 lastInsertRowid: undefined,
356 };
357 sinon.stub(db.statement.authenticationUpsert, 'run').returns(dbResult);
358 await db.authenticationUpsert(dbCtx, identifier, credential, otpKey);
359 });
360 it('failure', async function () {
361 const dbResult = {
362 changes: 0,
363 lastInsertRowid: undefined,
364 };
365 sinon.stub(db.statement.authenticationUpsert, 'run').returns(dbResult);
366 try {
367 await db.authenticationUpsert(dbCtx, identifier, credential, otpKey);
368 assert.fail(noExpectedException);
369 } catch (e) {
370 assert(e instanceof DBErrors.UnexpectedResult);
371 }
372 });
373 }); // authenticationUpsert
374
375 describe('authenticationUpdateCredential', function () {
376 let identifier, credential;
377 beforeEach(function () {
378 identifier = 'username';
379 credential = '$z$foo';
380 });
381 it('success', async function() {
382 const dbResult = {
383 changes: 1,
384 lastInsertRowid: undefined,
385 };
386 sinon.stub(db.statement.authenticationUpdateCredential, 'run').returns(dbResult);
387 await db.authenticationUpdateCredential(dbCtx, identifier, credential);
388 });
389 it('failure', async function () {
390 const dbResult = {
391 changes: 0,
392 lastInsertRowid: undefined,
393 };
394 sinon.stub(db.statement.authenticationUpdateCredential, 'run').returns(dbResult);
395 try {
396 await db.authenticationUpdateCredential(dbCtx, identifier, credential);
397 assert.fail(noExpectedException);
398 } catch (e) {
399 assert(e instanceof DBErrors.UnexpectedResult);
400 }
401 });
402 }); // authenticationUpdateCredential
403
404 describe('authenticationUpdateOTPKey', function () {
405 let identifier, otpKey;
406 beforeEach(function () {
407 identifier = 'username';
408 otpKey = '12345678901234567890123456789012';
409 });
410 it('success', async function() {
411 const dbResult = {
412 changes: 1,
413 lastInsertRowid: undefined,
414 };
415 sinon.stub(db.statement.authenticationUpdateOtpKey, 'run').returns(dbResult);
416 await db.authenticationUpdateOTPKey(dbCtx, identifier, otpKey);
417 });
418 it('failure', async function () {
419 const dbResult = {
420 changes: 0,
421 lastInsertRowid: undefined,
422 };
423 sinon.stub(db.statement.authenticationUpdateOtpKey, 'run').returns(dbResult);
424 try {
425 await db.authenticationUpdateOTPKey(dbCtx, identifier, otpKey);
426 assert.fail(noExpectedException);
427 } catch (e) {
428 assert(e instanceof DBErrors.UnexpectedResult);
429 }
430 });
431 }); // authenticationUpsert
432
433 describe('subscriptionsByTopicId', function () {
434 it('success', async function () {
435 const expected = [{ id: 3 }];
436 sinon.stub(db.statement.subscriptionsByTopicId, 'all').returns(expected);
437 const result = await db.subscriptionsByTopicId(dbCtx, topicUrl);
438 assert.deepStrictEqual(result, expected);
439 });
440 it('failure', async function () {
441 const expected = new Error();
442 sinon.stub(db.statement.subscriptionsByTopicId, 'all').throws(expected);
443 try {
444 await db.subscriptionsByTopicId(dbCtx, topicUrl);
445 assert.fail(noExpectedException);
446 } catch (e) {
447 assert.deepStrictEqual(e, expected);
448 }
449 });
450 }); // subscriptionsByTopicId
451
452 describe('subscriptionCountByTopicUrl', function () {
453 it('success', async function () {
454 const expected = { count: 3 };
455 sinon.stub(db.statement.subscriptionCountByTopicUrl, 'get').returns(expected);
456 const result = await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
457 assert.deepStrictEqual(result, expected);
458 });
459 it('failure', async function () {
460 const expected = new Error();
461 sinon.stub(db.statement.subscriptionCountByTopicUrl, 'get').throws(expected);
462 try {
463 await db.subscriptionCountByTopicUrl(dbCtx, topicUrl);
464 assert.fail(noExpectedException);
465 } catch (e) {
466 assert.deepStrictEqual(e, expected);
467 }
468 });
469 }); // subscriptionCountByTopicUrl
470
471 describe('subscriptionDelete', function () {
472 it('success', async function() {
473 const dbResult = {
474 changes: 1,
475 lastInsertRowid: undefined,
476 };
477 const expected = {
478 changes: 1,
479 lastInsertRowid: undefined,
480 };
481 sinon.stub(db.statement.subscriptionDelete, 'run').returns(dbResult);
482 const result = await db.subscriptionDelete(dbCtx, callback, topicId);
483 assert.deepStrictEqual(result, expected);
484 });
485 it('failure', async function () {
486 const dbResult = {
487 changes: 0,
488 lastInsertRowid: undefined,
489 };
490 sinon.stub(db.statement.subscriptionDelete, 'run').returns(dbResult);
491 try {
492 await db.subscriptionDelete(dbCtx, callback, topicId);
493 assert.fail(noExpectedException);
494 } catch (e) {
495 assert(e instanceof DBErrors.UnexpectedResult);
496 }
497 });
498 }); // subscriptionDelete
499
500 describe('subscriptionDeleteExpired', function () {
501 it('success', async function () {
502 const dbResult = {
503 changes: 1,
504 lastInsertRowid: undefined,
505 };
506 const expected = {
507 changes: 1,
508 lastInsertRowid: undefined,
509 };
510 sinon.stub(db.statement.subscriptionDeleteExpired, 'run').returns(dbResult);
511 const result = await db.subscriptionDeleteExpired(dbCtx, topicId);
512 assert.deepStrictEqual(result, expected);
513 });
514 it('failure', async function () {
515 const expected = new Error();
516 sinon.stub(db.statement.subscriptionDeleteExpired, 'run').throws(expected);
517 try {
518 await db.subscriptionDeleteExpired(dbCtx, topicId);
519 assert.fail(noExpectedException);
520 } catch (e) {
521 assert.deepStrictEqual(e, expected);
522 }
523 });
524 }); // subscriptionDeleteExpired
525
526 describe('subscriptionDeliveryClaim', function () {
527 it('success', async function () {
528 const dbAllResult = [
529 {
530 id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
531 },
532 ];
533 const dbRunResult = {
534 changes: 1,
535 lastInsertRowid: undefined,
536 };
537 const expected = ['c2e254c5-aa6e-4a8f-b1a1-e474b07392bb'];
538 sinon.stub(db.statement.subscriptionDeliveryNeeded, 'all').returns(dbAllResult);
539 sinon.stub(db.statement.subscriptionDeliveryClaimById, 'run').returns(dbRunResult);
540 const result = await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
541 assert.deepStrictEqual(result, expected);
542 });
543 it('failure', async function () {
544 const dbAllResult = [
545 {
546 id: 'c2e254c5-aa6e-4a8f-b1a1-e474b07392bb',
547 },
548 ];
549 const dbRunResult = {
550 changes: 0,
551 lastInsertRowid: undefined,
552 };
553 sinon.stub(db.statement.subscriptionDeliveryNeeded, 'all').returns(dbAllResult);
554 sinon.stub(db.statement.subscriptionDeliveryClaimById, 'run').returns(dbRunResult);
555 try {
556 await db.subscriptionDeliveryClaim(dbCtx, wanted, claimTimeoutSeconds, claimant );
557 assert.fail(noExpectedException);
558 } catch (e) {
559 assert(e instanceof DBErrors.UnexpectedResult);
560 }
561 });
562 }); // subscriptionDeliveryClaim
563
564 describe('subscriptionDeliveryClaimById', function () {
565 it('success', async function() {
566 const dbResult = {
567 changes: 1,
568 lastInsertRowid: undefined,
569 };
570 sinon.stub(db.statement.subscriptionDeliveryClaimById, 'run').returns(dbResult);
571 const result = await db.subscriptionDeliveryClaimById(dbCtx, subscriptionId, claimTimeoutSeconds, claimant);
572 assert.deepStrictEqual(result, dbResult);
573 });
574 it('failure', async function () {
575 const dbResult = {
576 changes: 0,
577 lastInsertRowid: undefined,
578 };
579 sinon.stub(db.statement.subscriptionDeliveryClaimById, 'run').returns(dbResult);
580 try {
581 await db.subscriptionDeliveryClaimById(dbCtx, subscriptionId, claimTimeoutSeconds, claimant);
582 assert.fail(noExpectedException);
583 } catch (e) {
584 assert(e instanceof DBErrors.UnexpectedResult);
585 }
586 });
587 }); // subscriptionDeliveryClaimById
588
589 describe('subscriptionDeliveryComplete', function () {
590 let topicContentUpdated;
591 before(function () {
592 topicContentUpdated = new Date();
593 });
594 it('success', async function() {
595 const dbResult = {
596 changes: 1,
597 };
598 sinon.stub(db.statement.subscriptionDeliverySuccess, 'run').returns(dbResult);
599 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult);
600 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
601 });
602 it('failure', async function () {
603 const dbResult = {
604 changes: 0,
605 };
606 sinon.stub(db.statement.subscriptionDeliverySuccess, 'run').returns(dbResult);
607 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult);
608 try {
609 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
610 assert.fail(noExpectedException);
611 } catch (e) {
612 assert(e instanceof DBErrors.UnexpectedResult);
613 }
614 });
615 it('second failure', async function () {
616 const dbResult0 = {
617 changes: 1,
618 };
619 const dbResult1 = {
620 changes: 0,
621 };
622 sinon.stub(db.statement.subscriptionDeliverySuccess, 'run').returns(dbResult0);
623 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult1);
624 try {
625 await db.subscriptionDeliveryComplete(dbCtx, callback, topicId, topicContentUpdated);
626 assert.fail(noExpectedException);
627 } catch (e) {
628 assert(e instanceof DBErrors.UnexpectedResult);
629 }
630 });
631 }); // subscriptionDeliveryComplete
632
633 describe('subscriptionDeliveryGone', function () {
634 it('success', async function() {
635 const dbResult = {
636 changes: 1,
637 };
638 sinon.stub(db.statement.subscriptionDelete, 'run').returns(dbResult);
639 await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
640 });
641 it('failure', async function () {
642 const dbResult = {
643 changes: 0,
644 };
645 sinon.stub(db.statement.subscriptionDelete, 'run').returns(dbResult);
646 try {
647 await db.subscriptionDeliveryGone(dbCtx, callback, topicId);
648 assert.fail(noExpectedException);
649 } catch (e) {
650 assert(e instanceof DBErrors.UnexpectedResult);
651 }
652 });
653 }); // subscriptionDeliveryGone
654
655 describe('subscriptionDeliveryIncomplete', function () {
656 it('success', async function() {
657 const dbGet = { deliveryAttemptsSinceSuccess: 0 };
658 const dbResult = {
659 changes: 1,
660 };
661 sinon.stub(db.statement.subscriptionDeliveryAttempts, 'get').returns(dbGet);
662 sinon.stub(db.statement.subscriptionDeliveryFailure, 'run').returns(dbResult);
663 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult);
664 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
665 });
666 it('success covers default', async function() {
667 const dbGet = { deliveryAttemptsSinceSuccess: 0 };
668 const dbResult = {
669 changes: 1,
670 };
671 sinon.stub(db.statement.subscriptionDeliveryAttempts, 'get').returns(dbGet);
672 sinon.stub(db.statement.subscriptionDeliveryFailure, 'run').returns(dbResult);
673 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult);
674 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId);
675 });
676 it('failure', async function () {
677 const dbGet = { deliveryAttemptsSinceSuccess: 0 };
678 const dbResult = {
679 changes: 0,
680 };
681 sinon.stub(db.statement.subscriptionDeliveryAttempts, 'get').returns(dbGet);
682 sinon.stub(db.statement.subscriptionDeliveryFailure, 'run').returns(dbResult);
683 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult);
684 try {
685 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
686 assert.fail(noExpectedException);
687 } catch (e) {
688 assert(e instanceof DBErrors.UnexpectedResult);
689 }
690 });
691 it('second failure', async function () {
692 const dbGet = { deliveryAttemptsSinceSuccess: 0 };
693 const dbResult0 = {
694 changes: 1,
695 };
696 const dbResult1 = {
697 changes: 0,
698 };
699 sinon.stub(db.statement.subscriptionDeliveryAttempts, 'get').returns(dbGet);
700 sinon.stub(db.statement.subscriptionDeliveryFailure, 'run').returns(dbResult0);
701 sinon.stub(db.statement.subscriptionDeliveryDone, 'run').returns(dbResult1);
702 try {
703 await db.subscriptionDeliveryIncomplete(dbCtx, callback, topicId, retryDelays);
704 assert.fail(noExpectedException);
705 } catch (e) {
706 assert(e instanceof DBErrors.UnexpectedResult);
707 }
708 });
709 }); // subscriptionDeliveryIncomplete
710
711 describe('subscriptionGet', function () {
712 it('success', async function() {
713 const expected = {
714 id: subscriptionId,
715 };
716 sinon.stub(db.statement.subscriptionGet, 'get').returns(expected);
717 const result = await db.subscriptionGet(dbCtx, callback, topicId);
718 assert.deepStrictEqual(result, expected);
719 });
720 it('failure', async function () {
721 const expected = new Error();
722 sinon.stub(db.statement.subscriptionGet, 'get').throws(expected);
723 try {
724 await db.subscriptionGet(dbCtx, callback, topicId);
725 assert.fail(noExpectedException);
726 } catch (e) {
727 assert.deepStrictEqual(e, expected);
728 }
729 });
730 }); // subscriptionGet
731
732 describe('subscriptionGetById', function () {
733 it('success', async function() {
734 const expected = {
735 id: subscriptionId,
736 };
737 sinon.stub(db.statement.subscriptionGetById, 'get').returns(expected);
738 const result = await db.subscriptionGetById(dbCtx, subscriptionId);
739 assert.deepStrictEqual(result, expected);
740 });
741 it('failure', async function () {
742 const expected = new Error();
743 sinon.stub(db.statement.subscriptionGetById, 'get').throws(expected);
744 try {
745 await db.subscriptionGetById(dbCtx, subscriptionId);
746 assert.fail(noExpectedException);
747 } catch (e) {
748 assert.deepStrictEqual(e, expected);
749 }
750 });
751 }); // subscriptionGetById
752
753 describe('subscriptionUpdate', function () {
754 let data;
755 beforeEach(function () {
756 data = {
757 subscriptionId,
758 signatureAlgorithm: 'sha256',
759 };
760 });
761 it('success', async function() {
762 const dbResult = {
763 changes: 1,
764 lastInsertRowid: subscriptionId,
765 };
766 sinon.stub(db.statement.subscriptionUpdate, 'run').returns(dbResult);
767 await db.subscriptionUpdate(dbCtx, data);
768 });
769 it('failure', async function () {
770 const dbResult = {
771 changes: 0,
772 };
773 sinon.stub(db.statement.subscriptionUpdate, 'run').returns(dbResult);
774 try {
775 await db.subscriptionUpdate(dbCtx, data);
776 assert.fail(noExpectedException);
777 } catch (e) {
778 assert(e instanceof DBErrors.UnexpectedResult, e);
779 }
780 });
781 }); // subscriptionUpdate
782
783 describe('subscriptionUpsert', function () {
784 let data;
785 beforeEach(function () {
786 data = {
787 callback,
788 topicId,
789 leaseSeconds,
790 secret,
791 httpRemoteAddr,
792 httpFrom,
793 };
794 });
795 it('success', async function() {
796 const dbResult = {
797 changes: 1,
798 lastInsertRowid: subscriptionId,
799 };
800 const expected = {
801 changes: 1,
802 lastInsertRowid: subscriptionId,
803 };
804 sinon.stub(db.statement.subscriptionUpsert, 'run').returns(dbResult);
805 const result = await db.subscriptionUpsert(dbCtx, data);
806 assert.deepStrictEqual(result, expected);
807 });
808 it('failure', async function () {
809 const dbResult = {
810 changes: 0,
811 };
812 sinon.stub(db.statement.subscriptionUpsert, 'run').returns(dbResult);
813 try {
814 await db.subscriptionUpsert(dbCtx, data);
815 assert.fail(noExpectedException);
816 } catch (e) {
817 assert(e instanceof DBErrors.UnexpectedResult);
818 }
819 });
820 }); // subscriptionUpsert
821
822 describe('topicDeleted', function () {
823 it('success', async function () {
824 sinon.stub(db.statement.topicDeleted, 'run').returns({ changes: 1 });
825 await db.topicDeleted(dbCtx, { topicId });
826 });
827 it('failure', async function () {
828 sinon.stub(db.statement.topicDeleted, 'run').returns({ changes: 0 });
829 try {
830 await db.topicDeleted(dbCtx, { topicId });
831 assert.fail(noExpectedException);
832 } catch (e) {
833 assert(e instanceof DBErrors.UnexpectedResult);
834 }
835 });
836 }); // topicDeleted
837
838 describe('topicFetchClaim', function () {
839 it('success', async function() {
840 const dbAll = [{ id: topicId }];
841 const dbResult = {
842 changes: 1,
843 };
844 const expected = [topicId];
845 sinon.stub(db.statement.topicContentFetchNeeded, 'all').returns(dbAll);
846 sinon.stub(db.statement.topicContentFetchClaimById, 'run').returns(dbResult);
847 const result = await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
848 assert.deepStrictEqual(result, expected);
849 });
850 it('failure', async function () {
851 const dbAll = [{ id: topicId }];
852 const dbResult = {
853 changes: 0,
854 };
855 sinon.stub(db.statement.topicContentFetchNeeded, 'all').returns(dbAll);
856 sinon.stub(db.statement.topicContentFetchClaimById, 'run').returns(dbResult);
857 try {
858 await db.topicFetchClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
859 assert.fail(noExpectedException);
860 } catch (e) {
861 assert(e instanceof DBErrors.UnexpectedResult);
862 }
863 });
864 }); // topicFetchClaim
865
866 describe('topicFetchClaimById', function () {
867 it('success', async function() {
868 const expected = {
869 changes: 1,
870 lastInsertRowid: undefined,
871 };
872 sinon.stub(db.statement.topicContentFetchClaimById, 'run').returns(expected);
873 const result = await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
874 assert.deepStrictEqual(result, expected);
875 });
876 it('failure', async function () {
877 const expected = {
878 changes: 0,
879 lastInsertRowid: undefined,
880 };
881 sinon.stub(db.statement.topicContentFetchClaimById, 'run').returns(expected);
882 try {
883 await db.topicFetchClaimById(dbCtx, topicId, claimTimeoutSeconds, claimant);
884 assert.fail(noExpectedException);
885 } catch (e) {
886 assert(e instanceof DBErrors.UnexpectedResult);
887 }
888 });
889 }); // topicFetchClaimById
890
891 describe('topicFetchComplete', function () {
892 it('success', async function() {
893 const dbResult = {
894 changes: 1,
895 lastInsertRowid: undefined,
896 };
897 sinon.stub(db.statement.topicAttemptsReset, 'run').returns(dbResult);
898 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult);
899 await db.topicFetchComplete(dbCtx, topicId);
900 });
901 it('failure', async function () {
902 const dbResult = {
903 changes: 0,
904 lastInsertRowid: undefined,
905 };
906 sinon.stub(db.statement.topicAttemptsReset, 'run').returns(dbResult);
907 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult);
908 try {
909 await db.topicFetchComplete(dbCtx, topicId);
910 assert.fail(noExpectedException);
911 } catch (e) {
912 assert(e instanceof DBErrors.UnexpectedResult);
913 }
914 });
915 it('second failure', async function () {
916 const dbResult0 = {
917 changes: 1,
918 lastInsertRowid: undefined,
919 };
920 const dbResult1 = {
921 changes: 0,
922 lastInsertRowid: undefined,
923 };
924 sinon.stub(db.statement.topicAttemptsReset, 'run').returns(dbResult0);
925 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult1);
926 try {
927 await db.topicFetchComplete(dbCtx, topicId);
928 assert.fail(noExpectedException);
929 } catch (e) {
930 assert(e instanceof DBErrors.UnexpectedResult);
931 }
932 });
933 }); // topicFetchComplete
934
935 describe('topicFetchIncomplete', function () {
936 it('success', async function() {
937 const dbGet = { currentAttempt: 0 };
938 const dbResult0 = {
939 changes: 1,
940 lastInsertRowid: undefined,
941 };
942 const dbResult1 = {
943 changes: 1,
944 lastInsertRowid: undefined,
945 };
946 const expected = {
947 changes: 1,
948 lastInsertRowid: undefined,
949 };
950 sinon.stub(db.statement.topicAttempts, 'get').returns(dbGet);
951 sinon.stub(db.statement.topicAttemptsIncrement, 'run').returns(dbResult0);
952 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult1);
953 const result = await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
954 assert.deepStrictEqual(result, expected);
955 });
956 it('covers defaults', async function() {
957 const dbGet = { currentAttempt: 0 };
958 const dbResult0 = {
959 changes: 1,
960 lastInsertRowid: undefined,
961 };
962 const dbResult1 = {
963 changes: 1,
964 lastInsertRowid: undefined,
965 };
966 const expected = {
967 changes: 1,
968 lastInsertRowid: undefined,
969 };
970 sinon.stub(db.statement.topicAttempts, 'get').returns(dbGet);
971 sinon.stub(db.statement.topicAttemptsIncrement, 'run').returns(dbResult0);
972 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult1);
973 const result = await db.topicFetchIncomplete(dbCtx, topicId);
974 assert.deepStrictEqual(result, expected);
975 });
976 it('failure', async function () {
977 const dbGet = { currentAttempt: 0 };
978 const dbResult0 = {
979 changes: 1,
980 lastInsertRowid: undefined,
981 };
982 const dbResult1 = {
983 changes: 0,
984 lastInsertRowid: undefined,
985 };
986 sinon.stub(db.statement.topicAttempts, 'get').returns(dbGet);
987 sinon.stub(db.statement.topicAttemptsIncrement, 'run').returns(dbResult0);
988 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult1);
989 try {
990 await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
991 assert.fail(noExpectedException);
992 } catch (e) {
993 assert(e instanceof DBErrors.UnexpectedResult);
994 }
995 });
996 it('second failure', async function () {
997 const dbGet = { currentAttempt: 0 };
998 const dbResult0 = {
999 changes: 0,
1000 lastInsertRowid: undefined,
1001 };
1002 const dbResult1 = {
1003 changes: 0,
1004 lastInsertRowid: undefined,
1005 };
1006 sinon.stub(db.statement.topicAttempts, 'get').returns(dbGet);
1007 sinon.stub(db.statement.topicAttemptsIncrement, 'run').returns(dbResult0);
1008 sinon.stub(db.statement.topicContentFetchDone, 'run').returns(dbResult1);
1009 try {
1010 await db.topicFetchIncomplete(dbCtx, topicId, retryDelays);
1011 assert.fail(noExpectedException);
1012 } catch (e) {
1013 assert(e instanceof DBErrors.UnexpectedResult);
1014 }
1015 });
1016 }); // topicFetchIncomplete
1017
1018 describe('topicFetchRequested', function () {
1019 it('success', async function() {
1020 const dbResult = {
1021 changes: 1,
1022 lastInsertRowid: undefined,
1023 };
1024 const expected = {
1025 changes: 1,
1026 lastInsertRowid: undefined,
1027 };
1028 sinon.stub(db.statement.topicContentFetchRequested, 'run').returns(dbResult);
1029 const result = await db.topicFetchRequested(dbCtx, topicId);
1030 assert.deepStrictEqual(result, expected);
1031 });
1032 it('failure', async function () {
1033 const dbResult = {
1034 changes: 0,
1035 lastInsertRowid: undefined,
1036 };
1037 sinon.stub(db.statement.topicContentFetchRequested, 'run').returns(dbResult);
1038 try {
1039 await db.topicFetchRequested(dbCtx, topicId);
1040 assert.fail(noExpectedException);
1041 } catch (e) {
1042 assert(e instanceof DBErrors.UnexpectedResult);
1043 }
1044 });
1045 }); // topicFetchRequested
1046
1047 describe('topicGetAll', function () {
1048 it('success', async function() {
1049 const expected = [{ id: topicId }];
1050 sinon.stub(db.statement.topicGetInfoAll, 'all').returns(expected);
1051 const result = await db.topicGetAll(dbCtx);
1052 assert.deepStrictEqual(result, expected);
1053 });
1054 it('covers none', async function() {
1055 const expected = undefined;
1056 sinon.stub(db.statement.topicGetInfoAll, 'all').returns(expected);
1057 const result = await db.topicGetAll(dbCtx);
1058 assert.deepStrictEqual(result, expected);
1059 });
1060 it('failure', async function () {
1061 const expected = new Error();
1062 sinon.stub(db.statement.topicGetInfoAll, 'all').throws(expected);
1063 try {
1064 await db.topicGetAll(dbCtx);
1065 assert.fail(noExpectedException);
1066 } catch (e) {
1067 assert.deepStrictEqual(e, expected);
1068 }
1069 });
1070 }); // topicGetById
1071
1072 describe('topicGetById', function () {
1073 it('success', async function() {
1074 const expected = { id: topicId };
1075 sinon.stub(db.statement.topicGetById, 'get').returns(expected);
1076 const result = await db.topicGetById(dbCtx, topicId);
1077 assert.deepStrictEqual(result, expected);
1078 });
1079 it('covers no defaults', async function () {
1080 const expected = { id: topicId };
1081 sinon.stub(db.statement.topicGetById, 'get').returns(expected);
1082 const result = await db.topicGetById(dbCtx, topicId, false);
1083 assert.deepStrictEqual(result, expected);
1084 });
1085 it('covers default', async function() {
1086 const expected = undefined;
1087 sinon.stub(db.statement.topicGetById, 'get').returns(expected);
1088 const result = await db.topicGetById(dbCtx, topicId);
1089 assert.deepStrictEqual(result, expected);
1090 });
1091 it('failure', async function () {
1092 const expected = new Error();
1093 sinon.stub(db.statement.topicGetById, 'get').throws(expected);
1094 try {
1095 await db.topicGetById(dbCtx, topicId);
1096 assert.fail(noExpectedException);
1097 } catch (e) {
1098 assert.deepStrictEqual(e, expected);
1099 }
1100 });
1101 }); // topicGetById
1102
1103 describe('topicGetByUrl', function () {
1104 it('success', async function() {
1105 const expected = [];
1106 sinon.stub(db.statement.topicGetByUrl, 'get').returns(expected);
1107 const result = await db.topicGetByUrl(dbCtx, topicUrl);
1108 assert.deepStrictEqual(result, expected);
1109 });
1110 it('success, no defaults', async function() {
1111 const expected = [];
1112 sinon.stub(db.statement.topicGetByUrl, 'get').returns(expected);
1113 const result = await db.topicGetByUrl(dbCtx, topicUrl, false);
1114 assert.deepStrictEqual(result, expected);
1115 });
1116 it('failure', async function () {
1117 const expected = new Error();
1118 sinon.stub(db.statement.topicGetByUrl, 'get').throws(expected);
1119 try {
1120 await db.topicGetByUrl(dbCtx, topicUrl);
1121 assert.fail(noExpectedException);
1122 } catch (e) {
1123 assert.deepStrictEqual(e, expected);
1124 }
1125 });
1126 }); // topicGetByUrl
1127
1128 describe('topicGetContentById', function () {
1129 it('success', async function() {
1130 const expected = { id: topicId };
1131 sinon.stub(db.statement.topicGetContentById, 'get').returns(expected);
1132 const result = await db.topicGetContentById(dbCtx, topicId);
1133 assert.deepStrictEqual(result, expected);
1134 });
1135 it('covers default', async function() {
1136 const expected = undefined;
1137 sinon.stub(db.statement.topicGetContentById, 'get').returns(expected);
1138 const result = await db.topicGetContentById(dbCtx, topicId);
1139 assert.deepStrictEqual(result, expected);
1140 });
1141 it('failure', async function () {
1142 const expected = new Error();
1143 sinon.stub(db.statement.topicGetContentById, 'get').throws(expected);
1144 try {
1145 await db.topicGetContentById(dbCtx, topicId);
1146 assert.fail(noExpectedException);
1147 } catch (e) {
1148 assert.deepStrictEqual(e, expected);
1149 }
1150 });
1151 }); // topicGetContentById
1152
1153 describe('topicPendingDelete', function () {
1154 beforeEach(function () {
1155 sinon.stub(db.statement.topicGetById, 'get');
1156 sinon.stub(db.statement.subscriptionCountByTopicUrl, 'get');
1157 sinon.stub(db.statement.topicDeleteById, 'run');
1158 });
1159 it('success', async function () {
1160 db.statement.topicGetById.get.returns({
1161 id: topicId,
1162 isDeleted: true,
1163 });
1164 db.statement.subscriptionCountByTopicUrl.get.returns({
1165 count: 0,
1166 });
1167 db.statement.topicDeleteById.run.returns({
1168 changes: 1,
1169 });
1170 db.topicPendingDelete(dbCtx, topicId);
1171 assert(db.statement.topicDeleteById.run.called);
1172 });
1173 it('does not delete non-deleted topic', async function () {
1174 db.statement.topicGetById.get.returns({
1175 id: topicId,
1176 isDeleted: false,
1177 });
1178 db.statement.subscriptionCountByTopicUrl.get.returns({
1179 count: 0,
1180 });
1181 db.statement.topicDeleteById.run.returns({
1182 changes: 1,
1183 });
1184 db.topicPendingDelete(dbCtx, topicId);
1185 assert(!db.statement.topicDeleteById.run.called);
1186 });
1187 it('does not delete topic with active subscriptions', async function () {
1188 db.statement.topicGetById.get.returns({
1189 id: topicId,
1190 isDeleted: true,
1191 });
1192 db.statement.subscriptionCountByTopicUrl.get.returns({
1193 count: 10,
1194 });
1195 db.statement.topicDeleteById.run.returns({
1196 changes: 1,
1197 });
1198 db.topicPendingDelete(dbCtx, topicId);
1199 assert(!db.statement.topicDeleteById.run.called);
1200 });
1201 it('covers no deletion', async function () {
1202 db.statement.topicGetById.get.returns({
1203 id: topicId,
1204 isDeleted: true,
1205 });
1206 db.statement.subscriptionCountByTopicUrl.get.returns({
1207 count: 0,
1208 });
1209 db.statement.topicDeleteById.run.returns({
1210 changes: 0,
1211 });
1212 try {
1213 db.topicPendingDelete(dbCtx, topicId);
1214 assert.fail(noExpectedException);
1215
1216 } catch (e) {
1217 assert(e instanceof DBErrors.UnexpectedResult);
1218 }
1219 assert(db.statement.topicDeleteById.run.called);
1220 });
1221 }); // topicPendingDelete
1222
1223 describe('topicPublishHistory', function () {
1224 beforeEach(function () {
1225 sinon.stub(db.statement.topicPublishHistory, 'all');
1226 });
1227 it('success', function () {
1228 db.statement.topicPublishHistory.all.returns([
1229 { daysAgo: 1, contentUpdates: 1 },
1230 { daysAgo: 3, contentUpdates: 2 },
1231 ]);
1232 const result = db.topicPublishHistory(dbCtx, topicId, 7);
1233 const expected = [0, 1, 0, 2, 0, 0, 0];
1234 assert.deepStrictEqual(result, expected);
1235 });
1236 }); // topicPublishHistory
1237
1238 describe('topicSet', function () {
1239 let data;
1240 beforeEach(function () {
1241 data = {
1242 url: topicUrl,
1243 };
1244 });
1245 it('success', async function() {
1246 const dbResult = {
1247 changes: 1,
1248 lastInsertRowid: topicId,
1249 };
1250 const expected = {
1251 changes: 1,
1252 lastInsertRowid: topicId,
1253 };
1254 sinon.stub(db.statement.topicUpsert, 'run').returns(dbResult);
1255 const result = await db.topicSet(dbCtx, data);
1256 assert.deepStrictEqual(result, expected);
1257 });
1258 it('failure', async function () {
1259 const dbResult = {
1260 changes: 0,
1261 lastInsertRowid: undefined,
1262 };
1263 sinon.stub(db.statement.topicUpsert, 'run').returns(dbResult);
1264 try {
1265 await db.topicSet(dbCtx, data);
1266 assert.fail(noExpectedException);
1267 } catch (e) {
1268 assert(e instanceof DBErrors.UnexpectedResult);
1269 }
1270 });
1271 it('fails invalid value', async function () {
1272 sinon.stub(db.statement.topicUpsert, 'run');
1273 try {
1274 data.leaseSecondsPreferred = -100;
1275 await db.topicSet(dbCtx, data);
1276 assert.fail(noExpectedException);
1277 } catch (e) {
1278 assert(e instanceof DBErrors.DataValidation);
1279 }
1280 assert(!db.statement.topicUpsert.run.called);
1281 });
1282 it('fails invalid values', async function () {
1283 sinon.stub(db.statement.topicUpsert, 'run');
1284 try {
1285 data.leaseSecondsPreferred = 10;
1286 data.leaseSecondsMax = 100;
1287 data.leaseSecondsMin = 50;
1288 await db.topicSet(dbCtx, data);
1289 assert.fail(noExpectedException);
1290 } catch (e) {
1291 assert(e instanceof DBErrors.DataValidation);
1292 }
1293 assert(!db.statement.topicUpsert.run.called);
1294 });
1295 }); // topicSet
1296
1297 describe('topicSetContent', function () {
1298 let data;
1299 beforeEach(function () {
1300 data = {
1301 content: 'content',
1302 contentType: 'text/plain',
1303 contentHash: 'abc123',
1304 };
1305 sinon.stub(db.statement.topicSetContent, 'run');
1306 sinon.stub(db.statement.topicSetContentHistory, 'run');
1307 });
1308 it('success', async function() {
1309 const dbResult = {
1310 changes: 1,
1311 lastInsertRowid: undefined,
1312 };
1313 const expected = {
1314 changes: 1,
1315 lastInsertRowid: undefined,
1316 };
1317 db.statement.topicSetContent.run.returns(dbResult);
1318 db.statement.topicSetContentHistory.run.returns(dbResult);
1319 const result = await db.topicSetContent(dbCtx, data);
1320 assert.deepStrictEqual(result, expected);
1321 });
1322 it('failure', async function () {
1323 const dbResult = {
1324 changes: 0,
1325 lastInsertRowid: undefined,
1326 };
1327 db.statement.topicSetContent.run.returns(dbResult);
1328 try {
1329 await db.topicSetContent(dbCtx, data);
1330 assert.fail(noExpectedException);
1331 } catch (e) {
1332 assert(e instanceof DBErrors.UnexpectedResult);
1333 }
1334 });
1335 it('failure 2', async function () {
1336 const dbResultSuccess = {
1337 changes: 1,
1338 lastInsertRowid: undefined,
1339 };
1340 const dbResultFail = {
1341 changes: 0,
1342 lastInsertRowid: undefined,
1343 };
1344 db.statement.topicSetContent.run.returns(dbResultSuccess);
1345 db.statement.topicSetContentHistory.run.returns(dbResultFail);
1346 try {
1347 await db.topicSetContent(dbCtx, data);
1348 assert.fail(noExpectedException);
1349 } catch (e) {
1350 assert(e instanceof DBErrors.UnexpectedResult);
1351 }
1352 });
1353 }); // topicSetContent
1354
1355 describe('topicUpdate', function () {
1356 let data;
1357 beforeEach(function () {
1358 data = {
1359 topicId,
1360 leaseSecondsPreferred: 9999,
1361 leaseSecondsMax: 99999,
1362 leaseSecondsMin: 999,
1363 publisherValidationUrl: null,
1364 contentHashAlgorithm: 'sha256',
1365 };
1366 });
1367 it('success', async function() {
1368 const dbResult = {
1369 changes: 1,
1370 lastInsertRowid: topicId,
1371 };
1372 sinon.stub(db.statement.topicUpdate, 'run').returns(dbResult);
1373 await db.topicUpdate(dbCtx, data);
1374 });
1375 it('failure', async function () {
1376 const dbResult = {
1377 changes: 0,
1378 lastInsertRowid: undefined,
1379 };
1380 sinon.stub(db.statement.topicUpdate, 'run').returns(dbResult);
1381 try {
1382 await db.topicUpdate(dbCtx, data);
1383 assert.fail(noExpectedException);
1384 } catch (e) {
1385 assert(e instanceof DBErrors.UnexpectedResult, e);
1386 }
1387 });
1388 it('fails invalid value', async function () {
1389 sinon.stub(db.statement.topicUpdate, 'run');
1390 try {
1391 data.leaseSecondsPreferred = -100;
1392 await db.topicUpdate(dbCtx, data);
1393 assert.fail(noExpectedException);
1394 } catch (e) {
1395 assert(e instanceof DBErrors.DataValidation, e);
1396 }
1397 assert(!db.statement.topicUpdate.run.called);
1398 });
1399 it('fails invalid values', async function () {
1400 sinon.stub(db.statement.topicUpdate, 'run');
1401 try {
1402 data.leaseSecondsPreferred = 10;
1403 data.leaseSecondsMax = 100;
1404 data.leaseSecondsMin = 50;
1405 await db.topicUpdate(dbCtx, data);
1406 assert.fail(noExpectedException);
1407 } catch (e) {
1408 assert(e instanceof DBErrors.DataValidation, e);
1409 }
1410 assert(!db.statement.topicUpdate.run.called);
1411 });
1412 }); // topicUpdate
1413
1414 describe('verificationClaim', function () {
1415 it('success', async function() {
1416 const dbAll = [{ id: verificationId }];
1417 const dbRun = {
1418 changes: 1,
1419 lastInsertRowid: undefined,
1420 };
1421 const expected = [verificationId];
1422 sinon.stub(db.statement.verificationNeeded, 'all').returns(dbAll);
1423 sinon.stub(db.statement.verificationClaimById, 'run').returns(dbRun);
1424 const result = await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1425 assert.deepStrictEqual(result, expected);
1426 });
1427 it('failure', async function () {
1428 const dbAll = [{ id: verificationId }];
1429 const dbRun = {
1430 changes: 0,
1431 lastInsertRowid: undefined,
1432 };
1433 sinon.stub(db.statement.verificationNeeded, 'all').returns(dbAll);
1434 sinon.stub(db.statement.verificationClaimById, 'run').returns(dbRun);
1435 try {
1436 await db.verificationClaim(dbCtx, wanted, claimTimeoutSeconds, claimant);
1437 assert.fail(noExpectedException);
1438 } catch (e) {
1439 assert(e instanceof DBErrors.UnexpectedResult);
1440 }
1441 });
1442 }); // verificationClaim
1443
1444 describe('verificationClaimById', function () {
1445 it('success', async function() {
1446 const dbRun = {
1447 changes: 1,
1448 lastInsertRowid: undefined,
1449 };
1450 sinon.stub(db.statement.verificationClaimById, 'run').returns(dbRun);
1451 const result = await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1452 assert.deepStrictEqual(result, dbRun);
1453 });
1454 it('failure', async function () {
1455 const dbRun = {
1456 changes: 0,
1457 lastInsertRowid: undefined,
1458 };
1459 sinon.stub(db.statement.verificationClaimById, 'run').returns(dbRun);
1460 try {
1461 await db.verificationClaimById(dbCtx, verificationId, claimTimeoutSeconds, claimant);
1462 assert.fail(noExpectedException);
1463 } catch (e) {
1464 assert(e instanceof DBErrors.UnexpectedResult);
1465 }
1466 });
1467 }); // verificationClaimById
1468
1469 describe('verificationComplete', function () {
1470 it('success', async function() {
1471 const dbResult = {
1472 changes: 1,
1473 lastInsertRowid: undefined,
1474 };
1475 sinon.stub(db.statement.verificationScrub, 'run').returns(dbResult);
1476 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1477 });
1478 it('failure', async function () {
1479 const dbResult = {
1480 changes: 0,
1481 lastInsertRowid: undefined,
1482 };
1483 sinon.stub(db.statement.verificationScrub, 'run').returns(dbResult);
1484 try {
1485 await db.verificationComplete(dbCtx, verificationId, callback, topicId);
1486 assert.fail(noExpectedException);
1487 } catch (e) {
1488 assert(e instanceof DBErrors.UnexpectedResult);
1489 }
1490 });
1491 }); // verificationComplete
1492
1493 describe('verificationGetById', function () {
1494 it('success', async function() {
1495 const dbOneOrNone = { id: verificationId, isPublisherValidated: 1 };
1496 const expected = { id: verificationId, isPublisherValidated: true };
1497 sinon.stub(db.statement.verificationGetById, 'get').returns(dbOneOrNone);
1498 const result = await db.verificationGetById(dbCtx, verificationId);
1499 assert.deepStrictEqual(result, expected);
1500 });
1501 it('failure', async function () {
1502 const expected = new Error();
1503 sinon.stub(db.statement.verificationGetById, 'get').throws(expected);
1504 try {
1505 await db.verificationGetById(dbCtx, verificationId);
1506 assert.fail(noExpectedException);
1507 } catch (e) {
1508 assert.deepStrictEqual(e, expected);
1509 }
1510 });
1511 }); // verificationGetById
1512
1513 describe('verificationIncomplete', function () {
1514 it('success', async function() {
1515 const dbOne = { attempts: 0 };
1516 const dbResult0 = {
1517 changes: 1,
1518 lastInsertRowid: undefined,
1519 };
1520 const dbResult1 = {
1521 changes: 1,
1522 lastInsertRowid: undefined,
1523 };
1524 sinon.stub(db.statement.verificationAttempts, 'get').returns(dbOne);
1525 sinon.stub(db.statement.verificationAttemptsIncrement, 'run').returns(dbResult0);
1526 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult1);
1527 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1528 });
1529 it('covers defaults', async function() {
1530 const dbOne = { attempts: 0 };
1531 const dbResult0 = {
1532 changes: 1,
1533 lastInsertRowid: undefined,
1534 };
1535 const dbResult1 = {
1536 changes: 1,
1537 lastInsertRowid: undefined,
1538 };
1539 sinon.stub(db.statement.verificationAttempts, 'get').returns(dbOne);
1540 sinon.stub(db.statement.verificationAttemptsIncrement, 'run').returns(dbResult0);
1541 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult1);
1542 await db.verificationIncomplete(dbCtx, verificationId);
1543 });
1544 it('failure', async function () {
1545 const dbOne = { attempts: 0 };
1546 const dbResult0 = {
1547 changes: 0,
1548 lastInsertRowid: undefined,
1549 };
1550 const dbResult1 = {
1551 changes: 1,
1552 lastInsertRowid: undefined,
1553 };
1554 sinon.stub(db.statement.verificationAttempts, 'get').returns(dbOne);
1555 sinon.stub(db.statement.verificationAttemptsIncrement, 'run').returns(dbResult0);
1556 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult1);
1557 try {
1558 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1559 assert.fail(noExpectedException);
1560 } catch (e) {
1561 assert(e instanceof DBErrors.UnexpectedResult);
1562 }
1563 });
1564 it('second failure', async function () {
1565 const dbOne = { attempts: 0 };
1566 const dbResult0 = {
1567 changes: 1,
1568 lastInsertRowid: undefined,
1569 };
1570 const dbResult1 = {
1571 changes: 0,
1572 lastInsertRowid: undefined,
1573 };
1574 sinon.stub(db.statement.verificationAttempts, 'get').returns(dbOne);
1575 sinon.stub(db.statement.verificationAttemptsIncrement, 'run').returns(dbResult0);
1576 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult1);
1577 try {
1578 await db.verificationIncomplete(dbCtx, verificationId, retryDelays);
1579 assert.fail(noExpectedException);
1580 } catch (e) {
1581 assert(e instanceof DBErrors.UnexpectedResult);
1582 }
1583 });
1584 }); // verificationIncomplete
1585
1586 describe('_verificationDataToEngine', function () {
1587 it('covers no data', function () {
1588 DB._verificationDataToEngine();
1589 });
1590 it('covers true', function () {
1591 const data = {
1592 isPublisherValidated: true,
1593 };
1594 DB._verificationDataToEngine(data);
1595 assert.strictEqual(data.isPublisherValidated, 1);
1596 });
1597 it('covers false', function () {
1598 const data = {
1599 isPublisherValidated: false,
1600 };
1601 DB._verificationDataToEngine(data);
1602 assert.strictEqual(data.isPublisherValidated, 0);
1603 });
1604 }); // _verificationDataToEngine
1605
1606 describe('verificationInsert', function () {
1607 let verification;
1608 beforeEach(function () {
1609 verification = {
1610 topicId,
1611 callback,
1612 mode: 'subscribe',
1613 isPublisherValidated: true,
1614 leaseSeconds: 86400,
1615 };
1616 });
1617 it('success', async function() {
1618 const dbResult = {
1619 changes: 1,
1620 lastInsertRowid: verificationId,
1621 };
1622 const expected = verificationId;
1623 sinon.stub(db.statement.verificationInsert, 'run').returns(dbResult);
1624 const result = await db.verificationInsert(dbCtx, verification);
1625 assert.deepStrictEqual(result, expected);
1626 });
1627 it('failure', async function () {
1628 const dbResult = {
1629 changes: 0,
1630 lastInsertRowid: undefined,
1631 };
1632 sinon.stub(db.statement.verificationInsert, 'run').returns(dbResult);
1633 try {
1634 await db.verificationInsert(dbCtx, verification);
1635 assert.fail(noExpectedException);
1636 } catch (e) {
1637 assert(e instanceof DBErrors.UnexpectedResult);
1638 }
1639 });
1640 it('fails validation', async function () {
1641 delete verification.leaseSeconds;
1642 try {
1643 await db.verificationInsert(dbCtx, verification);
1644 assert.fail(noExpectedException);
1645 } catch (e) {
1646 assert(e instanceof DBErrors.DataValidation);
1647 }
1648 });
1649 }); // verificationInsert
1650
1651 describe('verificationRelease', function () {
1652 it('success', async function() {
1653 const dbResult = {
1654 changes: 1,
1655 lastInsertRowid: undefined,
1656 };
1657 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult);
1658 await db.verificationRelease(dbCtx, verificationId);
1659 });
1660 it('failure', async function () {
1661 const dbResult = {
1662 changes: 0,
1663 lastInsertRowid: undefined,
1664 };
1665 sinon.stub(db.statement.verificationDone, 'run').returns(dbResult);
1666 try {
1667 await db.verificationRelease(dbCtx, verificationId);
1668 assert.fail(noExpectedException);
1669 } catch (e) {
1670 assert(e instanceof DBErrors.UnexpectedResult);
1671 }
1672 });
1673 }); // verificationRelease
1674
1675 describe('verificationUpdate', function () {
1676 let data;
1677 beforeEach(function () {
1678 data = {
1679 mode: 'subscribe',
1680 isPublisherValidated: true,
1681 };
1682 });
1683 it('success', async function() {
1684 const dbResult = {
1685 changes: 1,
1686 lastInsertRowid: undefined,
1687 };
1688 sinon.stub(db.statement.verificationUpdate, 'run').returns(dbResult);
1689 await db.verificationUpdate(dbCtx, verificationId, data);
1690 });
1691 it('failure', async function () {
1692 const dbResult = {
1693 changes: 0,
1694 lastInsertRowid: undefined,
1695 };
1696 sinon.stub(db.statement.verificationUpdate, 'run').returns(dbResult);
1697 try {
1698 await db.verificationUpdate(dbCtx, verificationId, data);
1699 assert.fail(noExpectedException);
1700 } catch (e) {
1701 assert(e instanceof DBErrors.UnexpectedResult, e.name);
1702 }
1703 });
1704 it('fails validation', async function () {
1705 delete data.mode;
1706 try {
1707 await db.verificationUpdate(dbCtx, data);
1708 assert.fail(noExpectedException);
1709 } catch (e) {
1710 assert(e instanceof DBErrors.DataValidation);
1711 }
1712 });
1713 }); // verificationUpdate
1714
1715 describe('verificationValidated', function () {
1716 it('success', async function() {
1717 const dbResult = {
1718 changes: 1,
1719 lastInsertRowid: undefined,
1720 };
1721 sinon.stub(db.statement.verificationValidate, 'run').returns(dbResult);
1722 await db.verificationValidated(dbCtx, verificationId);
1723 });
1724 it('failure', async function () {
1725 const dbResult = {
1726 changes: 0,
1727 lastInsertRowid: undefined,
1728 };
1729 sinon.stub(db.statement.verificationValidate, 'run').returns(dbResult);
1730 try {
1731 await db.verificationValidated(dbCtx, verificationId);
1732 assert.fail(noExpectedException);
1733 } catch (e) {
1734 assert(e instanceof DBErrors.UnexpectedResult);
1735 }
1736 });
1737 }); // verificationValidated
1738
1739 }); // DatabaseSQLite