fix(2meet-data-optimizer): stop false alarms in doctor, term-comment-diagnose and bridge-status

- doctor: entity flat tables come from the registry (TMDO_CLI::check_entity_tables()) instead of a hard-coded user list naming the never-registered sso group, which failed doctor on every site; it also reports Promotion Guard violations and the recorded drift status per entity (report_guard_and_drift()).
- term-comment-diagnose: filter status comes from each filter's is_enabled() (the garbage filter and misc bucket default to off but were shown enabled); a filter whose add-on is not active shows "not installed".
- bridge-status: flat_rows counts rows holding a value (wp_core keeps an all-NULL row per post: 15306% on dev36) and eav_ids of a post-type group counts only its post types.
- Tests: the WP_CLI stubs record their output in $GLOBALS['_wp_cli_output'].
- Verify: DoctorTest, CliFilterStatusTest, CliBridgeStatusTest (RED before); dev36 doctor exit 1 → 0, wp_core 15306% → 100%.
- PR: PR-076
This commit is contained in:
2026-09-27 01:00:13 +08:00
parent 9d1a7c5591
commit f15473527c
8 changed files with 352 additions and 46 deletions
+6 -4
View File
@@ -506,10 +506,12 @@ if ( ! function_exists( 'register_rest_route' ) ) {
// WP_CLI stub (needed before loading CLI classes).
if ( ! class_exists( 'WP_CLI' ) ) {
class WP_CLI {
public static function log( string $msg ): void {}
public static function warning( string $msg ): void {}
public static function success( string $msg ): void {}
public static function error( string $msg ): void {}
// Output is kept in $GLOBALS['_wp_cli_output'] so CLI tests can assert it.
public static function log( string $msg ): void { $GLOBALS['_wp_cli_output'][] = $msg; }
public static function line( string $msg = '' ): void { $GLOBALS['_wp_cli_output'][] = $msg; }
public static function warning( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Warning: ' . $msg; }
public static function success( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Success: ' . $msg; }
public static function error( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Error: ' . $msg; }
public static function add_command( string $name, $class ): void {}
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* `wp tmdo bridge-status` progress (2.2 PR-076): eav_ids of a post group that
* belongs to some post types counted every post holding one of its keys, so a
* fully backfilled group showed less than 100%.
*
* flat_rows counted rows without any value too (wp_core holds an all-NULL row
* for every post): dev36 showed wp_core at 15306%.
*
* Fixture: group bs_grp for hp_listing only; bs_price on post 1 (hp_listing)
* and post 2 (a plain post); flat rows for post 1 (a value) and post 3
* (hp_listing, all NULL).
*/
class CliBridgeStatusTest extends TestCase {
private const POSTS = 'wp_itest_posts';
private const POSTMETA = 'wp_itest_postmeta';
private const FLAT = 'wp_itest_wpdo_post_bs_grp';
public static function setUpBeforeClass(): void {
foreach ( array(
'includes/adapters/interface-entity-adapter.php',
'includes/adapters/class-tmdo-adapter-post.php',
'cli/class-tmdo-cli-v2.php',
) as $file ) {
require_once WPDO_PLUGIN_DIR . $file;
}
if ( ! function_exists( 'WP_CLI\Utils\format_items' ) ) {
eval( 'namespace WP_CLI\Utils; function format_items( $format, $items, $fields ) { $GLOBALS["_wp_cli_items"] = $items; }' ); // phpcs:ignore Squiz.PHP.Eval -- test-only WP-CLI stub.
}
}
protected function setUp(): void {
global $wpdb;
self::drop();
$wpdb->query( 'CREATE TABLE `' . self::POSTS . "` ( ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_type VARCHAR(20) NOT NULL DEFAULT 'post', PRIMARY KEY (ID) ) ENGINE=InnoDB" );
$wpdb->query( 'CREATE TABLE `' . self::POSTMETA . '` ( meta_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, post_id BIGINT UNSIGNED NOT NULL DEFAULT 0, meta_key VARCHAR(255) DEFAULT NULL, meta_value LONGTEXT, PRIMARY KEY (meta_id) ) ENGINE=InnoDB' );
$wpdb->query( 'CREATE TABLE `' . self::FLAT . '` ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, bs_price DECIMAL(18,6) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (post_id) ) ENGINE=InnoDB' );
$wpdb->insert( self::POSTS, array( 'ID' => 1, 'post_type' => 'hp_listing' ) );
$wpdb->insert( self::POSTS, array( 'ID' => 2, 'post_type' => 'post' ) );
$wpdb->insert( self::POSTMETA, array( 'post_id' => 1, 'meta_key' => 'bs_price', 'meta_value' => '10' ) );
$wpdb->insert( self::POSTMETA, array( 'post_id' => 2, 'meta_key' => 'bs_price', 'meta_value' => '20' ) );
$wpdb->insert( self::FLAT, array( 'post_id' => 1, 'bs_price' => 10 ) );
$wpdb->insert( self::POSTS, array( 'ID' => 3, 'post_type' => 'hp_listing' ) );
$wpdb->insert( self::FLAT, array( 'post_id' => 3 ) );
$GLOBALS['_wp_options'] = array( 'wpdo_bridge_modes' => array( 'post' => 'dual_write' ) );
$GLOBALS['_wp_cli_items'] = array();
$GLOBALS['_wp_cli_output'] = array();
TMDO_Mode_Manager::reset_cache();
WPDO_Entity_Registry::init();
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
WPDO_Entity_Registry::register_group( 'post', 'bs_grp', array( array( 'key' => 'bs_price', 'type' => 'decimal' ) ), array( 'post_types' => array( 'hp_listing' ) ) );
}
protected function tearDown(): void {
self::drop();
WPDO_Entity_Registry::init();
TMDO_Mode_Manager::reset_cache();
}
private static function drop(): void {
global $wpdb;
foreach ( array( self::POSTS, self::POSTMETA, self::FLAT ) as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
}
}
public function test_counts_only_flat_rows_that_hold_a_value_and_posts_of_the_groups_types(): void {
( new TMDO_CLI_V2() )->bridge_status( array(), array() );
$row = array_values( array_filter( $GLOBALS['_wp_cli_items'], static fn( $r ) => 'bs_grp' === $r['group'] ) )[0];
$this->assertSame( 1, $row['flat_rows'] );
$this->assertSame( 1, $row['eav_ids'] );
$this->assertSame( '100%', $row['progress_%'] );
}
}
+110
View File
@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* `wp tmdo doctor` false alarms (2.2 PR-076).
*
* The user flat tables were a hard-coded list including wpdo_user_sso, a group
* never registered: doctor reported it MISSING and failed on every site. The
* tables now come from the registry; doctor also reports Promotion Guard
* violations and the recorded drift status.
*/
class DoctorTest extends TestCase {
private const HAVE = 'wp_itest_wpdo_user_dt_have';
private const CORE = array( 'wp_itest_wpdo_migration_status', 'wp_itest_wpdo_user_points_ledger' );
public static function setUpBeforeClass(): void {
foreach ( array(
'includes/adapters/interface-entity-adapter.php',
'includes/adapters/class-tmdo-adapter-user.php',
'includes/engine/class-tmdo-promotion-guard.php',
'cli/class-tmdo-cli.php',
) as $file ) {
require_once WPDO_PLUGIN_DIR . $file;
}
}
protected function setUp(): void {
global $wpdb;
self::drop();
foreach ( array_merge( array( self::HAVE ), self::CORE ) as $table ) {
$wpdb->query( "CREATE TABLE `{$table}` ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINE=InnoDB" );
}
$GLOBALS['_wp_options'] = array();
$GLOBALS['_wp_cli_output'] = array();
TMDO_Mode_Manager::reset_cache();
WPDO_Entity_Registry::init();
WPDO_Entity_Registry::register_adapter( 'user', new WPDO_Adapter_User() );
WPDO_Entity_Registry::register_group( 'user', 'dt_have', array( array( 'key' => 'dt_a', 'type' => 'text' ) ) );
WPDO_Schema_Manager::flush_table_exists_cache();
}
protected function tearDown(): void {
self::drop();
WPDO_Entity_Registry::init();
TMDO_Mode_Manager::reset_cache();
}
private static function drop(): void {
global $wpdb;
foreach ( array_merge( array( self::HAVE ), self::CORE ) as $table ) {
$wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
}
}
private function cli_output(): string {
return implode( "\n", $GLOBALS['_wp_cli_output'] ?? array() );
}
public function test_entity_tables_come_from_the_registry(): void {
$ok = TMDO_CLI::check_entity_tables();
$this->assertTrue( $ok, $this->cli_output() );
$this->assertStringContainsString( '[OK] ' . self::HAVE, $this->cli_output() );
$this->assertStringNotContainsString( 'wpdo_user_sso', $this->cli_output() );
}
public function test_a_registered_group_without_its_table_is_reported(): void {
WPDO_Entity_Registry::register_group( 'user', 'dt_missing', array( array( 'key' => 'dt_b', 'type' => 'text' ) ) );
$ok = TMDO_CLI::check_entity_tables();
$this->assertFalse( $ok );
$this->assertStringContainsString( '[MISSING] wp_itest_wpdo_user_dt_missing', $this->cli_output() );
}
public function test_reports_drifting_groups(): void {
$GLOBALS['_wp_options']['wpdo_bridge_modes'] = array( 'user' => 'dual_write' );
$GLOBALS['_wp_options'][ TMDO_Drift_Check::OPTION ] = array(
'user' => array(
'dt_have' => array(
'miss' => 1,
'diff' => 2,
'extra' => 0,
'sample' => 0,
'checked_at' => '2026-09-27 00:00:00',
),
),
);
TMDO_Mode_Manager::reset_cache();
TMDO_CLI::report_guard_and_drift();
$this->assertStringContainsString( 'dt_have miss=1 diff=2 extra=0', $this->cli_output() );
$this->assertStringContainsString( 'wp tmdo drift-check user --fix', $this->cli_output() );
}
public function test_reports_an_entity_never_checked(): void {
$GLOBALS['_wp_options']['wpdo_bridge_modes'] = array( 'user' => 'dual_write' );
TMDO_Mode_Manager::reset_cache();
TMDO_CLI::report_guard_and_drift();
$this->assertStringContainsString( 'user: never checked', $this->cli_output() );
$this->assertStringContainsString( 'Promotion Guard: no violation', $this->cli_output() );
}
}
+6 -4
View File
@@ -517,10 +517,12 @@ if ( ! function_exists( 'wp_insert_comment' ) ) {
if ( ! class_exists( 'WP_CLI' ) ) {
class WP_CLI {
public static function log( string $msg ): void {}
public static function warning( string $msg ): void {}
public static function success( string $msg ): void {}
public static function error( string $msg ): void {}
// Output is kept in $GLOBALS['_wp_cli_output'] so CLI tests can assert it.
public static function log( string $msg ): void { $GLOBALS['_wp_cli_output'][] = $msg; }
public static function line( string $msg = '' ): void { $GLOBALS['_wp_cli_output'][] = $msg; }
public static function warning( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Warning: ' . $msg; }
public static function success( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Success: ' . $msg; }
public static function error( string $msg ): void { $GLOBALS['_wp_cli_output'][] = 'Error: ' . $msg; }
public static function add_command( string $name, $class ): void {}
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* `wp tmdo term-comment-diagnose` filter chain status (2.2 PR-076).
*
* It read each filter's option with a default of '1', while the garbage filter
* and misc bucket default to off: a site that never touched them was told they
* were enabled. The status now comes from each filter's own is_enabled().
*/
class CliFilterStatusTest extends TestCase {
protected function setUp(): void {
$GLOBALS['_wp_options'] = array();
$GLOBALS['_wp_cli_output'] = array();
}
private function filter_line( string $class, string $label ): string {
$method = new ReflectionMethod( TMDO_CLI_Term_Comment::class, 'print_filter_status' );
$method->setAccessible( true );
$method->invoke( null, $class, $label );
return (string) end( $GLOBALS['_wp_cli_output'] );
}
public function test_filters_off_by_default_show_disabled(): void {
$this->assertStringContainsString( 'disabled', $this->filter_line( 'TMDO_Term_Comment_Garbage_Filter', 'Garbage filter' ) );
$this->assertStringContainsString( 'disabled', $this->filter_line( 'TMDO_Term_Comment_Misc_Bucket', 'Misc bucket' ) );
}
public function test_an_enabled_filter_shows_enabled(): void {
$GLOBALS['_wp_options'][ TMDO_Term_Comment_Garbage_Filter::OPT_ENABLED ] = '1';
$this->assertStringContainsString( '✓ enabled', $this->filter_line( 'TMDO_Term_Comment_Garbage_Filter', 'Garbage filter' ) );
}
public function test_a_filter_whose_add_on_is_not_active_says_so(): void {
$this->assertStringContainsString( 'not installed', $this->filter_line( 'TMDO_No_Such_Filter', 'WC term count filter' ) );
}
}