d36bb954d1
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
141 lines
5.0 KiB
PHP
141 lines
5.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
if ( ! function_exists( '__' ) ) {
|
|
function __( string $text, string $domain = 'default' ): string {
|
|
return $text;
|
|
}
|
|
}
|
|
|
|
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-fsm-advisor.php';
|
|
|
|
/**
|
|
* Unit tests for WPDO_FSM_Advisor (v2.4.0 M12).
|
|
*
|
|
* Pure-logic tests — uses option-driven state injection (no real DB).
|
|
*/
|
|
class FSMAdvisorTest extends TestCase {
|
|
|
|
protected function setUp(): void {
|
|
$GLOBALS['_wp_options'] = array();
|
|
$this->setup_wpdb_mock();
|
|
}
|
|
|
|
private function setup_wpdb_mock(): void {
|
|
global $wpdb;
|
|
$wpdb = new class {
|
|
public string $prefix = 'wp_';
|
|
public string $postmeta = 'wp_postmeta';
|
|
public string $options = 'wp_options';
|
|
public function prepare( string $sql, ...$args ): string {
|
|
$i = 0;
|
|
return preg_replace_callback( '/%[sd]/', function() use ( &$i, $args ) {
|
|
return (string) ( $args[ $i++ ] ?? '?' );
|
|
}, $sql );
|
|
}
|
|
public function get_var( string $sql ) { return '0'; } // shadow_diffs table absent
|
|
public function get_results( string $sql, $output = ARRAY_A ): array { return array(); }
|
|
};
|
|
}
|
|
|
|
private function set_module_state( string $module, string $state, ?int $days_ago = null ): void {
|
|
// Set FSM state.
|
|
$flags = (array) get_option( 'wpdo_features', array() );
|
|
$flags[ $module ] = $state;
|
|
update_option( 'wpdo_features', $flags, false );
|
|
|
|
// Reset Feature_Flags request cache via reflection.
|
|
$ref = new ReflectionClass( WPDO_Feature_Flags::class );
|
|
$prop = $ref->getProperty( 'cache' );
|
|
$prop->setAccessible( true );
|
|
$prop->setValue( null, null );
|
|
|
|
if ( null !== $days_ago ) {
|
|
$entered = (array) get_option( 'wpdo_fsm_state_entered', array() );
|
|
$entered[ $module ] = array(
|
|
'state' => $state,
|
|
'entered_at' => gmdate( 'Y-m-d H:i:s', time() - $days_ago * 86400 ),
|
|
);
|
|
update_option( 'wpdo_fsm_state_entered', $entered, false );
|
|
}
|
|
}
|
|
|
|
public function test_idle_state_returns_HOLD(): void {
|
|
$this->set_module_state( 'reviews', 'idle' );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'HOLD', $advice['action'] );
|
|
$this->assertSame( 'info', $advice['level'] );
|
|
}
|
|
|
|
public function test_complete_state_returns_HOLD(): void {
|
|
$this->set_module_state( 'reviews', 'complete' );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'HOLD', $advice['action'] );
|
|
}
|
|
|
|
public function test_dual_write_with_short_soak_returns_WAIT(): void {
|
|
// In dual_write 0 days < 1 day min soak.
|
|
$this->set_module_state( 'reviews', 'dual_write', 0 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'WAIT', $advice['action'] );
|
|
$this->assertGreaterThanOrEqual( 1, $advice['days_remaining'] );
|
|
}
|
|
|
|
public function test_dual_write_after_min_soak_returns_PROMOTE(): void {
|
|
$this->set_module_state( 'reviews', 'dual_write', 2 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'PROMOTE', $advice['action'] );
|
|
$this->assertSame( 'backfill', $advice['next_state'] );
|
|
}
|
|
|
|
public function test_verify_with_long_soak_returns_PROMOTE(): void {
|
|
// verify needs 7 days; 10 days = should promote.
|
|
$this->set_module_state( 'reviews', 'verify', 10 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'PROMOTE', $advice['action'] );
|
|
$this->assertSame( 'cutover', $advice['next_state'] );
|
|
}
|
|
|
|
public function test_verify_with_short_soak_returns_WAIT(): void {
|
|
$this->set_module_state( 'reviews', 'verify', 3 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'WAIT', $advice['action'] );
|
|
$this->assertSame( 4, $advice['days_remaining'] );
|
|
}
|
|
|
|
public function test_cleanup_with_short_wash_returns_WAIT(): void {
|
|
$this->set_module_state( 'reviews', 'cleanup', 1 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'WAIT', $advice['action'] );
|
|
$this->assertSame( 2, $advice['days_remaining'] );
|
|
}
|
|
|
|
public function test_cleanup_after_wash_returns_PROMOTE(): void {
|
|
$this->set_module_state( 'reviews', 'cleanup', 5 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'PROMOTE', $advice['action'] );
|
|
$this->assertSame( 'complete', $advice['next_state'] );
|
|
}
|
|
|
|
public function test_advice_metrics_include_state_and_soak(): void {
|
|
$this->set_module_state( 'reviews', 'dual_write', 5 );
|
|
$advice = WPDO_FSM_Advisor::advise( 'reviews' );
|
|
$this->assertSame( 'dual_write', $advice['metrics']['state'] );
|
|
$this->assertSame( 5, $advice['metrics']['days_in_state'] );
|
|
$this->assertSame( 1, $advice['metrics']['min_soak_days'] );
|
|
}
|
|
|
|
public function test_advise_all_returns_map_for_all_modules(): void {
|
|
$advice = WPDO_FSM_Advisor::advise_all();
|
|
$this->assertNotEmpty( $advice );
|
|
// All known HPCT + zone modules should be present (default idle).
|
|
foreach ( WPDO_Feature_Flags::HPCT_MODULES as $m ) {
|
|
$this->assertArrayHasKey( $m, $advice );
|
|
}
|
|
}
|
|
}
|