81 lines
2.6 KiB
JavaScript
81 lines
2.6 KiB
JavaScript
import { randomUUID } from 'node:crypto';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { db } from '../src/db/knex.js';
|
|
import { runReminders, selectDrafts, selectSurveys } from '../src/services/reminders.js';
|
|
import { truncateTables } from './truncate.js';
|
|
|
|
const day = 24 * 60 * 60 * 1000;
|
|
|
|
/**
|
|
* @param {number} days
|
|
* @param {Date} [from]
|
|
*/
|
|
function daysAgo(days, from = new Date()) {
|
|
return new Date(from.getTime() - days * day);
|
|
}
|
|
|
|
describe('reminder selection', () => {
|
|
const now = new Date('2026-09-30T10:00:00.000Z');
|
|
|
|
it('selects a draft untouched for 3 days and skips recent, maxed, and rejected rows', () => {
|
|
const applications = [
|
|
{ id: 1, status: 'DRAFT', updated_at: daysAgo(3, now) },
|
|
{ id: 2, status: 'DRAFT', updated_at: daysAgo(4, now) },
|
|
{ id: 3, status: 'DRAFT', updated_at: daysAgo(10, now) },
|
|
{ id: 4, status: 'REJECTED', updated_at: daysAgo(10, now) },
|
|
{ id: 5, status: 'SUBMITTED', updated_at: daysAgo(10, now) },
|
|
];
|
|
const reminders = new Map([
|
|
[2, [{ status: 'logged', created_at: daysAgo(1, now) }]],
|
|
[
|
|
3,
|
|
[
|
|
{ status: 'logged', created_at: daysAgo(9, now) },
|
|
{ status: 'logged', created_at: daysAgo(6, now) },
|
|
{ status: 'logged', created_at: daysAgo(3, now) },
|
|
],
|
|
],
|
|
]);
|
|
const selected = selectDrafts(applications, reminders, now, 3).map((row) => row.id);
|
|
expect(selected).toEqual([1]);
|
|
});
|
|
|
|
it('selects an unfinished approved survey after 5 days and skips a completed one', () => {
|
|
const applications = [
|
|
{ id: 1, status: 'APPROVED', decided_at: daysAgo(5, now) },
|
|
{ id: 2, status: 'APPROVED', decided_at: daysAgo(8, now) },
|
|
];
|
|
const selected = selectSurveys(applications, new Set([2]), new Map(), now, 5).map(
|
|
(row) => row.id,
|
|
);
|
|
expect(selected).toEqual([1]);
|
|
});
|
|
});
|
|
|
|
describe('reminder delivery', () => {
|
|
afterEach(async () => {
|
|
await truncateTables();
|
|
});
|
|
|
|
it('records one reminder and does not send it again the same day', async () => {
|
|
const [id] = await db('applications').insert({
|
|
public_id: randomUUID(),
|
|
email: 'draft@example.com',
|
|
status: 'DRAFT',
|
|
full_name: 'Asha Rao',
|
|
version: 1,
|
|
});
|
|
await db('applications')
|
|
.where({ id })
|
|
.update({ updated_at: daysAgo(4) });
|
|
|
|
await runReminders();
|
|
await runReminders();
|
|
|
|
const emails = await db('email_log').where({ application_id: id, template: 'reminder_draft' });
|
|
expect(emails).toHaveLength(1);
|
|
expect(emails[0].reminder_count).toBe(1);
|
|
expect(emails[0].status).not.toBe('failed');
|
|
});
|
|
});
|