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.
		
		
		
		
		
			
		
			
				
	
	
		
			117 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			117 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			TypeScript
		
	
| import { describe, it, beforeEach, afterEach, 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 { AllowedGroups } from '../../../src/services/allowed-groups';
 | |
| import { ResponseQueue } from '../../../src/services/response-queue';
 | |
| 
 | |
| function seedGroup(db: Database, groupId: string) {
 | |
|   const cols = db.query(`PRAGMA table_info(groups)`).all() as any[];
 | |
|   const values: Record<string, any> = {};
 | |
|   const nowIso = new Date().toISOString().replace('T', ' ').replace('Z', '');
 | |
| 
 | |
|   for (const c of cols) {
 | |
|     const name = String(c.name);
 | |
|     const type = String(c.type || '').toUpperCase();
 | |
|     const notnull = Number(c.notnull || 0) === 1;
 | |
|     const hasDefault = c.dflt_value != null;
 | |
| 
 | |
|     if (name === 'id') { values[name] = groupId; continue; }
 | |
|     if (name === 'name' || name === 'title' || name === 'subject') { values[name] = 'Test Group'; continue; }
 | |
|     if (name === 'created_by') { values[name] = 'tester'; continue; }
 | |
|     if (name.endsWith('_at')) { values[name] = nowIso; continue; }
 | |
|     if (name === 'is_active' || name === 'active') { values[name] = 1; continue; }
 | |
| 
 | |
|     if (notnull && !hasDefault) {
 | |
|       if (type.includes('INT')) values[name] = 1;
 | |
|       else if (type.includes('REAL')) values[name] = 0;
 | |
|       else values[name] = 'N/A';
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!('id' in values)) values['id'] = groupId;
 | |
| 
 | |
|   const colsList = Object.keys(values);
 | |
|   const placeholders = colsList.map(() => '?').join(', ');
 | |
|   const sql = `INSERT OR REPLACE INTO groups (${colsList.join(', ')}) VALUES (${placeholders})`;
 | |
|   db.prepare(sql).run(...colsList.map(k => values[k]));
 | |
| }
 | |
| 
 | |
| describe('RemindersService - gating por grupos en modo enforce', () => {
 | |
|   const envBackup = process.env;
 | |
|   let memdb: Database;
 | |
|   let originalAdd: any;
 | |
|   let sent: any[] = [];
 | |
| 
 | |
|   beforeEach(() => {
 | |
|     process.env = { ...envBackup, NODE_ENV: 'test', GROUP_GATING_MODE: 'enforce', TZ: 'Europe/Madrid', REMINDERS_GRACE_MINUTES: '60' };
 | |
|     memdb = new Database(':memory:');
 | |
|     initializeDatabase(memdb);
 | |
|     (TaskService as any).dbInstance = memdb;
 | |
|     (RemindersService as any).dbInstance = memdb;
 | |
|     (AllowedGroups as any).dbInstance = memdb;
 | |
| 
 | |
|     // Stub de ResponseQueue
 | |
|     originalAdd = (ResponseQueue as any).add;
 | |
|     (ResponseQueue as any).add = async (msgs: any[]) => { sent.push(...msgs); };
 | |
|     sent = [];
 | |
| 
 | |
|     // Asegurar usuario receptor para satisfacer la FK de user_preferences
 | |
|     const iso = new Date().toISOString().replace('T', ' ').replace('Z', '');
 | |
|     memdb.exec(`
 | |
|       INSERT INTO users (id, first_seen, last_seen)
 | |
|       VALUES ('34600123456', '${iso}', '${iso}')
 | |
|       ON CONFLICT(id) DO NOTHING
 | |
|     `);
 | |
| 
 | |
|     // Preferencias del usuario receptor
 | |
|     memdb.exec(`
 | |
|       INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at)
 | |
|       VALUES ('34600123456', 'daily', '09:00', NULL, 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 = NULL,
 | |
|         updated_at = excluded.updated_at
 | |
|     `);
 | |
| 
 | |
|     // Sembrar grupos y estados
 | |
|     seedGroup(memdb, 'ok@g.us');
 | |
|     seedGroup(memdb, 'na@g.us');
 | |
|     AllowedGroups.setStatus('ok@g.us', 'allowed', 'OK');
 | |
|     AllowedGroups.setStatus('na@g.us', 'allowed', 'NA'); // inicialmente allowed para que las tareas se creen con group_id
 | |
| 
 | |
|     // Crear dos tareas, una en cada grupo, asignadas al usuario
 | |
|     TaskService.createTask(
 | |
|       { description: 'Tarea OK', created_by: '34600123456', group_id: 'ok@g.us', due_date: null },
 | |
|       [{ user_id: '34600123456', assigned_by: '34600123456' }]
 | |
|     );
 | |
|     TaskService.createTask(
 | |
|       { description: 'Tarea NA', created_by: '34600123456', group_id: 'na@g.us', due_date: null },
 | |
|       [{ user_id: '34600123456', assigned_by: '34600123456' }]
 | |
|     );
 | |
| 
 | |
|     // Cambiar a bloqueado uno de los grupos antes de correr los recordatorios
 | |
|     AllowedGroups.setStatus('na@g.us', 'blocked', 'NA');
 | |
|   });
 | |
| 
 | |
|   afterEach(() => {
 | |
|     (ResponseQueue as any).add = originalAdd;
 | |
|     memdb.close();
 | |
|     process.env = envBackup;
 | |
|   });
 | |
| 
 | |
|   it('omite tareas de grupos no allowed en los recordatorios', async () => {
 | |
|     const now = new Date('2025-09-08T07:40:00.000Z'); // ≥ 09:00 Europe/Madrid en un lunes y dentro de la ventana
 | |
|     await RemindersService.runOnce(now);
 | |
| 
 | |
|     expect(sent.length).toBe(1);
 | |
|     const msg = String(sent[0].message);
 | |
| 
 | |
|     // Debe incluir solo la tarea del grupo allowed y omitir la del bloqueado
 | |
|     expect(msg).toContain('Tarea OK');
 | |
|     expect(msg).not.toContain('Tarea NA');
 | |
|   });
 | |
| });
 |