Files
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

477 lines
18 KiB
PHP
Raw Permalink 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;
/**
* Integration test: WPDO_Post_Stress_Tester (v2.9.4).
*
* Verifies the post-side stress tester contract:
* - create() bulk-inserts N posts with 19 wc_product meta keys
* - count_test_posts() returns the correct count
* - cleanup() removes ALL test posts + their postmeta + flat rows
* - test posts use post_title prefix WPDO_STRESS_TEST_ for identification
*
* Mirrors the user-side WPDO_User_Stress_Tester contract but with a much
* narrower API surface — full polling/cron/benchmark UI deferred (v2.9.4
* scope is bulk fixture generation for v2.9.5 cutover validation).
*/
class PostStressTesterTest extends TestCase {
private const POSTS = 'wp_itest_posts';
private const POSTMETA = 'wp_itest_postmeta';
public static function setUpBeforeClass(): void {
global $wpdb;
if ( ! class_exists( 'WPDO_Post_Stress_Tester' ) ) {
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-post-stress-tester.php';
}
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::POSTS . '`' );
$wpdb->query(
'CREATE TABLE `' . self::POSTS . '` (
ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
post_title text NOT NULL,
post_type varchar(20) NOT NULL DEFAULT \'post\',
post_status varchar(20) NOT NULL DEFAULT \'publish\',
post_date datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_date_gmt datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_modified_gmt datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_author bigint(20) unsigned NOT NULL DEFAULT 0,
post_content longtext NOT NULL,
post_excerpt text NOT NULL,
comment_status varchar(20) NOT NULL DEFAULT \'open\',
ping_status varchar(20) NOT NULL DEFAULT \'open\',
post_password varchar(255) NOT NULL DEFAULT \'\',
post_name varchar(200) NOT NULL DEFAULT \'\',
to_ping text NOT NULL,
pinged text NOT NULL,
post_content_filtered longtext NOT NULL,
post_parent bigint(20) unsigned NOT NULL DEFAULT 0,
guid varchar(255) NOT NULL DEFAULT \'\',
menu_order int(11) NOT NULL DEFAULT 0,
post_mime_type varchar(100) NOT NULL DEFAULT \'\',
comment_count bigint(20) NOT NULL DEFAULT 0,
PRIMARY KEY (ID),
KEY post_type (post_type),
KEY post_title (post_title(64))
) DEFAULT CHARACTER SET utf8mb4'
);
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::POSTMETA . '`' );
$wpdb->query(
'CREATE TABLE `' . self::POSTMETA . '` (
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
post_id bigint(20) unsigned NOT NULL DEFAULT 0,
meta_key varchar(255) DEFAULT NULL,
meta_value longtext,
PRIMARY KEY (meta_id),
KEY post_id (post_id),
KEY meta_key (meta_key(191))
) DEFAULT CHARACTER SET utf8mb4'
);
}
public static function tearDownAfterClass(): void {
global $wpdb;
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::POSTS . '`' );
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::POSTMETA . '`' );
}
protected function setUp(): void {
global $wpdb;
$wpdb->query( 'TRUNCATE TABLE `' . self::POSTS . '`' );
$wpdb->query( 'TRUNCATE TABLE `' . self::POSTMETA . '`' );
}
// ── create() ──────────────────────────────────────────────────────────────
public function test_create_inserts_requested_count_of_products(): void {
$result = WPDO_Post_Stress_Tester::create( 'product', 5 );
$this->assertSame( 5, $result['created'] );
global $wpdb;
$count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTS . "` WHERE post_type = 'product'"
);
$this->assertSame( 5, $count );
}
public function test_create_uses_stress_test_prefix_in_post_title(): void {
WPDO_Post_Stress_Tester::create( 'product', 3 );
global $wpdb;
$prefix_count = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM `" . self::POSTS . "` WHERE post_title LIKE %s",
WPDO_Post_Stress_Tester::TEST_POST_PREFIX . '%'
)
);
$this->assertSame( 3, $prefix_count );
}
public function test_create_seeds_postmeta_for_each_post(): void {
WPDO_Post_Stress_Tester::create( 'product', 2 );
global $wpdb;
// Each test product should have at least the 5 critical wc_product keys.
$meta_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTMETA . "`"
);
$this->assertGreaterThanOrEqual( 10, $meta_count, 'At least 5 keys × 2 posts = 10 rows.' );
// Verify _price was set on every test product.
$price_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTMETA . "` WHERE meta_key = '_price'"
);
$this->assertSame( 2, $price_count );
}
public function test_create_supports_hp_listing_post_type(): void {
$result = WPDO_Post_Stress_Tester::create( 'hp_listing', 4 );
$this->assertSame( 4, $result['created'] );
global $wpdb;
$count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTS . "` WHERE post_type = 'hp_listing'"
);
$this->assertSame( 4, $count );
// hp_listing seeds hp_price, not _price.
$hp_price_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTMETA . "` WHERE meta_key = 'hp_price'"
);
$this->assertSame( 4, $hp_price_count );
}
public function test_create_rejects_unsupported_post_type(): void {
$this->expectException( InvalidArgumentException::class );
WPDO_Post_Stress_Tester::create( 'bogus_type', 3 );
}
public function test_create_rejects_zero_count(): void {
$this->expectException( InvalidArgumentException::class );
WPDO_Post_Stress_Tester::create( 'product', 0 );
}
public function test_create_rejects_excessive_count(): void {
$this->expectException( InvalidArgumentException::class );
WPDO_Post_Stress_Tester::create( 'product', 100001 );
}
// ── count_test_posts() ────────────────────────────────────────────────────
public function test_count_test_posts_returns_zero_for_empty(): void {
$this->assertSame( 0, WPDO_Post_Stress_Tester::count_test_posts() );
}
public function test_count_test_posts_counts_only_stress_prefix(): void {
// Seed 2 stress posts + 1 real post.
WPDO_Post_Stress_Tester::create( 'product', 2 );
global $wpdb;
$wpdb->insert( self::POSTS, array(
'ID' => 9999,
'post_title' => 'Real product not from stress',
'post_type' => 'product',
'post_content' => '',
'post_excerpt' => '',
'post_content_filtered' => '',
'to_ping' => '',
'pinged' => '',
) );
$this->assertSame( 2, WPDO_Post_Stress_Tester::count_test_posts() );
}
// ── cleanup() ─────────────────────────────────────────────────────────────
public function test_cleanup_removes_all_stress_posts_and_their_meta(): void {
WPDO_Post_Stress_Tester::create( 'product', 5 );
// Verify pre-state.
$this->assertSame( 5, WPDO_Post_Stress_Tester::count_test_posts() );
$result = WPDO_Post_Stress_Tester::cleanup();
$this->assertSame( 5, $result['deleted_posts'] );
global $wpdb;
$post_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::POSTS . "`" );
$meta_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::POSTMETA . "`" );
$this->assertSame( 0, $post_count );
$this->assertSame( 0, $meta_count, 'cleanup() must cascade delete postmeta.' );
}
public function test_cleanup_preserves_non_stress_posts(): void {
// Real post with prefix-collision-immune title.
global $wpdb;
$wpdb->insert( self::POSTS, array(
'ID' => 9999,
'post_title' => 'Real product not from stress',
'post_type' => 'product',
'post_content' => '',
'post_excerpt' => '',
'post_content_filtered' => '',
'to_ping' => '',
'pinged' => '',
) );
$wpdb->insert( self::POSTMETA, array(
'post_id' => 9999,
'meta_key' => '_price',
'meta_value' => '50.00',
) );
WPDO_Post_Stress_Tester::create( 'product', 3 );
$result = WPDO_Post_Stress_Tester::cleanup();
$this->assertSame( 3, $result['deleted_posts'] );
// Real post + its meta survive.
$remaining_posts = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::POSTS . "`" );
$remaining_meta = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `" . self::POSTMETA . "`" );
$this->assertSame( 1, $remaining_posts );
$this->assertSame( 1, $remaining_meta );
}
public function test_cleanup_idempotent_on_empty_state(): void {
$result1 = WPDO_Post_Stress_Tester::cleanup();
$result2 = WPDO_Post_Stress_Tester::cleanup();
$this->assertSame( 0, $result1['deleted_posts'] );
$this->assertSame( 0, $result2['deleted_posts'] );
}
// ── v2.11.2: create_realistic() — uses wp_insert_post + update_post_meta ─
public function test_create_realistic_uses_wp_insert_post(): void {
$result = WPDO_Post_Stress_Tester::create_realistic( 'product', 3 );
$this->assertSame( 3, $result['created'] );
$this->assertSame( 'realistic', $result['mode'] ?? '' );
global $wpdb;
$count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTS . "` WHERE post_type = 'product'"
);
$this->assertSame( 3, $count );
}
public function test_create_realistic_writes_postmeta_via_update_post_meta(): void {
$result = WPDO_Post_Stress_Tester::create_realistic( 'product', 2 );
$this->assertSame( 2, $result['created'] );
global $wpdb;
$price_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTMETA . "` WHERE meta_key = '_price'"
);
$this->assertSame( 2, $price_count, 'Each realistic product seeds _price.' );
}
public function test_create_realistic_uses_stress_test_prefix(): void {
WPDO_Post_Stress_Tester::create_realistic( 'product', 2 );
global $wpdb;
$prefixed = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM `" . self::POSTS . "` WHERE post_title LIKE %s",
WPDO_Post_Stress_Tester::TEST_POST_PREFIX . '%'
)
);
$this->assertSame( 2, $prefixed );
}
public function test_create_realistic_supports_hp_listing(): void {
$result = WPDO_Post_Stress_Tester::create_realistic( 'hp_listing', 4 );
$this->assertSame( 4, $result['created'] );
global $wpdb;
$hp_price_count = (int) $wpdb->get_var(
"SELECT COUNT(*) FROM `" . self::POSTMETA . "` WHERE meta_key = 'hp_price'"
);
$this->assertSame( 4, $hp_price_count );
}
public function test_create_realistic_rejects_unsupported_post_type(): void {
$this->expectException( InvalidArgumentException::class );
WPDO_Post_Stress_Tester::create_realistic( 'bogus_type', 2 );
}
public function test_create_realistic_rejects_invalid_count(): void {
$this->expectException( InvalidArgumentException::class );
WPDO_Post_Stress_Tester::create_realistic( 'product', 0 );
}
public function test_cleanup_removes_realistic_created_posts(): void {
WPDO_Post_Stress_Tester::create_realistic( 'product', 3 );
$this->assertSame( 3, WPDO_Post_Stress_Tester::count_test_posts() );
$cleanup = WPDO_Post_Stress_Tester::cleanup();
$this->assertSame( 3, $cleanup['deleted_posts'] );
$this->assertSame( 0, WPDO_Post_Stress_Tester::count_test_posts() );
}
// ── v2.11.4: state machine (start / cancel / get_state / get_progress / run_batch) ─
protected function tearDown(): void {
// Reset persisted state between state-machine tests so each test starts idle.
unset( $GLOBALS['_wp_options'][ WPDO_Post_Stress_Tester::OPT_STATE ] );
unset( $GLOBALS['_wp_transients'][ WPDO_Post_Stress_Tester::CANCEL_FLAG ] );
unset( $GLOBALS['_wp_transients']['wpdo_post_stress_pump_lock'] );
}
public function test_get_state_returns_empty_when_idle(): void {
$this->assertSame( array(), WPDO_Post_Stress_Tester::get_state() );
}
public function test_get_progress_returns_idle_when_no_state(): void {
$progress = WPDO_Post_Stress_Tester::get_progress( false );
$this->assertSame( 'idle', $progress['status'] );
$this->assertSame( 0, $progress['processed'] );
}
public function test_start_persists_state_with_running_status(): void {
$result = WPDO_Post_Stress_Tester::start( 'product', 10, 'fast', 5 );
$this->assertTrue( $result['ok'] );
$state = $result['state'];
$this->assertSame( 'running', $state['status'] );
$this->assertSame( 'product', $state['post_type'] );
$this->assertSame( 'fast', $state['mode'] );
$this->assertSame( 10, $state['target'] );
$this->assertSame( 5, $state['batch_size'] );
$this->assertSame( 0, $state['processed'] );
}
public function test_start_rejects_unsupported_post_type(): void {
$result = WPDO_Post_Stress_Tester::start( 'bogus_type', 10 );
$this->assertFalse( $result['ok'] );
$this->assertSame( 'unsupported_post_type', $result['error'] );
}
public function test_start_rejects_zero_target(): void {
$result = WPDO_Post_Stress_Tester::start( 'product', 0 );
$this->assertFalse( $result['ok'] );
}
public function test_start_rejects_excessive_target(): void {
$result = WPDO_Post_Stress_Tester::start( 'product', 100001 );
$this->assertFalse( $result['ok'] );
}
public function test_start_rejects_invalid_mode(): void {
$result = WPDO_Post_Stress_Tester::start( 'product', 10, 'turbo' );
$this->assertFalse( $result['ok'] );
$this->assertSame( 'invalid mode', $result['error'] );
}
public function test_start_rejects_concurrent_run(): void {
WPDO_Post_Stress_Tester::start( 'product', 10 );
$result = WPDO_Post_Stress_Tester::start( 'product', 5 );
$this->assertFalse( $result['ok'] );
$this->assertSame( 'already_running', $result['error'] );
}
public function test_start_clamps_batch_size_above_max(): void {
$result = WPDO_Post_Stress_Tester::start( 'product', 10, 'fast', 5000 );
$this->assertTrue( $result['ok'] );
$this->assertSame( WPDO_Post_Stress_Tester::MAX_BATCH_SIZE, $result['state']['batch_size'] );
}
public function test_run_batch_advances_processed_count(): void {
WPDO_Post_Stress_Tester::start( 'product', 6, 'fast', 3 );
WPDO_Post_Stress_Tester::run_batch();
$progress = WPDO_Post_Stress_Tester::get_progress( false );
$this->assertSame( 3, $progress['processed'] );
$this->assertSame( 1, $progress['batches_done'] );
$this->assertSame( 'running', $progress['status'] );
WPDO_Post_Stress_Tester::run_batch();
$progress = WPDO_Post_Stress_Tester::get_progress( false );
$this->assertSame( 6, $progress['processed'] );
$this->assertSame( 'completed', $progress['status'] );
}
public function test_run_batch_creates_actual_posts(): void {
WPDO_Post_Stress_Tester::start( 'product', 4, 'fast', 4 );
WPDO_Post_Stress_Tester::run_batch();
$this->assertSame( 4, WPDO_Post_Stress_Tester::count_test_posts() );
}
public function test_run_batch_finalizes_with_benchmark(): void {
WPDO_Post_Stress_Tester::start( 'product', 2, 'fast', 2 );
WPDO_Post_Stress_Tester::run_batch();
$state = WPDO_Post_Stress_Tester::get_state();
$this->assertSame( 'completed', $state['status'] );
$this->assertIsArray( $state['benchmark'] );
$this->assertArrayHasKey( 'write', $state['benchmark'] );
$this->assertArrayHasKey( 'db_sizes', $state['benchmark'] );
$this->assertSame( 'product', $state['benchmark']['post_type'] );
}
public function test_cancel_marks_state_as_cancelled(): void {
WPDO_Post_Stress_Tester::start( 'product', 100, 'fast', 50 );
$result = WPDO_Post_Stress_Tester::cancel();
$this->assertTrue( $result['ok'] );
$this->assertSame( 'cancelled', $result['state']['status'] );
// In-flight batch run after cancel must NOT bump status back to running.
WPDO_Post_Stress_Tester::run_batch();
$state = WPDO_Post_Stress_Tester::get_state();
$this->assertSame( 'cancelled', $state['status'] );
}
public function test_cancel_returns_no_active_job_when_idle(): void {
$result = WPDO_Post_Stress_Tester::cancel();
$this->assertTrue( $result['ok'] );
$this->assertSame( 'no_active_job', $result['message'] ?? '' );
}
public function test_get_progress_includes_pct_and_eta(): void {
WPDO_Post_Stress_Tester::start( 'product', 10, 'fast', 5 );
WPDO_Post_Stress_Tester::run_batch();
$progress = WPDO_Post_Stress_Tester::get_progress( false );
$this->assertArrayHasKey( 'pct', $progress );
$this->assertArrayHasKey( 'rate_per_sec', $progress );
$this->assertArrayHasKey( 'elapsed_sec', $progress );
$this->assertArrayHasKey( 'eta_sec', $progress );
$this->assertArrayHasKey( 'test_post_count', $progress );
$this->assertSame( 50.0, $progress['pct'] ); // 5/10 = 50%
}
public function test_run_benchmark_returns_post_type_aware_query_probes(): void {
WPDO_Post_Stress_Tester::start( 'hp_listing', 4, 'fast', 4 );
WPDO_Post_Stress_Tester::run_batch();
$state = WPDO_Post_Stress_Tester::get_state();
$bench = $state['benchmark'];
$this->assertSame( 'hp_listing', $bench['post_type'] );
// query is post_type-aware; in production the flat table exists so this
// returns 3 probes (point/range/eav_baseline). In integration tests the
// flat table doesn't exist (different prefix), so we only verify the
// structure is present and post_type-aware.
$this->assertIsArray( $bench['query'] );
}
public function test_run_batch_realistic_uses_wp_insert_post(): void {
WPDO_Post_Stress_Tester::start( 'product', 3, 'realistic', 3 );
WPDO_Post_Stress_Tester::run_batch();
$state = WPDO_Post_Stress_Tester::get_state();
$this->assertSame( 'completed', $state['status'] );
$this->assertSame( 3, $state['processed'] );
$this->assertSame( 'realistic', $state['mode'] );
}
}