6f117f4e9b
Anti-EAV Lint + Quality Gate / anti-eav-lint (pull_request) Successful in 21s
Tests / PHP Lint (pull_request) Successful in 18s
Tests / Unit Tests (pull_request) Successful in 41s
Tests / Integration Tests (pull_request) Successful in 1m25s
Tests / PHPCS (pull_request) Successful in 44s
Tests / PHPStan (pull_request) Successful in 47s
- Archive sweep waits until idx_original_meta exists (a plugin update does not run dbDelta; without the index NOT EXISTS scans the archive per row) - Duplicate archive cleanup runs from WP-CLI only, never in a web activation - Uninstall keeps the LatePoint add-on's tmdo_latepoint_db_version - AJAX save errors stay on screen instead of fading after 2.5 s - Verify: 2 new integration tests; unit 711, integration 612, phpcs 0, stan OK - PR: PR-141 (2.4.2 correctness review)
223 lines
8.5 KiB
PHP
223 lines
8.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Integration test: WPDO_Installer multisite cleanup (v2.14.0).
|
|
*
|
|
* Verifies the new `drop_all_tables_for_current_blog()` shared helper used
|
|
* by both `uninstall.php` and the `wp_uninitialize_site` hook handler.
|
|
*/
|
|
class InstallerCleanupTest extends TestCase {
|
|
|
|
// v2.14.0: dedicated prefix to avoid colliding with shared `wp_itest_*`
|
|
// fixtures created by other test classes. The cleanup helper uses
|
|
// `$wpdb->prefix` so changing the prefix scopes drops to our tables only.
|
|
private const TEST_PREFIX = 'wp_clnup_';
|
|
|
|
public static function setUpBeforeClass(): void {
|
|
global $wpdb;
|
|
|
|
if ( ! class_exists( 'WPDO_Installer' ) ) {
|
|
require_once WPDO_PLUGIN_DIR . 'includes/class-tmdo-installer.php';
|
|
}
|
|
|
|
$wpdb->prefix = self::TEST_PREFIX;
|
|
$wpdb->options = self::TEST_PREFIX . 'options';
|
|
|
|
// Ensure options table exists (needed for delete_option fallback path).
|
|
$wpdb->query(
|
|
'CREATE TABLE IF NOT EXISTS `' . self::TEST_PREFIX . 'options` (
|
|
option_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
option_name varchar(191) NOT NULL DEFAULT "",
|
|
option_value longtext NOT NULL,
|
|
autoload varchar(20) NOT NULL DEFAULT "yes",
|
|
PRIMARY KEY (option_id),
|
|
UNIQUE KEY option_name (option_name)
|
|
) DEFAULT CHARACTER SET utf8mb4'
|
|
);
|
|
}
|
|
|
|
public static function tearDownAfterClass(): void {
|
|
global $wpdb;
|
|
// Drop the dedicated options table + any residual prefix tables.
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TEST_PREFIX . 'options`' );
|
|
foreach ( array(
|
|
self::TEST_PREFIX . 'wpdo_archive',
|
|
self::TEST_PREFIX . 'wpdo_warm',
|
|
self::TEST_PREFIX . 'wpdo_errors',
|
|
self::TEST_PREFIX . 'wpdo_hot_test_type',
|
|
self::TEST_PREFIX . 'wpdo_user_profile',
|
|
self::TEST_PREFIX . 'wpdo_post_attachment',
|
|
self::TEST_PREFIX . 'wpdo_term_hp_taxonomy',
|
|
self::TEST_PREFIX . 'wpdo_comment_hp_review',
|
|
self::TEST_PREFIX . 'unrelated_table',
|
|
) as $tbl ) {
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . $tbl . '`' );
|
|
}
|
|
// Restore the shared integration test prefix so any teardown elsewhere
|
|
// that depends on `$wpdb->prefix === 'wp_itest_'` still works.
|
|
$wpdb->prefix = 'wp_itest_';
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
global $wpdb;
|
|
// Reset options table state.
|
|
$wpdb->query( 'TRUNCATE TABLE `' . self::TEST_PREFIX . 'options`' );
|
|
$GLOBALS['_wp_options'] = array();
|
|
}
|
|
|
|
/**
|
|
* Events scheduled with args (tmdo_drift_resync( 'post' ), backfill
|
|
* batches) are not cleared by wp_clear_scheduled_hook( $hook ); the user
|
|
* stress hook was misnamed and three hooks were missing (2.4.2 PR-138, F-4).
|
|
*/
|
|
private const ARG_AND_MISSED_HOOKS = array( 'tmdo_drift_resync', 'wpdo_entity_backfill_batch', 'wpdo_stress_test_batch', 'wpdo_migration_tick', 'wpdo_auto_promote_check' );
|
|
|
|
public function test_deactivate_unschedules_every_hook_whatever_its_args(): void {
|
|
$GLOBALS['_wp_unscheduled'] = array();
|
|
|
|
WPDO_Installer::deactivate();
|
|
|
|
foreach ( self::ARG_AND_MISSED_HOOKS as $hook ) {
|
|
$this->assertContains( $hook, $GLOBALS['_wp_unscheduled'], $hook );
|
|
}
|
|
}
|
|
|
|
public function test_uninstall_unschedules_every_hook_whatever_its_args(): void {
|
|
$GLOBALS['_wp_unscheduled'] = array();
|
|
|
|
WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
foreach ( self::ARG_AND_MISSED_HOOKS as $hook ) {
|
|
$this->assertContains( $hook, $GLOBALS['_wp_unscheduled'], $hook );
|
|
}
|
|
}
|
|
|
|
/** tmdo_* options and transients were left behind; a reinstall then saw stale deferrals and indexes (F-5). */
|
|
public function test_uninstall_removes_tmdo_options_and_transients(): void {
|
|
global $wpdb;
|
|
foreach ( array( 'tmdo_schema_deferred', 'tmdo_entity_indexes', '_transient_tmdo_cov_x', '_transient_timeout_tmdo_cov_x', 'hivepress_settings', 'tmdo_latepoint_db_version' ) as $name ) {
|
|
$wpdb->insert( $wpdb->options, array( 'option_name' => $name, 'option_value' => '1' ) );
|
|
$GLOBALS['_wp_options'][ $name ] = '1';
|
|
}
|
|
|
|
WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
foreach ( array( 'tmdo_schema_deferred', 'tmdo_entity_indexes', '_transient_tmdo_cov_x', '_transient_timeout_tmdo_cov_x' ) as $name ) {
|
|
$this->assertArrayNotHasKey( $name, $GLOBALS['_wp_options'], $name );
|
|
}
|
|
$this->assertArrayHasKey( 'hivepress_settings', $GLOBALS['_wp_options'], 'other plugins untouched' );
|
|
$this->assertArrayHasKey( 'tmdo_latepoint_db_version', $GLOBALS['_wp_options'], 'the LatePoint add-on owns it' );
|
|
}
|
|
|
|
public function test_drops_static_tables(): void {
|
|
global $wpdb;
|
|
|
|
// Create a few WPDO-prefixed tables that should be dropped.
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_archive` (id INT)' );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_warm` (id INT)' );
|
|
|
|
$counts = WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
$this->assertGreaterThanOrEqual( 2, $counts['tables_dropped'] );
|
|
|
|
$exists = (int) $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
|
|
self::TEST_PREFIX . 'wpdo_archive'
|
|
)
|
|
);
|
|
$this->assertSame( 0, $exists, 'wpdo_archive should be dropped' );
|
|
}
|
|
|
|
public function test_drops_dynamic_zone_tables(): void {
|
|
global $wpdb;
|
|
|
|
// Dynamic hot/cold zone tables should be discovered via LIKE pattern.
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_hot_test_type` (id INT)' );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_user_profile` (id INT)' );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_post_attachment` (id INT)' );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_term_hp_taxonomy` (id INT)' );
|
|
$wpdb->query( 'CREATE TABLE `' . self::TEST_PREFIX . 'wpdo_comment_hp_review` (id INT)' );
|
|
|
|
WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
foreach ( array(
|
|
'wpdo_hot_test_type',
|
|
'wpdo_user_profile',
|
|
'wpdo_post_attachment',
|
|
'wpdo_term_hp_taxonomy',
|
|
'wpdo_comment_hp_review',
|
|
) as $tbl_suffix ) {
|
|
$exists = (int) $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
|
|
self::TEST_PREFIX . $tbl_suffix
|
|
)
|
|
);
|
|
$this->assertSame( 0, $exists, $tbl_suffix . ' should be dropped' );
|
|
}
|
|
}
|
|
|
|
public function test_does_not_drop_unrelated_tables(): void {
|
|
global $wpdb;
|
|
// Defensive: a table named like wpdo_X should be dropped, but a table
|
|
// with a non-wpdo prefix MUST NEVER be dropped even if name pattern
|
|
// would match.
|
|
$unrelated = self::TEST_PREFIX . 'unrelated_table';
|
|
$wpdb->query( 'CREATE TABLE IF NOT EXISTS `' . $unrelated . '` (id INT)' );
|
|
|
|
WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
$exists = (int) $wpdb->get_var(
|
|
$wpdb->prepare(
|
|
'SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s',
|
|
$unrelated
|
|
)
|
|
);
|
|
$this->assertSame( 1, $exists, 'Non-wpdo table must not be dropped' );
|
|
|
|
$wpdb->query( 'DROP TABLE IF EXISTS `' . $unrelated . '`' );
|
|
}
|
|
|
|
public function test_returns_counts_structure(): void {
|
|
$counts = WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
$this->assertIsArray( $counts );
|
|
$this->assertArrayHasKey( 'tables_dropped', $counts );
|
|
$this->assertArrayHasKey( 'options_deleted', $counts );
|
|
$this->assertArrayHasKey( 'crons_cleared', $counts );
|
|
}
|
|
|
|
public function test_idempotent_on_empty_state(): void {
|
|
// Run twice — second run should be a no-op for tables (we already
|
|
// dropped them all in the first run). Options may not be 0 because
|
|
// other test classes share the same wp_itest_options table and may
|
|
// continually re-create wpdo_* rows; just verify that running cleanup
|
|
// twice in a row does not throw.
|
|
WPDO_Installer::drop_all_tables_for_current_blog();
|
|
$second = WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
$this->assertSame( 0, $second['tables_dropped'] );
|
|
$this->assertIsInt( $second['options_deleted'] );
|
|
$this->assertIsInt( $second['crons_cleared'] );
|
|
}
|
|
|
|
public function test_drops_known_options(): void {
|
|
// Stub `delete_option` does not interact with DB layer in our test stubs;
|
|
// it modifies `$GLOBALS['_wp_options']`. Verify counts work via the
|
|
// known-options list.
|
|
$GLOBALS['_wp_options']['wpdo_db_version'] = '2.14.0';
|
|
$GLOBALS['_wp_options']['wpdo_features'] = array();
|
|
$GLOBALS['_wp_options']['wpdo_health_alert'] = '1';
|
|
|
|
$counts = WPDO_Installer::drop_all_tables_for_current_blog();
|
|
|
|
// Note: the actual count depends on $wpdb->options interaction in
|
|
// the residual sweep. Just verify the structure works without errors.
|
|
$this->assertIsInt( $counts['options_deleted'] );
|
|
}
|
|
}
|