Files
roi-theme/tests/Integration/Infrastructure/DatabaseMigratorTest.php
FrankZamora e34fd28df7 Fase 2: Migración de Base de Datos - Clean Architecture
COMPLETADO: Fase 2 de la migración a Clean Architecture + POO

## DatabaseMigrator
- ✓ Clase DatabaseMigrator con estrategia completa de migración
- ✓ Creación de tablas v2 con nueva estructura (config_group)
- ✓ Migración de datos con transformación automática
- ✓ Validación de integridad de datos migrados
- ✓ Swap seguro de tablas (legacy → _backup, v2 → producción)
- ✓ Rollback automático en caso de error
- ✓ Logging detallado de todas las operaciones

## Transformaciones de BD
- ✓ Nueva columna config_group (visibility, content, styles, general)
- ✓ Renombrado: version → schema_version
- ✓ UNIQUE KEY actualizada: (component_name, config_group, config_key)
- ✓ Nuevos índices: idx_group, idx_schema_version
- ✓ Timestamps con DEFAULT CURRENT_TIMESTAMP

## MigrationCommand (WP-CLI)
- ✓ Comando: wp roi-theme migrate
- ✓ Opción --dry-run para simulación segura
- ✓ Comando: wp roi-theme cleanup-backup
- ✓ Output formateado y detallado
- ✓ Confirmación para operaciones destructivas
- ✓ Estadísticas de migración completas

## Tests de Integración
- ✓ 6 tests de integración implementados
- ✓ Test: Creación de tablas v2
- ✓ Test: Preservación de cantidad de registros
- ✓ Test: Inferencia correcta de grupos
- ✓ Test: Creación de backup
- ✓ Test: Rollback en error
- ✓ Test: Cleanup de backup

## Heurística de Inferencia de Grupos
- enabled, visible_* → visibility
- message_*, cta_*, title_* → content
- *_color, *_height, *_width, *_size, *_font → styles
- Resto → general

## Integración
- ✓ Comando WP-CLI registrado en functions.php
- ✓ Autoloader actualizado
- ✓ Strict types en todos los archivos
- ✓ PHPDoc completo

## Validación
- ✓ Script validate-phase-2.php (26/26 checks pasados)
- ✓ Sintaxis PHP válida en todos los archivos
- ✓ 100% de validaciones exitosas

## Seguridad
- ✓ Backup automático de tablas legacy (_backup)
- ✓ Rollback automático si falla validación
- ✓ Validación de integridad antes de swap
- ✓ Logging completo para auditoría

IMPORTANTE: La migración está lista pero NO ejecutada. Ejecutar con:
1. wp db export backup-antes-migracion.sql
2. wp roi-theme migrate --dry-run
3. wp roi-theme migrate

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 14:39:29 -06:00

