Files
whatsapp/tests/template_tests.php
T
2026-01-12 11:06:43 -05:00

692 lines
25 KiB
PHP

<?php
/**
* Tests específicos para el Sistema de Plantillas
* Fecha: 3 de enero de 2026
*/
class TemplateTestSuite {
private $db;
private $testTemplateIds = [];
public function __construct() {
$this->db = Database::getInstance();
}
public function runAllTemplateTests() {
echo "<h2>📝 Tests Exhaustivos de Sistema de Plantillas</h2>";
$this->testTemplateDatabase();
$this->testTemplateAPIs();
$this->testTemplateValidation();
$this->testTemplateIntegration();
$this->testTemplateWorkflow();
// Limpiar plantillas de prueba
$this->cleanup();
}
private function testTemplateDatabase() {
echo "<h3>💾 Tests de Base de Datos de Plantillas</h3>";
$this->templateTest("Estructura tabla message_templates", function() {
$columns = $this->db->fetchAll("DESCRIBE message_templates");
$requiredColumns = [
'id', 'name', 'template_name', 'language_code',
'category', 'status', 'created_at'
];
$existingColumns = array_column($columns, 'Field');
foreach ($requiredColumns as $col) {
if (!in_array($col, $existingColumns)) {
throw new Exception("Columna requerida '$col' faltante");
}
}
// Verificar tipos de datos
$statusColumn = array_filter($columns, function($col) {
return $col['Field'] === 'status';
});
$statusColumn = array_values($statusColumn)[0];
if (strpos($statusColumn['Type'], 'enum') === false) {
throw new Exception("Columna status debería ser ENUM");
}
if (strpos($statusColumn['Type'], 'pending') === false ||
strpos($statusColumn['Type'], 'approved') === false ||
strpos($statusColumn['Type'], 'rejected') === false) {
throw new Exception("Estados de plantilla incorrectos en ENUM");
}
return "Estructura de tabla message_templates correcta";
});
$this->templateTest("CRUD básico en message_templates", function() {
$testName = 'Test CRUD Template ' . time();
// CREATE
$id = $this->db->insert('message_templates', [
'name' => $testName,
'template_name' => 'test_crud_' . time(),
'language_code' => 'es',
'category' => 'utility',
'status' => 'pending'
]);
if (!$id) {
throw new Exception("No se pudo insertar plantilla");
}
$this->testTemplateIds[] = $id;
// READ
$template = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($template['name'] !== $testName) {
throw new Exception("Datos incorrectos al leer plantilla");
}
// UPDATE
$updated = $this->db->update('message_templates',
['status' => 'approved'],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo actualizar plantilla");
}
// Verificar actualización
$updatedTemplate = $this->db->fetch(
"SELECT status FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($updatedTemplate['status'] !== 'approved') {
throw new Exception("Estado no se actualizó correctamente");
}
// DELETE se hace en cleanup()
return "CRUD completo en message_templates funciona";
});
$this->templateTest("Constrains y validaciones", function() {
// Test unicidad de nombre
$duplicateName = 'Unique Test ' . time();
$id1 = $this->db->insert('message_templates', [
'name' => $duplicateName,
'template_name' => 'unique_test_1',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id1;
// Intentar insertar duplicado
try {
$id2 = $this->db->insert('message_templates', [
'name' => $duplicateName,
'template_name' => 'unique_test_2',
'language_code' => 'es',
'status' => 'pending'
]);
if ($id2) {
$this->testTemplateIds[] = $id2;
throw new Exception("Se permitió nombre duplicado");
}
} catch (Exception $e) {
// Error esperado por constraint
if (strpos($e->getMessage(), 'Duplicate') !== false ||
strpos($e->getMessage(), 'UNIQUE') !== false) {
return "Constraint de unicidad funciona correctamente";
}
throw $e;
}
return "Validaciones de base de datos funcionan";
});
}
private function testTemplateAPIs() {
echo "<h3>🔌 Tests de APIs de Plantillas</h3>";
// Configurar sesión de admin
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
$this->templateTest("API get_templates.php", function() {
ob_start();
include 'api/get_templates.php';
$output = ob_get_clean();
$templates = json_decode($output, true);
if (!is_array($templates)) {
throw new Exception("get_templates debe retornar array JSON");
}
return "API get_templates responde correctamente";
});
$this->templateTest("API save_template.php - plantilla válida", function() {
$testData = [
'name' => 'API Test Template ' . time(),
'template_name' => 'api_test_' . time(),
'language_code' => 'es',
'category' => 'utility'
];
$_POST = $testData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($testData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/save_template.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if (!$result['success']) {
throw new Exception("Error creando plantilla: " . ($result['error'] ?? 'Unknown'));
}
// Verificar que se creó en BD
$created = $this->db->fetch(
"SELECT * FROM message_templates WHERE name = :name",
['name' => $testData['name']]
);
if (!$created) {
throw new Exception("Plantilla no se guardó en base de datos");
}
$this->testTemplateIds[] = $created['id'];
return "API save_template crea plantilla correctamente";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
ob_end_clean();
}
});
$this->templateTest("API save_template.php - validaciones", function() {
$invalidData = [
['name' => '', 'template_name' => 'test', 'error_expected' => 'nombre es requerido'],
['name' => 'Test', 'template_name' => '', 'error_expected' => 'nombre de la plantilla'],
[/* datos vacíos */ 'error_expected' => 'inválidos']
];
foreach ($invalidData as $testCase) {
$_POST = $testCase;
unset($testCase['error_expected']);
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($testCase);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/save_template.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if ($result['success']) {
throw new Exception("Debería rechazar datos inválidos");
}
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
}
return "API save_template valida datos correctamente";
});
$this->templateTest("API update_template_status.php", function() {
// Crear plantilla para probar
$id = $this->db->insert('message_templates', [
'name' => 'Status Update Test',
'template_name' => 'status_update_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
$updateData = [
'id' => $id,
'status' => 'approved'
];
$_POST = $updateData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($updateData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/update_template_status.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if (!$result['success']) {
throw new Exception("Error actualizando estado: " . ($result['error'] ?? 'Unknown'));
}
// Verificar actualización en BD
$updated = $this->db->fetch(
"SELECT status FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($updated['status'] !== 'approved') {
throw new Exception("Estado no se actualizó en base de datos");
}
return "API update_template_status funciona correctamente";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
});
}
private function testTemplateValidation() {
echo "<h3"> Tests de Validación de Plantillas</h3>";
$this->templateTest("Validación de nombres", function() {
$invalidNames = [
'', // Vacío
str_repeat('a', 200), // Muy largo
'<script>alert(1)</script>', // XSS
'SELECT * FROM users', // SQL-like
'template with \x00 null byte'
];
foreach ($invalidNames as $name) {
try {
$id = $this->db->insert('message_templates', [
'name' => $name,
'template_name' => 'test_validation_' . time(),
'language_code' => 'es',
'status' => 'pending'
]);
if ($id) {
$this->testTemplateIds[] = $id;
// Verificar que se sanitizó o rechazó apropiadamente
$saved = $this->db->fetch(
"SELECT name FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($saved['name'] === $name && (
strpos($name, '<script>') !== false ||
strlen($name) > 100 ||
empty($name)
)) {
throw new Exception("Nombre inválido '$name' no fue filtrado");
}
}
} catch (Exception $e) {
// Error esperado para nombres inválidos
continue;
}
}
return "Validación de nombres de plantillas funciona";
});
$this->templateTest("Validación de estados", function() {
$id = $this->db->insert('message_templates', [
'name' => 'State Test',
'template_name' => 'state_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
// Estados válidos
$validStates = ['pending', 'approved', 'rejected'];
foreach ($validStates as $state) {
$updated = $this->db->update('message_templates',
['status' => $state],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo actualizar a estado válido: $state");
}
}
// Estado inválido
try {
$this->db->update('message_templates',
['status' => 'invalid_status'],
['id' => $id]
);
throw new Exception("Estado inválido fue aceptado");
} catch (Exception $e) {
// Error esperado por constraint ENUM
if (strpos($e->getMessage(), 'invalid_status') === false) {
throw $e;
}
}
return "Validación de estados funciona correctamente";
});
$this->templateTest("Validación de idiomas", function() {
$validLanguages = ['es', 'en', 'pt', 'fr'];
$invalidLanguages = ['', 'español', 'eng', 'xyz123'];
// Idiomas válidos
foreach ($validLanguages as $lang) {
$id = $this->db->insert('message_templates', [
'name' => "Lang Test $lang",
'template_name' => "lang_test_$lang",
'language_code' => $lang,
'status' => 'pending'
]);
if (!$id) {
throw new Exception("Idioma válido '$lang' rechazado");
}
$this->testTemplateIds[] = $id;
}
// Idiomas inválidos (dependiendo de validación implementada)
foreach ($invalidLanguages as $lang) {
try {
$id = $this->db->insert('message_templates', [
'name' => "Invalid Lang Test $lang",
'template_name' => "invalid_lang_$lang",
'language_code' => $lang,
'status' => 'pending'
]);
if ($id) {
$this->testTemplateIds[] = $id;
// Por ahora, idiomas inválidos pueden ser aceptados
// En futuras versiones se puede agregar validación más estricta
}
} catch (Exception $e) {
// Error esperado para idiomas inválidos
continue;
}
}
return "Validación de idiomas (básica implementada)";
});
}
private function testTemplateIntegration() {
echo "<h3">🔗 Tests de Integración de Plantillas</h3>";
$this->templateTest("Integración con WhatsAppService", function() {
// Crear plantilla aprobada
$id = $this->db->insert('message_templates', [
'name' => 'Integration Test',
'template_name' => 'integration_test',
'language_code' => 'es',
'category' => 'utility',
'status' => 'approved'
]);
$this->testTemplateIds[] = $id;
$whatsappService = new WhatsAppService();
// Verificar que el método sendTemplateMessage existe y acepta los parámetros
$reflection = new ReflectionClass($whatsappService);
if (!$reflection->hasMethod('sendTemplateMessage')) {
throw new Exception("WhatsAppService no tiene método sendTemplateMessage");
}
$method = $reflection->getMethod('sendTemplateMessage');
$params = $method->getParameters();
if (count($params) < 2) {
throw new Exception("sendTemplateMessage debe tener al menos 2 parámetros");
}
// Test estructura de llamada (sin envío real)
try {
// Este test no envía realmente, solo verifica estructura
$result = $whatsappService->sendTemplateMessage(
'+573001234567',
'integration_test',
'es',
[]
);
// En un entorno real, esto fallaría por token/conectividad
// Pero la estructura debería estar correcta
return "Integración WhatsAppService estructura correcta";
} catch (Exception $e) {
// Error esperado por falta de conectividad real
if (strpos($e->getMessage(), 'cURL') !== false ||
strpos($e->getMessage(), 'connection') !== false ||
strpos($e->getMessage(), 'token') !== false) {
return "Integración WhatsAppService - estructura OK, falla conectividad (esperado)";
}
throw $e;
}
});
$this->templateTest("Filtrado de plantillas por estado", function() {
// Crear plantillas con diferentes estados
$states = ['pending', 'approved', 'rejected'];
$templateIds = [];
foreach ($states as $state) {
$id = $this->db->insert('message_templates', [
'name' => "Filter Test $state",
'template_name' => "filter_test_$state",
'language_code' => 'es',
'status' => $state
]);
$templateIds[] = $id;
$this->testTemplateIds[] = $id;
}
// Test filtrar solo aprobadas
$approved = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE status = 'approved' AND name LIKE 'Filter Test%'"
);
if (count($approved) !== 1) {
throw new Exception("Filtro de plantillas aprobadas no funciona");
}
// Test filtrar todas
$all = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE name LIKE 'Filter Test%'"
);
if (count($all) !== 3) {
throw new Exception("No se crearon todas las plantillas de prueba");
}
return "Filtrado de plantillas por estado funciona";
});
}
private function testTemplateWorkflow() {
echo "<h3">🔄 Tests de Flujo de Trabajo de Plantillas</h3>";
$this->templateTest("Ciclo completo: Crear Aprobar Usar", function() {
// 1. Crear plantilla
$templateName = 'Workflow Test ' . time();
$whatsappName = 'workflow_test_' . time();
$id = $this->db->insert('message_templates', [
'name' => $templateName,
'template_name' => $whatsappName,
'language_code' => 'es',
'category' => 'utility',
'status' => 'pending'
]);
if (!$id) {
throw new Exception("No se pudo crear plantilla");
}
$this->testTemplateIds[] = $id;
// 2. Verificar estado inicial
$template = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($template['status'] !== 'pending') {
throw new Exception("Estado inicial debería ser 'pending'");
}
// 3. Aprobar plantilla
$updated = $this->db->update('message_templates',
['status' => 'approved'],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo aprobar plantilla");
}
// 4. Verificar que ahora está disponible para uso
$approvedTemplate = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id AND status = 'approved'",
['id' => $id]
);
if (!$approvedTemplate) {
throw new Exception("Plantilla aprobada no está disponible");
}
// 5. Simular uso en envío de mensaje
$_SESSION['admin_logged_in'] = true;
$messageData = [
'recipient' => '+573001234567',
'type' => 'template',
'template' => $whatsappName
];
$_POST = $messageData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($messageData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/send_message.php';
$output = ob_get_clean();
$result = json_decode($output, true);
// En testing, probablemente falle por conectividad, pero la validación debería pasar
if (isset($result['error']) && strpos($result['error'], $whatsappName) === false) {
// Si no menciona la plantilla en el error, es porque la validación pasó
return "Ciclo completo de plantilla: Crear Aprobar Validar uso";
}
if ($result['success']) {
return "Ciclo completo de plantilla exitoso (conexión real funcionando)";
}
return "Ciclo de plantilla completado - falla esperada en envío real";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
ob_end_clean();
}
});
$this->templateTest("Rechazo de plantillas no aprobadas", function() {
// Crear plantilla pendiente
$id = $this->db->insert('message_templates', [
'name' => 'Pending Test',
'template_name' => 'pending_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
// Intentar usar plantilla no aprobada
$pendingTemplates = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE status = 'approved' AND template_name = 'pending_test'"
);
if (!empty($pendingTemplates)) {
throw new Exception("Plantilla pendiente aparece como aprobada");
}
return "Plantillas no aprobadas correctamente excluidas";
});
}
private function templateTest($name, $callable) {
try {
$result = $callable();
echo "<div class='alert alert-success'><i class='fas fa-check'></i> <strong>$name:</strong> $result</div>";
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
}
}
private function cleanup() {
foreach ($this->testTemplateIds as $id) {
try {
$this->db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $id]);
} catch (Exception $e) {
// Ignorar errores de limpieza
}
}
}
public function __destruct() {
$this->cleanup();
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'template_tests.php') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
$suite = new TemplateTestSuite();
$suite->runAllTemplateTests();
}
?>