@acuminous/knuff
Version:
Reminds teams of recurring or future work by creating tasks in your issue tracker of choice
854 lines (709 loc) • 31.6 kB
JavaScript
const { strictEqual: eq, rejects } = require('node:assert');
const { describe, it } = require('zunit');
const { DateTime, Settings } = require('luxon');
const opi = require('object-path-immutable');
const StubDriver = require('./lib/StubDriver');
const Knuff = require('..');
describe('knuff', () => {
const repositories = {
'acuminous/foo': {
owner: 'acuminous',
repo: 'foo',
driver: 'github',
},
'acuminous/bar': {
owner: 'acuminous',
repo: 'bar',
driver: 'github',
},
'acuminous/baz': {
owner: 'acuminous',
repo: 'baz',
driver: 'gitlab',
},
};
const bumpDependencies = {
id: 'bump-dependencies',
schedule: 'DTSTART;TZID=Europe/London:20241219T080000;\nRRULE:FREQ=DAILY;COUNT=1',
title: 'Bump Dependencies',
body: 'Bump dependencies for all projects',
repositories: [
'acuminous/foo',
],
};
const auditDependencies = {
id: 'audit-dependencies',
schedule: 'DTSTART;TZID=Europe/London:20241219T080000;\nRRULE:FREQ=DAILY;COUNT=1',
title: 'Audit Dependencies',
body: 'Run npm audit --fix',
repositories: [
'acuminous/foo',
],
};
Settings.now = () => new Date('2024-01-01T11:00:00.000Z').getTime();
describe('dsl', () => {
it('should allow an id', async () => {
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'id', 'i-love-reminders'),
];
await knuff.process(reminders);
});
it('should require the id to be a string', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'id', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder '1' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should allow a description', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'description', 'Some description'),
];
await knuff.process(reminders);
});
it('should require the description to be a string', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'description', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should require a schedule', async () => {
const knuff = getKnuff();
const reminders = [
opi.del(bumpDependencies, 'schedule'),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, "must have required property 'schedule'");
return true;
});
});
it('should allow the schedule to be a string', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'schedule', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 3);
eq(error.details[0].message, 'must be string');
eq(error.details[1].message, 'must be array');
eq(error.details[2].message, 'must match exactly one schema in oneOf');
return true;
});
});
it('should allow the schedule to be an array', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'schedule', [
'DTSTART;TZID=Europe/London:20241219T080000;\nRRULE:FREQ=DAILY;COUNT=1',
'DTSTART;TZID=Europe/London:20250319T080000;\nRRULE:FREQ=DAILY;COUNT=1',
]),
];
await knuff.process(reminders);
});
it('should require the schedule items to be strings', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'schedule', [1]),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 3);
eq(error.details[0].message, 'must be string');
eq(error.details[1].message, 'must be string');
eq(error.details[2].message, 'must match exactly one schema in oneOf');
return true;
});
});
it('should require the schedule items to be unique', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'schedule', [
'DTSTART;TZID=Europe/London:20241219T080000;\nRRULE:FREQ=DAILY;COUNT=1',
'DTSTART;TZID=Europe/London:20241219T080000;\nRRULE:FREQ=DAILY;COUNT=1',
]),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 3);
eq(error.details[0].message, 'must be string');
eq(error.details[1].message, 'must NOT have duplicate items (items ## 1 and 0 are identical)');
eq(error.details[2].message, 'must match exactly one schema in oneOf');
return true;
});
});
it('should require a title', async () => {
const knuff = getKnuff();
const reminders = [
opi.del(bumpDependencies, 'title'),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, "must have required property 'title'");
return true;
});
});
it('should require the title to be a string', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'title', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should require a body', async () => {
const knuff = getKnuff();
const reminders = [
opi.del(bumpDependencies, 'body'),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, "must have required property 'body'");
return true;
});
});
it('should require the body to be a string', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'body', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should allow labels', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'labels', []),
];
await knuff.process(reminders);
});
it('should require the labels to be strings', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'labels', [1]),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should require labels to be unique', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'labels', ['one', 'one']),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must NOT have duplicate items (items ## 1 and 0 are identical)');
return true;
});
});
it('should require repositories', async () => {
const knuff = getKnuff();
const reminders = [
opi.del(bumpDependencies, 'repositories'),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, "must have required property 'repositories'");
return true;
});
});
it('should require at least one repository', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'repositories', []),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must NOT have fewer than 1 items');
return true;
});
});
it('should require repositories to be strings', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'repositories', [1]),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must be string');
return true;
});
});
it('should require repositories to be unique', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'repositories', ['one', 'one']),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must NOT have duplicate items (items ## 1 and 0 are identical)');
return true;
});
});
it('should report unknown reminder attributes', async () => {
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'wibble', 1),
];
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, "Reminder 'bump-dependencies' is invalid. See error.details for more information");
eq(error.details.length, 1);
eq(error.details[0].message, 'must NOT have additional properties');
return true;
});
});
});
describe('process', () => {
it('should create a reminder when the next occurrence is today (Europe/London)', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
eq(created[0].date.toISOString(), '2024-01-01T11:00:00.000Z');
eq(created[0].timezone, 'Europe/London');
});
it('should create a reminder when the next occurrence is today (UTC)', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
eq(created[0].date.toISOString(), '2024-01-01T11:00:00.000Z');
eq(created[0].timezone, 'UTC');
});
it('should create a reminder when there are multiple schedules all with the next occurrences being today', async () => {
const today1 = DateTime.now().toJSDate();
const today2 = new Date(today1.getTime() + 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', [
`DTSTART;TZID=Europe/London:${dtstart(today1)};\nRRULE:FREQ=DAILY;COUNT=1`,
`DTSTART;TZID=Europe/London:${dtstart(today2)};\nRRULE:FREQ=DAILY;COUNT=1`,
]),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
eq(created[0].date.toISOString(), '2024-01-01T11:00:00.000Z');
eq(created[0].timezone, 'Europe/London');
});
it('should create a reminder when there are two schedules but only the first schedules next occurrence is today', async () => {
const today = DateTime.now().toJSDate();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', [
`DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`,
`DTSTART;TZID=Europe/London:${dtstart(tomorrow)};\nRRULE:FREQ=DAILY;COUNT=1`,
]),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
eq(created[0].date.toISOString(), '2024-01-01T11:00:00.000Z');
eq(created[0].timezone, 'Europe/London');
});
it('should create a reminder when there are two schedules but only the second schedules next occurrence is today', async () => {
const today = DateTime.now().toJSDate();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', [
`DTSTART;TZID=Europe/London:${dtstart(tomorrow)};\nRRULE:FREQ=DAILY;COUNT=1`,
`DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`,
]),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
eq(created[0].date.toISOString(), '2024-01-01T11:00:00.000Z');
eq(created[0].timezone, 'Europe/London');
});
it('should not create reminders when the next occurrence is after today', async () => {
const today = DateTime.now().toJSDate();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(tomorrow)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 0);
});
it('should not create reminders when the last occurrence was before today', async () => {
const today = DateTime.now().toJSDate();
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(yesterday)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 0);
});
it('should default the id to the sluggified title', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.wrap(bumpDependencies)
.del('id')
.set('title', 'I ♥ Reminders')
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.value(),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].id, '6a81e4c3');
});
it('should add the specified labels', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.wrap(bumpDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.set('labels', ['chore', 'reminder'])
.value(),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].labels.length, 4);
eq(created[0].labels[0], 'chore');
eq(created[0].labels[1], 'reminder');
eq(created[0].labels[2], 'knuff:bump-dependencies');
eq(created[0].labels[3], 'knuff:2024-01-01');
});
it('should create reminders in all specified repositories', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.wrap(bumpDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.set('repositories', ['acuminous/foo', 'acuminous/bar'])
.value(),
];
await knuff.process(reminders);
const fooReminders = driver.repositories('acuminous/foo').reminders;
eq(fooReminders.length, 1);
const barReminders = driver.repositories('acuminous/bar').reminders;
eq(barReminders.length, 1);
});
it('should create reminders using the correct driver', async () => {
const today = DateTime.now().toJSDate();
const driver1 = new StubDriver('github');
const driver2 = new StubDriver('gitlab');
const drivers = { github: driver1, gitlab: driver2 };
const knuff = new Knuff({ repositories }, drivers);
const reminders = [
opi.wrap(bumpDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.set('repositories', ['acuminous/foo', 'acuminous/baz'])
.value(),
];
await knuff.process(reminders);
const githubReminders = driver1.repositories('acuminous/baz').reminders;
eq(githubReminders.length, 0);
const gitlabReminders = driver2.repositories('acuminous/baz').reminders;
eq(gitlabReminders.length, 1);
});
it('should suppress duplicate reminders', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].title, 'Bump Dependencies');
eq(created[0].body, 'Bump dependencies for all projects');
});
it('should respect the reminder time zone at the end of day prior to the reminder day', async () => {
setNow('2023-12-31T23:59:59.999', 'Australia/Sydney');
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', 'DTSTART;TZID=Australia/Sydney:20240101T000000;\nRRULE:FREQ=DAILY;COUNT=1'),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 0);
});
it('should respect the reminder time zone at the start of reminder day', async () => {
setNow('2024-01-01T00:00:00.000', 'Australia/Sydney');
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', 'DTSTART;TZID=Australia/Sydney:20240101T000000;\nRRULE:FREQ=DAILY;COUNT=1'),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].date.toISOString(), '2023-12-31T13:00:00.000Z');
eq(created[0].timezone, 'Australia/Sydney');
eq(created[0].labels[1], 'knuff:2024-01-01');
});
it('should respect the reminder time zone at the end of the reminder day', async () => {
setNow('2024-01-01T23:59:59.999', 'Australia/Sydney');
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', 'DTSTART;TZID=Australia/Sydney:20240101T000000;\nRRULE:FREQ=DAILY;COUNT=1'),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 1);
eq(created[0].date.toISOString(), '2023-12-31T13:00:00.000Z');
eq(created[0].timezone, 'Australia/Sydney');
eq(created[0].labels[1], 'knuff:2024-01-01');
});
it('should respect the reminder time zone at the start of day after the reminder day', async () => {
setNow('2024-01-02T00:00:00.000', 'Australia/Sydney');
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', 'DTSTART;TZID=Australia/Sydney:20240101T000000;\nRRULE:FREQ=DAILY;COUNT=1'),
];
await knuff.process(reminders);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 0);
});
});
describe('error handling', () => {
it('should report missing repositories', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.wrap(bumpDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.set('repositories', ['acuminous/meh'])
.value(),
];
const errors = [];
knuff.on('error', (error) => errors.push(error));
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, '1 of 1 reminders could not be processed');
return true;
});
eq(errors.length, 1);
eq(errors[0].message, "Reminder 'bump-dependencies' has an unknown repository 'acuminous/meh'");
});
it('should report invalid schedules', async () => {
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', 'DTSTART:20241201T08x000Z;\nRRULE:FREQ=DAILY;COUNT=1'),
];
const errors = [];
knuff.on('error', (error) => errors.push(error));
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, '1 of 1 reminders could not be processed');
return true;
});
eq(errors.length, 1);
eq(errors[0].message, "Reminder 'bump-dependencies' has an invalid schedule 'DTSTART:20241201T08x000Z;\nRRULE:FREQ=DAILY;COUNT=1'");
eq(errors[0].cause.message, 'Invalid UNTIL value: 20241201T08x000Z');
});
it('should continue on failure', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = [
opi.wrap(bumpDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.set('repositories', ['acuminous/meh'])
.value(),
opi.wrap(auditDependencies)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.value(),
];
const errors = [];
knuff.on('error', (error) => errors.push(error));
await rejects(() => knuff.process(reminders), (error) => {
eq(error.message, '1 of 2 reminders could not be processed');
return true;
});
eq(errors.length, 1);
eq(errors[0].message, "Reminder 'bump-dependencies' has an unknown repository 'acuminous/meh'");
const fooReminders = driver.repositories('acuminous/foo').reminders;
eq(fooReminders.length, 1);
eq(fooReminders[0].title, 'Audit Dependencies');
eq(fooReminders[0].body, 'Run npm audit --fix');
});
});
describe('stats', () => {
it('should report stats', async () => {
const today = DateTime.now().toJSDate();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const knuff = getKnuff();
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
opi.set(auditDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(tomorrow)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
const stats = await knuff.process(reminders);
eq(stats.reminders, 3);
eq(stats.due, 2);
eq(stats.duplicates, 1);
eq(stats.created, 1);
eq(stats.errors, 0);
});
});
describe('pretend', () => {
it('should pretend to create reminders', async () => {
const today = DateTime.now().toJSDate();
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
const driver = new StubDriver('github');
const knuff = getKnuff({ pretend: true }, { github: driver });
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
opi.set(auditDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(tomorrow)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
const stats = await knuff.process(reminders);
eq(stats.reminders, 3);
eq(stats.due, 2);
eq(stats.duplicates, 0);
eq(stats.created, 2);
eq(stats.errors, 0);
const created = driver.repositories('acuminous/foo').reminders;
eq(created.length, 0);
});
it('should support overriding now so that the current date can be controlled from parent modules using a different require cache', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
let called = false;
const now = () => {
called = true;
return Date.now();
};
const knuff = new Knuff({ repositories }, { github: driver }, now);
const reminders = [
opi.set(bumpDependencies, 'schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`),
];
await knuff.process(reminders);
eq(called, true);
});
});
describe('progress', () => {
it('should report progress every 10 reminders by default', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const knuff = getKnuff({}, { github: driver });
const reminders = new Array(100).fill().map((_, index) => {
return opi.wrap(bumpDependencies)
.set('id', `reminder-${index + 1}`)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.value();
});
const reports = [];
knuff.on('progress', (stats) => reports.push(stats));
await knuff.process(reminders);
eq(reports.length, 10);
eq(reports[0].due, 10);
});
it('should report progress every N reminders when specified', async () => {
const today = DateTime.now().toJSDate();
const driver = new StubDriver('github');
const drivers = { github: driver };
const knuff = new Knuff({ progress: 50, repositories }, drivers);
const reminders = new Array(100).fill().map((_, index) => {
return opi.wrap(bumpDependencies)
.set('id', `reminder-${index + 1}`)
.set('schedule', `DTSTART;TZID=Europe/London:${dtstart(today)};\nRRULE:FREQ=DAILY;COUNT=1`)
.value();
});
const reports = [];
knuff.on('progress', (stats) => reports.push(stats));
await knuff.process(reminders);
eq(reports.length, 2);
eq(reports[0].due, 50);
});
});
function getKnuff(config = {}, drivers = getDrivers()) {
return new Knuff({ ...config, repositories }, drivers);
}
function getDrivers() {
const driver = new StubDriver('github');
return { github: driver };
}
function dtstart(date) {
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
return `${year}${month}${day}T${hours}${minutes}${seconds}`;
}
function setNow(text, timezone) {
const now = DateTime.fromISO(text).setZone(timezone, { keepLocalTime: true }).toJSDate().getTime();
Settings.now = () => now;
}
});