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
323 lines
12 KiB
PHP
323 lines
12 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
|
||
/**
|
||
* Integration test: WPDO_Comment_Stress_Tester (v2.13.1).
|
||
*
|
||
* Verifies the comment stress tester contract:
|
||
* - State machine: start / get_state / get_progress / cancel
|
||
* - count_test_comments() matches email-domain marker
|
||
* - cleanup() removes test comments + cascade
|
||
* - Input validation throws / errors correctly
|
||
* - Run benchmark structure
|
||
*
|
||
* Note: Realistic mode tests (wp_insert_comment path) use bootstrap stub which
|
||
* inserts directly without firing filter/action chain.
|
||
*/
|
||
class CommentStressTesterTest extends TestCase {
|
||
|
||
private const POSTS = 'wp_itest_posts';
|
||
private const COMMENTS = 'wp_itest_comments';
|
||
private const COMMENTMETA = 'wp_itest_commentmeta';
|
||
|
||
private static int $test_post_id = 0;
|
||
|
||
public static function setUpBeforeClass(): void {
|
||
global $wpdb;
|
||
|
||
if ( ! class_exists( 'WPDO_Comment_Stress_Tester' ) ) {
|
||
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-comment-stress-tester.php';
|
||
}
|
||
|
||
$wpdb->posts = self::POSTS;
|
||
$wpdb->comments = self::COMMENTS;
|
||
$wpdb->commentmeta = self::COMMENTMETA;
|
||
|
||
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . self::POSTS . '` (
|
||
ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
post_title text NOT NULL DEFAULT "",
|
||
post_status varchar(20) NOT NULL DEFAULT "publish",
|
||
post_type varchar(20) NOT NULL DEFAULT "post",
|
||
comment_count bigint(20) NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (ID)
|
||
) DEFAULT CHARACTER SET utf8mb4' );
|
||
|
||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::COMMENTS . '`' );
|
||
$wpdb->query(
|
||
'CREATE TABLE `' . self::COMMENTS . '` (
|
||
comment_ID bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
comment_post_ID bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
comment_author tinytext NOT NULL,
|
||
comment_author_email varchar(100) NOT NULL DEFAULT "",
|
||
comment_author_url varchar(200) NOT NULL DEFAULT "",
|
||
comment_author_IP varchar(100) NOT NULL DEFAULT "",
|
||
comment_date datetime NOT NULL DEFAULT "1970-01-01 00:00:00",
|
||
comment_date_gmt datetime NOT NULL DEFAULT "1970-01-01 00:00:00",
|
||
comment_content text NOT NULL,
|
||
comment_karma int(11) NOT NULL DEFAULT 0,
|
||
comment_approved varchar(20) NOT NULL DEFAULT "1",
|
||
comment_agent varchar(255) NOT NULL DEFAULT "",
|
||
comment_type varchar(20) NOT NULL DEFAULT "comment",
|
||
comment_parent bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
user_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (comment_ID),
|
||
KEY comment_author_email (comment_author_email(10)),
|
||
KEY comment_post_ID (comment_post_ID)
|
||
) DEFAULT CHARACTER SET utf8mb4'
|
||
);
|
||
|
||
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . self::COMMENTMETA . '` (
|
||
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||
comment_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||
meta_key varchar(255) DEFAULT NULL,
|
||
meta_value longtext,
|
||
PRIMARY KEY (meta_id),
|
||
KEY comment_id (comment_id),
|
||
KEY meta_key (meta_key(191))
|
||
) DEFAULT CHARACTER SET utf8mb4' );
|
||
|
||
// Seed a single fixture post to satisfy post_exists() checks.
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::POSTS . '`' );
|
||
$wpdb->insert( self::POSTS, array(
|
||
'post_title' => 'WPDO Comment Stress Fixture Post',
|
||
'post_status' => 'publish',
|
||
'post_type' => 'post',
|
||
) );
|
||
self::$test_post_id = (int) $wpdb->insert_id;
|
||
}
|
||
|
||
public static function tearDownAfterClass(): void {
|
||
global $wpdb;
|
||
foreach ( array( self::COMMENTS, self::COMMENTMETA ) as $tbl ) {
|
||
$wpdb->query( 'DROP TABLE IF EXISTS `' . $tbl . '`' );
|
||
}
|
||
// Don't drop wp_itest_posts — shared fixture across test classes.
|
||
}
|
||
|
||
protected function setUp(): void {
|
||
global $wpdb;
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::COMMENTS . '`' );
|
||
$wpdb->query( 'TRUNCATE TABLE `' . self::COMMENTMETA . '`' );
|
||
// Reset state per test so each starts idle.
|
||
unset( $GLOBALS['_wp_options'][ WPDO_Comment_Stress_Tester::OPT_STATE ] );
|
||
unset( $GLOBALS['_wp_transients'][ WPDO_Comment_Stress_Tester::CANCEL_FLAG ] );
|
||
unset( $GLOBALS['_wp_transients']['wpdo_comment_stress_pump_lock'] );
|
||
}
|
||
|
||
// ── create() (fast-path direct SQL) ──────────────────────────────────────
|
||
|
||
public function test_create_inserts_comments_for_post(): void {
|
||
$result = WPDO_Comment_Stress_Tester::create( self::$test_post_id, 5 );
|
||
|
||
$this->assertSame( 5, $result['created'] );
|
||
$this->assertSame( self::$test_post_id, $result['post_id'] );
|
||
$this->assertNotNull( $result['first_id'] );
|
||
$this->assertNotNull( $result['last_id'] );
|
||
|
||
global $wpdb;
|
||
$count = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::COMMENTS . '`' );
|
||
$this->assertSame( 5, $count );
|
||
}
|
||
|
||
public function test_create_uses_stress_email_domain(): void {
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 3 );
|
||
|
||
global $wpdb;
|
||
$prefix_count = (int) $wpdb->get_var(
|
||
$wpdb->prepare(
|
||
"SELECT COUNT(*) FROM `" . self::COMMENTS . "` WHERE comment_author_email LIKE %s",
|
||
'%@' . WPDO_Comment_Stress_Tester::TEST_EMAIL_DOMAIN
|
||
)
|
||
);
|
||
$this->assertSame( 3, $prefix_count );
|
||
}
|
||
|
||
public function test_create_seeds_commentmeta_keys(): void {
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 3 );
|
||
|
||
global $wpdb;
|
||
$total_meta = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::COMMENTMETA . '`' );
|
||
// 3 comments × 1 key (hp_rating) = 3.
|
||
$this->assertSame( 3, $total_meta );
|
||
}
|
||
|
||
public function test_create_rejects_bad_post_id(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Comment_Stress_Tester::create( 0, 3 );
|
||
}
|
||
|
||
public function test_create_rejects_zero_count(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 0 );
|
||
}
|
||
|
||
public function test_create_rejects_excessive_count(): void {
|
||
$this->expectException( InvalidArgumentException::class );
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 100001 );
|
||
}
|
||
|
||
// ── count_test_comments() ────────────────────────────────────────────────
|
||
|
||
public function test_count_test_comments_returns_zero_for_empty(): void {
|
||
$this->assertSame( 0, WPDO_Comment_Stress_Tester::count_test_comments() );
|
||
}
|
||
|
||
public function test_count_test_comments_counts_only_stress_emails(): void {
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 4 );
|
||
|
||
global $wpdb;
|
||
$wpdb->insert( self::COMMENTS, array(
|
||
'comment_post_ID' => self::$test_post_id,
|
||
'comment_author' => 'Real',
|
||
'comment_author_email' => 'real@example.com',
|
||
'comment_content' => 'Real comment',
|
||
'comment_approved' => '1',
|
||
) );
|
||
|
||
$this->assertSame( 4, WPDO_Comment_Stress_Tester::count_test_comments() );
|
||
}
|
||
|
||
// ── cleanup() ─────────────────────────────────────────────────────────────
|
||
|
||
public function test_cleanup_removes_test_comments_and_cascade(): void {
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 5 );
|
||
$this->assertSame( 5, WPDO_Comment_Stress_Tester::count_test_comments() );
|
||
|
||
$result = WPDO_Comment_Stress_Tester::cleanup();
|
||
$this->assertSame( 5, $result['deleted_comments'] );
|
||
|
||
global $wpdb;
|
||
$this->assertSame( 0, (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::COMMENTS . '`' ) );
|
||
$this->assertSame( 0, (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::COMMENTMETA . '`' ) );
|
||
}
|
||
|
||
public function test_cleanup_preserves_non_stress_comments(): void {
|
||
global $wpdb;
|
||
$wpdb->insert( self::COMMENTS, array(
|
||
'comment_post_ID' => self::$test_post_id,
|
||
'comment_author' => 'Real',
|
||
'comment_author_email' => 'real@example.com',
|
||
'comment_content' => 'Real comment',
|
||
'comment_approved' => '1',
|
||
) );
|
||
WPDO_Comment_Stress_Tester::create( self::$test_post_id, 3 );
|
||
|
||
$result = WPDO_Comment_Stress_Tester::cleanup();
|
||
$this->assertSame( 3, $result['deleted_comments'] );
|
||
|
||
$remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::COMMENTS . '`' );
|
||
$this->assertSame( 1, $remaining );
|
||
}
|
||
|
||
public function test_cleanup_idempotent_on_empty(): void {
|
||
$first = WPDO_Comment_Stress_Tester::cleanup();
|
||
$second = WPDO_Comment_Stress_Tester::cleanup();
|
||
$this->assertSame( 0, $first['deleted_comments'] );
|
||
$this->assertSame( 0, $second['deleted_comments'] );
|
||
}
|
||
|
||
// ── State machine ────────────────────────────────────────────────────────
|
||
|
||
public function test_get_state_returns_empty_when_idle(): void {
|
||
$this->assertSame( array(), WPDO_Comment_Stress_Tester::get_state() );
|
||
}
|
||
|
||
public function test_get_progress_returns_idle_when_no_state(): void {
|
||
$progress = WPDO_Comment_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 'idle', $progress['status'] );
|
||
}
|
||
|
||
public function test_start_persists_state_with_running_status(): void {
|
||
$result = WPDO_Comment_Stress_Tester::start( self::$test_post_id, 10, 'fast', 5 );
|
||
|
||
$this->assertTrue( $result['ok'], 'start should succeed' );
|
||
$state = $result['state'];
|
||
$this->assertSame( 'running', $state['status'] );
|
||
$this->assertSame( self::$test_post_id, $state['post_id'] );
|
||
$this->assertSame( 'fast', $state['mode'] );
|
||
$this->assertSame( 10, $state['target'] );
|
||
$this->assertSame( 5, $state['batch_size'] );
|
||
}
|
||
|
||
public function test_start_rejects_unknown_post(): void {
|
||
$result = WPDO_Comment_Stress_Tester::start( 999999, 10 );
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertStringContainsString( 'unknown_post', $result['error'] );
|
||
}
|
||
|
||
public function test_start_rejects_invalid_mode(): void {
|
||
$result = WPDO_Comment_Stress_Tester::start( self::$test_post_id, 10, 'turbo' );
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertSame( 'invalid mode', $result['error'] );
|
||
}
|
||
|
||
public function test_start_rejects_concurrent_run(): void {
|
||
WPDO_Comment_Stress_Tester::start( self::$test_post_id, 10 );
|
||
$result = WPDO_Comment_Stress_Tester::start( self::$test_post_id, 5 );
|
||
|
||
$this->assertFalse( $result['ok'] );
|
||
$this->assertSame( 'already_running', $result['error'] );
|
||
}
|
||
|
||
public function test_run_batch_advances_processed_count(): void {
|
||
WPDO_Comment_Stress_Tester::start( self::$test_post_id, 6, 'fast', 3 );
|
||
|
||
WPDO_Comment_Stress_Tester::run_batch();
|
||
$progress = WPDO_Comment_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 3, $progress['processed'] );
|
||
$this->assertSame( 1, $progress['batches_done'] );
|
||
$this->assertSame( 'running', $progress['status'] );
|
||
|
||
WPDO_Comment_Stress_Tester::run_batch();
|
||
$progress = WPDO_Comment_Stress_Tester::get_progress( false );
|
||
$this->assertSame( 6, $progress['processed'] );
|
||
$this->assertSame( 'completed', $progress['status'] );
|
||
}
|
||
|
||
public function test_cancel_marks_state_as_cancelled(): void {
|
||
WPDO_Comment_Stress_Tester::start( self::$test_post_id, 100, 'fast', 50 );
|
||
|
||
$result = WPDO_Comment_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_Comment_Stress_Tester::run_batch();
|
||
$state = WPDO_Comment_Stress_Tester::get_state();
|
||
$this->assertSame( 'cancelled', $state['status'] );
|
||
}
|
||
|
||
public function test_cancel_returns_no_active_job_when_idle(): void {
|
||
$result = WPDO_Comment_Stress_Tester::cancel();
|
||
$this->assertTrue( $result['ok'] );
|
||
$this->assertSame( 'no_active_job', $result['message'] ?? '' );
|
||
}
|
||
|
||
public function test_get_progress_includes_pct_and_eta_keys(): void {
|
||
WPDO_Comment_Stress_Tester::start( self::$test_post_id, 10, 'fast', 5 );
|
||
WPDO_Comment_Stress_Tester::run_batch();
|
||
|
||
$progress = WPDO_Comment_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_comment_count', $progress );
|
||
$this->assertSame( 50.0, $progress['pct'] );
|
||
}
|
||
|
||
public function test_run_benchmark_returns_structured_payload(): void {
|
||
WPDO_Comment_Stress_Tester::start( self::$test_post_id, 4, 'fast', 4 );
|
||
WPDO_Comment_Stress_Tester::run_batch();
|
||
|
||
$state = WPDO_Comment_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( self::$test_post_id, $state['benchmark']['post_id'] );
|
||
}
|
||
}
|