2e066d219315bf8a41286b90f23cd7f2a83b88db
[squeep-chores] / test / lib / chores.js
1 /* eslint-env mocha */
2 /* eslint-disable node/no-unpublished-require */
3 'use strict';
4
5 const Chores = require('../../lib/chores');
6 const { ChoreError } = require('../../lib/errors');
7 const { StubLogger } = require('@squeep/test-helper');
8 const { fileScope } = require('../../lib/common');
9 const assert = require('assert');
10 const sinon = require('sinon');
11
12 const snooze = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
13 const expectedException = new Error('oh no');
14
15 describe('Chores', function () {
16 let chores, logger, task;
17 beforeEach(function () {
18 this.timeout(1000);
19 this.slow(200);
20
21 logger = new StubLogger();
22 logger._reset();
23
24 chores = new Chores(logger);
25 task = sinon.spy((...args) => {
26 logger.debug('task', 'called', { args });
27 });
28 });
29 afterEach(function () {
30 chores.stopAllChores();
31 sinon.restore();
32 });
33
34 describe('_getChore', function () {
35 it('covers failure', function () {
36 assert.throws(() => chores._getChore('no chore'), ChoreError);
37 });
38 it('covers success', function () {
39 chores.establishChore('chore1', task, 0);
40 const chore = chores._getChore('chore1');
41 assert(chore);
42 });
43 }); // _getChore
44
45 describe('establishChore', function () {
46 it('establishes non-recurring chore', async function () {
47 chores.establishChore('chore1', task, 0);
48 await snooze(20);
49 assert(task.notCalled);
50 });
51 it('refuses to establish same chore twice', function () {
52 const choreName = 'chore1';
53 chores.establishChore(choreName, task, 0);
54 assert.throws(() => chores.establishChore(choreName, task, 0), ChoreError);
55 });
56 it('establishes recurring chore', async function () {
57 chores.establishChore('chore1', task, 10);
58 await snooze(25);
59 assert(task.called);
60 });
61 }); // establishChore
62
63 describe('runChore', function () {
64 it('requires existing chore', async function () {
65 await assert.rejects(chores.runChore('missingChore'), ChoreError);
66 });
67 it('runs non-recurring chore', async function () {
68 chores.establishChore('chore1', task, 0);
69 chores.runChore('chore1');
70 assert(task.called);
71 });
72 it('does not overlap runs', async function () {
73 const longTask = sinon.spy(async (...args) => {
74 await snooze(200);
75 logger.debug('task', 'called', { args });
76 });
77 chores.establishChore('longChore', longTask);
78 chores.runChore('longChore');
79 chores.runChore('longChore');
80 snooze(300);
81 assert(longTask.called);
82 assert.strictEqual(longTask.callCount, 1);
83 });
84 it('covers recurring task exception', async function () {
85 const failTask = sinon.spy(function () {
86 throw expectedException;
87 });
88 chores.establishChore('failChore', failTask, 10);
89 await snooze(50);
90 assert(failTask.called);
91 });
92 it('covers called task exception', async function () {
93 const failTask = sinon.spy(function () {
94 throw expectedException;
95 });
96 chores.establishChore('failChore', failTask, 0);
97 await assert.rejects(chores.runChore('failChore', 'argument', 'argument'), expectedException);
98 });
99 }); // runChore
100
101 describe('miscellaneous coverage', function () {
102 it('names error', function () {
103 const e = new ChoreError();
104 assert.strictEqual(e.name, 'ChoreError');
105 });
106 it('covers unused log helper line', function () {
107 const fs = fileScope('/foo/bar/index.js');
108 const s = fs('m');
109 assert(s.endsWith(':bar:m'), s);
110 });
111 });
112
113 }); // Chores