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
205 lines
8.6 KiB
PHP
205 lines
8.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Integration test: WPDO_Postmeta_Cleaner — wp_postmeta garbage cleanup (v2.9.0).
|
|
*
|
|
* Verifies count_garbage() and delete_garbage() against real MariaDB:
|
|
* - target=transients → meta_key LIKE '_transient_%' OR LIKE '_transient_timeout_%'
|
|
* - target=wp_old_date → meta_key = '_wp_old_date'
|
|
* - target=edit_locks → meta_key = '_edit_lock' AND lock_ts < now - 86400 (stale)
|
|
* - target=all → union of all three
|
|
*
|
|
* Requires real MariaDB (WPDO_TEST_DB_PASS env var must be set).
|
|
*/
|
|
class PostmetaCleanerIntegrationTest extends TestCase {
|
|
|
|
private const POSTMETA = 'wp_itest_postmeta';
|
|
|
|
// ── Fixture lifecycle ─────────────────────────────────────────────────────
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
global $wpdb;
|
|
|
|
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-postmeta-cleaner.php';
|
|
|
|
$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 COLLATE utf8mb4_unicode_ci'
|
|
);
|
|
}
|
|
|
|
public static function tearDownAfterClass(): void {
|
|
global $wpdb;
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::POSTMETA . '`' );
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
global $wpdb;
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::POSTMETA . '`' );
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
private function seed_postmeta( array $rows ): void {
|
|
global $wpdb;
|
|
foreach ( $rows as $row ) {
|
|
$wpdb->insert( self::POSTMETA, $row );
|
|
}
|
|
}
|
|
|
|
// ── count_garbage() ───────────────────────────────────────────────────────
|
|
|
|
public function test_count_garbage_returns_zero_for_empty_table(): void {
|
|
$counts = WPDO_Postmeta_Cleaner::count_garbage( 'all' );
|
|
$this->assertSame( 0, $counts['transients'] );
|
|
$this->assertSame( 0, $counts['wp_old_date'] );
|
|
$this->assertSame( 0, $counts['edit_locks'] );
|
|
$this->assertSame( 0, $counts['total'] );
|
|
}
|
|
|
|
public function test_count_garbage_counts_transients(): void {
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_transient_hp_models/listing/v1', 'meta_value' => 'a' ),
|
|
array( 'post_id' => 1, 'meta_key' => '_transient_timeout_hp_models/listing/v1', 'meta_value' => '9999' ),
|
|
array( 'post_id' => 2, 'meta_key' => '_transient_foo', 'meta_value' => 'b' ),
|
|
array( 'post_id' => 2, 'meta_key' => 'hp_price', 'meta_value' => '99' ),
|
|
) );
|
|
|
|
$counts = WPDO_Postmeta_Cleaner::count_garbage( 'transients' );
|
|
$this->assertSame( 3, $counts['transients'] );
|
|
$this->assertSame( 0, $counts['wp_old_date'] );
|
|
$this->assertSame( 0, $counts['edit_locks'] );
|
|
$this->assertSame( 3, $counts['total'] );
|
|
}
|
|
|
|
public function test_count_garbage_counts_wp_old_date(): void {
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_wp_old_date', 'meta_value' => '2024-01-01' ),
|
|
array( 'post_id' => 2, 'meta_key' => '_wp_old_date', 'meta_value' => '2024-02-01' ),
|
|
array( 'post_id' => 3, 'meta_key' => 'hp_price', 'meta_value' => '99' ),
|
|
) );
|
|
|
|
$counts = WPDO_Postmeta_Cleaner::count_garbage( 'wp_old_date' );
|
|
$this->assertSame( 0, $counts['transients'] );
|
|
$this->assertSame( 2, $counts['wp_old_date'] );
|
|
$this->assertSame( 0, $counts['edit_locks'] );
|
|
$this->assertSame( 2, $counts['total'] );
|
|
}
|
|
|
|
public function test_count_garbage_counts_only_stale_edit_locks(): void {
|
|
$now = time();
|
|
$one_day_ago = $now - 86400 - 60; // stale by 1 day + 1 min
|
|
$one_hour_ago = $now - 3600; // fresh, not stale
|
|
$five_min_ago = $now - 300; // very fresh, not stale
|
|
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_edit_lock', 'meta_value' => $one_day_ago . ':1' ), // stale ✓
|
|
array( 'post_id' => 2, 'meta_key' => '_edit_lock', 'meta_value' => $one_hour_ago . ':2' ), // fresh
|
|
array( 'post_id' => 3, 'meta_key' => '_edit_lock', 'meta_value' => $five_min_ago . ':3' ), // fresh
|
|
array( 'post_id' => 4, 'meta_key' => '_edit_last', 'meta_value' => '4' ), // not edit_lock
|
|
) );
|
|
|
|
$counts = WPDO_Postmeta_Cleaner::count_garbage( 'edit_locks' );
|
|
$this->assertSame( 0, $counts['transients'] );
|
|
$this->assertSame( 0, $counts['wp_old_date'] );
|
|
$this->assertSame( 1, $counts['edit_locks'], 'Only stale (>24h old) _edit_lock rows count' );
|
|
$this->assertSame( 1, $counts['total'] );
|
|
}
|
|
|
|
public function test_count_garbage_target_all_unions_all_three(): void {
|
|
$one_day_ago = time() - 86400 - 60;
|
|
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_transient_foo', 'meta_value' => 'a' ),
|
|
array( 'post_id' => 2, 'meta_key' => '_transient_timeout_foo', 'meta_value' => '99' ),
|
|
array( 'post_id' => 3, 'meta_key' => '_wp_old_date', 'meta_value' => '2024-01-01' ),
|
|
array( 'post_id' => 4, 'meta_key' => '_edit_lock', 'meta_value' => $one_day_ago . ':1' ),
|
|
array( 'post_id' => 5, 'meta_key' => 'hp_price', 'meta_value' => '99' ),
|
|
) );
|
|
|
|
$counts = WPDO_Postmeta_Cleaner::count_garbage( 'all' );
|
|
$this->assertSame( 2, $counts['transients'] );
|
|
$this->assertSame( 1, $counts['wp_old_date'] );
|
|
$this->assertSame( 1, $counts['edit_locks'] );
|
|
$this->assertSame( 4, $counts['total'] );
|
|
}
|
|
|
|
public function test_count_garbage_rejects_invalid_target(): void {
|
|
$this->expectException( InvalidArgumentException::class );
|
|
WPDO_Postmeta_Cleaner::count_garbage( 'bogus' );
|
|
}
|
|
|
|
// ── delete_garbage() ──────────────────────────────────────────────────────
|
|
|
|
public function test_delete_garbage_removes_only_target_rows(): void {
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_transient_foo', 'meta_value' => 'a' ),
|
|
array( 'post_id' => 2, 'meta_key' => '_wp_old_date', 'meta_value' => '2024-01-01' ),
|
|
array( 'post_id' => 3, 'meta_key' => 'hp_price', 'meta_value' => '99' ),
|
|
) );
|
|
|
|
$deleted = WPDO_Postmeta_Cleaner::delete_garbage( 'transients' );
|
|
$this->assertSame( 1, $deleted['transients'] );
|
|
$this->assertSame( 0, $deleted['wp_old_date'] );
|
|
$this->assertSame( 0, $deleted['edit_locks'] );
|
|
$this->assertSame( 1, $deleted['total'] );
|
|
|
|
global $wpdb;
|
|
$remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::POSTMETA . '`' );
|
|
$this->assertSame( 2, $remaining, 'Non-transient rows should remain (wp_old_date + hp_price)' );
|
|
}
|
|
|
|
public function test_delete_garbage_target_all_clears_all_three(): void {
|
|
$one_day_ago = time() - 86400 - 60;
|
|
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_transient_foo', 'meta_value' => 'a' ),
|
|
array( 'post_id' => 2, 'meta_key' => '_wp_old_date', 'meta_value' => '2024-01-01' ),
|
|
array( 'post_id' => 3, 'meta_key' => '_edit_lock', 'meta_value' => $one_day_ago . ':1' ),
|
|
array( 'post_id' => 4, 'meta_key' => 'hp_price', 'meta_value' => '99' ),
|
|
) );
|
|
|
|
$deleted = WPDO_Postmeta_Cleaner::delete_garbage( 'all' );
|
|
$this->assertSame( 1, $deleted['transients'] );
|
|
$this->assertSame( 1, $deleted['wp_old_date'] );
|
|
$this->assertSame( 1, $deleted['edit_locks'] );
|
|
$this->assertSame( 3, $deleted['total'] );
|
|
|
|
global $wpdb;
|
|
$remaining = $wpdb->get_results( 'SELECT meta_key FROM `' . self::POSTMETA . '`', ARRAY_A );
|
|
$this->assertCount( 1, $remaining );
|
|
$this->assertSame( 'hp_price', $remaining[0]['meta_key'] );
|
|
}
|
|
|
|
public function test_delete_garbage_does_not_touch_fresh_edit_lock(): void {
|
|
$one_hour_ago = time() - 3600;
|
|
|
|
$this->seed_postmeta( array(
|
|
array( 'post_id' => 1, 'meta_key' => '_edit_lock', 'meta_value' => $one_hour_ago . ':1' ),
|
|
) );
|
|
|
|
$deleted = WPDO_Postmeta_Cleaner::delete_garbage( 'edit_locks' );
|
|
$this->assertSame( 0, $deleted['edit_locks'] );
|
|
|
|
global $wpdb;
|
|
$remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::POSTMETA . '`' );
|
|
$this->assertSame( 1, $remaining, 'Fresh edit_lock must survive cleanup' );
|
|
}
|
|
|
|
public function test_delete_garbage_rejects_invalid_target(): void {
|
|
$this->expectException( InvalidArgumentException::class );
|
|
WPDO_Postmeta_Cleaner::delete_garbage( 'bogus' );
|
|
}
|
|
}
|