Files
2meet-data-optimizer/tests/unit/TypeCasterSerializeTest.php
T
wpdev 08f9e313e0 fix(2meet-data-optimizer): serialize arrays in text fields instead of storing "Array"
- TMDO_Type_Caster: text / textarea values that are arrays or objects are
  stored with maybe_serialize() (was (string) → the literal "Array", which
  bridge-restore-native then wrote back over native meta); from_db()
  unserializes them with TMDO_Safe_Unserialize. text columns are
  VARCHAR(255): longer values are stored NULL (native keeps them) and
  logged as text_overflow.
- Add `wp tmdo repair-array-literals [--entity] [--confirm]`
  (TMDO_Array_Literal_Repair): rewrites flat "Array" values from native
  meta and lists native rows that are "Array" themselves (backup needed).
- Verify: TypeCasterSerializeTest 13 OK, ArrayLiteralRepairTest 3 OK;
  unit 685 OK, integration 483 OK, phpcs (phpcs.xml) 0, stan OK;
  live `serialized` 3 PASS; dev36 repair dry-run lists the value corrupted
  by the 2026-09-26 round trip (user #1 community-events-location)
- PR: PR-055
2026-09-26 18:45:59 +08:00

100 lines
3.5 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* text / textarea fields holding arrays (e.g. usermeta wp_persisted_preferences,
* community-events-location) were cast with (string) and stored as "Array";
* restoring native meta then wrote "Array" back over the real data.
*/
class TypeCasterSerializeTest extends TestCase {
/** Rows passed to $wpdb->insert() (wpdo_errors via TMDO_Logger::warning). */
public static array $inserts = array();
private const TEXT = array( 'type' => 'text', 'group' => 'admin_prefs', 'key' => 'community-events-location' );
private const TEXTAREA = array( 'type' => 'textarea', 'group' => 'admin_prefs', 'key' => 'wp_persisted_preferences' );
protected function setUp(): void {
self::$inserts = array();
ini_set( 'error_log', '/dev/null' );
global $wpdb;
$wpdb = new class {
public string $prefix = 'wp_';
public function insert( string $table, array $data, $format = null ) {
TypeCasterSerializeTest::$inserts[] = array( $table, $data );
return 1;
}
};
}
public static function arrays(): array {
return array(
'flat' => array( array( 'ip' => '118.163.181.0' ) ),
'nested' => array( array( 'core/edit-post' => array( 'welcomeGuide' => false, 'panels' => array( 'a', 'b' ) ) ) ),
'empty' => array( array() ),
'list' => array( array( 1, 2, 3 ) ),
);
}
/**
* @dataProvider arrays
*/
public function test_text_array_round_trips( array $value ): void {
$stored = TMDO_Type_Caster::to_db( $value, self::TEXT );
$this->assertNotSame( 'Array', $stored );
$this->assertSame( serialize( $value ), $stored );
$this->assertSame( $value, TMDO_Type_Caster::from_db( $stored, self::TEXT ) );
}
/**
* @dataProvider arrays
*/
public function test_textarea_array_round_trips( array $value ): void {
$stored = TMDO_Type_Caster::to_db( $value, self::TEXTAREA );
$this->assertSame( serialize( $value ), $stored );
$this->assertSame( $value, TMDO_Type_Caster::from_db( $stored, self::TEXTAREA ) );
}
public function test_object_is_serialized_not_cast(): void {
$value = (object) array( 'a' => 1 );
$this->assertSame( serialize( $value ), TMDO_Type_Caster::to_db( $value, self::TEXTAREA ) );
}
public function test_plain_strings_are_unchanged(): void {
foreach ( array( 'fresh', '', 'a:b', '0' ) as $s ) {
$this->assertSame( $s, TMDO_Type_Caster::to_db( $s, self::TEXT ) );
$this->assertSame( $s, TMDO_Type_Caster::from_db( $s, self::TEXT ) );
}
$this->assertSame( '42', TMDO_Type_Caster::to_db( 42, self::TEXT ) );
}
public function test_text_over_255_chars_is_stored_as_null_and_logged(): void {
$long = str_repeat( 'é', 256 );
$this->assertNull( TMDO_Type_Caster::to_db( $long, self::TEXT ) );
$this->assertCount( 1, self::$inserts );
$this->assertStringContainsString( 'text_overflow', self::$inserts[0][1]['hook'] );
$this->assertStringContainsString( 'community-events-location', (string) self::$inserts[0][1]['context'] );
}
public function test_text_of_exactly_255_chars_is_kept(): void {
$max = str_repeat( 'é', 255 );
$this->assertSame( $max, TMDO_Type_Caster::to_db( $max, self::TEXT ) );
$this->assertCount( 0, self::$inserts );
}
public function test_serialized_array_over_255_chars_overflows_too(): void {
$value = array( 'k' => str_repeat( 'x', 300 ) );
$this->assertNull( TMDO_Type_Caster::to_db( $value, self::TEXT ) );
$this->assertSame( serialize( $value ), TMDO_Type_Caster::to_db( $value, self::TEXTAREA ), 'textarea is TEXT, no 255 limit' );
}
}