query(). */ public static string $last_query = ''; /** Configurable return value for $wpdb->get_var(). */ public static ?string $get_var_return = null; protected function setUp(): void { $this->bridge = new WPDO_Sync_Bridge(); // Reset globals. $GLOBALS['_wp_options'] = []; $GLOBALS['_wp_post_types'] = []; self::$last_query = ''; self::$get_var_return = null; // Reset private statics via reflection. $ref = new ReflectionClass( WPDO_Sync_Bridge::class ); $ref->getProperty( 'bypassing' )->setValue( null, false ); $ref->getProperty( 'field_cache' )->setValue( null, [] ); // Reset Schema Registry singleton. $sr = new ReflectionClass( WPDO_Schema_Registry::class ); $sr->getProperty( 'instance' )->setValue( null, null ); // Reset Feature Flags request cache. $ff = new ReflectionClass( WPDO_Feature_Flags::class ); $ff->getProperty( 'cache' )->setValue( null, null ); $this->setup_wpdb_mock(); } private function setup_wpdb_mock(): void { global $wpdb; $wpdb = new class { public string $prefix = 'wp_'; public string $postmeta = 'wp_postmeta'; public string $posts = 'wp_posts'; public function prepare( string $sql, ...$args ): string { $i = 0; return preg_replace_callback( '/%([sd])/', function ( $m ) use ( &$i, $args ) { $val = $args[ $i++ ] ?? ''; return $m[1] === 'd' ? (string) (int) $val : "'" . addslashes( (string) $val ) . "'"; }, $sql ); } public function get_var( string $sql ): ?string { return SyncBridgeTest::$get_var_return; } public function get_row( string $sql, $output = OBJECT ) { return null; } public function get_results( string $sql, $output = OBJECT ): array { return []; } public function insert( string $table, array $data, $format = null ): int|false { return 1; } public function update( string $table, array $data, array $where, $f = null, $wf = null ): int|false { return 1; } public function delete( string $table, array $where, $format = null ): int|false { return 1; } public function query( string $sql ): int|bool { SyncBridgeTest::$last_query = $sql; return 1; } }; } // ── Helper: register a hot field ───────────────────────────────────────── private function register_hot_field( string $post_type = 'hp_listing', string $meta_key = 'hp_price' ): void { WPDO_Schema_Registry::instance()->register( 'test', [ 'post_type' => $post_type, 'meta_key' => $meta_key, 'zone' => 'hot', 'column' => $meta_key, 'type' => 'decimal', ] ); } // ── intercept_get: early-return guards ─────────────────────────────────── public function test_intercept_get_returns_null_when_bypassing(): void { $ref = new ReflectionClass( WPDO_Sync_Bridge::class ); $ref->getProperty( 'bypassing' )->setValue( null, true ); $result = $this->bridge->intercept_get( null, 1, 'hp_price', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_null_for_zero_post_id(): void { $result = $this->bridge->intercept_get( null, 0, 'hp_price', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_null_for_empty_meta_key(): void { $result = $this->bridge->intercept_get( null, 1, '', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_null_when_post_type_unknown(): void { // Post ID 99 not in _wp_post_types — get_post_type returns false. $result = $this->bridge->intercept_get( null, 99, 'hp_price', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_null_when_field_not_registered(): void { $GLOBALS['_wp_post_types'][1] = 'hp_listing'; // No field registered → returns unchanged $value. $result = $this->bridge->intercept_get( null, 1, 'unregistered_key', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_null_when_module_not_cutover(): void { $GLOBALS['_wp_post_types'][1] = 'hp_listing'; $this->register_hot_field(); // Module stays idle (not set) → is_read_custom returns false. $result = $this->bridge->intercept_get( null, 1, 'hp_price', true ); $this->assertNull( $result ); } public function test_intercept_get_returns_zone_value_when_cutover(): void { $GLOBALS['_wp_post_types'][2] = 'hp_listing'; $this->register_hot_field(); WPDO_Feature_Flags::set( 'hot_hp_listing', 'cutover' ); self::$get_var_return = '42'; $result = $this->bridge->intercept_get( null, 2, 'hp_price', true ); // Returns array-wrapped value (WordPress unwraps on $single=true). $this->assertSame( [ '42' ], $result ); } public function test_intercept_get_returns_null_when_zone_returns_null(): void { $GLOBALS['_wp_post_types'][3] = 'hp_listing'; $this->register_hot_field(); WPDO_Feature_Flags::set( 'hot_hp_listing', 'cutover' ); self::$get_var_return = null; // Zone returns nothing. $result = $this->bridge->intercept_get( null, 3, 'hp_price', true ); $this->assertNull( $result ); } // ── intercept_update ───────────────────────────────────────────────────── public function test_intercept_update_skips_when_bypassing(): void { $ref = new ReflectionClass( WPDO_Sync_Bridge::class ); $ref->getProperty( 'bypassing' )->setValue( null, true ); $result = $this->bridge->intercept_update( null, 1, 'hp_price', '99', '' ); $this->assertNull( $result ); $this->assertEmpty( self::$last_query ); } public function test_intercept_update_passes_through_when_no_field_registered(): void { $GLOBALS['_wp_post_types'][1] = 'hp_listing'; // No field registered → returns $check unchanged. $result = $this->bridge->intercept_update( null, 1, 'hp_price', '99', '' ); $this->assertNull( $result ); } public function test_intercept_update_writes_to_zone_when_write_active(): void { $GLOBALS['_wp_post_types'][5] = 'hp_listing'; $this->register_hot_field(); WPDO_Feature_Flags::set( 'hot_hp_listing', 'dual_write' ); $this->bridge->intercept_update( null, 5, 'hp_price', '150', '' ); // Zone Hot set() executes an UPSERT query. $this->assertStringContainsString( 'ON DUPLICATE KEY UPDATE', self::$last_query ); } // ── intercept_add ──────────────────────────────────────────────────────── public function test_intercept_add_writes_when_module_write_active(): void { $GLOBALS['_wp_post_types'][6] = 'hp_listing'; $this->register_hot_field(); WPDO_Feature_Flags::set( 'hot_hp_listing', 'dual_write' ); $this->bridge->intercept_add( null, 6, 'hp_price', '200', false ); $this->assertStringContainsString( 'ON DUPLICATE KEY UPDATE', self::$last_query ); } // ── cleanup_post ───────────────────────────────────────────────────────── public function test_cleanup_post_does_nothing_for_unknown_post_type(): void { // Post ID 999 has no type → early return. $this->bridge->cleanup_post( 999 ); $this->assertEmpty( self::$last_query ); } public function test_cleanup_post_deletes_hot_zone_data(): void { $GLOBALS['_wp_post_types'][10] = 'hp_listing'; $this->register_hot_field(); $this->bridge->cleanup_post( 10 ); // WPDO_Zone_Hot::delete() calls $wpdb->delete() — but our mock captures query(). // The hot delete uses $wpdb->delete(), not query(). Just assert no exception thrown. $this->assertTrue( true ); } }