Files
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

227 lines
8.4 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* Integration test: wp_usermeta → wp_wpdo_user_membership backfill.
*
* Creates isolated tables (wp_itest_usermeta, wp_itest_wpdo_user_membership,
* wp_itest_wpdo_migration_status) and verifies that
* WPDO_Entity_Migration_Engine::migrate_group('user', 'membership') reads the
* EAV rows and produces correct flat-table rows.
*
* Requires real MariaDB (WPDO_TEST_DB_PASS env var must be set).
*/
class MemberBackfillIntegrationTest extends TestCase {
private const MEM_TABLE = 'wp_itest_wpdo_user_membership';
private const STATUS_TABLE = 'wp_itest_wpdo_migration_status';
private const USERMETA = 'wp_itest_usermeta';
// ── Fixture lifecycle ─────────────────────────────────────────────────────
public static function setUpBeforeClass(): void {
self::load_engine_classes();
self::create_tables();
self::register_user_entity();
}
public static function tearDownAfterClass(): void {
global $wpdb;
foreach ( array( self::MEM_TABLE, self::STATUS_TABLE, self::USERMETA ) as $t ) {
$wpdb->query( "DROP TABLE IF EXISTS `{$t}`" );
}
}
protected function setUp(): void {
global $wpdb;
$wpdb->query( 'TRUNCATE TABLE `' . self::MEM_TABLE . '`' );
$wpdb->query( 'TRUNCATE TABLE `' . self::STATUS_TABLE . '`' );
$wpdb->query( 'TRUNCATE TABLE `' . self::USERMETA . '`' );
}
// ── Tests ─────────────────────────────────────────────────────────────────
public function test_migrates_membership_level_and_points(): void {
$this->seed_usermeta( array(
array( 'user_id' => 1, 'meta_key' => 'membership_level', 'meta_value' => 'gold' ),
array( 'user_id' => 1, 'meta_key' => 'points_balance', 'meta_value' => '500' ),
array( 'user_id' => 2, 'meta_key' => 'membership_level', 'meta_value' => 'silver' ),
array( 'user_id' => 2, 'meta_key' => 'points_balance', 'meta_value' => '200' ),
) );
$result = WPDO_Entity_Migration_Engine::migrate_group( 'user', 'membership', array( 'sleep_ms' => 0 ) );
$this->assertSame( 2, $result['migrated'], 'Expected 2 migrated rows' );
$this->assertSame( 0, $result['errors'] );
global $wpdb;
$row1 = $wpdb->get_row( "SELECT * FROM `" . self::MEM_TABLE . "` WHERE user_id = 1", ARRAY_A );
$this->assertNotNull( $row1 );
$this->assertSame( 'gold', $row1['membership_level'] );
$this->assertSame( '500', $row1['points_balance'] );
$row2 = $wpdb->get_row( "SELECT * FROM `" . self::MEM_TABLE . "` WHERE user_id = 2", ARRAY_A );
$this->assertNotNull( $row2 );
$this->assertSame( 'silver', $row2['membership_level'] );
$this->assertSame( '200', $row2['points_balance'] );
}
public function test_skips_users_with_no_managed_keys(): void {
$this->seed_usermeta( array(
array( 'user_id' => 3, 'meta_key' => 'some_other_meta', 'meta_value' => 'value' ),
) );
$result = WPDO_Entity_Migration_Engine::migrate_group( 'user', 'membership', array( 'sleep_ms' => 0 ) );
$this->assertSame( 0, $result['migrated'] );
$this->assertSame( 0, $result['errors'] );
}
public function test_dry_run_does_not_write_to_flat_table(): void {
$this->seed_usermeta( array(
array( 'user_id' => 4, 'meta_key' => 'membership_level', 'meta_value' => 'platinum' ),
) );
$result = WPDO_Entity_Migration_Engine::migrate_group(
'user', 'membership',
array( 'sleep_ms' => 0, 'dry_run' => true )
);
$this->assertTrue( $result['dry_run'] );
$this->assertSame( 1, $result['migrated'] );
global $wpdb;
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::MEM_TABLE . "`" );
$this->assertSame( 0, $count, 'Dry run must not write to flat table' );
}
public function test_row_count_matches_seeded_users(): void {
$this->seed_usermeta( array(
array( 'user_id' => 10, 'meta_key' => 'membership_level', 'meta_value' => 'bronze' ),
array( 'user_id' => 11, 'meta_key' => 'membership_level', 'meta_value' => 'bronze' ),
array( 'user_id' => 12, 'meta_key' => 'points_balance', 'meta_value' => '50' ),
) );
$result = WPDO_Entity_Migration_Engine::migrate_group( 'user', 'membership', array( 'sleep_ms' => 0 ) );
// user_id 10, 11 have membership_level; user_id 12 has points_balance.
$this->assertSame( 3, $result['migrated'] );
global $wpdb;
$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::MEM_TABLE . "`" );
$this->assertSame( 3, $count );
}
// ── Helpers ───────────────────────────────────────────────────────────────
private function seed_usermeta( array $rows ): void {
global $wpdb;
foreach ( $rows as $row ) {
$wpdb->insert( self::USERMETA, $row );
}
}
private static function load_engine_classes(): void {
$base = WPDO_PLUGIN_DIR;
$files = array(
'includes/adapters/interface-entity-adapter.php',
'includes/engine/class-tmdo-type-caster.php',
'includes/engine/class-tmdo-schema-manager.php',
'includes/engine/class-tmdo-entity-registry.php',
'includes/adapters/class-tmdo-adapter-user.php',
'includes/engine/class-tmdo-entity-migration-engine.php',
);
foreach ( $files as $file ) {
if ( ! class_exists( self::class_for_file( $file ) ) ) {
require_once $base . $file;
}
}
}
private static function class_for_file( string $file ): string {
$map = array(
'interface-entity-adapter.php' => 'WPDO_Entity_Adapter_Interface',
'class-tmdo-type-caster.php' => 'WPDO_Type_Caster',
'class-tmdo-schema-manager.php' => 'WPDO_Schema_Manager',
'class-tmdo-entity-registry.php' => 'WPDO_Entity_Registry',
'class-tmdo-adapter-user.php' => 'WPDO_Adapter_User',
'class-tmdo-entity-migration-engine.php' => 'WPDO_Entity_Migration_Engine',
);
return $map[ basename( $file ) ] ?? '';
}
private static function create_tables(): void {
global $wpdb;
$wpdb->query(
'CREATE TABLE IF NOT EXISTS `' . self::USERMETA . '` (
`umeta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL DEFAULT 0,
`meta_key` varchar(255) DEFAULT NULL,
`meta_value` longtext DEFAULT NULL,
PRIMARY KEY (`umeta_id`),
KEY `user_id` (`user_id`),
KEY `meta_key` (`meta_key`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
$wpdb->query(
'CREATE TABLE IF NOT EXISTS `' . self::MEM_TABLE . '` (
`user_id` bigint(20) NOT NULL,
`membership_level` varchar(100) DEFAULT NULL,
`points_balance` bigint(20) DEFAULT 0,
`expires_at` datetime DEFAULT NULL,
`activated_at` datetime DEFAULT NULL,
`tier_source` varchar(255) DEFAULT NULL,
`custom_tier` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
$wpdb->query(
'CREATE TABLE IF NOT EXISTS `' . self::STATUS_TABLE . '` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`entity_type` varchar(50) NOT NULL,
`group_name` varchar(50) NOT NULL,
`last_id` bigint(20) unsigned NOT NULL DEFAULT 0,
`total_migrated` bigint(20) unsigned NOT NULL DEFAULT 0,
`status` varchar(20) NOT NULL DEFAULT \'pending\',
`started_at` datetime DEFAULT NULL,
`completed_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `entity_group` (`entity_type`, `group_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4'
);
}
private static function register_user_entity(): void {
if ( ! class_exists( 'WPDO_Entity_Registry' ) ) {
return;
}
WPDO_Entity_Registry::register_adapter( 'user', new WPDO_Adapter_User() );
WPDO_Entity_Registry::register_group(
'user',
'membership',
array(
array(
'key' => 'membership_level',
'type' => 'enum',
'searchable' => true,
'options' => array( 'bronze', 'silver', 'gold', 'platinum', 'custom' ),
),
array( 'key' => 'points_balance', 'type' => 'integer', 'searchable' => true, 'default' => 0 ),
array( 'key' => 'expires_at', 'type' => 'datetime', 'searchable' => true ),
array( 'key' => 'activated_at', 'type' => 'datetime' ),
array( 'key' => 'tier_source', 'type' => 'text' ),
array( 'key' => 'custom_tier', 'type' => 'text' ),
)
);
}
}