hardening 2.2: 2meet-data-optimizer 1.2.0 (drift detection, scope, CLI and diagnostics) #6
@@ -442,9 +442,9 @@ class TMDO_CLI_Term_Comment {
|
||||
|
||||
WP_CLI::log( '' );
|
||||
WP_CLI::log( '── Filter chain status ──' );
|
||||
self::print_filter_status( 'wpdo_term_comment_garbage_filter_enabled', 'Garbage filter (Phase 1)' );
|
||||
self::print_filter_status( 'wpdo_wc_term_count_filter_enabled', 'WC term count filter (Phase 3)' );
|
||||
self::print_filter_status( 'wpdo_term_comment_misc_bucket_enabled', 'Misc bucket (Phase 4)' );
|
||||
self::print_filter_status( 'TMDO_Term_Comment_Garbage_Filter', 'Garbage filter (Phase 1)' );
|
||||
self::print_filter_status( 'TMDO_WC_Term_Count_Filter', 'WC term count filter (Phase 3)' );
|
||||
self::print_filter_status( 'TMDO_Term_Comment_Misc_Bucket', 'Misc bucket (Phase 4)' );
|
||||
|
||||
WP_CLI::log( '' );
|
||||
WP_CLI::log( 'Recommendation: ' . self::recommend_next_action( $term_mode, $comment_mode ) );
|
||||
@@ -504,15 +504,19 @@ class TMDO_CLI_Term_Comment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print a filter's enabled/disabled status.
|
||||
* Print a filter's enabled/disabled status, as the filter itself decides it
|
||||
* (each has its own default).
|
||||
*
|
||||
* @param string $option_key Option storing the toggle value.
|
||||
* @param string $filter_class Filter class with a static is_enabled().
|
||||
* @param string $label Human-readable name.
|
||||
* @return void
|
||||
*/
|
||||
private static function print_filter_status( string $option_key, string $label ): void {
|
||||
$enabled = (bool) get_option( $option_key, '1' );
|
||||
WP_CLI::log( sprintf( '%-32s%s', $label . ':', $enabled ? '✓ enabled' : '✗ disabled' ) );
|
||||
private static function print_filter_status( string $filter_class, string $label ): void {
|
||||
if ( ! class_exists( $filter_class ) ) {
|
||||
WP_CLI::log( sprintf( '%-32s%s', $label . ':', '— not installed' ) );
|
||||
return;
|
||||
}
|
||||
WP_CLI::log( sprintf( '%-32s%s', $label . ':', $filter_class::is_enabled() ? '✓ enabled' : '✗ disabled' ) );
|
||||
}
|
||||
|
||||
// ── term-comment-shadow-report (v2.12.5 Phase 5) ──────────────────────────
|
||||
|
||||
@@ -91,13 +91,15 @@ class TMDO_CLI_V2 {
|
||||
$wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $flat_table )
|
||||
);
|
||||
|
||||
$flat_count = $flat_exists
|
||||
// Flat rows holding at least one value (wp_core keeps an all-NULL row per post).
|
||||
$managed_keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group_name );
|
||||
$has_value = implode( ' OR ', array_map( static fn( $k ) => '`' . TMDO_Schema_Manager::sanitize_column_name( $k ) . '` IS NOT NULL', $managed_keys ) );
|
||||
$flat_count = $flat_exists && '' !== $has_value
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$flat_table}`" )
|
||||
? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$flat_table}` WHERE {$has_value}" )
|
||||
: 0;
|
||||
|
||||
// EAV source: count distinct entity IDs that have at least one managed key.
|
||||
$managed_keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group_name );
|
||||
$eav_table = $eav_tables[ $entity_type ] ?? '';
|
||||
$eav_count = 0;
|
||||
|
||||
@@ -106,7 +108,12 @@ class TMDO_CLI_V2 {
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $managed_keys ), '%s' ) );
|
||||
// $id_col comes from a hardcoded map; $eav_table is $wpdb->*meta (framework-managed); $placeholders is %s repeats only.
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$sql = "SELECT COUNT(DISTINCT `{$id_col}`) FROM `{$eav_table}` WHERE `meta_key` IN ({$placeholders})";
|
||||
$sql = "SELECT COUNT(DISTINCT m.`{$id_col}`) FROM `{$eav_table}` m WHERE m.`meta_key` IN ({$placeholders})";
|
||||
// A group of some post types only: other posts holding its keys are not its rows.
|
||||
$post_types = 'post' === $entity_type ? TMDO_Entity_Registry::get_group_post_types( 'post', $group_name ) : array();
|
||||
if ( $post_types ) {
|
||||
$sql .= " AND m.post_id IN ( SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('" . implode( "','", array_map( 'esc_sql', $post_types ) ) . "') )";
|
||||
}
|
||||
$eav_count = (int) $wpdb->get_var( $wpdb->prepare( $sql, ...$managed_keys ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
}
|
||||
|
||||
|
||||
+81
-22
@@ -94,6 +94,83 @@ class TMDO_CLI {
|
||||
WP_CLI::success( 'Tables installed. DB version: ' . get_option( 'wpdo_db_version' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the flat table of every backfillable group of every entity type,
|
||||
* plus the installer's migration status and points ledger tables.
|
||||
*
|
||||
* @return bool Every table exists.
|
||||
*/
|
||||
public static function check_entity_tables(): bool {
|
||||
global $wpdb;
|
||||
|
||||
$tables = array( $wpdb->prefix . 'wpdo_migration_status', $wpdb->prefix . 'wpdo_user_points_ledger' );
|
||||
foreach ( TMDO_Entity_Registry::VALID_ENTITY_TYPES as $entity_type ) {
|
||||
foreach ( TMDO_Entity_Migration_Engine::backfillable_groups( $entity_type ) as $group ) {
|
||||
$tables[] = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
|
||||
}
|
||||
}
|
||||
|
||||
$ok = true;
|
||||
foreach ( $tables as $t ) {
|
||||
if ( TMDO_IS_SQLITE ) {
|
||||
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=%s", $t ) );
|
||||
} else {
|
||||
$exists = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $t ) );
|
||||
}
|
||||
if ( $exists ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" );
|
||||
WP_CLI::log( " [OK] {$t} ({$rows} rows)" );
|
||||
} else {
|
||||
WP_CLI::warning( " [MISSING] {$t} — run `wp wpdo install` to create." );
|
||||
$ok = false;
|
||||
}
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report Promotion Guard violations and the recorded drift status of every
|
||||
* entity that writes flat tables or has been checked (informational).
|
||||
*/
|
||||
public static function report_guard_and_drift(): void {
|
||||
WP_CLI::log( '' );
|
||||
$violations = TMDO_Promotion_Guard::violations();
|
||||
if ( empty( $violations ) ) {
|
||||
WP_CLI::log( ' [OK] Promotion Guard: no violation' );
|
||||
}
|
||||
foreach ( $violations as $entity_type => $integrations ) {
|
||||
WP_CLI::warning( " [GUARD] {$entity_type} is aeav_only while " . implode( ', ', $integrations ) . " reads native meta — write flat back with `wp tmdo bridge-restore-native {$entity_type} --demote`." );
|
||||
}
|
||||
|
||||
$status = get_option( TMDO_Drift_Check::OPTION, array() );
|
||||
$status = is_array( $status ) ? $status : array();
|
||||
WP_CLI::log( 'Drift (last `wp tmdo drift-check` or daily sample):' );
|
||||
foreach ( TMDO_Entity_Registry::VALID_ENTITY_TYPES as $entity_type ) {
|
||||
$groups = TMDO_Entity_Migration_Engine::backfillable_groups( $entity_type );
|
||||
$last = $status[ $entity_type ] ?? array();
|
||||
if ( empty( $groups ) || ( empty( $last ) && ! TMDO_Mode_Manager::writes_to_flat( $entity_type ) ) ) {
|
||||
continue;
|
||||
}
|
||||
if ( empty( $last ) ) {
|
||||
WP_CLI::log( " {$entity_type}: never checked — run `wp tmdo drift-check {$entity_type}`" );
|
||||
continue;
|
||||
}
|
||||
$drifting = array();
|
||||
foreach ( $last as $group => $s ) {
|
||||
if ( (int) $s['miss'] + (int) $s['diff'] + (int) $s['extra'] > 0 ) {
|
||||
$drifting[] = sprintf( '%s miss=%d diff=%d extra=%d', $group, $s['miss'], $s['diff'], $s['extra'] );
|
||||
}
|
||||
}
|
||||
$oldest = min( array_column( $last, 'checked_at' ) );
|
||||
if ( $drifting ) {
|
||||
WP_CLI::warning( " {$entity_type}: " . implode( '; ', $drifting ) . " (checked {$oldest} UTC or later) — `wp tmdo drift-check {$entity_type} --fix`" );
|
||||
} else {
|
||||
WP_CLI::log( sprintf( ' [OK] %s: %d group(s) clean (oldest check %s UTC)', $entity_type, count( $last ), $oldest ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run health check on all tables.
|
||||
*
|
||||
@@ -195,30 +272,12 @@ class TMDO_CLI {
|
||||
WP_CLI::log( $exists ? " [OK] {$t}" : " [NOT CREATED] {$t} (will be created on first use)" );
|
||||
}
|
||||
|
||||
// Check user entity flat tables (v2.5.5+).
|
||||
$user_entity_tables = array(
|
||||
$wpdb->prefix . 'wpdo_user_membership',
|
||||
$wpdb->prefix . 'wpdo_user_activity',
|
||||
$wpdb->prefix . 'wpdo_user_profile',
|
||||
$wpdb->prefix . 'wpdo_user_sso',
|
||||
$wpdb->prefix . 'wpdo_user_points_ledger',
|
||||
$wpdb->prefix . 'wpdo_migration_status',
|
||||
);
|
||||
foreach ( $user_entity_tables as $t ) {
|
||||
if ( TMDO_IS_SQLITE ) {
|
||||
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=%s", $t ) );
|
||||
} else {
|
||||
$exists = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s', $t ) );
|
||||
}
|
||||
if ( $exists ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$t}`" );
|
||||
WP_CLI::log( " [OK] {$t} ({$rows} rows)" );
|
||||
} else {
|
||||
WP_CLI::warning( " [MISSING] {$t} — run \`wp wpdo install\` to create." );
|
||||
// Entity flat tables, from the registry (was a hard-coded user list that
|
||||
// named the never-registered sso group).
|
||||
if ( ! self::check_entity_tables() ) {
|
||||
$all_ok = false;
|
||||
}
|
||||
}
|
||||
self::report_guard_and_drift();
|
||||
|
||||
// Check partner plugin custom tables via Custom Table Registry (v2.6.0+).
|
||||
if ( class_exists( 'TMDO_Custom_Table_Registry' ) ) {
|
||||
|
||||
+6
-4
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_%'] );
|
||||
}
|
||||
}
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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' ) );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user