Files
2meet-data-optimizer/tests/integration/PostLegacyCutoverTest.php
wpdev d36bb954d1 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
2026-07-31 05:06:36 +08:00

240 lines
8.3 KiB
PHP

<?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'] );
}
}