json string */ public static array $db_store = []; /** Control whether get_var returns the "id" existence check */ public static bool $row_exists = false; protected function setUp(): void { self::$db_store = []; self::$row_exists = false; $GLOBALS['_wp_cache'] = []; $this->setup_wpdb_mock(); } private function setup_wpdb_mock(): void { global $wpdb; $wpdb = new class { public string $prefix = 'wp_'; 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 ); } /** * get_var is used for two things: * 1. SELECT data ... → return JSON blob * 2. SELECT id ... → return '1' if exists, else null */ public function get_var( string $sql ): ?string { $flat = preg_replace( '/\s+/', ' ', $sql ); // Existence check (save_blob path). if ( stripos( $flat, 'SELECT id' ) !== false ) { if ( preg_match( "/post_id = (\d+)/", $flat, $m ) ) { return isset( ZoneColdTest::$db_store[ (int) $m[1] ] ) ? '1' : null; } return null; } // Data fetch. if ( preg_match( "/post_id = (\d+)/", $flat, $m ) ) { return ZoneColdTest::$db_store[ (int) $m[1] ] ?? null; } return null; } 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 { if ( isset( $data['post_id'], $data['data'] ) ) { ZoneColdTest::$db_store[ (int) $data['post_id'] ] = $data['data']; } return 1; } public function update( string $table, array $data, array $where, $format = null, $where_format = null ): int|false { if ( isset( $where['post_id'], $data['data'] ) ) { ZoneColdTest::$db_store[ (int) $where['post_id'] ] = $data['data']; } return 1; } public function delete( string $table, array $where, $format = null ): int|false { if ( isset( $where['post_id'] ) ) { unset( ZoneColdTest::$db_store[ (int) $where['post_id'] ] ); } return 1; } public function query( string $sql ): int|bool { $flat = preg_replace( '/\s+/', ' ', $sql ); // JSON_REMOVE UPDATE — from TMDO_Zone_Cold::remove (MySQL path). // SQL: UPDATE `{table}` SET data = JSON_REMOVE(..., '$.key'), ... WHERE post_id = N if ( stripos( $flat, 'JSON_REMOVE' ) !== false ) { if ( preg_match( "/'\\\$\\.([^']+)'/", $flat, $km ) && preg_match( '/WHERE post_id = (\d+)/i', $flat, $wm ) ) { $post_id = (int) $wm[1]; $key = $km[1]; $data = json_decode( ZoneColdTest::$db_store[ $post_id ] ?? '{}', true ); $data = is_array( $data ) ? $data : array(); unset( $data[ $key ] ); ZoneColdTest::$db_store[ $post_id ] = (string) wp_json_encode( $data ); } return 1; } // INSERT...ON DUPLICATE KEY with JSON_MERGE_PATCH — from save_patch (MySQL path). // Merges the patch into the existing blob, preserving untouched keys. if ( stripos( $flat, 'ON DUPLICATE KEY' ) !== false && stripos( $flat, 'JSON_MERGE_PATCH' ) !== false ) { if ( preg_match( "/VALUES \((\d+), '((?:[^'\\\\]|\\\\.)*)'/i", $flat, $m ) ) { $post_id = (int) $m[1]; $patch = json_decode( stripslashes( $m[2] ), true ); $patch = is_array( $patch ) ? $patch : array(); $existing = json_decode( ZoneColdTest::$db_store[ $post_id ] ?? '{}', true ); $existing = is_array( $existing ) ? $existing : array(); ZoneColdTest::$db_store[ $post_id ] = (string) wp_json_encode( array_merge( $existing, $patch ) ); } return 1; } // INSERT...ON DUPLICATE KEY UPDATE (from TMDO_DB::upsert via save_blob). // Pattern: VALUES (post_id, 'json_data', 'datetime') ON DUPLICATE KEY UPDATE col = VALUES(col) if ( stripos( $flat, 'ON DUPLICATE KEY' ) !== false ) { if ( preg_match( "/VALUES \((\d+), '((?:[^'\\\\]|\\\\.)*)'/i", $flat, $m ) ) { ZoneColdTest::$db_store[ (int) $m[1] ] = stripslashes( $m[2] ); } } return 1; } }; } // ── table() ────────────────────────────────────────────────────────────── public function test_table_returns_prefixed_name(): void { $this->assertSame( 'wp_wpdo_cold_hp_listing', WPDO_Zone_Cold::table( 'hp_listing' ) ); } // ── get() ───────────────────────────────────────────────────────────────── public function test_get_returns_null_for_missing_key(): void { // Cache miss + no DB row → blob is empty array → key missing → null. $result = WPDO_Zone_Cold::get( 99, 'hp_listing', 'hp_description' ); $this->assertNull( $result ); } public function test_get_reads_from_cache_on_hit(): void { // Pre-populate cache so DB should NOT be hit. $group = 'wpdo_cold_hp_listing'; $cache_key = 'cold_1'; $GLOBALS['_wp_cache'][ $group ][ $cache_key ] = [ 'hp_bio' => 'cached value' ]; $result = WPDO_Zone_Cold::get( 1, 'hp_listing', 'hp_bio' ); $this->assertSame( 'cached value', $result ); // DB store should remain empty (DB was not queried for data). $this->assertEmpty( self::$db_store ); } // ── get_blob() ─────────────────────────────────────────────────────────── public function test_get_blob_queries_db_on_cache_miss(): void { self::$db_store[5] = json_encode( [ 'hp_description' => 'Hello World', 'hp_location' => 'Paris' ] ); $blob = WPDO_Zone_Cold::get_blob( 5, 'hp_listing' ); $this->assertSame( 'Hello World', $blob['hp_description'] ); $this->assertSame( 'Paris', $blob['hp_location'] ); } // ── set() ───────────────────────────────────────────────────────────────── public function test_set_merges_new_key_into_blob(): void { // Seed an existing blob. self::$db_store[10] = json_encode( [ 'a' => 1 ] ); WPDO_Zone_Cold::set( 10, 'hp_listing', 'b', 2 ); $stored = json_decode( self::$db_store[10], true ); $this->assertArrayHasKey( 'a', $stored ); $this->assertArrayHasKey( 'b', $stored ); $this->assertSame( 1, $stored['a'] ); $this->assertSame( 2, $stored['b'] ); } // ── set_many() ─────────────────────────────────────────────────────────── public function test_set_many_merges_multiple_keys(): void { WPDO_Zone_Cold::set_many( 20, 'hp_listing', [ 'key1' => 'v1', 'key2' => 'v2', 'key3' => 'v3', ] ); $stored = json_decode( self::$db_store[20], true ); $this->assertSame( 'v1', $stored['key1'] ); $this->assertSame( 'v2', $stored['key2'] ); $this->assertSame( 'v3', $stored['key3'] ); } // ── remove() ───────────────────────────────────────────────────────────── public function test_remove_deletes_key_from_blob(): void { self::$db_store[30] = json_encode( [ 'keep' => 'yes', 'drop' => 'no' ] ); WPDO_Zone_Cold::remove( 30, 'hp_listing', 'drop' ); $stored = json_decode( self::$db_store[30], true ); $this->assertArrayHasKey( 'keep', $stored ); $this->assertArrayNotHasKey( 'drop', $stored ); } // ── delete() ───────────────────────────────────────────────────────────── public function test_delete_clears_cache(): void { $group = 'wpdo_cold_hp_listing'; $cache_key = 'cold_1'; // Pre-populate cache. $GLOBALS['_wp_cache'][ $group ][ $cache_key ] = [ 'some' => 'data' ]; WPDO_Zone_Cold::delete( 1, 'hp_listing' ); // Cache entry must be gone. $this->assertFalse( isset( $GLOBALS['_wp_cache'][ $group ][ $cache_key ] ) ); } // ── Additional edge-case tests ──────────────────────────────────────────── public function test_set_invalidates_object_cache(): void { $group = 'wpdo_cold_hp_listing'; $cache_key = 'cold_50'; // Pre-populate cache with stale data. $GLOBALS['_wp_cache'][ $group ][ $cache_key ] = [ 'stale' => 'old_value' ]; WPDO_Zone_Cold::set( 50, 'hp_listing', 'fresh', 'new_value' ); // Cache must be invalidated after write. $this->assertFalse( isset( $GLOBALS['_wp_cache'][ $group ][ $cache_key ] ) ); } public function test_get_blob_populates_cache_on_db_hit(): void { // Seed the DB store so get_blob has something to fetch. self::$db_store[60] = json_encode( [ 'cached_key' => 'cached_val' ] ); WPDO_Zone_Cold::get_blob( 60, 'hp_listing' ); // Cache must now contain the fetched data. $group = 'wpdo_cold_hp_listing'; $cached = $GLOBALS['_wp_cache'][ $group ]['cold_60'] ?? false; $this->assertIsArray( $cached ); $this->assertSame( 'cached_val', $cached['cached_key'] ); } public function test_remove_nonexistent_key_is_safe(): void { // Store an existing blob. self::$db_store[70] = json_encode( [ 'keep' => 'this' ] ); // Remove a key that doesn't exist — should not throw. WPDO_Zone_Cold::remove( 70, 'hp_listing', 'nonexistent_key' ); $stored = json_decode( self::$db_store[70], true ); $this->assertArrayHasKey( 'keep', $stored ); $this->assertArrayNotHasKey( 'nonexistent_key', $stored ); } }