query( 'CREATE TABLE IF NOT EXISTS `' . 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' ); } protected function setUp(): void { global $wpdb; $wpdb->query( 'TRUNCATE TABLE `' . self::POSTMETA . '`' ); // Reset wp_options state for filter tests $GLOBALS['_wp_options'] = array(); // Default the toggle to enabled update_option( WPDO_Hivepress_Transient_Filter::OPT_ENABLED, '1' ); } // ── is_target_key() ───────────────────────────────────────────────────── public function test_is_target_key_matches_hp_transient_value(): void { $this->assertTrue( WPDO_Hivepress_Transient_Filter::is_target_key( '_transient_hp_models/listing_category/version' ) ); } public function test_is_target_key_matches_hp_transient_timeout(): void { $this->assertTrue( WPDO_Hivepress_Transient_Filter::is_target_key( '_transient_timeout_hp_models/term/listing_availability/version' ) ); } public function test_is_target_key_rejects_non_hp_transient(): void { $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( '_transient_other_plugin_cache' ) ); $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( '_transient_timeout_other' ) ); } public function test_is_target_key_rejects_regular_meta(): void { $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( 'hp_price' ) ); $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( '_thumbnail_id' ) ); } public function test_is_target_key_rejects_non_string(): void { $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( null ) ); $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( 123 ) ); $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_target_key( array() ) ); } // ── translate_key() ───────────────────────────────────────────────────── public function test_translate_key_namespaces_by_post_id(): void { $key1 = WPDO_Hivepress_Transient_Filter::translate_key( 100, '_transient_hp_models/cat/v1' ); $key2 = WPDO_Hivepress_Transient_Filter::translate_key( 200, '_transient_hp_models/cat/v1' ); $this->assertNotSame( $key1, $key2, 'Same meta_key on different posts must yield different translated keys.' ); $this->assertStringContainsString( 'wpdo_hp_pm_100_', $key1 ); $this->assertStringContainsString( 'wpdo_hp_pm_200_', $key2 ); } public function test_translate_key_strips_transient_prefix(): void { $value_key = WPDO_Hivepress_Transient_Filter::translate_key( 100, '_transient_hp_models/cat' ); $timeout_key = WPDO_Hivepress_Transient_Filter::translate_key( 100, '_transient_timeout_hp_models/cat' ); // Value and timeout SHOULD share the same translated suffix (md5 of stripped name) // since they refer to the same logical cache entry. $this->assertSame( $value_key, $timeout_key ); } public function test_translate_key_md5_handles_long_input(): void { $long_key = '_transient_hp_models/term/listing_availability/' . str_repeat( 'a', 200 ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 1, $long_key ); // Result must fit within wp_options.option_name (172 chars used as proxy). $option_name = '_transient_timeout_' . $translated; $this->assertLessThanOrEqual( 191, strlen( $option_name ), 'Translated option_name must fit MySQL VARCHAR(191).' ); } // ── on_read() callback ────────────────────────────────────────────────── public function test_on_read_returns_pre_for_non_target_key(): void { $result = WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_thumbnail_id', true ); $this->assertNull( $result, 'Non-target keys must pass through (return $pre).' ); } public function test_on_read_returns_value_from_wp_options_when_present(): void { // Seed a "translated" wp_options entry as if filter previously wrote it. $key = '_transient_hp_models/listing_category/version'; $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); update_option( '_transient_' . $translated, 'cached-value-xyz' ); $result = WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ); $this->assertSame( array( 'cached-value-xyz' ), $result ); } public function test_on_read_returns_pre_on_cache_miss(): void { // No wp_options entry seeded. $result = WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_hp_models/cat/v1', true ); $this->assertNull( $result, 'Cache miss should fall through (return $pre = null).' ); } // ── on_add() / on_update() callbacks ──────────────────────────────────── public function test_on_add_writes_to_wp_options_and_short_circuits(): void { $key = '_transient_hp_models/cat/v1'; $result = WPDO_Hivepress_Transient_Filter::on_add( null, 100, $key, 'value-xyz', false ); $this->assertTrue( $result, 'Filter must short-circuit (return truthy).' ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); $this->assertSame( 'value-xyz', get_option( '_transient_' . $translated ) ); } public function test_on_update_writes_to_wp_options(): void { $key = '_transient_hp_models/cat/v1'; WPDO_Hivepress_Transient_Filter::on_update( null, 200, $key, 'updated-value', '' ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 200, $key ); $this->assertSame( 'updated-value', get_option( '_transient_' . $translated ) ); } public function test_on_update_handles_timeout_separately(): void { $value_key = '_transient_hp_models/cat/v1'; $timeout_key = '_transient_timeout_hp_models/cat/v1'; WPDO_Hivepress_Transient_Filter::on_update( null, 100, $value_key, 'val', '' ); WPDO_Hivepress_Transient_Filter::on_update( null, 100, $timeout_key, '99999999', '' ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $value_key ); $this->assertSame( 'val', get_option( '_transient_' . $translated ) ); $this->assertSame( '99999999', get_option( '_transient_timeout_' . $translated ) ); } public function test_on_add_passes_through_non_target_keys(): void { $result = WPDO_Hivepress_Transient_Filter::on_add( null, 100, '_thumbnail_id', '50', false ); $this->assertNull( $result ); // And nothing was written to wp_options $this->assertFalse( get_option( '_transient_wpdo_hp_pm_100_' . md5( '' ) ) ); } // ── on_delete() callback ──────────────────────────────────────────────── public function test_on_delete_clears_wp_options_entry(): void { $key = '_transient_hp_models/cat/v1'; WPDO_Hivepress_Transient_Filter::on_update( null, 100, $key, 'value', '' ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); $this->assertSame( 'value', get_option( '_transient_' . $translated ) ); WPDO_Hivepress_Transient_Filter::on_delete( null, 100, $key, '', false ); $this->assertFalse( get_option( '_transient_' . $translated ) ); } public function test_on_delete_passes_through_non_target_keys(): void { $result = WPDO_Hivepress_Transient_Filter::on_delete( null, 100, 'hp_price', '', false ); $this->assertNull( $result ); } // ── Round-trip: write then read ───────────────────────────────────────── public function test_full_round_trip_write_then_read(): void { $key = '_transient_hp_models/listing_category/abc123'; WPDO_Hivepress_Transient_Filter::on_update( null, 500, $key, array( 'serialized', 'data' ), '' ); $value = WPDO_Hivepress_Transient_Filter::on_read( null, 500, $key, true ); $this->assertSame( array( array( 'serialized', 'data' ) ), $value ); } // ── count/purge legacy ────────────────────────────────────────────────── public function test_count_legacy_postmeta_rows_returns_zero_when_clean(): void { $this->assertSame( 0, WPDO_Hivepress_Transient_Filter::count_legacy_postmeta_rows() ); } public function test_count_legacy_postmeta_rows_counts_hp_transients_only(): void { global $wpdb; // Insert a mix of rows: target hp transients + non-target rows. $rows = array( array( 1, '_transient_hp_models/cat/v1', 'val1' ), array( 1, '_transient_timeout_hp_models/cat/v1', '99999' ), array( 2, '_transient_hp_models/tag/v2', 'val2' ), array( 1, 'hp_price', '100' ), array( 1, '_thumbnail_id', '50' ), array( 2, '_transient_other_plugin', 'foreign' ), // non-hp transient ); foreach ( $rows as $r ) { $wpdb->insert( self::POSTMETA, array( 'post_id' => $r[0], 'meta_key' => $r[1], 'meta_value' => $r[2], ) ); } $count = WPDO_Hivepress_Transient_Filter::count_legacy_postmeta_rows(); $this->assertSame( 3, $count, 'Must count only _transient_hp_* rows, not other-plugin transients or normal meta.' ); } public function test_purge_legacy_postmeta_rows_deletes_only_hp_transients(): void { global $wpdb; $wpdb->insert( self::POSTMETA, array( 'post_id' => 1, 'meta_key' => '_transient_hp_models/cat/v1', 'meta_value' => 'a' ) ); $wpdb->insert( self::POSTMETA, array( 'post_id' => 1, 'meta_key' => '_transient_timeout_hp_models/cat/v1', 'meta_value' => '99' ) ); $wpdb->insert( self::POSTMETA, array( 'post_id' => 1, 'meta_key' => 'hp_price', 'meta_value' => '50' ) ); $wpdb->insert( self::POSTMETA, array( 'post_id' => 2, 'meta_key' => '_transient_woo_thing', 'meta_value' => 'foreign' ) ); $deleted = WPDO_Hivepress_Transient_Filter::purge_legacy_postmeta_rows(); $this->assertSame( 2, $deleted ); $remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::POSTMETA . '`' ); $this->assertSame( 2, $remaining, 'hp_price + _transient_woo_thing must remain.' ); } // ── is_enabled() toggle ───────────────────────────────────────────────── public function test_is_enabled_defaults_true(): void { delete_option( WPDO_Hivepress_Transient_Filter::OPT_ENABLED ); $this->assertTrue( WPDO_Hivepress_Transient_Filter::is_enabled() ); } public function test_is_enabled_respects_zero_value(): void { update_option( WPDO_Hivepress_Transient_Filter::OPT_ENABLED, '0' ); $this->assertFalse( WPDO_Hivepress_Transient_Filter::is_enabled() ); } }