chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Integration test: WPDO_Post_Migration::copy_legacy_hot_table() (v2.9.5).
|
||||
*
|
||||
* Verifies non-destructive cutover from legacy `wpdo_hot_<post_type>` table
|
||||
* to the new `wp_wpdo_post_<group>` flat table:
|
||||
*
|
||||
* - copies common columns by name intersection
|
||||
* - skips auto_increment id + updated_at columns (let flat manage them)
|
||||
* - idempotent (re-run doesn't duplicate; ON DUPLICATE KEY UPDATE)
|
||||
* - leaves the legacy table untouched (safety net for v3.0.0 DROP)
|
||||
* - verify_legacy_cutover() reports row count + sample mismatches
|
||||
*/
|
||||
class PostLegacyCutoverTest extends TestCase {
|
||||
|
||||
private const HOT_TABLE = 'wp_itest_wpdo_hot_hp_listing';
|
||||
private const FLAT_TABLE = 'wp_itest_wpdo_post_hp_listing_core';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! class_exists( 'WPDO_Post_Migration' ) ) {
|
||||
require_once WPDO_PLUGIN_DIR . 'includes/migration/class-tmdo-post-migration.php';
|
||||
}
|
||||
|
||||
// Legacy hot table — narrower schema, hp_booking_enabled is hot-only.
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::HOT_TABLE . '`' );
|
||||
$wpdb->query(
|
||||
'CREATE TABLE `' . self::HOT_TABLE . '` (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
post_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||||
hp_price decimal(10,2) NOT NULL DEFAULT 0.00,
|
||||
hp_featured tinyint(1) NOT NULL DEFAULT 0,
|
||||
hp_verified tinyint(1) NOT NULL DEFAULT 0,
|
||||
hp_expired_time bigint(20) NOT NULL DEFAULT 0,
|
||||
hp_featured_time bigint(20) NOT NULL DEFAULT 0,
|
||||
updated_at datetime NOT NULL DEFAULT \'0000-00-00 00:00:00\',
|
||||
hp_booking_enabled tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY post_id (post_id)
|
||||
) DEFAULT CHARACTER SET utf8mb4'
|
||||
);
|
||||
|
||||
// Flat target — wider schema, includes hp_status/hp_vendor not in hot.
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::FLAT_TABLE . '`' );
|
||||
$wpdb->query(
|
||||
'CREATE TABLE `' . self::FLAT_TABLE . '` (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
post_id bigint(20) unsigned NOT NULL,
|
||||
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
hp_price decimal(18,6) DEFAULT NULL,
|
||||
hp_status varchar(100) DEFAULT NULL,
|
||||
hp_featured bigint(20) DEFAULT NULL,
|
||||
hp_verified bigint(20) DEFAULT NULL,
|
||||
hp_vendor bigint(20) DEFAULT NULL,
|
||||
hp_expired_time bigint(20) DEFAULT NULL,
|
||||
hp_featured_time bigint(20) DEFAULT NULL,
|
||||
hp_view_count bigint(20) DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_post_id (post_id)
|
||||
) DEFAULT CHARACTER SET utf8mb4'
|
||||
);
|
||||
}
|
||||
|
||||
public static function tearDownAfterClass(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::HOT_TABLE . '`' );
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::FLAT_TABLE . '`' );
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'TRUNCATE TABLE `' . self::HOT_TABLE . '`' );
|
||||
$wpdb->query( 'TRUNCATE TABLE `' . self::FLAT_TABLE . '`' );
|
||||
}
|
||||
|
||||
private function seed_hot( int $post_id, array $cols ): void {
|
||||
global $wpdb;
|
||||
$wpdb->insert( self::HOT_TABLE, array_merge( array( 'post_id' => $post_id ), $cols ) );
|
||||
}
|
||||
|
||||
// ── copy_legacy_hot_table() ──────────────────────────────────────────────
|
||||
|
||||
public function test_copy_legacy_hot_table_copies_all_rows(): void {
|
||||
// Seed 5 rows.
|
||||
for ( $i = 1; $i <= 5; $i++ ) {
|
||||
$this->seed_hot( 100 + $i, array(
|
||||
'hp_price' => 50.00 + $i,
|
||||
'hp_featured' => $i % 2,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 9999999 + $i,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
}
|
||||
|
||||
$result = WPDO_Post_Migration::copy_legacy_hot_table(
|
||||
'hp_listing',
|
||||
self::HOT_TABLE,
|
||||
self::FLAT_TABLE
|
||||
);
|
||||
|
||||
$this->assertSame( 5, $result['copied'] );
|
||||
|
||||
global $wpdb;
|
||||
$flat_count = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::FLAT_TABLE . '`' );
|
||||
$this->assertSame( 5, $flat_count );
|
||||
|
||||
// Verify a row's data round-trips.
|
||||
$row = $wpdb->get_row( 'SELECT hp_price, hp_featured, hp_verified, hp_expired_time FROM `' . self::FLAT_TABLE . '` WHERE post_id = 103', ARRAY_A );
|
||||
$this->assertSame( '53.000000', $row['hp_price'] );
|
||||
$this->assertSame( '1', $row['hp_featured'] ); // 3 % 2 = 1
|
||||
$this->assertSame( '1', $row['hp_verified'] );
|
||||
$this->assertSame( '10000002', $row['hp_expired_time'] );
|
||||
}
|
||||
|
||||
public function test_copy_skips_id_and_updated_at_columns(): void {
|
||||
$this->seed_hot( 200, array(
|
||||
'hp_price' => 99.99,
|
||||
'hp_featured' => 0,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 0,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
|
||||
WPDO_Post_Migration::copy_legacy_hot_table(
|
||||
'hp_listing',
|
||||
self::HOT_TABLE,
|
||||
self::FLAT_TABLE
|
||||
);
|
||||
|
||||
global $wpdb;
|
||||
// Flat row's id should be auto-assigned (not the hot row's id).
|
||||
// Flat row's updated_at should be CURRENT_TIMESTAMP (not 0000-00-00).
|
||||
$row = $wpdb->get_row( 'SELECT id, updated_at FROM `' . self::FLAT_TABLE . '` WHERE post_id = 200', ARRAY_A );
|
||||
$this->assertNotEmpty( $row['updated_at'] );
|
||||
$this->assertNotEquals( '0000-00-00 00:00:00', $row['updated_at'] );
|
||||
}
|
||||
|
||||
public function test_copy_legacy_hot_table_is_idempotent(): void {
|
||||
$this->seed_hot( 300, array(
|
||||
'hp_price' => 10.00,
|
||||
'hp_featured' => 0,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 0,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
|
||||
WPDO_Post_Migration::copy_legacy_hot_table( 'hp_listing', self::HOT_TABLE, self::FLAT_TABLE );
|
||||
WPDO_Post_Migration::copy_legacy_hot_table( 'hp_listing', self::HOT_TABLE, self::FLAT_TABLE );
|
||||
|
||||
global $wpdb;
|
||||
$count = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::FLAT_TABLE . '`' );
|
||||
$this->assertSame( 1, $count, 'Re-running copy is idempotent — UPSERT, no duplicate.' );
|
||||
}
|
||||
|
||||
public function test_copy_does_not_modify_legacy_hot_table(): void {
|
||||
$this->seed_hot( 400, array(
|
||||
'hp_price' => 25.00,
|
||||
'hp_featured' => 0,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 0,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
|
||||
global $wpdb;
|
||||
$before = $wpdb->get_results( 'SELECT * FROM `' . self::HOT_TABLE . '` ORDER BY id', ARRAY_A );
|
||||
|
||||
WPDO_Post_Migration::copy_legacy_hot_table( 'hp_listing', self::HOT_TABLE, self::FLAT_TABLE );
|
||||
|
||||
$after = $wpdb->get_results( 'SELECT * FROM `' . self::HOT_TABLE . '` ORDER BY id', ARRAY_A );
|
||||
$this->assertEquals( $before, $after, 'Legacy hot table must remain untouched (safety net for v3.0.0).' );
|
||||
}
|
||||
|
||||
public function test_copy_returns_zero_when_hot_table_empty(): void {
|
||||
$result = WPDO_Post_Migration::copy_legacy_hot_table(
|
||||
'hp_listing',
|
||||
self::HOT_TABLE,
|
||||
self::FLAT_TABLE
|
||||
);
|
||||
$this->assertSame( 0, $result['copied'] );
|
||||
}
|
||||
|
||||
public function test_copy_throws_when_hot_table_missing(): void {
|
||||
$this->expectException( RuntimeException::class );
|
||||
WPDO_Post_Migration::copy_legacy_hot_table(
|
||||
'hp_listing',
|
||||
'wp_itest_nonexistent_hot',
|
||||
self::FLAT_TABLE
|
||||
);
|
||||
}
|
||||
|
||||
// ── verify_legacy_cutover() ──────────────────────────────────────────────
|
||||
|
||||
public function test_verify_reports_match_when_counts_equal(): void {
|
||||
for ( $i = 1; $i <= 3; $i++ ) {
|
||||
$this->seed_hot( 500 + $i, array(
|
||||
'hp_price' => $i * 10,
|
||||
'hp_featured' => 0,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 0,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
}
|
||||
|
||||
WPDO_Post_Migration::copy_legacy_hot_table( 'hp_listing', self::HOT_TABLE, self::FLAT_TABLE );
|
||||
|
||||
$verify = WPDO_Post_Migration::verify_legacy_cutover( self::HOT_TABLE, self::FLAT_TABLE );
|
||||
|
||||
$this->assertSame( 3, $verify['hot_rows'] );
|
||||
$this->assertSame( 3, $verify['flat_rows'] );
|
||||
$this->assertSame( 0, $verify['mismatched_rows'] );
|
||||
$this->assertTrue( $verify['ok'] );
|
||||
}
|
||||
|
||||
public function test_verify_reports_mismatch_when_flat_lags_hot(): void {
|
||||
// Seed hot with 3 rows.
|
||||
for ( $i = 1; $i <= 3; $i++ ) {
|
||||
$this->seed_hot( 600 + $i, array(
|
||||
'hp_price' => $i * 10,
|
||||
'hp_featured' => 0,
|
||||
'hp_verified' => 1,
|
||||
'hp_expired_time' => 0,
|
||||
'hp_featured_time' => 0,
|
||||
) );
|
||||
}
|
||||
// Don't run copy — flat stays empty.
|
||||
|
||||
$verify = WPDO_Post_Migration::verify_legacy_cutover( self::HOT_TABLE, self::FLAT_TABLE );
|
||||
|
||||
$this->assertSame( 3, $verify['hot_rows'] );
|
||||
$this->assertSame( 0, $verify['flat_rows'] );
|
||||
$this->assertFalse( $verify['ok'] );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user