Files
2meet-data-optimizer/tests/unit/Advisor/ModuleDetectorTest.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

161 lines
6.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
if ( ! function_exists( '__' ) ) {
function __( string $text, string $domain = 'default' ): string { return $text; }
}
if ( ! function_exists( 'number_format_i18n' ) ) {
function number_format_i18n( $n, int $decimals = 0 ): string {
return number_format( (float) $n, $decimals );
}
}
require_once dirname( __DIR__, 3 ) . '/includes/class-tmdo-logger.php';
require_once dirname( __DIR__, 3 ) . '/includes/class-tmdo-feature-flags.php';
require_once dirname( __DIR__, 3 ) . '/includes/advisor/class-tmdo-module-rules.php';
require_once dirname( __DIR__, 3 ) . '/includes/advisor/class-tmdo-module-detector.php';
/**
* Unit tests for WPDO_Module_Rules + WPDO_Module_Detector (v2.5.0 M16).
*
* Pure-logic tests using mocked WPDO_Compatibility (via dynamic class
* substitution where needed) and option-driven post counts.
*/
class ModuleDetectorTest extends TestCase {
protected function setUp(): void {
$GLOBALS['_wp_options'] = array();
// Reset Feature_Flags cache.
$ref = new ReflectionClass( WPDO_Feature_Flags::class );
$prop = $ref->getProperty( 'cache' );
$prop->setAccessible( true );
$prop->setValue( null, null );
$this->setup_wpdb_mock();
}
private function setup_wpdb_mock(): void {
global $wpdb;
$wpdb = new class {
public string $prefix = 'wp_';
public string $posts = 'wp_posts';
public string $postmeta = 'wp_postmeta';
public string $options = 'wp_options';
public array $count_overrides = array();
public function prepare( string $sql, ...$args ): string {
$i = 0;
return preg_replace_callback( '/%[sd]/', function() use ( &$i, $args ) {
return is_string( $args[ $i ] ?? null ) ? "'" . $args[ $i++ ] . "'" : (string) ( $args[ $i++ ] ?? '?' );
}, $sql );
}
public function get_var( string $sql ) {
// Mock by inspecting SQL pattern.
if ( str_contains( $sql, "post_type =" ) ) {
if ( preg_match( "/post_type = '([^']+)'/", $sql, $m ) ) {
return (string) ( $this->count_overrides[ $m[1] ] ?? 0 );
}
}
if ( str_contains( $sql, "post_status = 'trash'" ) ) {
return (string) ( $this->count_overrides['__trash__'] ?? 0 );
}
return '0';
}
public function get_results( string $sql, $output = ARRAY_A ): array { return array(); }
};
}
public function test_module_rules_known_modules_count(): void {
$modules = WPDO_Module_Rules::known_modules();
$this->assertGreaterThanOrEqual( 14, count( $modules ), '預期 ≥ 14 個 module9 HPCT + 6 zone - 1 dup' );
$this->assertContains( 'reviews', $modules );
$this->assertContains( 'warm', $modules );
$this->assertContains( 'archive', $modules );
$this->assertContains( 'hot_hp_listing', $modules );
}
public function test_module_rule_for_module_returns_array(): void {
$rule = WPDO_Module_Rules::for_module( 'reviews' );
$this->assertIsArray( $rule );
$this->assertArrayHasKey( 'compat_required', $rule );
$this->assertArrayHasKey( 'description', $rule );
}
public function test_module_rule_unknown_module_returns_null(): void {
$this->assertNull( WPDO_Module_Rules::for_module( 'totally_fake_module_xyz' ) );
}
public function test_detector_skip_when_module_not_idle(): void {
// Set reviews to dual_write.
WPDO_Feature_Flags::set( 'reviews', 'dual_write' );
$r = WPDO_Module_Detector::detect_one( 'reviews' );
$this->assertSame( 'skip', $r['recommendation'] );
$this->assertFalse( $r['available'] );
$this->assertNotEmpty( $r['blockers'] );
$this->assertSame( 'dual_write', $r['current_state'] );
}
public function test_detector_warm_module_recommends_for_any_site(): void {
// warm module's compat_required is empty + no post_type.
$r = WPDO_Module_Detector::detect_one( 'warm' );
$this->assertTrue( $r['available'] );
$this->assertSame( 'enable', $r['recommendation'] );
$this->assertGreaterThan( 0, $r['confidence'] );
$this->assertNotEmpty( $r['suggested_action'] );
$this->assertSame( 'dual_write', $r['suggested_action']['to_state'] );
}
public function test_detector_archive_blocked_when_no_trashed_posts(): void {
// trashed = 0, threshold = 50.
global $wpdb;
$wpdb->count_overrides['__trash__'] = 0;
$r = WPDO_Module_Detector::detect_one( 'archive' );
$this->assertSame( 'wait', $r['recommendation'] );
$this->assertFalse( $r['available'] );
}
public function test_detector_archive_passes_when_enough_trashed(): void {
global $wpdb;
$wpdb->count_overrides['__trash__'] = 100;
$r = WPDO_Module_Detector::detect_one( 'archive' );
$this->assertSame( 'enable', $r['recommendation'] );
$this->assertTrue( $r['available'] );
}
public function test_detector_hivepress_required_blocks_when_inactive(): void {
// reviews requires hivepress; Compatibility class isn't loaded in this test.
// Without Compatibility class, the helper returns the full required list as missing.
$r = WPDO_Module_Detector::detect_one( 'reviews' );
$this->assertSame( 'skip', $r['recommendation'] );
$this->assertNotEmpty( $r['blockers'] );
}
public function test_get_actionable_filters_by_confidence(): void {
$actionable = WPDO_Module_Detector::get_actionable( 0.5 );
$this->assertIsArray( $actionable );
// Each result should have available=true and recommendation=enable.
foreach ( $actionable as $module => $r ) {
$this->assertTrue( $r['available'], "$module should be available" );
$this->assertSame( 'enable', $r['recommendation'] );
$this->assertGreaterThanOrEqual( 0.5, $r['confidence'] );
}
}
public function test_get_actionable_sorts_by_confidence_desc(): void {
$actionable = WPDO_Module_Detector::get_actionable( 0.0 );
$last_confidence = 1.0;
foreach ( $actionable as $r ) {
$this->assertLessThanOrEqual( $last_confidence, (float) $r['confidence'] );
$last_confidence = (float) $r['confidence'];
}
}
public function test_detect_all_returns_entry_for_each_known_module(): void {
$all = WPDO_Module_Detector::detect_all( true );
$known = WPDO_Module_Rules::known_modules();
foreach ( $known as $m ) {
$this->assertArrayHasKey( $m, $all, "Detector should return entry for $m" );
}
}
}