366 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace ROITheme\Tests\Integration\Infrastructure;
use PHPUnit\Framework\TestCase;
use ROITheme\Infrastructure\Persistence\WordPress\DatabaseMigrator;
/**
* Tests de Integración para DatabaseMigrator
*
* IMPORTANTE: Estos tests modifican la base de datos
* Solo ejecutar en entorno de testing
*/
class DatabaseMigratorTest extends TestCase
{
/**
* @var \wpdb
*/
private \wpdb $wpdb;
/**
* @var DatabaseMigrator
*/
private DatabaseMigrator $migrator;
/**
* @var string
*/
private string $prefix;
/**
* Setup antes de cada test
*/
protected function setUp(): void
{
if (!defined('ABSPATH')) {
define('ABSPATH', dirname(__DIR__, 5) . '/');
}
if (!function_exists('update_option')) {
function update_option($option, $value) { return true; }
}
if (!function_exists('delete_option')) {
function delete_option($option) { return true; }
}
if (!function_exists('current_time')) {
function current_time($type) { return date('Y-m-d H:i:s'); }
}
global $wpdb;
if (!isset($wpdb)) {
$this->markTestSkipped('WordPress not loaded - skipping integration tests');
return;
}
$this->wpdb = $wpdb;
$this->prefix = $wpdb->prefix;
$this->migrator = new DatabaseMigrator($wpdb);
// Limpiar tablas anteriores
$this->cleanupTables();
// Crear tablas legacy con datos de prueba
$this->createLegacyTables();
$this->seedLegacyData();
}
/**
* Teardown después de cada test
*/
protected function tearDown(): void
{
$this->cleanupTables();
}
/**
* Test: La migración crea tablas v2 correctamente
*
* @test
*/
public function it_creates_v2_tables(): void
{
$result = $this->migrator->migrate();
$this->assertTrue($result['success'], $result['message']);
// Verificar que tabla components existe
$components_table = $this->prefix . 'roi_theme_components';
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$components_table}'"
) === $components_table;
$this->assertTrue($table_exists, 'Tabla components no existe después de migración');
// Verificar que tabla defaults existe
$defaults_table = $this->prefix . 'roi_theme_components_defaults';
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$defaults_table}'"
) === $defaults_table;
$this->assertTrue($table_exists, 'Tabla defaults no existe después de migración');
}
/**
* Test: La migración preserva la cantidad de registros
*
* @test
*/
public function it_preserves_record_count(): void
{
// Contar antes de migración
$legacy_count = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->prefix}roi_theme_components"
);
// Ejecutar migración
$result = $this->migrator->migrate();
$this->assertTrue($result['success']);
// Contar después de migración
$v2_count = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$this->prefix}roi_theme_components"
);
$this->assertEquals(
$legacy_count,
$v2_count,
"Cantidad de registros no coincide: legacy={$legacy_count}, v2={$v2_count}"
);
}
/**
* Test: La migración infiere grupos correctamente
*
* @test
*/
public function it_infers_config_groups_correctly(): void
{
$result = $this->migrator->migrate();
$this->assertTrue($result['success']);
// Verificar que "enabled" se migró a grupo "visibility"
$group = $this->wpdb->get_var(
"SELECT config_group FROM {$this->prefix}roi_theme_components
WHERE config_key = 'enabled' LIMIT 1"
);
$this->assertEquals('visibility', $group);
// Verificar que "message_text" se migró a grupo "content"
$group = $this->wpdb->get_var(
"SELECT config_group FROM {$this->prefix}roi_theme_components
WHERE config_key = 'message_text' LIMIT 1"
);
$this->assertEquals('content', $group);
// Verificar que "background_color" se migró a grupo "styles"
$group = $this->wpdb->get_var(
"SELECT config_group FROM {$this->prefix}roi_theme_components
WHERE config_key = 'background_color' LIMIT 1"
);
$this->assertEquals('styles', $group);
}
/**
* Test: La migración crea backup de tablas legacy
*
* @test
*/
public function it_creates_backup_tables(): void
{
$result = $this->migrator->migrate();
$this->assertTrue($result['success']);
// Verificar que tabla backup existe
$backup_table = $this->prefix . 'roi_theme_components_backup';
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$backup_table}'"
) === $backup_table;
$this->assertTrue($table_exists, 'Tabla backup no fue creada');
// Verificar que backup tiene datos
$backup_count = $this->wpdb->get_var(
"SELECT COUNT(*) FROM {$backup_table}"
);
$this->assertGreaterThan(0, $backup_count, 'Tabla backup está vacía');
}
/**
* Test: Rollback elimina tablas v2 en caso de error
*
* @test
*/
public function it_rolls_back_on_error(): void
{
// Crear escenario de error: tabla legacy vacía después de crear v2
$this->wpdb->query("DELETE FROM {$this->prefix}roi_theme_components");
$result = $this->migrator->migrate();
// La migración debe fallar por mismatch de conteo
$this->assertFalse($result['success']);
// Verificar que tablas v2 fueron eliminadas
$v2_table = $this->prefix . 'roi_theme_components_v2';
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$v2_table}'"
) === $v2_table;
$this->assertFalse($table_exists, 'Tabla v2 no fue eliminada en rollback');
}
/**
* Test: cleanup_backup elimina tablas de respaldo
*
* @test
*/
public function it_removes_backup_tables(): void
{
// Ejecutar migración
$result = $this->migrator->migrate();
$this->assertTrue($result['success']);
// Verificar que backup existe
$backup_table = $this->prefix . 'roi_theme_components_backup';
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$backup_table}'"
) === $backup_table;
$this->assertTrue($table_exists);
// Ejecutar cleanup
$this->migrator->cleanupBackup();
// Verificar que backup fue eliminado
$table_exists = $this->wpdb->get_var(
"SHOW TABLES LIKE '{$backup_table}'"
) === $backup_table;
$this->assertFalse($table_exists, 'Tabla backup no fue eliminada');
}
/**
* Helper: Crear tablas legacy para testing
*/
private function createLegacyTables(): void
{
if (!defined('ABSPATH')) {
return;
}
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
$charset_collate = $this->wpdb->get_charset_collate();
$components_sql = "CREATE TABLE {$this->prefix}roi_theme_components (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
component_name VARCHAR(50) NOT NULL,
config_key VARCHAR(100) NOT NULL,
config_value TEXT NOT NULL,
data_type VARCHAR(20) NOT NULL DEFAULT 'string',
version VARCHAR(10) NOT NULL DEFAULT '1.0.0',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY component_config (component_name, config_key)
) {$charset_collate};";
$defaults_sql = "CREATE TABLE {$this->prefix}roi_theme_components_defaults (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
component_name VARCHAR(50) NOT NULL,
config_key VARCHAR(100) NOT NULL,
config_value TEXT NOT NULL,
data_type VARCHAR(20) NOT NULL DEFAULT 'string',
version VARCHAR(10) NOT NULL DEFAULT '1.0.0',
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY component_config (component_name, config_key)
) {$charset_collate};";
dbDelta($components_sql);
dbDelta($defaults_sql);
}
/**
* Helper: Insertar datos de prueba en tablas legacy
*/
private function seedLegacyData(): void
{
$timestamp = current_time('mysql');
// Components
$components_data = [
['top_bar', 'enabled', '1', 'boolean'],
['top_bar', 'message_text', 'Welcome!', 'string'],
['top_bar', 'background_color', '#000000', 'string'],
['footer', 'enabled', '1', 'boolean'],
['footer', 'cta_url', 'https://example.com', 'string'],
['footer', 'cta_text', 'Click here', 'string'],
];
foreach ($components_data as $data) {
$this->wpdb->insert(
$this->prefix . 'roi_theme_components',
[
'component_name' => $data[0],
'config_key' => $data[1],
'config_value' => $data[2],
'data_type' => $data[3],
'version' => '1.0.0',
'created_at' => $timestamp,
'updated_at' => $timestamp
],
['%s', '%s', '%s', '%s', '%s', '%s', '%s']
);
}
// Defaults (misma estructura)
$this->wpdb->insert(
$this->prefix . 'roi_theme_components_defaults',
[
'component_name' => 'top_bar',
'config_key' => 'enabled',
'config_value' => '1',
'data_type' => 'boolean',
'version' => '1.0.0',
'created_at' => $timestamp,
'updated_at' => $timestamp
],
['%s', '%s', '%s', '%s', '%s', '%s', '%s']
);
}
/**
* Helper: Limpiar todas las tablas del test
*/
private function cleanupTables(): void
{
$tables = [
'roi_theme_components',
'roi_theme_components_defaults',
'roi_theme_components_v2',
'roi_theme_components_defaults_v2',
'roi_theme_components_backup',
'roi_theme_components_defaults_backup'
];
foreach ($tables as $table) {
$this->wpdb->query("DROP TABLE IF EXISTS {$this->prefix}{$table}");
}
// Limpiar opciones
delete_option('roi_theme_migration_date');
delete_option('roi_theme_migration_backup_date');
delete_option('roi_theme_migration_stats');
}
}