Files
2meet-data-optimizer/tests/integration/TermCommentShadowVerifierTest.php
T
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

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
}
}