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' ); } }