test: 加入 integration 測試套件(連真實 MariaDB)
Tests / PHP Lint (push) Successful in 4s
Tests / Unit Tests (push) Successful in 16s
Tests / Integration Tests (push) Successful in 18s

從 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:
2026-07-31 09:56:48 +08:00
parent abe738e900
commit b9e24814c8
4 changed files with 286 additions and 0 deletions
+33
View File
@@ -44,3 +44,36 @@ jobs:
- name: Run unit tests - name: Run unit tests
working-directory: 2meet-data-optimizer-woocommerce-addon working-directory: 2meet-data-optimizer-woocommerce-addon
run: php vendor/bin/phpunit --configuration phpunit.xml --testdox run: php vendor/bin/phpunit --configuration phpunit.xml --testdox
integration:
name: Integration Tests
runs-on: ubuntu-latest
# Host-mode runner: no service containers. Uses the host MariaDB, same as
# the core plugin's integration job.
steps:
- uses: actions/checkout@v4
with:
path: 2meet-data-optimizer-woocommerce-addon
- name: Checkout core plugin
uses: actions/checkout@v4
with:
repository: wpdev/2meet-data-optimizer
path: 2meet-data-optimizer
- name: Install dependencies
working-directory: 2meet-data-optimizer-woocommerce-addon
run: composer install --no-interaction --prefer-dist
- name: Install core dev dependencies (test bootstrap needs them)
working-directory: 2meet-data-optimizer
run: composer install --no-interaction --prefer-dist
- name: Run integration tests
working-directory: 2meet-data-optimizer-woocommerce-addon
env:
TMDO_TEST_DB_HOST: 127.0.0.1
TMDO_TEST_DB_USER: dbo
TMDO_TEST_DB_PASS: ${{ secrets.TMDO_TEST_DB_PASS }}
TMDO_TEST_DB_NAME: wp_wpdo_test
run: php vendor/bin/phpunit --configuration phpunit-integration.xml --testdox
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/integration/bootstrap.php"
colors="true"
beStrictAboutOutputDuringTests="true"
failOnWarning="true"
>
<testsuites>
<testsuite name="woocommerce-addon-integration">
<directory>tests/integration</directory>
</testsuite>
</testsuites>
</phpunit>
+186
View File
@@ -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() );
}
}
+52
View File
@@ -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 );