Files
bree/test/index.js
T
2024-07-03 10:44:00 -07:00

533 lines
11 KiB
JavaScript

const path = require('node:path');
const { once } = require('node:events');
const test = require('ava');
const delay = require('delay');
const humanInterval = require('human-interval');
const FakeTimers = require('@sinonjs/fake-timers');
const Bree = require('../src');
const root = path.join(__dirname, 'jobs');
const baseConfig = {
root,
timeout: 0,
interval: 0,
hasSeconds: false,
defaultExtension: 'js'
};
test('successfully run job', async (t) => {
t.plan(2);
const logger = {
info() {},
warn() {},
error() {}
};
const bree = new Bree({
jobs: ['infinite'],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
t.true(bree.workers.has(name));
});
bree.on('worker deleted', (name) => {
t.false(bree.workers.has(name));
});
await delay(100);
await bree.stop();
});
test('preset and override is set if config.hasSeconds is "true"', (t) => {
const bree = new Bree({
jobs: ['basic'],
...baseConfig,
hasSeconds: true
});
t.is(bree.config.cronValidate.preset, 'default');
t.true(typeof bree.config.cronValidate.override === 'object');
});
test('preset and override is set by cronValidate config', (t) => {
const bree = new Bree({
jobs: ['basic'],
...baseConfig,
hasSeconds: true,
cronValidate: {
preset: 'test',
override: { test: 'works' }
}
});
t.is(bree.config.cronValidate.preset, 'test');
t.true(typeof bree.config.cronValidate.override === 'object');
t.is(bree.config.cronValidate.override.test, 'works');
});
test('throws if jobs is not an array and logs ERR_MODULE_NOT_FOUND error by default', async (t) => {
t.plan(3);
const logger = {
info() {},
warn() {},
error(err) {
t.is(err.code, 'ERR_MODULE_NOT_FOUND');
}
};
const bree = new Bree({
jobs: null,
...baseConfig,
logger,
root: path.join(__dirname, 'noIndexJobs')
});
const err = await t.throwsAsync(bree.init());
t.is(err.message, 'Jobs must be an Array');
});
test('logs ERR_MODULE_NOT_FOUND error if array is empty', async (t) => {
t.plan(2);
const logger = {
info() {},
warn() {},
error(err) {
t.is(err.code, 'ERR_MODULE_NOT_FOUND');
}
};
const bree = new Bree({
jobs: [],
...baseConfig,
logger,
root: path.join(__dirname, 'noIndexJobs')
});
await bree.init();
t.true(bree instanceof Bree);
});
test('does not log ERR_MODULE_NOT_FOUND error if silenceRootCheckError is false', async (t) => {
const logger = {
info() {},
warn() {},
error() {
t.fail();
}
};
const bree = new Bree({
jobs: [],
...baseConfig,
logger,
root: path.join(__dirname, 'noIndexJobs'),
silenceRootCheckError: true
});
await bree.init();
t.true(bree instanceof Bree);
});
test('does not log ERR_MODULE_NOT_FOUND error if doRootCheck is false', async (t) => {
const logger = {
info() {},
warn() {},
error() {
t.fail();
}
};
const bree = new Bree({
jobs: [],
...baseConfig,
logger,
root: path.join(__dirname, 'noIndexJobs'),
doRootCheck: false
});
await bree.init();
t.true(bree instanceof Bree);
});
test('throws during constructor if job-validator throws', async (t) => {
const bree = new Bree({
jobs: [{ name: 'basic', hasSeconds: 'test' }],
...baseConfig
});
const err = await t.throwsAsync(bree.init());
t.is(
err.message,
'Job #1 named "basic" had hasSeconds value of test (it must be a Boolean)'
);
});
test('emits "worker created" and "worker started" events', async (t) => {
t.plan(2);
const bree = new Bree({
root,
jobs: ['basic'],
timeout: 100
});
await bree.start();
bree.on('worker created', (name) => {
t.true(bree.workers.has(name));
});
bree.on('worker deleted', (name) => {
t.false(bree.workers.has(name));
});
await delay(1000);
await bree.stop();
});
test.serial('job with long timeout runs', async (t) => {
t.plan(2);
const bree = new Bree({
root,
jobs: ['infinite'],
timeout: '3 months'
});
await bree.init();
t.is(bree.config.jobs[0].timeout, humanInterval('3 months'));
const now = Date.now();
const clock = FakeTimers.install({ now: Date.now() });
await bree.start('infinite');
bree.on('worker created', () => {
// Only complicated because of runtime - this removes flakiness
t.true(
clock.now - now === humanInterval('3 months') ||
clock.now - now === humanInterval('3 months') + 1
);
});
// Should run till worker stops running
clock.runAll();
clock.uninstall();
});
test.serial(
'job created with cron string is using local timezone',
async (t) => {
t.plan(2);
const bree = new Bree({
root,
jobs: [{ name: 'basic', cron: '0 18 * * *' }]
});
const clock = FakeTimers.install({ now: Date.now() });
await bree.start('basic');
bree.on('worker created', () => {
const now = new Date(clock.now);
const offsetOfLocalDates = new Date().getTimezoneOffset();
t.is(now.getTimezoneOffset(), offsetOfLocalDates);
t.is(now.getHours(), 18);
});
clock.next();
clock.uninstall();
}
);
test.serial(
'job created with human interval is using local timezone',
async (t) => {
t.plan(2);
const bree = new Bree({
root,
jobs: [{ name: 'basic', interval: 'at 13:26' }]
});
const clock = FakeTimers.install({ now: Date.now() });
await bree.start('basic');
bree.on('worker created', () => {
const now = new Date(clock.now);
t.is(now.getHours(), 13);
t.is(now.getMinutes(), 26);
});
clock.next();
clock.uninstall();
}
);
test('throws if acceptedExtensions is not an array', (t) => {
const err = t.throws(
() =>
new Bree({
jobs: ['basic'],
...baseConfig,
acceptedExtensions: 'test string'
})
);
t.is(err.message, '`acceptedExtensions` must be defined and an Array');
});
test('throws if acceptedExtensions is false', (t) => {
const err = t.throws(
() =>
new Bree({
jobs: ['basic'],
...baseConfig,
acceptedExtensions: false
})
);
t.is(err.message, '`acceptedExtensions` must be defined and an Array');
});
test('throws if root is not a directory', async (t) => {
const bree = new Bree({
jobs: ['basic'],
...baseConfig,
root: path.resolve(__dirname, 'add.js')
});
const err = await t.throwsAsync(bree.init());
t.regex(err.message, /Root directory of .+ does not exist/);
});
test('sets logger to noop if set to false', (t) => {
const bree = new Bree({ root, logger: false });
t.true(typeof bree.config.logger === 'object');
t.true(typeof bree.config.logger.info === 'function');
t.true(typeof bree.config.logger.warn === 'function');
t.true(typeof bree.config.logger.error === 'function');
});
test('removes job on completion when config.removeCompleted is `true`', async (t) => {
const bree = new Bree({
jobs: ['basic'],
...baseConfig,
logger: false,
removeCompleted: true
});
await bree.run('basic');
await once(bree.workers.get('basic'), 'exit');
t.is(bree.config.jobs.length, 0);
});
test('ensures defaultExtension does not start with a period', (t) => {
const err = t.throws(() => new Bree({ defaultExtension: '.js' }));
t.is(
err.message,
'`defaultExtension` should not start with a ".", please enter the file extension without a leading period'
);
});
test('handleJobCompletion throws if bree.init() was not called', (t) => {
const bree = new Bree();
const err = t.throws(() => bree.handleJobCompletion('foo'));
t.is(
err.message,
'bree.init() was not called, see <https://github.com/breejs/bree/blob/master/UPGRADING.md#upgrading-from-v8-to-v9>'
);
});
test('getWorkerMetadata throws if bree.init() was not called', (t) => {
const bree = new Bree();
const err = t.throws(() => bree.getWorkerMetadata());
t.is(
err.message,
'bree.init() was not called, see <https://github.com/breejs/bree/blob/master/UPGRADING.md#upgrading-from-v8-to-v9>'
);
});
test(`bree.init() is called if bree.remove() is called`, async (t) => {
const bree = new Bree({ root, jobs: ['basic'] });
await bree.remove('basic');
t.true(bree._init);
});
test(`bree.init() is called if bree.stop() is called`, async (t) => {
const bree = new Bree({ root, jobs: ['basic'] });
await bree.stop('basic');
t.true(bree._init);
});
test(`bree.init() is called if bree.add() is called`, async (t) => {
const bree = new Bree({ root, jobs: ['basic'] });
await bree.add('short');
t.true(bree._init);
await bree.add('message');
t.true(bree._init);
});
test('successfully run job without "runAs" or config "runJobsAs" will make workers.', async (t) => {
t.plan(2);
const logger = {
info() {}
};
const bree = new Bree({
jobs: [
{
name: 'basic'
}
],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
const worker = bree.workers.get(name);
t.true(Boolean(worker));
t.true(Boolean(worker?.postMessage));
});
await delay(100);
await bree.stop();
});
test('successfully run job with runAs === "worker" to make worker.', async (t) => {
t.plan(2);
const logger = {
info() {}
};
const bree = new Bree({
jobs: [
{
name: 'basic',
runAs: 'worker'
}
],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
const worker = bree.workers.get(name);
t.true(Boolean(worker));
t.true(Boolean(worker?.postMessage));
});
await delay(100);
await bree.stop();
});
test('successfully run job with runAs === "process" to make fork.', async (t) => {
t.plan(2);
const logger = {
info() {},
warn() {},
error() {}
};
const bree = new Bree({
jobs: [
{
name: 'basic',
runAs: 'process'
}
],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
const worker = bree.workers.get(name);
t.true(Boolean(worker));
t.true(worker?.postMessage === undefined); // processes don't have postMessage
});
await delay(100);
await bree.stop();
});
test('successfully apply config runJobAs: "process" to make forked jobs.', async (t) => {
t.plan(2);
const logger = {
info() {},
warn() {},
error() {}
};
const bree = new Bree({
runJobsAs: 'process',
jobs: [
{
name: 'basic'
}
],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
const worker = bree.workers.get(name);
t.true(Boolean(worker));
t.true(worker?.postMessage === undefined); // processes don't have postMessage
});
await delay(100);
await bree.stop();
});
test('job runAs is overwritten by bree config successfully.', async (t) => {
t.plan(2);
const logger = {
info() {},
warn() {},
error() {}
};
const bree = new Bree({
runJobsAs: 'process',
jobs: [
{
name: 'basic',
runAs: 'worker'
}
],
...baseConfig,
logger
});
await bree.start();
bree.on('worker created', (name) => {
const worker = bree.workers.get(name);
t.true(Boolean(worker));
t.true(worker?.postMessage === undefined); // processes don't have postMessage
});
await delay(100);
await bree.stop();
});