d36bb954d1
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
293 lines
10 KiB
PHP
293 lines
10 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Integration test: WPDO_Term_Comment_Shadow_Verifier (v2.12.5 Phase 5).
|
|
*
|
|
* Verifies the sample-and-compare contract:
|
|
* - sample_compare counts matched / diffs / missing_flat / missing_meta
|
|
* - throws on invalid entity_type / sample_size / empty keys
|
|
* - cron_tick gated by mode (no-op when neither term nor comment is shadow_read)
|
|
* - run_all aggregates per-group results
|
|
*/
|
|
class TermCommentShadowVerifierTest extends TestCase {
|
|
|
|
private const TERMS = 'wp_itest_terms';
|
|
private const TERMMETA = 'wp_itest_termmeta';
|
|
private const COMMENTS = 'wp_itest_comments';
|
|
private const COMMENTMETA = 'wp_itest_commentmeta';
|
|
private const TERM_FLAT = 'wp_itest_wpdo_term_hp_taxonomy';
|
|
private const COMMENT_FLAT = 'wp_itest_wpdo_comment_hp_review';
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
global $wpdb;
|
|
|
|
if ( ! class_exists( 'WPDO_Term_Comment_Shadow_Verifier' ) ) {
|
|
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-term-comment-shadow-verifier.php';
|
|
}
|
|
|
|
$wpdb->terms = self::TERMS;
|
|
$wpdb->termmeta = self::TERMMETA;
|
|
$wpdb->comments = self::COMMENTS;
|
|
$wpdb->commentmeta = self::COMMENTMETA;
|
|
|
|
// Source tables (terms / comments) need term_id / comment_ID columns.
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TERMS . '`' );
|
|
$wpdb->query(
|
|
'CREATE TABLE `' . self::TERMS . '` (
|
|
term_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
name varchar(200) NOT NULL DEFAULT "",
|
|
PRIMARY KEY (term_id)
|
|
) DEFAULT CHARACTER SET utf8mb4'
|
|
);
|
|
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::COMMENTS . '`' );
|
|
$wpdb->query(
|
|
'CREATE TABLE `' . self::COMMENTS . '` (
|
|
comment_ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
comment_post_ID bigint(20) unsigned NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (comment_ID)
|
|
) DEFAULT CHARACTER SET utf8mb4'
|
|
);
|
|
|
|
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . self::TERMMETA . '` (
|
|
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
term_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
|
meta_key varchar(255) DEFAULT NULL,
|
|
meta_value longtext,
|
|
PRIMARY KEY (meta_id),
|
|
KEY term_id (term_id),
|
|
KEY meta_key (meta_key(191))
|
|
) DEFAULT CHARACTER SET utf8mb4' );
|
|
|
|
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . self::COMMENTMETA . '` (
|
|
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
comment_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
|
meta_key varchar(255) DEFAULT NULL,
|
|
meta_value longtext,
|
|
PRIMARY KEY (meta_id),
|
|
KEY comment_id (comment_id),
|
|
KEY meta_key (meta_key(191))
|
|
) DEFAULT CHARACTER SET utf8mb4' );
|
|
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TERM_FLAT . '`' );
|
|
$wpdb->query(
|
|
'CREATE TABLE `' . self::TERM_FLAT . '` (
|
|
term_id bigint(20) unsigned NOT NULL,
|
|
hp_sort_order int(11) DEFAULT NULL,
|
|
hp_default tinyint(1) DEFAULT NULL,
|
|
hp_icon varchar(64) DEFAULT NULL,
|
|
PRIMARY KEY (term_id)
|
|
) DEFAULT CHARACTER SET utf8mb4'
|
|
);
|
|
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::COMMENT_FLAT . '`' );
|
|
$wpdb->query(
|
|
'CREATE TABLE `' . self::COMMENT_FLAT . '` (
|
|
comment_id bigint(20) unsigned NOT NULL,
|
|
hp_rating tinyint(1) DEFAULT NULL,
|
|
PRIMARY KEY (comment_id)
|
|
) DEFAULT CHARACTER SET utf8mb4'
|
|
);
|
|
}
|
|
|
|
public static function tearDownAfterClass(): void {
|
|
global $wpdb;
|
|
foreach ( array( self::TERMS, self::COMMENTS, self::TERM_FLAT, self::COMMENT_FLAT ) as $tbl ) {
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . $tbl . '`' );
|
|
}
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
global $wpdb;
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::TERMS . '`' );
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::COMMENTS . '`' );
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::TERMMETA . '`' );
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::COMMENTMETA . '`' );
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::TERM_FLAT . '`' );
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::COMMENT_FLAT . '`' );
|
|
}
|
|
|
|
// ── sample_compare contract ─────────────────────────────────────────────
|
|
|
|
public function test_sample_compare_invalid_entity_type_throws(): void {
|
|
$this->expectException( InvalidArgumentException::class );
|
|
WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'bogus',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' )
|
|
);
|
|
}
|
|
|
|
public function test_sample_compare_zero_sample_size_throws(): void {
|
|
$this->expectException( InvalidArgumentException::class );
|
|
WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
0
|
|
);
|
|
}
|
|
|
|
public function test_sample_compare_empty_keys_throws(): void {
|
|
$this->expectException( InvalidArgumentException::class );
|
|
WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array()
|
|
);
|
|
}
|
|
|
|
public function test_sample_compare_returns_zeros_for_empty_db(): void {
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' )
|
|
);
|
|
$this->assertSame( 0, $result['sampled'] );
|
|
$this->assertSame( 'term', $result['entity_type'] );
|
|
$this->assertSame( 'hp_taxonomy', $result['group'] );
|
|
}
|
|
|
|
public function test_sample_compare_counts_matches_when_in_sync(): void {
|
|
global $wpdb;
|
|
|
|
// 3 terms, all in sync between wp_termmeta and flat table.
|
|
for ( $i = 1; $i <= 3; $i++ ) {
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => $i, 'name' => 'term_' . $i ) );
|
|
$wpdb->insert( self::TERMMETA, array( 'term_id' => $i, 'meta_key' => 'hp_sort_order', 'meta_value' => $i * 10 ) );
|
|
$wpdb->insert( self::TERM_FLAT, array( 'term_id' => $i, 'hp_sort_order' => $i * 10 ) );
|
|
}
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
10
|
|
);
|
|
$this->assertSame( 3, $result['sampled'] );
|
|
$this->assertSame( 3, $result['matched'] );
|
|
$this->assertSame( 0, $result['diffs'] );
|
|
$this->assertSame( 0, $result['missing_flat'] );
|
|
$this->assertSame( 0, $result['missing_meta'] );
|
|
}
|
|
|
|
public function test_sample_compare_detects_missing_flat(): void {
|
|
global $wpdb;
|
|
|
|
// 2 terms with wp_termmeta but no flat row.
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 1, 'name' => 'a' ) );
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 2, 'name' => 'b' ) );
|
|
$wpdb->insert( self::TERMMETA, array( 'term_id' => 1, 'meta_key' => 'hp_sort_order', 'meta_value' => 5 ) );
|
|
$wpdb->insert( self::TERMMETA, array( 'term_id' => 2, 'meta_key' => 'hp_sort_order', 'meta_value' => 10 ) );
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
10
|
|
);
|
|
$this->assertSame( 2, $result['sampled'] );
|
|
$this->assertSame( 0, $result['matched'] );
|
|
$this->assertSame( 2, $result['missing_flat'] );
|
|
}
|
|
|
|
public function test_sample_compare_detects_missing_meta(): void {
|
|
global $wpdb;
|
|
|
|
// 2 terms with flat rows but no wp_termmeta.
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 1, 'name' => 'a' ) );
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 2, 'name' => 'b' ) );
|
|
$wpdb->insert( self::TERM_FLAT, array( 'term_id' => 1, 'hp_sort_order' => 5 ) );
|
|
$wpdb->insert( self::TERM_FLAT, array( 'term_id' => 2, 'hp_sort_order' => 10 ) );
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
10
|
|
);
|
|
$this->assertSame( 2, $result['sampled'] );
|
|
$this->assertSame( 2, $result['missing_meta'] );
|
|
}
|
|
|
|
public function test_sample_compare_detects_value_diff(): void {
|
|
global $wpdb;
|
|
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 1, 'name' => 'a' ) );
|
|
$wpdb->insert( self::TERMMETA, array( 'term_id' => 1, 'meta_key' => 'hp_sort_order', 'meta_value' => '5' ) );
|
|
$wpdb->insert( self::TERM_FLAT, array( 'term_id' => 1, 'hp_sort_order' => 99 ) );
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
10
|
|
);
|
|
$this->assertSame( 1, $result['diffs'] );
|
|
$this->assertSame( 0, $result['matched'] );
|
|
}
|
|
|
|
public function test_sample_compare_loose_equal_matches_numeric(): void {
|
|
global $wpdb;
|
|
|
|
$wpdb->insert( self::TERMS, array( 'term_id' => 1, 'name' => 'a' ) );
|
|
// wp_termmeta stores '5' as string, flat stores 5 as int — should match
|
|
$wpdb->insert( self::TERMMETA, array( 'term_id' => 1, 'meta_key' => 'hp_sort_order', 'meta_value' => '5' ) );
|
|
$wpdb->insert( self::TERM_FLAT, array( 'term_id' => 1, 'hp_sort_order' => 5 ) );
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'term',
|
|
'hp_taxonomy',
|
|
self::TERM_FLAT,
|
|
array( 'hp_sort_order' ),
|
|
10
|
|
);
|
|
$this->assertSame( 1, $result['matched'], 'String "5" must loose-equal int 5' );
|
|
$this->assertSame( 0, $result['diffs'] );
|
|
}
|
|
|
|
public function test_sample_compare_handles_comment_entity(): void {
|
|
global $wpdb;
|
|
|
|
$wpdb->insert( self::COMMENTS, array( 'comment_ID' => 1, 'comment_post_ID' => 100 ) );
|
|
$wpdb->insert( self::COMMENTMETA, array( 'comment_id' => 1, 'meta_key' => 'hp_rating', 'meta_value' => 5 ) );
|
|
$wpdb->insert( self::COMMENT_FLAT, array( 'comment_id' => 1, 'hp_rating' => 5 ) );
|
|
|
|
$result = WPDO_Term_Comment_Shadow_Verifier::sample_compare(
|
|
'comment',
|
|
'hp_review',
|
|
self::COMMENT_FLAT,
|
|
array( 'hp_rating' ),
|
|
10
|
|
);
|
|
$this->assertSame( 1, $result['sampled'] );
|
|
$this->assertSame( 1, $result['matched'] );
|
|
$this->assertSame( 'comment', $result['entity_type'] );
|
|
}
|
|
|
|
// ── cron_tick mode-gating ───────────────────────────────────────────────
|
|
|
|
public function test_cron_tick_no_op_when_neither_in_shadow_read(): void {
|
|
// Set both modes to dual_write so cron should no-op.
|
|
if ( class_exists( 'WPDO_Mode_Manager' ) ) {
|
|
WPDO_Mode_Manager::set( 'term', 'dual_write' );
|
|
WPDO_Mode_Manager::set( 'comment', 'dual_write' );
|
|
}
|
|
|
|
// cron_tick should return without error and not touch the tables.
|
|
WPDO_Term_Comment_Shadow_Verifier::cron_tick();
|
|
$this->assertTrue( true ); // No exception = no-op succeeded
|
|
}
|
|
}
|