|  |  | import { describe, test, expect, beforeEach, afterEach, beforeAll, afterAll } from 'bun:test';
 | 
						
						
						
							|  |  | import { Database } from 'bun:sqlite';
 | 
						
						
						
							|  |  | import { WebhookServer } from '../../src/server';
 | 
						
						
						
							|  |  | import { ResponseQueue } from '../../src/services/response-queue';
 | 
						
						
						
							|  |  | import { GroupSyncService } from '../../src/services/group-sync';
 | 
						
						
						
							|  |  | import { initializeDatabase, ensureUserExists } from '../../src/db';
 | 
						
						
						
							|  |  | import { TaskService } from '../../src/tasks/service';
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | // Simulated ResponseQueue for testing (in-memory array)
 | 
						
						
						
							|  |  | let simulatedQueue: any[] = [];
 | 
						
						
						
							|  |  | let originalAdd: any;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | class SimulatedResponseQueue {
 | 
						
						
						
							|  |  | 	static async add(responses: any[]) {
 | 
						
						
						
							|  |  | 		simulatedQueue.push(...responses);
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	static getQueue() {
 | 
						
						
						
							|  |  | 		return simulatedQueue;
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	static clear() {
 | 
						
						
						
							|  |  | 		simulatedQueue = [];
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | }
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | // Test database instance
 | 
						
						
						
							|  |  | let testDb: Database;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | beforeAll(() => {
 | 
						
						
						
							|  |  | 	// Create in-memory test database
 | 
						
						
						
							|  |  | 	testDb = new Database(':memory:');
 | 
						
						
						
							|  |  | 	// Initialize schema
 | 
						
						
						
							|  |  | 	initializeDatabase(testDb);
 | 
						
						
						
							|  |  | 	// Guardar implementación original de ResponseQueue.add para restaurar después
 | 
						
						
						
							|  |  | 	originalAdd = (ResponseQueue as any).add;
 | 
						
						
						
							|  |  | });
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | afterAll(() => {
 | 
						
						
						
							|  |  | 	(ResponseQueue as any).add = originalAdd;
 | 
						
						
						
							|  |  | 	// Close the test database
 | 
						
						
						
							|  |  | 	testDb.close();
 | 
						
						
						
							|  |  | });
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | beforeEach(() => {
 | 
						
						
						
							|  |  | 	// Clear simulated queue
 | 
						
						
						
							|  |  | 	SimulatedResponseQueue.clear();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Replace ResponseQueue with simulated version
 | 
						
						
						
							|  |  | 	(ResponseQueue as any).add = SimulatedResponseQueue.add;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Inject testDb for WebhookServer to use
 | 
						
						
						
							|  |  | 	WebhookServer.dbInstance = testDb;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Inject testDb for GroupSyncService to use
 | 
						
						
						
							|  |  | 	GroupSyncService.dbInstance = testDb;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Inject testDb for TaskService to use
 | 
						
						
						
							|  |  | 	(TaskService as any).dbInstance = testDb;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Ensure database is initialized (recreates tables if dropped)
 | 
						
						
						
							|  |  | 	initializeDatabase(testDb);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Reset database state between tests (borrar raíz primero; ON DELETE CASCADE limpia assignments)
 | 
						
						
						
							|  |  | 	testDb.exec('DELETE FROM response_queue');
 | 
						
						
						
							|  |  | 	try { testDb.exec('DELETE FROM task_origins'); } catch { }
 | 
						
						
						
							|  |  | 	testDb.exec('DELETE FROM tasks');
 | 
						
						
						
							|  |  | 	testDb.exec('DELETE FROM users');
 | 
						
						
						
							|  |  | 	testDb.exec('DELETE FROM groups');
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Insert test data for active group
 | 
						
						
						
							|  |  | 	testDb.exec(`
 | 
						
						
						
							|  |  |     INSERT OR IGNORE INTO groups (id, community_id, name, active)
 | 
						
						
						
							|  |  |     VALUES ('group-id@g.us', 'test-community', 'Test Group', 1)
 | 
						
						
						
							|  |  |   `);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Populate active groups cache with test data
 | 
						
						
						
							|  |  | 	GroupSyncService['cacheActiveGroups']();
 | 
						
						
						
							|  |  | });
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | describe('WebhookServer', () => {
 | 
						
						
						
							|  |  | 	const envBackup = process.env;
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	beforeEach(() => {
 | 
						
						
						
							|  |  | 		process.env = {
 | 
						
						
						
							|  |  | 			...envBackup,
 | 
						
						
						
							|  |  | 			INSTANCE_NAME: 'test-instance',
 | 
						
						
						
							|  |  | 			NODE_ENV: 'test'
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	afterEach(() => {
 | 
						
						
						
							|  |  | 		process.env = envBackup;
 | 
						
						
						
							|  |  | 		(ResponseQueue as any).add = originalAdd;
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	const createTestRequest = (payload: any) =>
 | 
						
						
						
							|  |  | 		new Request('http://localhost:3007', {
 | 
						
						
						
							|  |  | 			method: 'POST',
 | 
						
						
						
							|  |  | 			headers: { 'Content-Type': 'application/json' },
 | 
						
						
						
							|  |  | 			body: JSON.stringify(payload)
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should reject non-POST requests', async () => {
 | 
						
						
						
							|  |  | 		const request = new Request('http://localhost:3007', { method: 'GET' });
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(405);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should require JSON content type', async () => {
 | 
						
						
						
							|  |  | 		const request = new Request('http://localhost:3007', {
 | 
						
						
						
							|  |  | 			method: 'POST',
 | 
						
						
						
							|  |  | 			headers: { 'Content-Type': 'text/plain' }
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(400);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should validate payload structure', async () => {
 | 
						
						
						
							|  |  | 		const invalidPayloads = [
 | 
						
						
						
							|  |  | 			{},
 | 
						
						
						
							|  |  | 			{ event: null },
 | 
						
						
						
							|  |  | 			{ event: 'messages.upsert', instance: null }
 | 
						
						
						
							|  |  | 		];
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		for (const invalidPayload of invalidPayloads) {
 | 
						
						
						
							|  |  | 			const request = createTestRequest(invalidPayload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(400);
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should verify instance name', async () => {
 | 
						
						
						
							|  |  | 		process.env.TEST_VERIFY_INSTANCE = 'true';
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'wrong-instance',
 | 
						
						
						
							|  |  | 			data: {}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(403);
 | 
						
						
						
							|  |  | 		delete process.env.TEST_VERIFY_INSTANCE;
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle valid messages.upsert', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore empty message content', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle very long messages', async () => {
 | 
						
						
						
							|  |  | 		const longMessage = '/tarea nueva ' + 'A'.repeat(5000);
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: longMessage }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle messages with special characters and emojis', async () => {
 | 
						
						
						
							|  |  | 		const specialMessage = '/tarea nueva Test 😊 你好 @#$%^&*()';
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: specialMessage }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore non-/tarea commands', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/othercommand test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore message with mentions but no command', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: {
 | 
						
						
						
							|  |  | 					conversation: 'Hello everyone!',
 | 
						
						
						
							|  |  | 					contextInfo: {
 | 
						
						
						
							|  |  | 						mentionedJid: ['1234567890@s.whatsapp.net']
 | 
						
						
						
							|  |  | 					}
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore media attachment messages', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: {
 | 
						
						
						
							|  |  | 					imageMessage: { caption: 'This is an image' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should process command from extendedTextMessage', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: {
 | 
						
						
						
							|  |  | 					extendedTextMessage: { text: '/t n Test ext' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should process command from image caption when caption starts with a command', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: {
 | 
						
						
						
							|  |  | 					imageMessage: { caption: '/t n From caption' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle requests on configured port', async () => {
 | 
						
						
						
							|  |  | 		const originalPort = process.env.PORT;
 | 
						
						
						
							|  |  | 		process.env.PORT = '3007';
 | 
						
						
						
							|  |  | 		// Satisfacer validación de entorno en start()
 | 
						
						
						
							|  |  | 		const prevEnv = {
 | 
						
						
						
							|  |  | 			EVOLUTION_API_URL: process.env.EVOLUTION_API_URL,
 | 
						
						
						
							|  |  | 			EVOLUTION_API_KEY: process.env.EVOLUTION_API_KEY,
 | 
						
						
						
							|  |  | 			EVOLUTION_API_INSTANCE: process.env.EVOLUTION_API_INSTANCE,
 | 
						
						
						
							|  |  | 			CHATBOT_PHONE_NUMBER: process.env.CHATBOT_PHONE_NUMBER,
 | 
						
						
						
							|  |  | 			WEBHOOK_URL: process.env.WEBHOOK_URL
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		process.env.EVOLUTION_API_URL = 'http://localhost:3000';
 | 
						
						
						
							|  |  | 		process.env.EVOLUTION_API_KEY = 'test-key';
 | 
						
						
						
							|  |  | 		process.env.EVOLUTION_API_INSTANCE = 'test-instance';
 | 
						
						
						
							|  |  | 		process.env.CHATBOT_PHONE_NUMBER = '9999999999';
 | 
						
						
						
							|  |  | 		process.env.WEBHOOK_URL = 'http://localhost:3007';
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		try {
 | 
						
						
						
							|  |  | 			const server = await WebhookServer.start();
 | 
						
						
						
							|  |  | 			const response = await fetch('http://localhost:3007/health');
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			server.stop();
 | 
						
						
						
							|  |  | 		} finally {
 | 
						
						
						
							|  |  | 			process.env.PORT = originalPort;
 | 
						
						
						
							|  |  | 			process.env.EVOLUTION_API_URL = prevEnv.EVOLUTION_API_URL;
 | 
						
						
						
							|  |  | 			process.env.EVOLUTION_API_KEY = prevEnv.EVOLUTION_API_KEY;
 | 
						
						
						
							|  |  | 			process.env.EVOLUTION_API_INSTANCE = prevEnv.EVOLUTION_API_INSTANCE;
 | 
						
						
						
							|  |  | 			process.env.CHATBOT_PHONE_NUMBER = prevEnv.CHATBOT_PHONE_NUMBER;
 | 
						
						
						
							|  |  | 			process.env.WEBHOOK_URL = prevEnv.WEBHOOK_URL;
 | 
						
						
						
							|  |  | 		}
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	function getFutureDate(days: number): string {
 | 
						
						
						
							|  |  | 		const date = new Date();
 | 
						
						
						
							|  |  | 		date.setDate(date.getDate() + days);
 | 
						
						
						
							|  |  | 		return date.toISOString().split('T')[0];
 | 
						
						
						
							|  |  | 	}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	describe('/tarea command logging', () => {
 | 
						
						
						
							|  |  | 		test('should log basic /tarea command', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: 'user123@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Check that a response was queued (indicating command processing)
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should log command with due date', async () => {
 | 
						
						
						
							|  |  | 			const futureDate = getFutureDate(3); // Get date 3 days in future
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: 'user123@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: {
 | 
						
						
						
							|  |  | 						conversation: `/tarea nueva Finish project @user2 ${futureDate}`,
 | 
						
						
						
							|  |  | 						contextInfo: {
 | 
						
						
						
							|  |  | 							mentionedJid: ['user2@s.whatsapp.net']
 | 
						
						
						
							|  |  | 						}
 | 
						
						
						
							|  |  | 					}
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify command processing by checking queue
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle XSS/SQL injection attempts', async () => {
 | 
						
						
						
							|  |  | 		const maliciousMessage = `/tarea nueva <script>alert('xss')</script>'; DROP TABLE tasks; --`;
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: maliciousMessage }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle multiple dates in command (use last one as due date)', async () => {
 | 
						
						
						
							|  |  | 		const futureDate1 = getFutureDate(3);
 | 
						
						
						
							|  |  | 		const futureDate2 = getFutureDate(5);
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: `/tarea nueva Test task ${futureDate1} some text ${futureDate2}` }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore past dates as due dates', async () => {
 | 
						
						
						
							|  |  | 		const pastDate = '2020-01-01';
 | 
						
						
						
							|  |  | 		const futureDate = getFutureDate(2);
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: `/tarea nueva Test task ${pastDate} more text ${futureDate}` }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle multiple past dates correctly', async () => {
 | 
						
						
						
							|  |  | 		const pastDate1 = '2020-01-01';
 | 
						
						
						
							|  |  | 		const pastDate2 = '2021-01-01';
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: `/tarea nueva Test task ${pastDate1} and ${pastDate2}` }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should handle mixed valid and invalid date formats', async () => {
 | 
						
						
						
							|  |  | 		const futureDate = getFutureDate(2);
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'sender-id@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: `/tarea nueva Test task 2023-13-01 (invalid) ${futureDate} 25/12/2023 (invalid)` }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should normalize sender ID before processing', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: '1234567890:12@s.whatsapp.net' // ID with participant
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore messages with invalid sender ID', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: 'invalid-id!' // Invalid ID
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ensure user exists and use normalized ID', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		// Verify user was created in real database
 | 
						
						
						
							|  |  | 		const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890');
 | 
						
						
						
							|  |  | 		expect(user).toBeDefined();
 | 
						
						
						
							|  |  | 		expect(user.id).toBe('1234567890');
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	test('should ignore messages if user creation fails', async () => {
 | 
						
						
						
							|  |  | 		const payload = {
 | 
						
						
						
							|  |  | 			event: 'messages.upsert',
 | 
						
						
						
							|  |  | 			instance: 'test-instance',
 | 
						
						
						
							|  |  | 			data: {
 | 
						
						
						
							|  |  | 				key: {
 | 
						
						
						
							|  |  | 					remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					participant: null // Invalid participant
 | 
						
						
						
							|  |  | 				},
 | 
						
						
						
							|  |  | 				message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		};
 | 
						
						
						
							|  |  | 		const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 		const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 		expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 		expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		// Verify no user was created
 | 
						
						
						
							|  |  | 		const userCount = testDb.query("SELECT COUNT(*) as count FROM users").get();
 | 
						
						
						
							|  |  | 		expect(userCount.count).toBe(0);
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	// Integration tests with real database
 | 
						
						
						
							|  |  | 	describe('User validation in handleMessageUpsert', () => {
 | 
						
						
						
							|  |  | 		test('should proceed with valid user', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify user was created in real database
 | 
						
						
						
							|  |  | 			const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890');
 | 
						
						
						
							|  |  | 			expect(user).toBeDefined();
 | 
						
						
						
							|  |  | 			expect(user.id).toBe('1234567890');
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should ignore message if user validation fails', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: 'invalid!user@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify no user was created
 | 
						
						
						
							|  |  | 			const userCount = testDb.query("SELECT COUNT(*) as count FROM users").get();
 | 
						
						
						
							|  |  | 			expect(userCount.count).toBe(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should handle database errors during user validation', async () => {
 | 
						
						
						
							|  |  | 			// Force a database error by corrupting the database state
 | 
						
						
						
							|  |  | 			testDb.exec('DROP TABLE users');
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Reinitialize database for subsequent tests (force full migration)
 | 
						
						
						
							|  |  | 			testDb.exec('DROP TABLE IF EXISTS schema_migrations');
 | 
						
						
						
							|  |  | 			initializeDatabase(testDb);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should integrate user validation completely in handleMessageUpsert with valid user', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify user was created/updated in database
 | 
						
						
						
							|  |  | 			const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890');
 | 
						
						
						
							|  |  | 			expect(user).toBeDefined();
 | 
						
						
						
							|  |  | 			expect(user.id).toBe('1234567890');
 | 
						
						
						
							|  |  | 			expect(user.first_seen).toBeDefined();
 | 
						
						
						
							|  |  | 			expect(user.last_seen).toBeDefined();
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should use normalized ID in command service', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890:12@s.whatsapp.net' // Raw ID with participant
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify that a response was queued, indicating command processing
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should handle end-to-end flow with valid user and command processing', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test task' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify user was created/updated
 | 
						
						
						
							|  |  | 			const user = testDb.query("SELECT * FROM users WHERE id = ?").get('1234567890');
 | 
						
						
						
							|  |  | 			expect(user).toBeDefined();
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// Verify that a response was queued
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	describe('Group validation in handleMessageUpsert', () => {
 | 
						
						
						
							|  |  | 		test('should ignore messages from inactive groups', async () => {
 | 
						
						
						
							|  |  | 			// Insert inactive group
 | 
						
						
						
							|  |  | 			testDb.exec(`
 | 
						
						
						
							|  |  |         INSERT OR REPLACE INTO groups (id, community_id, name, active)
 | 
						
						
						
							|  |  |         VALUES ('inactive-group@g.us', 'test-community', 'Inactive Group', 0)
 | 
						
						
						
							|  |  |       `);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'inactive-group@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBe(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should proceed with messages from active groups', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/tarea nueva Test' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should accept /t alias and process command', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t n Tarea alias hoy' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 			expect(SimulatedResponseQueue.getQueue().length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should never send responses to the group (DM only policy)', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t n Probar silencio grupo mañana' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const request = createTestRequest(payload);
 | 
						
						
						
							|  |  | 			await WebhookServer.handleRequest(request);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const out = SimulatedResponseQueue.getQueue();
 | 
						
						
						
							|  |  | 			expect(out.length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 			for (const r of out) {
 | 
						
						
						
							|  |  | 				expect(r.recipient.endsWith('@g.us')).toBe(false);
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 	describe('Advanced listings via WebhookServer', () => {
 | 
						
						
						
							|  |  | 		test('should process "/t ver sin" in group as DM-only with pagination line', async () => {
 | 
						
						
						
							|  |  | 			// 12 sin dueño en el grupo activo
 | 
						
						
						
							|  |  | 			for (let i = 1; i <= 12; i++) {
 | 
						
						
						
							|  |  | 				TaskService.createTask({
 | 
						
						
						
							|  |  | 					description: `Sin dueño ${i}`,
 | 
						
						
						
							|  |  | 					due_date: '2025-12-31',
 | 
						
						
						
							|  |  | 					group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					created_by: '9999999999',
 | 
						
						
						
							|  |  | 				});
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 			// 2 asignadas (no deben aparecer en "sin")
 | 
						
						
						
							|  |  | 			TaskService.createTask({
 | 
						
						
						
							|  |  | 				description: 'Asignada 1',
 | 
						
						
						
							|  |  | 				due_date: '2025-10-10',
 | 
						
						
						
							|  |  | 				group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 				created_by: '1111111111',
 | 
						
						
						
							|  |  | 			}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			TaskService.createTask({
 | 
						
						
						
							|  |  | 				description: 'Asignada 2',
 | 
						
						
						
							|  |  | 				due_date: '2025-10-11',
 | 
						
						
						
							|  |  | 				group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 				created_by: '1111111111',
 | 
						
						
						
							|  |  | 			}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t ver sin' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const out = SimulatedResponseQueue.getQueue();
 | 
						
						
						
							|  |  | 			expect(out.length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 			for (const r of out) {
 | 
						
						
						
							|  |  | 				expect(r.recipient.endsWith('@g.us')).toBe(false);
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 			const msg = out.map(x => x.message).join('\n');
 | 
						
						
						
							|  |  | 			expect(msg).toContain('No respondo en grupos.');
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should process "/t ver sin" in DM returning instruction', async () => {
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: '1234567890@s.whatsapp.net', // DM
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t ver sin' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const out = SimulatedResponseQueue.getQueue();
 | 
						
						
						
							|  |  | 			expect(out.length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 			const msg = out.map(x => x.message).join('\n');
 | 
						
						
						
							|  |  | 			expect(msg).toContain('No tienes tareas pendientes.');
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should process "/t ver todos" in group showing "Tus tareas" + "Sin dueño (grupo actual)" with pagination in unassigned section', async () => {
 | 
						
						
						
							|  |  | 			// Tus tareas (2 asignadas)
 | 
						
						
						
							|  |  | 			TaskService.createTask({
 | 
						
						
						
							|  |  | 				description: 'Mi Tarea 1',
 | 
						
						
						
							|  |  | 				due_date: '2025-10-10',
 | 
						
						
						
							|  |  | 				group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 				created_by: '2222222222',
 | 
						
						
						
							|  |  | 			}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			TaskService.createTask({
 | 
						
						
						
							|  |  | 				description: 'Mi Tarea 2',
 | 
						
						
						
							|  |  | 				due_date: '2025-10-11',
 | 
						
						
						
							|  |  | 				group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 				created_by: '2222222222',
 | 
						
						
						
							|  |  | 			}, [{ user_id: '1234567890', assigned_by: '2222222222' }]);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			// 12 sin dueño para provocar paginación
 | 
						
						
						
							|  |  | 			for (let i = 1; i <= 12; i++) {
 | 
						
						
						
							|  |  | 				TaskService.createTask({
 | 
						
						
						
							|  |  | 					description: `Sin dueño ${i}`,
 | 
						
						
						
							|  |  | 					due_date: '2025-12-31',
 | 
						
						
						
							|  |  | 					group_id: 'group-id@g.us',
 | 
						
						
						
							|  |  | 					created_by: '9999999999',
 | 
						
						
						
							|  |  | 				});
 | 
						
						
						
							|  |  | 			}
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: 'group-id@g.us',
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t ver todos' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const out = SimulatedResponseQueue.getQueue();
 | 
						
						
						
							|  |  | 			expect(out.length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 			const msg = out.map(x => x.message).join('\n');
 | 
						
						
						
							|  |  | 			expect(msg).toContain('No respondo en grupos.');
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 		test('should process "/t ver todos" in DM showing "Tus tareas" + instructive note', async () => {
 | 
						
						
						
							|  |  | 			TaskService.createTask({
 | 
						
						
						
							|  |  | 				description: 'Mi Tarea A',
 | 
						
						
						
							|  |  | 				due_date: '2025-11-20',
 | 
						
						
						
							|  |  | 				group_id: 'group-2@g.us',
 | 
						
						
						
							|  |  | 				created_by: '1111111111',
 | 
						
						
						
							|  |  | 			}, [{ user_id: '1234567890', assigned_by: '1111111111' }]);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const payload = {
 | 
						
						
						
							|  |  | 				event: 'messages.upsert',
 | 
						
						
						
							|  |  | 				instance: 'test-instance',
 | 
						
						
						
							|  |  | 				data: {
 | 
						
						
						
							|  |  | 					key: {
 | 
						
						
						
							|  |  | 						remoteJid: '1234567890@s.whatsapp.net', // DM
 | 
						
						
						
							|  |  | 						participant: '1234567890@s.whatsapp.net'
 | 
						
						
						
							|  |  | 					},
 | 
						
						
						
							|  |  | 					message: { conversation: '/t ver todos' }
 | 
						
						
						
							|  |  | 				}
 | 
						
						
						
							|  |  | 			};
 | 
						
						
						
							|  |  | 			const response = await WebhookServer.handleRequest(createTestRequest(payload));
 | 
						
						
						
							|  |  | 			expect(response.status).toBe(200);
 | 
						
						
						
							|  |  | 
 | 
						
						
						
							|  |  | 			const out = SimulatedResponseQueue.getQueue();
 | 
						
						
						
							|  |  | 			expect(out.length).toBeGreaterThan(0);
 | 
						
						
						
							|  |  | 			const msg = out.map(x => x.message).join('\n');
 | 
						
						
						
							|  |  | 			expect(msg).toContain('Tus tareas');
 | 
						
						
						
							|  |  | 			expect(msg).toContain('ℹ️ Para ver tareas sin responsable');
 | 
						
						
						
							|  |  | 		});
 | 
						
						
						
							|  |  | 	});
 | 
						
						
						
							|  |  | });
 |