e33ae4e626
Anti-EAV Lint + Quality Gate / anti-eav-lint (push) Successful in 9s
Tests / Unit Tests (push) Successful in 9s
Tests / Integration Tests (push) Successful in 31s
Tests / PHP Lint (push) Successful in 8s
Tests / PHPCS (push) Successful in 20s
Tests / PHPStan (push) Successful in 24s
CoreBoundaryTest 靜態掃描核心 126 個生產檔,斷言每一處對 AddOn 類別的 static 呼叫都在同一個函式內有 class_exists() 守衛。上一個 commit 修的 TMDO_Listing_Stats fatal 就是這類缺陷,這個測試讓它不會再回來。 它當場又抓到 3 處同類違規(都是實際會 fatal 的路徑),一併修掉: - admin render_hpct_import():改印 admin notice 並 return - wp tmdo import-hpct:改 WP_CLI::error 明示需要 hivepress-addon - cli-post cleanup-hp-transients 其實早有守衛,是測試的行距啟發式太窄; 判斷範圍改成「同一個函式內」而非固定 12 行 負向驗證:暫時注入一處無守衛呼叫 → 測試如預期失敗;還原後回綠。 同時補完計畫階段 7 PR-I 列的兩個缺漏測試: - 核心 tests/unit/StandardPostInterceptorTest.php(10 tests) - HP AddOn tests/unit/ListingStatsTest.php(9 tests)——AddOn 的 unit bootstrap 先前刻意不載入真實 TMDO_Listing_Stats,改以 TMDO_TEST_SKIP_LISTING_STATS_STUB 常數讓它跳過核心的 stub - 核心 unit bootstrap 補 add_post_meta() stub(flush 路徑用得到) 核心 unit 451 → 587、HP AddOn 145 → 154。
220 lines
7.4 KiB
PHP
220 lines
7.4 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
if ( ! class_exists( 'WP_Post' ) ) {
|
|
class WP_Post {
|
|
public int $ID = 0;
|
|
public string $post_type = '';
|
|
public string $post_status = 'publish';
|
|
public string $post_title = '';
|
|
public string $post_content = '';
|
|
public int $post_author = 0;
|
|
public int $post_parent = 0;
|
|
public function __construct( object $data ) {
|
|
foreach ( (array) $data as $k => $v ) {
|
|
$this->$k = $v;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Concrete test double for TMDO_Standard_Post_Interceptor.
|
|
*/
|
|
class TMDO_Test_Post_Interceptor extends TMDO_Standard_Post_Interceptor {
|
|
|
|
protected string $module = 'test_module';
|
|
|
|
public const FIELD_MAP = array(
|
|
'test_meta' => 'test_col',
|
|
'other_meta' => 'other_col',
|
|
);
|
|
|
|
protected function get_post_type(): string {
|
|
return 'test_post';
|
|
}
|
|
|
|
protected function get_table_key(): string {
|
|
return 'test_table';
|
|
}
|
|
|
|
protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array {
|
|
return array(
|
|
'values' => array(
|
|
'post_id' => $post_id,
|
|
'status' => $post->post_status,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
),
|
|
'formats' => array( '%d', '%s', '%s', '%s' ),
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unit tests for TMDO_Standard_Post_Interceptor abstract base.
|
|
*
|
|
* Exercises the three shared hook methods (action_delete_post,
|
|
* filter_update_meta, action_insert_post) via the TMDO_Test_Post_Interceptor
|
|
* concrete subclass, which never needs to live outside this test file.
|
|
*/
|
|
class StandardPostInterceptorTest extends TestCase {
|
|
|
|
private TMDO_Test_Post_Interceptor $interceptor;
|
|
|
|
/** Last $wpdb->delete() call: [table, where, formats]. */
|
|
public static array $last_delete = [];
|
|
|
|
/** Last SQL passed to $wpdb->query(). */
|
|
public static string $last_query = '';
|
|
|
|
/** Last $wpdb->insert() call: [table, data, formats]. */
|
|
public static array $last_insert = [];
|
|
|
|
protected function setUp(): void {
|
|
self::$last_delete = [];
|
|
self::$last_query = '';
|
|
self::$last_insert = [];
|
|
|
|
$this->interceptor = new TMDO_Test_Post_Interceptor();
|
|
|
|
// Reset Feature Flags request cache.
|
|
$ff = new ReflectionClass( TMDO_Feature_Flags::class );
|
|
$ff->getProperty( 'cache' )->setValue( null, null );
|
|
$GLOBALS['_wp_options'] = [];
|
|
|
|
$this->setup_wpdb_mock();
|
|
}
|
|
|
|
private function setup_wpdb_mock(): void {
|
|
global $wpdb;
|
|
$wpdb = new class {
|
|
public string $prefix = 'wp_';
|
|
public function prepare( string $sql, mixed ...$args ): string {
|
|
$i = 0;
|
|
return preg_replace_callback( '/%([sd])/', static function ( $m ) use ( &$i, $args ) {
|
|
$val = $args[ $i++ ] ?? '';
|
|
return $m[1] === 'd' ? (string) (int) $val : "'" . addslashes( (string) $val ) . "'";
|
|
}, $sql );
|
|
}
|
|
public function query( string $sql ): int|bool {
|
|
StandardPostInterceptorTest::$last_query = $sql;
|
|
return 1;
|
|
}
|
|
public function delete( string $table, array $where, array $formats ): int|false {
|
|
StandardPostInterceptorTest::$last_delete = [ $table, $where, $formats ];
|
|
return 1;
|
|
}
|
|
public function insert( string $table, array $data, array $formats ): int|false {
|
|
StandardPostInterceptorTest::$last_insert = [ $table, $data, $formats ];
|
|
return 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
private function make_post( string $type = 'test_post', int $id = 1 ): WP_Post {
|
|
$post = new WP_Post( (object) [] );
|
|
$post->ID = $id;
|
|
$post->post_type = $type;
|
|
$post->post_status = 'publish';
|
|
$post->post_title = 'Test';
|
|
$post->post_content = '';
|
|
$post->post_author = 0;
|
|
$post->post_parent = 0;
|
|
return $post;
|
|
}
|
|
|
|
// ── action_delete_post ────────────────────────────────────────────────────
|
|
|
|
public function test_delete_post_calls_wpdb_delete_for_correct_type(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$post = $this->make_post( 'test_post', 99 );
|
|
$this->interceptor->action_delete_post( 99, $post );
|
|
$this->assertStringContainsString( 'wp_test_table', self::$last_delete[0] ?? '' );
|
|
$this->assertSame( [ 'post_id' => 99 ], self::$last_delete[1] );
|
|
}
|
|
|
|
public function test_delete_post_skips_wrong_post_type(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$post = $this->make_post( 'other_type', 99 );
|
|
$this->interceptor->action_delete_post( 99, $post );
|
|
$this->assertSame( [], self::$last_delete );
|
|
}
|
|
|
|
public function test_delete_post_skips_when_not_active(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'idle' );
|
|
$post = $this->make_post( 'test_post', 99 );
|
|
$this->interceptor->action_delete_post( 99, $post );
|
|
$this->assertSame( [], self::$last_delete );
|
|
}
|
|
|
|
// ── filter_update_meta ────────────────────────────────────────────────────
|
|
|
|
public function test_update_meta_issues_sql_update_for_known_key(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
// get_post_type() stub returns 'test_post' for post ID 5.
|
|
$GLOBALS['_wp_post_types'][5] = 'test_post';
|
|
|
|
$result = $this->interceptor->filter_update_meta( null, 5, 'test_meta', 'newval', '' );
|
|
|
|
$this->assertNull( $result );
|
|
$this->assertStringContainsString( 'UPDATE', self::$last_query );
|
|
$this->assertStringContainsString( 'test_col', self::$last_query );
|
|
$this->assertStringContainsString( 'newval', self::$last_query );
|
|
}
|
|
|
|
public function test_update_meta_skips_unregistered_key(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$GLOBALS['_wp_post_types'][5] = 'test_post';
|
|
|
|
$this->interceptor->filter_update_meta( null, 5, 'unknown_key', 'val', '' );
|
|
|
|
$this->assertSame( '', self::$last_query );
|
|
}
|
|
|
|
public function test_update_meta_skips_wrong_post_type(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$GLOBALS['_wp_post_types'][5] = 'other_type';
|
|
|
|
$this->interceptor->filter_update_meta( null, 5, 'test_meta', 'val', '' );
|
|
|
|
$this->assertSame( '', self::$last_query );
|
|
}
|
|
|
|
public function test_update_meta_passes_through_check_unchanged(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$GLOBALS['_wp_post_types'][5] = 'test_post';
|
|
$sentinel = 'original_check';
|
|
|
|
$result = $this->interceptor->filter_update_meta( $sentinel, 5, 'test_meta', 'v', '' );
|
|
|
|
$this->assertSame( $sentinel, $result );
|
|
}
|
|
|
|
// ── action_insert_post ────────────────────────────────────────────────────
|
|
|
|
public function test_insert_post_calls_wpdb_insert_for_new_post(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$post = $this->make_post( 'test_post', 42 );
|
|
$this->interceptor->action_insert_post( 42, $post, false );
|
|
$this->assertStringContainsString( 'wp_test_table', self::$last_insert[0] ?? '' );
|
|
$this->assertSame( 42, self::$last_insert[1]['post_id'] ?? 0 );
|
|
}
|
|
|
|
public function test_insert_post_skips_updates(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$post = $this->make_post( 'test_post', 42 );
|
|
$this->interceptor->action_insert_post( 42, $post, true );
|
|
$this->assertSame( [], self::$last_insert );
|
|
}
|
|
|
|
public function test_insert_post_skips_wrong_post_type(): void {
|
|
TMDO_Feature_Flags::set( 'test_module', 'complete' );
|
|
$post = $this->make_post( 'other_type', 42 );
|
|
$this->interceptor->action_insert_post( 42, $post, false );
|
|
$this->assertSame( [], self::$last_insert );
|
|
}
|
|
}
|