test: 加入 integration 測試套件(連真實 MariaDB)
從 wp-data-optimizer v3.4.6 移植 1 個 integration 測試檔,並新增 tests/integration/bootstrap.php(複用核心的 integration bootstrap,不重複 與 WP stub)+ phpunit-integration.xml + CI integration job。
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Integration test: WPDO_WC_Term_Count_Filter (v2.12.3).
|
||||
*
|
||||
* Mirrors HivepressTransientFilterTest pattern. Verifies:
|
||||
* - is_target_key matches `product_count_*` only
|
||||
* - translate_key namespaces by term_id + md5 of meta_key
|
||||
* - Round-trip: write → read returns correct value from wp_options
|
||||
* - delete clears wp_options entry
|
||||
* - Non-target keys pass through (filter returns null)
|
||||
* - count/purge legacy SQL helpers
|
||||
* - Toggle option respected
|
||||
*/
|
||||
class WCTermCountFilterTest extends TestCase {
|
||||
|
||||
private const TERMMETA = 'wp_itest_termmeta';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
global $wpdb;
|
||||
|
||||
// Ensure termmeta table exists for purge SQL tests.
|
||||
$wpdb->query(
|
||||
'CREATE TABLE IF NOT EXISTS `' . self::TERMMETA . '` (
|
||||
meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
term_id bigint(20) unsigned NOT NULL DEFAULT 0,
|
||||
meta_key varchar(255) DEFAULT NULL,
|
||||
meta_value longtext,
|
||||
PRIMARY KEY (meta_id),
|
||||
KEY term_id (term_id),
|
||||
KEY meta_key (meta_key(191))
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci'
|
||||
);
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'TRUNCATE TABLE `' . self::TERMMETA . '`' );
|
||||
$GLOBALS['_wp_options'] = array();
|
||||
update_option( WPDO_WC_Term_Count_Filter::OPT_ENABLED, '1' );
|
||||
}
|
||||
|
||||
// ── is_target_key ────────────────────────────────────────────────────────
|
||||
|
||||
public function test_is_target_key_matches_product_count_prefix(): void {
|
||||
$this->assertTrue( WPDO_WC_Term_Count_Filter::is_target_key( 'product_count_product_cat' ) );
|
||||
$this->assertTrue( WPDO_WC_Term_Count_Filter::is_target_key( 'product_count_product_tag' ) );
|
||||
$this->assertTrue( WPDO_WC_Term_Count_Filter::is_target_key( 'product_count_pa_color' ) );
|
||||
}
|
||||
|
||||
public function test_is_target_key_rejects_non_product_count(): void {
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( 'hp_sort_order' ) );
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( 'hp_default' ) );
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( 'product_count' ) ); // exact, not prefix
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( 'something_product_count_foo' ) );
|
||||
}
|
||||
|
||||
public function test_is_target_key_rejects_non_string(): void {
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( null ) );
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_target_key( 123 ) );
|
||||
}
|
||||
|
||||
// ── translate_key ────────────────────────────────────────────────────────
|
||||
|
||||
public function test_translate_key_namespaces_by_term_id(): void {
|
||||
$key1 = WPDO_WC_Term_Count_Filter::translate_key( 5, 'product_count_product_cat' );
|
||||
$key2 = WPDO_WC_Term_Count_Filter::translate_key( 6, 'product_count_product_cat' );
|
||||
$this->assertNotSame( $key1, $key2 );
|
||||
$this->assertStringContainsString( 'wpdo_wc_termcount_5_', $key1 );
|
||||
$this->assertStringContainsString( 'wpdo_wc_termcount_6_', $key2 );
|
||||
}
|
||||
|
||||
public function test_translate_key_md5_handles_long_taxonomy(): void {
|
||||
$long_key = 'product_count_my_super_long_taxonomy_slug_' . str_repeat( 'x', 200 );
|
||||
$translated = WPDO_WC_Term_Count_Filter::translate_key( 1, $long_key );
|
||||
$option_name = '_transient_' . $translated;
|
||||
$this->assertLessThanOrEqual( 191, strlen( $option_name ) );
|
||||
}
|
||||
|
||||
// ── on_read ──────────────────────────────────────────────────────────────
|
||||
|
||||
public function test_on_read_returns_pre_for_non_target_key(): void {
|
||||
$result = WPDO_WC_Term_Count_Filter::on_read( null, 1, 'hp_sort_order', true );
|
||||
$this->assertNull( $result );
|
||||
}
|
||||
|
||||
public function test_on_read_returns_value_when_present(): void {
|
||||
$translated = WPDO_WC_Term_Count_Filter::translate_key( 5, 'product_count_product_cat' );
|
||||
update_option( '_transient_' . $translated, '42' );
|
||||
|
||||
$result = WPDO_WC_Term_Count_Filter::on_read( null, 5, 'product_count_product_cat', true );
|
||||
$this->assertSame( array( '42' ), $result );
|
||||
}
|
||||
|
||||
public function test_on_read_falls_through_on_cache_miss(): void {
|
||||
$result = WPDO_WC_Term_Count_Filter::on_read( null, 5, 'product_count_product_cat', true );
|
||||
$this->assertNull( $result, 'Cache miss should fall through (return $pre)' );
|
||||
}
|
||||
|
||||
// ── on_add / on_update ───────────────────────────────────────────────────
|
||||
|
||||
public function test_on_add_writes_to_wp_options_and_short_circuits(): void {
|
||||
$result = WPDO_WC_Term_Count_Filter::on_add( null, 5, 'product_count_product_cat', '42', false );
|
||||
$this->assertTrue( $result );
|
||||
|
||||
$translated = WPDO_WC_Term_Count_Filter::translate_key( 5, 'product_count_product_cat' );
|
||||
$this->assertSame( '42', get_option( '_transient_' . $translated ) );
|
||||
}
|
||||
|
||||
public function test_on_update_writes_to_wp_options(): void {
|
||||
WPDO_WC_Term_Count_Filter::on_update( null, 7, 'product_count_product_tag', '15', '' );
|
||||
$translated = WPDO_WC_Term_Count_Filter::translate_key( 7, 'product_count_product_tag' );
|
||||
$this->assertSame( '15', get_option( '_transient_' . $translated ) );
|
||||
}
|
||||
|
||||
public function test_on_add_passes_through_non_target(): void {
|
||||
$this->assertNull( WPDO_WC_Term_Count_Filter::on_add( null, 5, 'hp_sort_order', '5', false ) );
|
||||
}
|
||||
|
||||
// ── on_delete ────────────────────────────────────────────────────────────
|
||||
|
||||
public function test_on_delete_clears_wp_options_entry(): void {
|
||||
WPDO_WC_Term_Count_Filter::on_update( null, 5, 'product_count_product_cat', '42', '' );
|
||||
$translated = WPDO_WC_Term_Count_Filter::translate_key( 5, 'product_count_product_cat' );
|
||||
$this->assertSame( '42', get_option( '_transient_' . $translated ) );
|
||||
|
||||
WPDO_WC_Term_Count_Filter::on_delete( null, 5, 'product_count_product_cat', '', false );
|
||||
$this->assertFalse( get_option( '_transient_' . $translated ) );
|
||||
}
|
||||
|
||||
// ── Round-trip ───────────────────────────────────────────────────────────
|
||||
|
||||
public function test_full_round_trip(): void {
|
||||
WPDO_WC_Term_Count_Filter::on_update( null, 12, 'product_count_product_cat', 'serialized_value', '' );
|
||||
|
||||
$value = WPDO_WC_Term_Count_Filter::on_read( null, 12, 'product_count_product_cat', true );
|
||||
$this->assertSame( array( 'serialized_value' ), $value );
|
||||
}
|
||||
|
||||
// ── count/purge legacy ──────────────────────────────────────────────────
|
||||
|
||||
public function test_count_legacy_termmeta_returns_zero_for_clean(): void {
|
||||
$this->assertSame( 0, WPDO_WC_Term_Count_Filter::count_legacy_termmeta_rows() );
|
||||
}
|
||||
|
||||
public function test_count_legacy_termmeta_counts_only_product_count(): void {
|
||||
global $wpdb;
|
||||
$rows = array(
|
||||
array( 'term_id' => 1, 'meta_key' => 'product_count_product_cat', 'meta_value' => '42' ),
|
||||
array( 'term_id' => 2, 'meta_key' => 'product_count_product_tag', 'meta_value' => '15' ),
|
||||
array( 'term_id' => 1, 'meta_key' => 'hp_sort_order', 'meta_value' => '5' ), // not target
|
||||
);
|
||||
foreach ( $rows as $r ) {
|
||||
$wpdb->insert( self::TERMMETA, $r );
|
||||
}
|
||||
|
||||
$this->assertSame( 2, WPDO_WC_Term_Count_Filter::count_legacy_termmeta_rows() );
|
||||
}
|
||||
|
||||
public function test_purge_legacy_termmeta_deletes_only_product_count(): void {
|
||||
global $wpdb;
|
||||
$wpdb->insert( self::TERMMETA, array( 'term_id' => 1, 'meta_key' => 'product_count_product_cat', 'meta_value' => '42' ) );
|
||||
$wpdb->insert( self::TERMMETA, array( 'term_id' => 2, 'meta_key' => 'hp_sort_order', 'meta_value' => '5' ) );
|
||||
|
||||
$deleted = WPDO_WC_Term_Count_Filter::purge_legacy_termmeta_rows();
|
||||
$this->assertSame( 1, $deleted );
|
||||
|
||||
$remaining = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TERMMETA . '`' );
|
||||
$this->assertSame( 1, $remaining );
|
||||
}
|
||||
|
||||
// ── is_enabled ──────────────────────────────────────────────────────────
|
||||
|
||||
public function test_is_enabled_defaults_true(): void {
|
||||
delete_option( WPDO_WC_Term_Count_Filter::OPT_ENABLED );
|
||||
$this->assertTrue( WPDO_WC_Term_Count_Filter::is_enabled() );
|
||||
}
|
||||
|
||||
public function test_is_enabled_respects_zero_value(): void {
|
||||
update_option( WPDO_WC_Term_Count_Filter::OPT_ENABLED, '0' );
|
||||
$this->assertFalse( WPDO_WC_Term_Count_Filter::is_enabled() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPUnit integration bootstrap for the WooCommerce AddOn.
|
||||
*
|
||||
* Reuses the core plugin's integration bootstrap (real MariaDB $wpdb, WP
|
||||
* function stubs, constants, WPDO_* aliases) and loads this AddOn's classes on
|
||||
* top. The same TMDO_TEST_DB_* environment variables the core suite needs
|
||||
* apply here.
|
||||
*
|
||||
* @package TMDO_WOOCOMMERCE
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$tmdo_core_bootstrap = dirname( __DIR__, 3 ) . '/2meet-data-optimizer/tests/integration/bootstrap.php';
|
||||
if ( ! file_exists( $tmdo_core_bootstrap ) ) {
|
||||
throw new RuntimeException(
|
||||
'WooCommerce AddOn integration tests require the core plugin at '
|
||||
. dirname( __DIR__, 3 ) . '/2meet-data-optimizer'
|
||||
);
|
||||
}
|
||||
require_once $tmdo_core_bootstrap;
|
||||
|
||||
define( 'TMDO_WOOCOMMERCE_PATH', dirname( __DIR__, 2 ) . '/' );
|
||||
define( 'TMDO_WOOCOMMERCE_URL', 'http://localhost/wp-content/plugins/2meet-data-optimizer-woocommerce-addon/' );
|
||||
define( 'TMDO_WOOCOMMERCE_FILE', TMDO_WOOCOMMERCE_PATH . '2meet-data-optimizer-woocommerce-addon.php' );
|
||||
define( 'TMDO_WOOCOMMERCE_VERSION', '0.1.0' );
|
||||
|
||||
foreach ( array(
|
||||
'includes/class-tmdo-woocommerce.php',
|
||||
'includes/class-tmdo-wc-orders-interceptor.php',
|
||||
'includes/class-tmdo-wc-term-count-filter.php',
|
||||
'admin/class-tmdo-admin-wc.php',
|
||||
) as $tmdo_wc_file ) {
|
||||
$tmdo_wc_path = TMDO_WOOCOMMERCE_PATH . $tmdo_wc_file;
|
||||
if ( file_exists( $tmdo_wc_path ) ) {
|
||||
require_once $tmdo_wc_path;
|
||||
}
|
||||
}
|
||||
unset( $tmdo_wc_file, $tmdo_wc_path, $tmdo_core_bootstrap );
|
||||
|
||||
// Mirrors includes/back-compat-aliases.php, which runs on plugins_loaded:7 and
|
||||
// therefore never fires under PHPUnit.
|
||||
foreach ( get_declared_classes() as $tmdo_declared ) {
|
||||
if ( str_starts_with( $tmdo_declared, 'TMDO_' ) ) {
|
||||
$tmdo_alias = 'WPDO_' . substr( $tmdo_declared, 5 );
|
||||
if ( ! class_exists( $tmdo_alias, false ) ) {
|
||||
class_alias( $tmdo_declared, $tmdo_alias );
|
||||
}
|
||||
}
|
||||
}
|
||||
unset( $tmdo_declared, $tmdo_alias );
|
||||
Reference in New Issue
Block a user