d36bb954d1
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
230 lines
8.2 KiB
PHP
230 lines
8.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* Tests for WPDO_Zone_Cold — JSON blob storage with Object Cache integration.
|
|
*/
|
|
class ZoneColdTest extends TestCase {
|
|
|
|
/** In-memory "database" store: post_id => 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 {
|
|
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 );
|
|
}
|
|
}
|