Skip to content

Commit cd89563

Browse files
committed
Add test to ensure updates inserted during deletes are not also deleted
1 parent a850382 commit cd89563

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

tests/phpunit/tests/rest-api/rest-sync-server.php

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,136 @@ public function test_sync_should_compact_is_false_for_non_compactor() {
779779
$this->assertFalse( $data['rooms'][0]['should_compact'] );
780780
}
781781

782+
public function test_sync_compaction_does_not_delete_update_inserted_during_delete() {
783+
global $wpdb;
784+
785+
wp_set_current_user( self::$editor_id );
786+
787+
$room = $this->get_post_room();
788+
$storage = new WP_Sync_Post_Meta_Storage();
789+
790+
// Seed three updates so there's something to compact.
791+
for ( $i = 1; $i <= 3; $i++ ) {
792+
$this->assertTrue(
793+
$storage->add_update(
794+
$room,
795+
array(
796+
'client_id' => $i,
797+
'type' => 'update',
798+
'data' => base64_encode( "seed-$i" ),
799+
)
800+
)
801+
);
802+
}
803+
804+
// Capture the cursor after all seeds are in place.
805+
$storage->get_updates_after_cursor( $room, 0 );
806+
$compaction_cursor = $storage->get_cursor( $room );
807+
$this->assertGreaterThan( 0, $compaction_cursor );
808+
809+
$storage_posts = get_posts(
810+
array(
811+
'post_type' => WP_Sync_Post_Meta_Storage::POST_TYPE,
812+
'posts_per_page' => 1,
813+
'post_status' => 'publish',
814+
'name' => md5( $room ),
815+
'fields' => 'ids',
816+
)
817+
);
818+
$storage_post_id = array_first( $storage_posts );
819+
$this->assertIsInt( $storage_post_id );
820+
821+
$concurrent_update = array(
822+
'client_id' => 9999,
823+
'type' => 'update',
824+
'data' => base64_encode( 'arrived-during-compaction' ),
825+
);
826+
827+
$original_wpdb = $wpdb;
828+
$proxy_wpdb = new class( $original_wpdb, $storage_post_id, $concurrent_update ) {
829+
private $wpdb;
830+
private $storage_post_id;
831+
private $concurrent_update;
832+
public $did_inject = false;
833+
834+
public function __construct( $wpdb, int $storage_post_id, array $concurrent_update ) {
835+
$this->wpdb = $wpdb;
836+
$this->storage_post_id = $storage_post_id;
837+
$this->concurrent_update = $concurrent_update;
838+
}
839+
840+
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Proxy forwards fully prepared core queries.
841+
public function prepare( ...$args ) {
842+
return $this->wpdb->prepare( ...$args );
843+
}
844+
845+
public function query( $query ) {
846+
$result = $this->wpdb->query( $query );
847+
848+
// After the DELETE executes, inject a concurrent update via
849+
// raw SQL through the real $wpdb to avoid metadata cache
850+
// interactions while the proxy is active.
851+
if ( ! $this->did_inject
852+
&& is_string( $query )
853+
&& 0 === strpos( $query, "DELETE FROM {$this->wpdb->postmeta}" )
854+
&& false !== strpos( $query, "post_id = {$this->storage_post_id}" )
855+
) {
856+
$this->did_inject = true;
857+
$this->wpdb->insert(
858+
$this->wpdb->postmeta,
859+
array(
860+
'post_id' => $this->storage_post_id,
861+
'meta_key' => WP_Sync_Post_Meta_Storage::SYNC_UPDATE_META_KEY,
862+
'meta_value' => maybe_serialize( $this->concurrent_update ),
863+
),
864+
array( '%d', '%s', '%s' )
865+
);
866+
}
867+
868+
return $result;
869+
}
870+
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
871+
872+
public function __call( $name, $arguments ) {
873+
return $this->wpdb->$name( ...$arguments );
874+
}
875+
876+
public function __get( $name ) {
877+
return $this->wpdb->$name;
878+
}
879+
880+
public function __set( $name, $value ) {
881+
$this->wpdb->$name = $value;
882+
}
883+
};
884+
885+
// Run compaction through the proxy so the concurrent update
886+
// is injected immediately after the DELETE executes.
887+
$wpdb = $proxy_wpdb;
888+
try {
889+
$result = $storage->remove_updates_before_cursor( $room, $compaction_cursor );
890+
} finally {
891+
$wpdb = $original_wpdb;
892+
}
893+
894+
$this->assertTrue( $result );
895+
$this->assertTrue( $proxy_wpdb->did_inject, 'Expected concurrent update injection to occur.' );
896+
897+
// Clear caches since the injection bypassed add_update().
898+
wp_cache_delete( $storage_post_id, 'post_meta' );
899+
wp_cache_delete( 'sync_room_state_' . md5( $room ), 'sync' );
900+
901+
// The concurrent update must survive the compaction delete.
902+
$updates = $storage->get_updates_after_cursor( $room, 0 );
903+
904+
$update_data = wp_list_pluck( $updates, 'data' );
905+
$this->assertContains(
906+
$concurrent_update['data'],
907+
$update_data,
908+
'Concurrent update should survive compaction.'
909+
);
910+
}
911+
782912
/*
783913
* Awareness tests.
784914
*/

0 commit comments

Comments
 (0)