You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
231 lines
8.3 KiB
TypeScript
231 lines
8.3 KiB
TypeScript
import { describe, it, beforeEach, expect } from 'bun:test';
|
|
import { Database } from 'bun:sqlite';
|
|
import { initializeDatabase } from '../../../src/db';
|
|
import { TaskService } from '../../../src/tasks/service';
|
|
import { RemindersService } from '../../../src/services/reminders';
|
|
import { ResponseQueue } from '../../../src/services/response-queue';
|
|
|
|
function toIso(dt: Date): string {
|
|
return dt.toISOString().replace('T', ' ').replace('Z', '');
|
|
}
|
|
|
|
describe('RemindersService', () => {
|
|
let memdb: Database;
|
|
const USER = '34600123456';
|
|
|
|
beforeEach(() => {
|
|
process.env.NODE_ENV = 'test';
|
|
process.env.TZ = 'Europe/Madrid';
|
|
process.env.REMINDERS_GRACE_MINUTES = '60';
|
|
|
|
memdb = new Database(':memory:');
|
|
initializeDatabase(memdb);
|
|
|
|
// Inyectar DB en servicios
|
|
(TaskService as any).dbInstance = memdb;
|
|
(RemindersService as any).dbInstance = memdb;
|
|
(ResponseQueue as any).dbInstance = memdb;
|
|
|
|
// Limpiar tablas entre tests por seguridad
|
|
memdb.exec(`DELETE FROM response_queue;`);
|
|
memdb.exec(`DELETE FROM user_preferences;`);
|
|
memdb.exec(`DELETE FROM users;`);
|
|
memdb.exec(`DELETE FROM tasks;`);
|
|
memdb.exec(`DELETE FROM task_assignments;`);
|
|
|
|
// Asegurar usuario
|
|
memdb.exec(`
|
|
INSERT INTO users (id, first_seen, last_seen)
|
|
VALUES ('${USER}', '${toIso(new Date())}', '${toIso(new Date())}')
|
|
ON CONFLICT(id) DO NOTHING;
|
|
`);
|
|
});
|
|
|
|
function insertPref(freq: 'daily' | 'weekly' | 'weekdays' | 'off', time: string = '08:30', last: string | null = null) {
|
|
memdb.prepare(`
|
|
INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at)
|
|
VALUES (?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%f', 'now'))
|
|
ON CONFLICT(user_id) DO UPDATE SET
|
|
reminder_freq = excluded.reminder_freq,
|
|
reminder_time = excluded.reminder_time,
|
|
last_reminded_on = excluded.last_reminded_on,
|
|
updated_at = excluded.updated_at
|
|
`).run(USER, freq, time, last);
|
|
}
|
|
|
|
function countQueued(): number {
|
|
const row = memdb.prepare(`SELECT COUNT(*) AS c FROM response_queue`).get() as any;
|
|
return Number(row?.c || 0);
|
|
}
|
|
|
|
function getLastReminded(): string | null {
|
|
const row = memdb.prepare(`SELECT last_reminded_on AS d FROM user_preferences WHERE user_id = ?`).get(USER) as any;
|
|
return row?.d ?? null;
|
|
}
|
|
|
|
it('envía recordatorio diario cuando hora alcanzada y no enviado hoy', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
// Lunes 2025-09-08 09:30 Europe/Madrid ≈ 07:30Z
|
|
const now = new Date('2025-09-08T07:30:00.000Z');
|
|
|
|
// Crear 1 tarea asignada al usuario
|
|
TaskService.createTask(
|
|
{ description: 'Tarea A', due_date: '2025-09-10', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
|
|
expect(countQueued()).toBe(1);
|
|
expect(getLastReminded()).toBe('2025-09-08'); // YYYY-MM-DD en TZ
|
|
});
|
|
|
|
it('no duplica recordatorio diario en el mismo día', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
const now = new Date('2025-09-08T07:30:00.000Z'); // dentro de ventana (≤ 60 min)
|
|
|
|
TaskService.createTask(
|
|
{ description: 'Tarea B', due_date: '2025-09-11', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(1);
|
|
|
|
// Segunda ejecución el mismo día (fuera de ventana, pero ya enviado hoy → no duplica)
|
|
await RemindersService.runOnce(new Date('2025-09-08T08:00:00.000Z'));
|
|
expect(countQueued()).toBe(1); // sin incremento
|
|
});
|
|
|
|
it('no envía diario antes de la hora configurada', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
const now = new Date('2025-09-08T05:00:00.000Z'); // 07:00 local < 08:30
|
|
|
|
TaskService.createTask(
|
|
{ description: 'Tarea C', due_date: '2025-09-12', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(0);
|
|
expect(getLastReminded()).toBeNull();
|
|
});
|
|
|
|
it('no envía si el usuario no tiene tareas; no marca last_reminded_on', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
const now = new Date('2025-09-08T07:40:00.000Z'); // ≥ 08:30 local
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(0);
|
|
expect(getLastReminded()).toBeNull();
|
|
});
|
|
|
|
it('weekly: solo envía en lunes y a la hora, no en martes', async () => {
|
|
insertPref('weekly', '08:30', null);
|
|
|
|
// Crear 1 tarea
|
|
TaskService.createTask(
|
|
{ description: 'Tarea W', due_date: '2025-09-15', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
// Lunes (Mon) 2025-09-08 07:00 local (05:00Z) -> antes de hora: no envía
|
|
await RemindersService.runOnce(new Date('2025-09-08T05:00:00.000Z'));
|
|
expect(countQueued()).toBe(0);
|
|
expect(getLastReminded()).toBeNull();
|
|
|
|
// Lunes 09:30 local (07:30Z) -> envía
|
|
await RemindersService.runOnce(new Date('2025-09-08T07:30:00.000Z'));
|
|
expect(countQueued()).toBe(1);
|
|
expect(getLastReminded()).toBe('2025-09-08');
|
|
|
|
// Martes 2025-09-09 ≥ hora -> no debe enviar (weekly solo lunes)
|
|
await RemindersService.runOnce(new Date('2025-09-09T07:40:00.000Z'));
|
|
expect(countQueued()).toBe(1); // sin incremento
|
|
// last_reminded_on permanece en lunes
|
|
expect(getLastReminded()).toBe('2025-09-08');
|
|
});
|
|
|
|
it('incluye “… y X más” cuando hay más de 10 tareas (tope de listado)', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
const now = new Date('2025-09-08T07:30:00.000Z'); // dentro de ventana
|
|
|
|
// Crear 12 tareas asignadas al usuario
|
|
for (let i = 0; i < 12; i++) {
|
|
TaskService.createTask(
|
|
{ description: `Tarea ${i + 1}`, due_date: '2025-09-20', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
}
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(1);
|
|
|
|
const row = memdb.prepare(`SELECT message FROM response_queue LIMIT 1`).get() as any;
|
|
const msg: string = String(row?.message || '');
|
|
expect(msg.includes('… y 2 más')).toBe(true);
|
|
});
|
|
|
|
it('weekdays: envía en martes a la hora configurada', async () => {
|
|
insertPref('weekdays', '08:00', null);
|
|
// Martes 2025-09-09 08:05 Europe/Madrid ≈ 06:05Z
|
|
const now = new Date('2025-09-09T06:05:00.000Z');
|
|
|
|
// Crear 1 tarea asignada al usuario
|
|
TaskService.createTask(
|
|
{ description: 'Tarea LV', due_date: '2025-09-10', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(1);
|
|
expect(getLastReminded()).toBe('2025-09-09');
|
|
});
|
|
|
|
it('weekdays: no envía en sábado', async () => {
|
|
insertPref('weekdays', '08:00', null);
|
|
// Sábado 2025-09-13 08:05 Europe/Madrid ≈ 06:05Z
|
|
const now = new Date('2025-09-13T06:05:00.000Z');
|
|
|
|
TaskService.createTask(
|
|
{ description: 'Tarea LV2', due_date: '2025-09-14', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(0);
|
|
expect(getLastReminded()).toBeNull();
|
|
});
|
|
|
|
it('no envía fuera de la ventana de gracia tras reinicio tardío', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
// 08:30 local + 90 min ≈ 10:00 local => 08:00Z en CEST
|
|
const now = new Date('2025-09-08T08:00:00.000Z');
|
|
|
|
// Crear una tarea asignada después de la hora configurada
|
|
TaskService.createTask(
|
|
{ description: 'Tarea fuera de ventana', due_date: '2025-09-20', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(0);
|
|
expect(getLastReminded()).toBeNull();
|
|
});
|
|
|
|
it('envía dentro de la ventana de gracia si hay tareas', async () => {
|
|
insertPref('daily', '08:30', null);
|
|
// 08:30 local + 45 min ≈ 09:15 local => 07:15Z en CEST
|
|
const now = new Date('2025-09-08T07:15:00.000Z');
|
|
|
|
TaskService.createTask(
|
|
{ description: 'Tarea dentro de ventana', due_date: '2025-09-20', group_id: null, created_by: USER },
|
|
[{ user_id: USER, assigned_by: USER }]
|
|
);
|
|
|
|
await RemindersService.runOnce(now);
|
|
expect(countQueued()).toBe(1);
|
|
expect(getLastReminded()).toBe('2025-09-08');
|
|
});
|
|
});
|