startTime = microtime(true); } public function runAllTests() { echo $this->getHtmlHeader(); echo "

🧪 Suite de Tests Completa - WhatsApp Bot

"; echo "
"; echo "
Suite de Tests Comprensiva
"; echo "

Esta suite ejecuta más de 50 tests cubriendo todas las funcionalidades críticas:

"; echo ""; echo "
"; // Tests de infraestructura $this->runInfrastructureTests(); // Tests de servicios $this->runServiceTests(); // Tests específicos incluidos $this->runSpecializedTests(); // Tests de APIs $this->runAPITests(); // Tests de webhook $this->runWebhookTests(); // Tests de seguridad $this->runSecurityTests(); // Tests de plantillas $this->runTemplateTests(); // Tests de base de datos $this->runDatabaseTests(); $this->showSummary(); echo $this->getHtmlFooter(); } private function runSpecializedTests() { $this->showTestSection("🔬 Tests Especializados"); $this->test("Ejecutar Suite de APIs", function() { ob_start(); require_once 'tests/api_tests.php'; $apiSuite = new APITestSuite(); $apiSuite->runAllAPITests(); $output = ob_get_clean(); // Contar tests exitosos en la salida $successCount = substr_count($output, 'alert-success'); $errorCount = substr_count($output, 'alert-danger'); if ($errorCount > $successCount) { throw new Exception("APIs: $errorCount errores, $successCount éxitos"); } return "Suite de APIs: $successCount tests pasaron, $errorCount fallaron"; }); $this->test("Ejecutar Suite de Servicios", function() { ob_start(); require_once 'tests/service_tests.php'; $serviceSuite = new ServiceTestSuite(); $serviceSuite->runAllServiceTests(); $output = ob_get_clean(); $successCount = substr_count($output, 'alert-success'); $errorCount = substr_count($output, 'alert-danger'); return "Suite de Servicios: $successCount tests pasaron, $errorCount fallaron"; }); $this->test("Ejecutar Suite de Webhook", function() { ob_start(); require_once 'tests/webhook_tests.php'; $webhookSuite = new WebhookTestSuite(); $webhookSuite->runAllWebhookTests(); $output = ob_get_clean(); $successCount = substr_count($output, 'alert-success'); $errorCount = substr_count($output, 'alert-danger'); return "Suite de Webhook: $successCount tests pasaron, $errorCount fallaron"; }); $this->test("Ejecutar Suite de Seguridad", function() { ob_start(); require_once 'tests/security_tests.php'; $securitySuite = new SecurityTestSuite(); $securitySuite->runAllSecurityTests(); $output = ob_get_clean(); $successCount = substr_count($output, 'alert-success'); $warningCount = substr_count($output, 'alert-warning'); $errorCount = substr_count($output, 'alert-danger'); if ($warningCount > 0) { return "Suite de Seguridad: $successCount OK, $warningCount advertencias, $errorCount errores"; } return "Suite de Seguridad: $successCount tests pasaron, $errorCount fallaron"; }); $this->test("Ejecutar Suite de Plantillas", function() { ob_start(); require_once 'tests/template_tests.php'; $templateSuite = new TemplateTestSuite(); $templateSuite->runAllTemplateTests(); $output = ob_get_clean(); $successCount = substr_count($output, 'alert-success'); $errorCount = substr_count($output, 'alert-danger'); return "Suite de Plantillas: $successCount tests pasaron, $errorCount fallaron"; }); } private function runInfrastructureTests() { $this->showTestSection("🏗️ Tests de Infraestructura"); $this->test("Verificar archivos principales", function() { $required = [ __DIR__ . '/../config/config.php', __DIR__ . '/../classes/Database.php', __DIR__ . '/../services/WhatsAppService.php', __DIR__ . '/../services/BotService.php', __DIR__ . '/../api/webhook.php', __DIR__ . '/../index.php' ]; foreach ($required as $file) { if (!file_exists($file)) { throw new Exception("Archivo faltante: $file"); } } return "Todos los archivos principales existen"; }); $this->test("Verificar configuración", function() { if (!defined('DB_HOST') || !defined('WHATSAPP_TOKEN')) { throw new Exception("Constantes de configuración faltantes"); } return "Configuración cargada correctamente"; }); $this->test("Conexión a base de datos", function() { $db = Database::getInstance(); $result = $db->fetch("SELECT 1 as test"); if ($result['test'] !== 1) { throw new Exception("Test de conexión fallido"); } return "Conexión DB exitosa"; }); } private function runServiceTests() { $this->showTestSection("🔧 Tests de Servicios"); $this->test("Inicialización WhatsAppService", function() { $service = new WhatsAppService(); if (!$service) { throw new Exception("No se pudo inicializar WhatsAppService"); } return "WhatsAppService inicializado correctamente"; }); $this->test("Inicialización BotService", function() { $service = new BotService(); if (!$service) { throw new Exception("No se pudo inicializar BotService"); } return "BotService inicializado correctamente"; }); $this->test("Formateo de número de teléfono", function() { $service = new WhatsAppService(); $reflection = new ReflectionClass($service); $method = $reflection->getMethod('formatPhoneNumber'); $method->setAccessible(true); $result1 = $method->invokeArgs($service, ['573001234567']); $result2 = $method->invokeArgs($service, ['+573001234567']); if ($result1 !== '573001234567' || $result2 !== '573001234567') { throw new Exception("Formateo incorrecto: $result1, $result2"); } return "Formateo de números funciona correctamente"; }); } private function runAPITests() { $this->showTestSection("🌐 Tests de APIs"); // Test API de estadísticas $this->test("API get_stats.php", function() { $result = $this->makeAPICall('get_stats.php'); if (!isset($result['total_users'])) { throw new Exception("Estructura de respuesta incorrecta"); } return "API de estadísticas responde correctamente"; }); // Test API de usuarios $this->test("API get_users.php", function() { $result = $this->makeAPICall('get_users.php'); if (!is_array($result)) { throw new Exception("Respuesta debe ser un array"); } return "API de usuarios responde correctamente"; }); // Test API de plantillas $this->test("API get_templates.php", function() { $result = $this->makeAPICall('get_templates.php'); if (!is_array($result)) { throw new Exception("Respuesta debe ser un array"); } return "API de plantillas responde correctamente"; }); // Test creación de plantilla $this->test("API save_template.php", function() { $testData = [ 'name' => 'Test Template ' . time(), 'template_name' => 'test_template_' . time(), 'language_code' => 'es', 'category' => 'utility' ]; $result = $this->makeAPICall('save_template.php', 'POST', $testData); if (!$result['success']) { throw new Exception("Error creando plantilla: " . ($result['error'] ?? 'Unknown')); } return "API de creación de plantillas funciona"; }); } private function runWebhookTests() { $this->showTestSection("🔗 Tests de Webhook"); $this->test("Webhook verification", function() { $testUrl = '/api/webhook.php?hub_mode=subscribe&hub_verify_token=' . WEBHOOK_VERIFY_TOKEN . '&hub_challenge=test123'; // Simular petición GET de verificación $_GET = [ 'hub_mode' => 'subscribe', 'hub_verify_token' => WEBHOOK_VERIFY_TOKEN, 'hub_challenge' => 'test123' ]; ob_start(); $webhook = new WhatsAppWebhook(); $webhook->handleRequest(); $output = ob_get_clean(); if ($output !== 'test123') { throw new Exception("Verificación de webhook fallida: $output"); } return "Webhook verification funcionando"; }); $this->test("Webhook procesamiento de mensaje", function() { // Test básico de estructura de webhook $testPayload = [ 'object' => 'whatsapp_business_account', 'entry' => [ [ 'changes' => [ [ 'value' => [ 'conversations' => [ [ 'from' => '573001234567', 'text' => ['body' => 'test message'], 'timestamp' => time() ] ] ] ] ] ] ] ]; // Esto requeriría más setup para simular completamente return "Estructura de webhook reconocida (test básico)"; }); } private function runSecurityTests() { $this->showTestSection("🔒 Tests de Seguridad"); $this->test("Verificar autenticación requerida", function() { // Simular usuario no logueado unset($_SESSION['admin_logged_in']); if (isUserLoggedIn()) { throw new Exception("Función de autenticación no funciona correctamente"); } return "Verificación de autenticación funciona"; }); $this->test("Verificar función requireAuthentication", function() { unset($_SESSION['admin_logged_in']); ob_start(); try { requireAuthentication(); $output = ob_get_clean(); throw new Exception("requireAuthentication no bloqueó usuario no autenticado"); } catch (Exception $e) { $output = ob_get_clean(); // Si hay output, significa que la función funcionó if (empty($output)) { throw new Exception("requireAuthentication no generó respuesta JSON"); } } return "Función requireAuthentication protege correctamente"; }); $this->test("Validación de tokens", function() { if (empty(WHATSAPP_TOKEN) || WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI') { throw new Exception("Token de WhatsApp no configurado"); } if (empty(WEBHOOK_VERIFY_TOKEN) || WEBHOOK_VERIFY_TOKEN === 'mi_token_secreto_123') { return "⚠️ Token de webhook usando valor por defecto (cambiar en producción)"; } return "Tokens configurados correctamente"; }); } private function runTemplateTests() { $this->showTestSection("📝 Tests de Plantillas"); $this->test("Verificar tabla message_templates", function() { $db = Database::getInstance(); $tables = $db->fetchAll("SHOW TABLES LIKE 'message_templates'"); if (empty($tables)) { throw new Exception("Tabla message_templates no existe"); } return "Tabla message_templates existe"; }); $this->test("Insertar plantilla de prueba", function() { $db = Database::getInstance(); $testName = 'test_template_' . time(); $id = $db->insert('message_templates', [ 'name' => $testName, 'template_name' => $testName, 'language_code' => 'es', 'category' => 'utility', 'status' => 'pending' ]); if (!$id) { throw new Exception("No se pudo insertar plantilla de prueba"); } // Limpiar $db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $id]); return "Inserción y eliminación de plantillas funciona"; }); $this->test("Estados de plantillas", function() { $db = Database::getInstance(); $validStatuses = ['pending', 'approved', 'rejected']; // Verificar que la columna acepta solo estos valores $columns = $db->fetchAll("DESCRIBE message_templates"); $statusColumn = null; foreach ($columns as $column) { if ($column['Field'] === 'status') { $statusColumn = $column; break; } } if (!$statusColumn || strpos($statusColumn['Type'], 'enum') === false) { throw new Exception("Columna status no es ENUM o no existe"); } return "Estados de plantillas correctamente definidos"; }); } private function runDatabaseTests() { $this->showTestSection("💾 Tests de Base de Datos"); $this->test("Verificar todas las tablas principales", function() { $db = Database::getInstance(); $requiredTables = [ 'users', 'conversations', 'menus', 'menu_options', 'system_config', 'autoresponses', 'message_templates', 'webhook_logs' ]; $existingTables = []; foreach ($requiredTables as $table) { $result = $db->fetchAll("SHOW TABLES LIKE '$table'"); if (empty($result)) { throw new Exception("Tabla faltante: $table"); } $existingTables[] = $table; } return "Todas las tablas principales existen: " . implode(', ', $existingTables); }); $this->test("CRUD básico en tabla users", function() { $db = Database::getInstance(); $testPhone = '573999' . rand(100000, 999999); // Create $id = $db->insert('users', [ 'phone_number' => $testPhone, 'name' => 'Test User', 'status' => 'active' ]); if (!$id) throw new Exception("Insert fallido"); // Read $user = $db->fetch("SELECT * FROM users WHERE id = :id", ['id' => $id]); if ($user['phone_number'] !== $testPhone) { throw new Exception("Select fallido"); } // Update $updated = $db->update('users', ['name' => 'Updated User'], ['id' => $id]); if (!$updated) throw new Exception("Update fallido"); // Delete $deleted = $db->execute("DELETE FROM users WHERE id = :id", ['id' => $id]); if (!$deleted) throw new Exception("Delete fallido"); return "CRUD completo funciona correctamente"; }); $this->test("Integridad referencial", function() { $db = Database::getInstance(); // Verificar que las foreign keys están definidas $foreignKeys = $db->fetchAll(" SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL "); if (empty($foreignKeys)) { return "⚠️ Sin foreign keys definidas (recomendado para integridad)"; } return "Integridad referencial configurada: " . count($foreignKeys) . " FK"; }); } private function test($name, $callable) { $this->totalTests++; try { $result = $callable(); $this->passedTests++; $this->showTestResult($name, true, $result); } catch (Exception $e) { $this->failedTests++; $this->showTestResult($name, false, $e->getMessage()); } } private function makeAPICall($endpoint, $method = 'GET', $data = null) { // Simular sesión de administrador para tests $_SESSION['admin_logged_in'] = true; $_SESSION['last_activity'] = time(); $url = "api/$endpoint"; if ($method === 'GET' && $data) { $url .= '?' . http_build_query($data); } $context = stream_context_create([ 'http' => [ 'method' => $method, 'header' => 'Content-Type: application/json', 'content' => $data ? json_encode($data) : null ] ]); // Para tests, incluir el archivo directamente ob_start(); if ($method === 'POST' && $data) { // Simular POST data $_POST = $data; file_put_contents('php://input', json_encode($data)); } try { include $url; $output = ob_get_clean(); return json_decode($output, true); } catch (Exception $e) { ob_get_clean(); throw $e; } } private function showTestSection($title) { echo "
"; echo "
$title
"; echo "
"; } private function showTestResult($name, $passed, $message) { $class = $passed ? 'success' : 'danger'; $icon = $passed ? 'check-circle' : 'times-circle'; echo "
"; echo " "; echo "$name: $message"; echo "
"; if (!$passed) { echo "
"; // Cerrar card si hay error } } private function showSummary() { echo ""; // Cerrar última card $successRate = $this->totalTests > 0 ? ($this->passedTests / $this->totalTests) * 100 : 0; $duration = round((microtime(true) - $this->startTime) * 1000, 2); $alertClass = $successRate === 100.0 ? 'success' : ($successRate >= 80 ? 'warning' : 'danger'); echo "
"; echo "

Resumen de Tests

"; echo "
"; echo "
Total: {$this->totalTests}
"; echo "
Pasaron: {$this->passedTests}
"; echo "
Fallaron: {$this->failedTests}
"; echo "
Éxito: " . number_format($successRate, 1) . "%
"; echo "
"; echo "

Tiempo de ejecución: {$duration}ms

"; if ($successRate === 100.0) { echo "

🎉 ¡Todos los tests pasaron! El sistema está funcionando correctamente.

"; } else if ($successRate >= 80) { echo "

⚠️ La mayoría de tests pasaron, pero hay algunas fallas que requieren atención.

"; } else { echo "

🚨 Múltiples fallas detectadas. El sistema requiere correcciones urgentes.

"; } echo "
"; } private function getHtmlHeader() { return ' Test Suite - WhatsApp Bot
'; } private function getHtmlFooter() { return '
Ir al Panel Test Básico Test WhatsApp

Suite de tests ejecutada el: ' . date('Y-m-d H:i:s') . '

'; } } // Ejecutar todos los tests $runner = new TestRunner(); $runner->runAllTests(); ?>