$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 ); } }