From 0f1a0b703394434ba6db871eb685e2572287d0a9 Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 16:41:01 -0400 Subject: [PATCH 1/6] Collaboration: Move db_version check to wp_is_collaboration_allowed(). The database schema requirement is a system-level constraint, not a user preference. Move it from wp_is_collaboration_enabled() to wp_is_collaboration_allowed() where it belongs. --- src/wp-includes/collaboration.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/collaboration.php b/src/wp-includes/collaboration.php index 6b26d60ac8dab..277d168c75a4d 100644 --- a/src/wp-includes/collaboration.php +++ b/src/wp-includes/collaboration.php @@ -11,8 +11,6 @@ * * If the WP_ALLOW_COLLABORATION constant is false, * collaboration is always disabled regardless of the database option. - * Otherwise, the feature requires both the 'wp_collaboration_enabled' - * option and the database schema introduced in db_version 62282. * * @since 7.0.0 * @@ -21,8 +19,7 @@ function wp_is_collaboration_enabled(): bool { return ( wp_is_collaboration_allowed() && - get_option( 'wp_collaboration_enabled' ) && - get_option( 'db_version' ) >= 62282 + get_option( 'wp_collaboration_enabled' ) ); } @@ -34,11 +31,17 @@ function wp_is_collaboration_enabled(): bool { * The constant defaults to true, unless the WP_ALLOW_COLLABORATION * environment variable is set to string "false". * + * Also requires the database schema introduced in db_version 62282. + * * @since 7.0.0 * * @return bool Whether real-time collaboration is allowed. */ function wp_is_collaboration_allowed(): bool { + if ( get_option( 'db_version' ) < 62282 ) { + return false; + } + if ( ! defined( 'WP_ALLOW_COLLABORATION' ) ) { $env_value = getenv( 'WP_ALLOW_COLLABORATION' ); if ( false === $env_value ) { From 4744b42918b6a777a04b81de9f537a7c08460827 Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 16:18:56 -0400 Subject: [PATCH 2/6] Update tests/phpunit/tests/rest-api/rest-collaboration-server.php Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> --- tests/phpunit/tests/rest-api/rest-collaboration-server.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-collaboration-server.php b/tests/phpunit/tests/rest-api/rest-collaboration-server.php index c8d695808b91f..f5ef1b5ec19d4 100644 --- a/tests/phpunit/tests/rest-api/rest-collaboration-server.php +++ b/tests/phpunit/tests/rest-api/rest-collaboration-server.php @@ -1931,8 +1931,8 @@ public function test_cron_cleanup_preserves_recent_rows(): void { * @ticket 64696 */ public function test_cron_cleanup_boundary_at_exactly_seven_days(): void { - $this->insert_collaboration_row( WEEK_IN_SECONDS + 1, 'expired' ); - $this->insert_collaboration_row( WEEK_IN_SECONDS - 1, 'just-inside' ); + $this->insert_collaboration_row( WEEK_IN_SECONDS + 60, 'expired' ); + $this->insert_collaboration_row( WEEK_IN_SECONDS - 60, 'just-inside' ); wp_delete_old_collaboration_data(); From 703fe9b6de18f57700d31bd9705174a5aa117b74 Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 16:43:06 -0400 Subject: [PATCH 3/6] Tests: Address review feedback for collaboration tests. Remove redundant DELETE in set_up() as transaction rollback handles cleanup. Use REST_TESTS_IMPOSSIBLY_HIGH_NUMBER for nonexistent post ID test. --- tests/phpunit/tests/rest-api/rest-collaboration-server.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-collaboration-server.php b/tests/phpunit/tests/rest-api/rest-collaboration-server.php index f5ef1b5ec19d4..586f2a3d8f4c0 100644 --- a/tests/phpunit/tests/rest-api/rest-collaboration-server.php +++ b/tests/phpunit/tests/rest-api/rest-collaboration-server.php @@ -38,11 +38,6 @@ public function set_up() { // Enable option for tests. add_filter( 'pre_option_wp_collaboration_enabled', '__return_true' ); - // Uses DELETE (not TRUNCATE) to preserve transaction rollback support - // in the test suite. TRUNCATE implicitly commits the transaction. - global $wpdb; - $wpdb->query( "DELETE FROM {$wpdb->collaboration}" ); - // Reset the global REST server so rest_get_server() builds a fresh instance based on the option setting. $GLOBALS['wp_rest_server'] = null; } @@ -359,7 +354,7 @@ public function test_collaboration_non_posttype_entity_with_object_id_rejected() public function test_collaboration_nonexistent_post_rejected(): void { wp_set_current_user( self::$editor_id ); - $response = $this->dispatch_collaboration( array( $this->build_room( 'postType/post:999999' ) ) ); + $response = $this->dispatch_collaboration( array( $this->build_room( 'postType/post:' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER ) ) ); $this->assertErrorResponse( 'rest_cannot_edit', $response, 403 ); } From b4090f4d8b7df72357ca8456e4cc747507dbaca0 Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 17:00:10 -0400 Subject: [PATCH 4/6] Tests: Move cron cleanup tests to dedicated file. Separate cron cleanup tests from the REST endpoint test file into tests/phpunit/tests/collaboration/collaborationCronCleanup.php. --- .../collaborationCronCleanup.php | 269 ++++++++++++++++++ .../rest-api/rest-collaboration-server.php | 235 --------------- 2 files changed, 269 insertions(+), 235 deletions(-) create mode 100644 tests/phpunit/tests/collaboration/collaborationCronCleanup.php diff --git a/tests/phpunit/tests/collaboration/collaborationCronCleanup.php b/tests/phpunit/tests/collaboration/collaborationCronCleanup.php new file mode 100644 index 0000000000000..c2f43bddd5768 --- /dev/null +++ b/tests/phpunit/tests/collaboration/collaborationCronCleanup.php @@ -0,0 +1,269 @@ +user->create( array( 'role' => 'editor' ) ); + self::$post_id = $factory->post->create( array( 'post_author' => self::$editor_id ) ); + + update_option( 'wp_collaboration_enabled', 1 ); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$editor_id ); + delete_option( 'wp_collaboration_enabled' ); + wp_delete_post( self::$post_id, true ); + } + + public function set_up() { + parent::set_up(); + + update_option( 'wp_collaboration_enabled', 1 ); + } + + /** + * Returns the room identifier for the test post. + * + * @return string Room identifier. + */ + private function get_post_room() { + return 'postType/post:' . self::$post_id; + } + + /** + * Inserts a row directly into the collaboration table with a given age. + * + * @param positive-int $age_in_seconds How old the row should be. + * @param string $label A label stored in the data column for identification. + */ + private function insert_collaboration_row( int $age_in_seconds, string $label = 'test' ): void { + global $wpdb; + + $wpdb->insert( + $wpdb->collaboration, + array( + 'room' => $this->get_post_room(), + 'type' => 'update', + 'client_id' => '1', + 'user_id' => self::$editor_id, + 'data' => wp_json_encode( + array( + 'type' => 'update', + 'data' => $label, + ) + ), + 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - $age_in_seconds ), + ), + array( '%s', '%s', '%s', '%d', '%s', '%s' ) + ); + } + + /** + * Returns the number of non-awareness rows in the collaboration table. + * + * @return positive-int Row count. + */ + private function get_collaboration_row_count(): int { + global $wpdb; + + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->collaboration} WHERE type != 'awareness'" ); + } + + /** + * Returns the number of awareness rows in the collaboration table. + * + * @return positive-int Row count. + */ + private function get_awareness_row_count(): int { + global $wpdb; + + return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->collaboration} WHERE type = 'awareness'" ); + } + + /** + * @ticket 64696 + */ + public function test_cron_cleanup_deletes_old_rows(): void { + $this->insert_collaboration_row( 8 * DAY_IN_SECONDS ); + + $this->assertSame( 1, $this->get_collaboration_row_count(), 'Old row should exist before cleanup.' ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 0, $this->get_collaboration_row_count(), 'Old row should be deleted after cleanup.' ); + } + + /** + * @ticket 64696 + */ + public function test_cron_cleanup_preserves_recent_rows(): void { + $this->insert_collaboration_row( DAY_IN_SECONDS ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 1, $this->get_collaboration_row_count() ); + } + + /** + * @ticket 64696 + */ + public function test_cron_cleanup_boundary_at_exactly_seven_days(): void { + $this->insert_collaboration_row( WEEK_IN_SECONDS + 60, 'expired' ); + $this->insert_collaboration_row( WEEK_IN_SECONDS - 60, 'just-inside' ); + + wp_delete_old_collaboration_data(); + + global $wpdb; + $remaining = $wpdb->get_col( "SELECT data FROM {$wpdb->collaboration}" ); + + $this->assertCount( 1, $remaining, 'Only the row within the 7-day window should remain.' ); + $this->assertStringContainsString( 'just-inside', $remaining[0], 'The surviving row should be the one inside the window.' ); + } + + /** + * @ticket 64696 + */ + public function test_cron_cleanup_selectively_deletes_mixed_rows(): void { + // 3 expired rows. + $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); + $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); + $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); + + // 2 recent rows. + $this->insert_collaboration_row( HOUR_IN_SECONDS ); + $this->insert_collaboration_row( HOUR_IN_SECONDS ); + + $this->assertSame( 5, $this->get_collaboration_row_count() ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 2, $this->get_collaboration_row_count(), 'Only the 2 recent rows should survive cleanup.' ); + } + + /** + * @ticket 64696 + */ + public function test_cron_cleanup_hook_is_registered(): void { + $this->assertSame( + 10, + has_action( 'wp_delete_old_collaboration_data', 'wp_delete_old_collaboration_data' ), + 'The wp_delete_old_collaboration_data action should be hooked in default-filters.php.' + ); + } + + /** + * When collaboration is disabled, the cron callback should still clean up + * stale rows and then unschedule itself so it does not continue to run. + * + * @ticket 64696 + */ + public function test_cron_cleanup_when_collaboration_disabled(): void { + global $wpdb; + + // Insert a stale sync row (older than 7 days). + $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); + + // Insert a stale awareness row (older than 60 seconds). + $wpdb->insert( + $wpdb->collaboration, + array( + 'room' => $this->get_post_room(), + 'type' => 'awareness', + 'client_id' => '42', + 'user_id' => self::$editor_id, + 'data' => wp_json_encode( array( 'cursor' => 'stale' ) ), + 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 120 ), + ), + array( '%s', '%s', '%s', '%d', '%s', '%s' ) + ); + + $this->assertSame( 1, $this->get_collaboration_row_count(), 'Should have 1 sync row before cleanup.' ); + $this->assertSame( 1, $this->get_awareness_row_count(), 'Should have 1 awareness row before cleanup.' ); + + // Schedule the cron event so we can verify it gets cleared. + wp_schedule_event( time(), 'hourly', 'wp_delete_old_collaboration_data' ); + $this->assertIsInt( wp_next_scheduled( 'wp_delete_old_collaboration_data' ), 'Cron event should be scheduled before cleanup.' ); + + // Disable collaboration. + update_option( 'wp_collaboration_enabled', false ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 0, $this->get_collaboration_row_count(), 'Stale sync rows should be deleted when collaboration is disabled.' ); + $this->assertSame( 0, $this->get_awareness_row_count(), 'Stale awareness rows should be deleted when collaboration is disabled.' ); + $this->assertFalse( wp_next_scheduled( 'wp_delete_old_collaboration_data' ), 'Cron hook should be unscheduled when collaboration is disabled.' ); + } + + /** + * Cron cleanup should remove expired awareness rows. + * + * @ticket 64696 + */ + public function test_cron_cleanup_deletes_expired_awareness_rows(): void { + global $wpdb; + + // Insert an awareness row older than 60 seconds. + $wpdb->insert( + $wpdb->collaboration, + array( + 'room' => $this->get_post_room(), + 'type' => 'awareness', + 'client_id' => '42', + 'user_id' => self::$editor_id, + 'data' => wp_json_encode( array( 'cursor' => 'old' ) ), + 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 120 ), + ), + array( '%s', '%s', '%s', '%d', '%s', '%s' ) + ); + + // Insert a recent collaboration row (should survive). + $this->insert_collaboration_row( HOUR_IN_SECONDS ); + + $this->assertSame( 1, $this->get_collaboration_row_count(), 'Collaboration table should have 1 sync row.' ); + $this->assertSame( 1, $this->get_awareness_row_count(), 'Collaboration table should have 1 awareness row.' ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 1, $this->get_collaboration_row_count(), 'Only the recent sync row should survive cron cleanup.' ); + $this->assertSame( 0, $this->get_awareness_row_count(), 'Expired awareness row should be deleted after cron cleanup.' ); + } + + /** + * Verifies that a fresh awareness row (younger than 60 seconds) survives cron cleanup. + * + * @ticket 64696 + */ + public function test_cron_cleanup_preserves_fresh_awareness_rows(): void { + global $wpdb; + + // Insert a fresh awareness row (30 seconds old — well within the 60s threshold). + $wpdb->insert( + $wpdb->collaboration, + array( + 'room' => $this->get_post_room(), + 'type' => 'awareness', + 'client_id' => '1', + 'user_id' => self::$editor_id, + 'data' => wp_json_encode( array( 'cursor' => 'active' ) ), + 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 30 ), + ), + array( '%s', '%s', '%s', '%d', '%s', '%s' ) + ); + + $this->assertSame( 1, $this->get_awareness_row_count(), 'Should have 1 awareness row before cleanup.' ); + + wp_delete_old_collaboration_data(); + + $this->assertSame( 1, $this->get_awareness_row_count(), 'Fresh awareness row should survive cron cleanup.' ); + } +} diff --git a/tests/phpunit/tests/rest-api/rest-collaboration-server.php b/tests/phpunit/tests/rest-api/rest-collaboration-server.php index 586f2a3d8f4c0..3e7f65b69e50d 100644 --- a/tests/phpunit/tests/rest-api/rest-collaboration-server.php +++ b/tests/phpunit/tests/rest-api/rest-collaboration-server.php @@ -1844,207 +1844,6 @@ public function test_collaboration_compaction_with_integer_client_ids(): void { $this->assertTrue( $data['rooms'][0]['should_compact'], 'Integer client ID should be correctly identified as compactor.' ); } - /* - * Cron cleanup tests. - */ - - /** - * Inserts a row directly into the collaboration table with a given age. - * - * @param positive-int $age_in_seconds How old the row should be. - * @param string $label A label stored in the data column for identification. - */ - private function insert_collaboration_row( int $age_in_seconds, string $label = 'test' ): void { - global $wpdb; - - $wpdb->insert( - $wpdb->collaboration, - array( - 'room' => $this->get_post_room(), - 'type' => 'update', - 'client_id' => '1', - 'user_id' => self::$editor_id, - 'data' => wp_json_encode( - array( - 'type' => 'update', - 'data' => $label, - ) - ), - 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - $age_in_seconds ), - ), - array( '%s', '%s', '%s', '%d', '%s', '%s' ) - ); - } - - /** - * Returns the number of non-awareness rows in the collaboration table. - * - * @return positive-int Row count. - */ - private function get_collaboration_row_count(): int { - global $wpdb; - - return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->collaboration} WHERE type != 'awareness'" ); - } - - /** - * Returns the number of awareness rows in the collaboration table. - * - * @return positive-int Row count. - */ - private function get_awareness_row_count(): int { - global $wpdb; - - return (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->collaboration} WHERE type = 'awareness'" ); - } - - /** - * @ticket 64696 - */ - public function test_cron_cleanup_deletes_old_rows(): void { - $this->insert_collaboration_row( 8 * DAY_IN_SECONDS ); - - $this->assertSame( 1, $this->get_collaboration_row_count() ); - - wp_delete_old_collaboration_data(); - - $this->assertSame( 0, $this->get_collaboration_row_count() ); - } - - /** - * @ticket 64696 - */ - public function test_cron_cleanup_preserves_recent_rows(): void { - $this->insert_collaboration_row( DAY_IN_SECONDS ); - - wp_delete_old_collaboration_data(); - - $this->assertSame( 1, $this->get_collaboration_row_count() ); - } - - /** - * @ticket 64696 - */ - public function test_cron_cleanup_boundary_at_exactly_seven_days(): void { - $this->insert_collaboration_row( WEEK_IN_SECONDS + 60, 'expired' ); - $this->insert_collaboration_row( WEEK_IN_SECONDS - 60, 'just-inside' ); - - wp_delete_old_collaboration_data(); - - global $wpdb; - $remaining = $wpdb->get_col( "SELECT data FROM {$wpdb->collaboration}" ); - - $this->assertCount( 1, $remaining, 'Only the row within the 7-day window should remain.' ); - $this->assertStringContainsString( 'just-inside', $remaining[0], 'The surviving row should be the one inside the window.' ); - } - - /** - * @ticket 64696 - */ - public function test_cron_cleanup_selectively_deletes_mixed_rows(): void { - // 3 expired rows. - $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); - $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); - $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); - - // 2 recent rows. - $this->insert_collaboration_row( HOUR_IN_SECONDS ); - $this->insert_collaboration_row( HOUR_IN_SECONDS ); - - $this->assertSame( 5, $this->get_collaboration_row_count() ); - - wp_delete_old_collaboration_data(); - - $this->assertSame( 2, $this->get_collaboration_row_count(), 'Only the 2 recent rows should survive cleanup.' ); - } - - /** - * @ticket 64696 - */ - public function test_cron_cleanup_hook_is_registered(): void { - $this->assertSame( - 10, - has_action( 'wp_delete_old_collaboration_data', 'wp_delete_old_collaboration_data' ), - 'The wp_delete_old_collaboration_data action should be hooked in default-filters.php.' - ); - } - - /** - * When collaboration is disabled, the cron callback should still clean up - * stale rows and then unschedule itself so it does not continue to run. - * - * @ticket 64696 - */ - public function test_cron_cleanup_when_collaboration_disabled(): void { - global $wpdb; - - // Insert a stale sync row (older than 7 days). - $this->insert_collaboration_row( 10 * DAY_IN_SECONDS ); - - // Insert a stale awareness row (older than 60 seconds). - $wpdb->insert( - $wpdb->collaboration, - array( - 'room' => $this->get_post_room(), - 'type' => 'awareness', - 'client_id' => '42', - 'user_id' => self::$editor_id, - 'data' => wp_json_encode( array( 'cursor' => 'stale' ) ), - 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 120 ), - ), - array( '%s', '%s', '%s', '%d', '%s', '%s' ) - ); - - $this->assertSame( 1, $this->get_collaboration_row_count(), 'Should have 1 sync row before cleanup.' ); - $this->assertSame( 1, $this->get_awareness_row_count(), 'Should have 1 awareness row before cleanup.' ); - - // Schedule the cron event so we can verify it gets cleared. - wp_schedule_event( time(), 'hourly', 'wp_delete_old_collaboration_data' ); - $this->assertIsInt( wp_next_scheduled( 'wp_delete_old_collaboration_data' ), 'Cron event should be scheduled before cleanup.' ); - - // Disable collaboration. - add_filter( 'pre_option_wp_collaboration_enabled', '__return_zero' ); // __return_false fails for pre-flight hook. - - wp_delete_old_collaboration_data(); - - $this->assertSame( 0, $this->get_collaboration_row_count(), 'Stale sync rows should be deleted when collaboration is disabled.' ); - $this->assertSame( 0, $this->get_awareness_row_count(), 'Stale awareness rows should be deleted when collaboration is disabled.' ); - $this->assertFalse( wp_next_scheduled( 'wp_delete_old_collaboration_data' ), 'Cron hook should be unscheduled when collaboration is disabled.' ); - } - - /** - * Verifies that a fresh awareness row (younger than 60 seconds) survives cron cleanup. - * - * Existing tests verify expired awareness rows are deleted. This ensures - * the cleanup does not delete awareness rows that are still within the - * 60-second freshness window. - * - * @ticket 64696 - */ - public function test_cron_cleanup_preserves_fresh_awareness_rows(): void { - global $wpdb; - - // Insert a fresh awareness row (30 seconds old — well within the 60s threshold). - $wpdb->insert( - $wpdb->collaboration, - array( - 'room' => $this->get_post_room(), - 'type' => 'awareness', - 'client_id' => '1', - 'user_id' => self::$editor_id, - 'data' => wp_json_encode( array( 'cursor' => 'active' ) ), - 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 30 ), - ), - array( '%s', '%s', '%s', '%d', '%s', '%s' ) - ); - - $this->assertSame( 1, $this->get_awareness_row_count(), 'Should have 1 awareness row before cleanup.' ); - - wp_delete_old_collaboration_data(); - - $this->assertSame( 1, $this->get_awareness_row_count(), 'Fresh awareness row should survive cron cleanup.' ); - } - /* * Route registration guard tests. */ @@ -2262,40 +2061,6 @@ public function test_collaboration_expired_awareness_rows_cleaned_up(): void { $this->assertSame( 0, $post_cron_count, 'Expired awareness row should be deleted after cron cleanup.' ); } - /** - * Cron cleanup should remove expired awareness rows. - * - * @ticket 64696 - */ - public function test_cron_cleanup_deletes_expired_awareness_rows(): void { - global $wpdb; - - // Insert an awareness row older than 60 seconds. - $wpdb->insert( - $wpdb->collaboration, - array( - 'room' => $this->get_post_room(), - 'type' => 'awareness', - 'client_id' => '42', - 'user_id' => self::$editor_id, - 'data' => wp_json_encode( array( 'cursor' => 'old' ) ), - 'date_gmt' => gmdate( 'Y-m-d H:i:s', time() - 120 ), - ), - array( '%s', '%s', '%s', '%d', '%s', '%s' ) - ); - - // Insert a recent collaboration row (should survive). - $this->insert_collaboration_row( HOUR_IN_SECONDS ); - - $this->assertSame( 1, $this->get_collaboration_row_count(), 'Collaboration table should have 1 sync row.' ); - $this->assertSame( 1, $this->get_awareness_row_count(), 'Collaboration table should have 1 awareness row.' ); - - wp_delete_old_collaboration_data(); - - $this->assertSame( 1, $this->get_collaboration_row_count(), 'Only the recent sync row should survive cron cleanup.' ); - $this->assertSame( 0, $this->get_awareness_row_count(), 'Expired awareness row should be deleted after cron cleanup.' ); - } - /** * Verifies that user_id is stored as a dedicated column, * not embedded inside the data JSON blob. From c137db2953f39fa092be68e35534aa87da77a9d1 Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 16:45:43 -0400 Subject: [PATCH 5/6] Tests: Add assertion messages and use pre_option filter for db_version. Add message parameters to bare assertions for easier failure identification. Replace update_option('db_version') with pre_option_db_version filter so the value auto-restores on test teardown. --- tests/phpunit/tests/rest-api/rest-collaboration-server.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-collaboration-server.php b/tests/phpunit/tests/rest-api/rest-collaboration-server.php index 3e7f65b69e50d..dd1d87c15bd30 100644 --- a/tests/phpunit/tests/rest-api/rest-collaboration-server.php +++ b/tests/phpunit/tests/rest-api/rest-collaboration-server.php @@ -596,9 +596,8 @@ public function test_collaboration_end_cursor_is_non_negative_integer(): void { $response = $this->dispatch_collaboration( array( $this->build_room( $this->get_post_room() ) ) ); $data = $response->get_data(); - $this->assertIsInt( $data['rooms'][0]['end_cursor'] ); - // Cursor is 0 for an empty room (no rows in the table yet). - $this->assertGreaterThanOrEqual( 0, $data['rooms'][0]['end_cursor'] ); + $this->assertIsInt( $data['rooms'][0]['end_cursor'], 'end_cursor should be an integer.' ); + $this->assertGreaterThanOrEqual( 0, $data['rooms'][0]['end_cursor'], 'end_cursor should be 0 or greater for an empty room.' ); } /** From 07dd8fb8f5164e1fc6aa8aff747ee7fd55250b5a Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Tue, 5 May 2026 16:54:25 -0400 Subject: [PATCH 6/6] Tests: Add assertion messages to all multi-assertion collaboration tests. --- .../rest-api/rest-collaboration-server.php | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/tests/phpunit/tests/rest-api/rest-collaboration-server.php b/tests/phpunit/tests/rest-api/rest-collaboration-server.php index dd1d87c15bd30..d74db9c904c47 100644 --- a/tests/phpunit/tests/rest-api/rest-collaboration-server.php +++ b/tests/phpunit/tests/rest-api/rest-collaboration-server.php @@ -559,19 +559,19 @@ public function test_collaboration_response_structure(): void { $response = $this->dispatch_collaboration( array( $this->build_room( $this->get_post_room() ) ) ); - $this->assertSame( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status(), 'Response status should be 200.' ); $data = $response->get_data(); - $this->assertArrayHasKey( 'rooms', $data ); - $this->assertCount( 1, $data['rooms'] ); + $this->assertArrayHasKey( 'rooms', $data, 'Response should contain rooms key.' ); + $this->assertCount( 1, $data['rooms'], 'Response should contain exactly one room.' ); $room_data = $data['rooms'][0]; - $this->assertArrayHasKey( 'room', $room_data ); - $this->assertArrayHasKey( 'awareness', $room_data ); - $this->assertArrayHasKey( 'updates', $room_data ); - $this->assertArrayHasKey( 'end_cursor', $room_data ); - $this->assertArrayHasKey( 'total_updates', $room_data ); - $this->assertArrayHasKey( 'should_compact', $room_data ); + $this->assertArrayHasKey( 'room', $room_data, 'Room data should contain room key.' ); + $this->assertArrayHasKey( 'awareness', $room_data, 'Room data should contain awareness key.' ); + $this->assertArrayHasKey( 'updates', $room_data, 'Room data should contain updates key.' ); + $this->assertArrayHasKey( 'end_cursor', $room_data, 'Room data should contain end_cursor key.' ); + $this->assertArrayHasKey( 'total_updates', $room_data, 'Room data should contain total_updates key.' ); + $this->assertArrayHasKey( 'should_compact', $room_data, 'Room data should contain should_compact key.' ); } /** @@ -609,8 +609,8 @@ public function test_collaboration_empty_updates_returns_zero_total(): void { $response = $this->dispatch_collaboration( array( $this->build_room( $this->get_post_room() ) ) ); $data = $response->get_data(); - $this->assertSame( 0, $data['rooms'][0]['total_updates'] ); - $this->assertEmpty( $data['rooms'][0]['updates'] ); + $this->assertSame( 0, $data['rooms'][0]['total_updates'], 'total_updates should be 0 for an empty room.' ); + $this->assertEmpty( $data['rooms'][0]['updates'], 'updates should be empty for an empty room.' ); } /* @@ -646,10 +646,10 @@ public function test_collaboration_update_delivered_to_other_client(): void { $data = $response->get_data(); $updates = $data['rooms'][0]['updates']; - $this->assertNotEmpty( $updates ); + $this->assertNotEmpty( $updates, 'Updates should not be empty after a sync update.' ); $types = wp_list_pluck( $updates, 'type' ); - $this->assertContains( 'update', $types ); + $this->assertContains( 'update', $types, 'Updates should contain an entry with type update.' ); } /** @@ -819,8 +819,8 @@ public function test_collaboration_multiple_updates_in_single_request(): void { $data = $response->get_data(); $room_updates = $data['rooms'][0]['updates']; - $this->assertCount( 2, $room_updates ); - $this->assertSame( 2, $data['rooms'][0]['total_updates'] ); + $this->assertCount( 2, $room_updates, 'Should receive both updates.' ); + $this->assertSame( 2, $data['rooms'][0]['total_updates'], 'total_updates should reflect both updates.' ); } /** @@ -852,8 +852,8 @@ public function test_collaboration_update_data_preserved(): void { $data = $response->get_data(); $room_updates = $data['rooms'][0]['updates']; - $this->assertSame( 'cHJlc2VydmVkIGRhdGE=', $room_updates[0]['data'] ); - $this->assertSame( 'update', $room_updates[0]['type'] ); + $this->assertSame( 'cHJlc2VydmVkIGRhdGE=', $room_updates[0]['data'], 'Update data should be preserved verbatim.' ); + $this->assertSame( 'update', $room_updates[0]['type'], 'Update type should be preserved.' ); } /** @@ -1218,10 +1218,10 @@ public function test_collaboration_awareness_shows_multiple_clients(): void { $data = $response->get_data(); $awareness = $data['rooms'][0]['awareness']; - $this->assertArrayHasKey( '1', $awareness ); - $this->assertArrayHasKey( '2', $awareness ); - $this->assertSame( array( 'name' => 'Client 1' ), $awareness['1'] ); - $this->assertSame( array( 'name' => 'Client 2' ), $awareness['2'] ); + $this->assertArrayHasKey( '1', $awareness, 'Client 1 should be present in awareness.' ); + $this->assertArrayHasKey( '2', $awareness, 'Client 2 should be present in awareness.' ); + $this->assertSame( array( 'name' => 'Client 1' ), $awareness['1'], 'Client 1 awareness state should match.' ); + $this->assertSame( array( 'name' => 'Client 2' ), $awareness['2'], 'Client 2 awareness state should match.' ); } /** @@ -1250,8 +1250,8 @@ public function test_collaboration_awareness_updates_existing_client(): void { $awareness = $data['rooms'][0]['awareness']; // Should have exactly one entry for client 1 with updated state. - $this->assertCount( 1, $awareness ); - $this->assertSame( array( 'cursor' => 'updated' ), $awareness['1'] ); + $this->assertCount( 1, $awareness, 'Should have exactly one awareness entry.' ); + $this->assertSame( array( 'cursor' => 'updated' ), $awareness['1'], 'Awareness state should reflect the latest update.' ); } /** @@ -1376,12 +1376,12 @@ public function test_collaboration_multiple_rooms_in_single_request(): void { ) ); - $this->assertSame( 200, $response->get_status() ); + $this->assertSame( 200, $response->get_status(), 'Multi-room request should return 200.' ); $data = $response->get_data(); - $this->assertCount( 2, $data['rooms'] ); - $this->assertSame( $room1, $data['rooms'][0]['room'] ); - $this->assertSame( $room2, $data['rooms'][1]['room'] ); + $this->assertCount( 2, $data['rooms'], 'Response should contain both rooms.' ); + $this->assertSame( $room1, $data['rooms'][0]['room'], 'First room identifier should match.' ); + $this->assertSame( $room2, $data['rooms'][1]['room'], 'Second room identifier should match.' ); } /** @@ -1416,11 +1416,8 @@ public function test_collaboration_rooms_are_isolated(): void { $data = $response->get_data(); - // Room 1 should have the update. - $this->assertNotEmpty( $data['rooms'][0]['updates'] ); - - // Room 2 should have no updates. - $this->assertEmpty( $data['rooms'][1]['updates'] ); + $this->assertNotEmpty( $data['rooms'][0]['updates'], 'Room 1 should have the update.' ); + $this->assertEmpty( $data['rooms'][1]['updates'], 'Room 2 should have no updates.' ); } /* @@ -1902,8 +1899,8 @@ public function test_collaboration_awareness_preserved_across_separate_upserts() $this->assertArrayHasKey( '1', $awareness, 'Client 1 awareness should be present.' ); $this->assertArrayHasKey( '2', $awareness, 'Client 2 awareness should be present.' ); - $this->assertSame( array( 'cursor' => 'pos-a' ), $awareness['1'] ); - $this->assertSame( array( 'cursor' => 'pos-b' ), $awareness['2'] ); + $this->assertSame( array( 'cursor' => 'pos-a' ), $awareness['1'], 'Client 1 awareness state should be preserved.' ); + $this->assertSame( array( 'cursor' => 'pos-b' ), $awareness['2'], 'Client 2 awareness state should be preserved.' ); } /** @@ -2445,9 +2442,9 @@ public function test_collaboration_oversized_body_rejected(): void { $result = $server->validate_request( $request ); - $this->assertWPError( $result ); - $this->assertSame( 'rest_collaboration_body_too_large', $result->get_error_code() ); - $this->assertSame( 413, $result->get_error_data()['status'] ); + $this->assertWPError( $result, 'Oversized body should return a WP_Error.' ); + $this->assertSame( 'rest_collaboration_body_too_large', $result->get_error_code(), 'Error code should indicate body too large.' ); + $this->assertSame( 413, $result->get_error_data()['status'], 'HTTP status should be 413.' ); } /**