query( "CREATE TABLE `{$wpdb->users}` ( ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_login VARCHAR(60) NOT NULL DEFAULT '', PRIMARY KEY (ID) ) ENGINE=InnoDB" ); $wpdb->query( "CREATE TABLE `{$wpdb->usermeta}` ( umeta_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL DEFAULT 0, meta_key VARCHAR(255) DEFAULT NULL, meta_value LONGTEXT, PRIMARY KEY (umeta_id), KEY user_id (user_id) ) ENGINE=InnoDB" ); $wpdb->query( 'CREATE TABLE `' . self::TABLE . '` ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, num DECIMAL(18,6) DEFAULT NULL, cnt BIGINT(20) DEFAULT NULL, name VARCHAR(255) DEFAULT NULL, prefs TEXT DEFAULT NULL, meta_json LONGTEXT DEFAULT NULL, flag BIGINT(20) NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_entity (user_id) ) ENGINE=InnoDB' ); for ( $id = 1; $id <= 7; $id++ ) { $wpdb->insert( $wpdb->users, array( 'ID' => $id, 'user_login' => "u{$id}" ) ); } $prefs = serialize( array( 'core' => array( 'welcomeGuide' => false ) ) ); $this->native( 1, array( 'num' => '50', 'cnt' => '3', 'name' => 'a', 'prefs' => $prefs, 'meta_json' => serialize( array( 'w' => 640, 'tags' => array( 'x' ) ) ) ) ); $this->flat( array( 'user_id' => 1, 'num' => '50.000000', 'cnt' => 3, 'name' => 'a', 'prefs' => $prefs, 'meta_json' => '{"w":640,"tags":["x"]}' ) ); $this->native( 2, array( 'name' => 'b', 'cnt' => '5' ) ); $this->flat( array( 'user_id' => 2, 'name' => 'B', 'cnt' => 6 ) ); $this->native( 3, array( 'name' => 'c' ) ); $this->flat( array( 'user_id' => 3, 'cnt' => null ) ); $this->flat( array( 'user_id' => 4, 'cnt' => 9 ) ); $this->native( 5, array( 'name' => '' ) ); $this->flat( array( 'user_id' => 5 ) ); $this->native( 6, array( 'name' => str_repeat( 'x', 300 ) ) ); $this->flat( array( 'user_id' => 6 ) ); $this->native( 7, array( 'cnt' => '7' ) ); $GLOBALS['_wp_options'] = array(); $GLOBALS['_wp_cache'] = array(); WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'user', new WPDO_Adapter_User() ); WPDO_Entity_Registry::register_group( 'user', 'dc_grp', array( array( 'key' => 'num', 'type' => 'decimal' ), array( 'key' => 'cnt', 'type' => 'integer' ), array( 'key' => 'name', 'type' => 'text' ), array( 'key' => 'prefs', 'type' => 'textarea' ), array( 'key' => 'meta_json', 'type' => 'json' ), array( 'key' => 'flag', 'type' => 'integer', 'default' => 0, 'required' => true ), ) ); WPDO_Schema_Manager::flush_table_exists_cache(); } protected function tearDown(): void { self::drop(); WPDO_Entity_Registry::init(); } private static function drop(): void { global $wpdb; foreach ( array( $wpdb->users, $wpdb->usermeta, self::TABLE ) as $table ) { $wpdb->query( "DROP TABLE IF EXISTS `{$table}`" ); } } private function native( int $user_id, array $values ): void { global $wpdb; foreach ( $values as $key => $value ) { $wpdb->insert( $wpdb->usermeta, array( 'user_id' => $user_id, 'meta_key' => $key, 'meta_value' => $value ) ); } } private function flat( array $row ): void { global $wpdb; $wpdb->insert( self::TABLE, $row ); } private function snapshot(): string { global $wpdb; return md5( wp_json_encode( array( $wpdb->get_results( "SELECT * FROM `{$wpdb->usermeta}` ORDER BY umeta_id", ARRAY_A ), $wpdb->get_results( 'SELECT * FROM `' . self::TABLE . '` ORDER BY id', ARRAY_A ) ) ) ); } public function test_counts_miss_diff_and_extra_per_key(): void { $report = TMDO_Drift_Check::run( 'user' ); $this->assertSame( array( 'miss' => 2, 'diff' => 2, 'extra' => 1 ), $report['total'] ); $keys = $report['groups']['dc_grp']['keys']; $this->assertSame( array( 'miss' => 1, 'diff' => 1, 'extra' => 0 ), $keys['name'] ); $this->assertSame( array( 'miss' => 1, 'diff' => 1, 'extra' => 1 ), $keys['cnt'] ); foreach ( array( 'num', 'prefs', 'meta_json', 'flag' ) as $key ) { $this->assertSame( array( 'miss' => 0, 'diff' => 0, 'extra' => 0 ), $keys[ $key ], "{$key}: equal by value" ); } } public function test_records_the_outcome_per_group(): void { TMDO_Drift_Check::run( 'user', 'dc_grp' ); $status = get_option( TMDO_Drift_Check::OPTION )['user']['dc_grp']; $this->assertSame( 2, $status['miss'] ); $this->assertSame( 2, $status['diff'] ); $this->assertSame( 1, $status['extra'] ); $this->assertSame( 0, $status['sample'] ); $this->assertNotEmpty( $status['checked_at'] ); } public function test_a_sample_covering_every_user_matches_the_full_check(): void { $report = TMDO_Drift_Check::run( 'user', '', 100 ); $this->assertSame( array( 'miss' => 2, 'diff' => 2, 'extra' => 1 ), $report['total'] ); $this->assertSame( 100, get_option( TMDO_Drift_Check::OPTION )['user']['dc_grp']['sample'] ); } public function test_never_writes_native_or_flat(): void { $before = $this->snapshot(); TMDO_Drift_Check::run( 'user' ); $this->assertSame( $before, $this->snapshot() ); } public function test_rejects_an_unknown_group(): void { $this->expectException( InvalidArgumentException::class ); TMDO_Drift_Check::run( 'user', 'no_such_group' ); } }