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,292 @@
|
||||
<?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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user