diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 10a556d81..8d70ab914 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,4 +1,4 @@ Contributing code to SyTest =========================== -sytest follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst +sytest follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md diff --git a/lib/SyTest/Federation/Client.pm b/lib/SyTest/Federation/Client.pm index 12be421ff..67bfa8689 100644 --- a/lib/SyTest/Federation/Client.pm +++ b/lib/SyTest/Federation/Client.pm @@ -16,8 +16,7 @@ use SyTest::Assertions qw( :all ); use URI::Escape qw( uri_escape ); use constant SUPPORTED_ROOM_VERSIONS => [qw( - 1 2 3 4 5 - org.matrix.msc2260 + 1 2 3 4 5 6 )]; sub configure diff --git a/lib/SyTest/Federation/Datastore.pm b/lib/SyTest/Federation/Datastore.pm index 2a3d6bb93..aedd71a71 100644 --- a/lib/SyTest/Federation/Datastore.pm +++ b/lib/SyTest/Federation/Datastore.pm @@ -268,10 +268,13 @@ sub get_backfill_events my $event = eval { $self->get_event( $id ) } or next; + my $room = $self->get_room( $event->{room_id} ) or + croak "Unknown room $event->{room_id}"; + push @events, $event; push @event_ids, grep { !$exclude{$_} } - map { $_->[0] } @{ $event->{prev_events} }; + @{ $room->event_ids_from_refs( $event->{prev_events} ) }; # Don't include this event if we encounter it again $exclude{$id} = 1; diff --git a/lib/SyTest/Homeserver/Dendrite.pm b/lib/SyTest/Homeserver/Dendrite.pm index 8971ac8ae..4057b49a4 100644 --- a/lib/SyTest/Homeserver/Dendrite.pm +++ b/lib/SyTest/Homeserver/Dendrite.pm @@ -130,8 +130,8 @@ sub _get_config output_room_event => 'roomserverOutput', output_client_data => 'clientapiOutput', user_updates => 'userUpdates', - output_typing_event => 'typingServerOutput', - user_updates => 'userUpdates', + output_typing_event => 'eduServerTypingOutput', + output_send_to_device_event => 'eduServerSendToDeviceOutput', }, }, diff --git a/lib/SyTest/Homeserver/Synapse.pm b/lib/SyTest/Homeserver/Synapse.pm index 4c4d69a3a..1b111319d 100644 --- a/lib/SyTest/Homeserver/Synapse.pm +++ b/lib/SyTest/Homeserver/Synapse.pm @@ -286,6 +286,17 @@ sub start }, ) : (), + instance_map => { + "frontend_proxy1" => { + host => "$bind_host", + port => $self->{ports}{frontend_proxy}, + }, + }, + + stream_writers => { + events => $self->{redis_host} ne '' ? "frontend_proxy1" : "master", + }, + # We use a high limit so the limit is never reached, but enabling the # limit ensures that the code paths get hit. This helps testing the # feature with worker mode. @@ -997,6 +1008,7 @@ sub wrap_synapse_command { my $frontend_proxy_config_path = $self->write_yaml_file( "frontend_proxy.yaml" => { "worker_app" => "synapse.app.frontend_proxy", + "worker_name" => "frontend_proxy1", "worker_pid_file" => "$hsdir/frontend_proxy.pid", "worker_log_config" => $self->configure_logger("frontend_proxy"), "worker_replication_host" => "$bind_host", @@ -1006,7 +1018,7 @@ sub wrap_synapse_command "worker_listeners" => [ { type => "http", - resources => [{ names => ["client"] }], + resources => [{ names => ["client", "replication"] }], port => $self->{ports}{frontend_proxy}, bind_address => $bind_host, }, diff --git a/tests/00expect_http_fail.pl b/tests/00expect_http_fail.pl index 2e7ccdf7c..fc3cda3e9 100644 --- a/tests/00expect_http_fail.pl +++ b/tests/00expect_http_fail.pl @@ -172,3 +172,13 @@ sub expect_m_not_found ); } push @EXPORT, qw( expect_m_not_found ); + + +sub expect_m_bad_json +{ + my $f = shift; + return expect_matrix_error( + $f, 400, 'M_BAD_JSON', + ); +} +push @EXPORT, qw( expect_m_bad_json ); diff --git a/tests/10apidoc/13ui-auth.pl b/tests/10apidoc/13ui-auth.pl index 40d831a79..bb08bb796 100644 --- a/tests/10apidoc/13ui-auth.pl +++ b/tests/10apidoc/13ui-auth.pl @@ -80,8 +80,8 @@ sub make_ticket_request assert_json_list $body->{flows}; # Note that this uses the unstable value. - die "org.matrix.login.sso was not listed" unless - any { $_->{stages}[0] eq "org.matrix.login.sso" } @{ $body->{flows} }; + die "m.login.sso was not listed" unless + any { $_->{stages}[0] eq "m.login.sso" } @{ $body->{flows} }; Future->done( 1 ); }); diff --git a/tests/10apidoc/33room-members.pl b/tests/10apidoc/33room-members.pl index 12339f002..1fc8b22ed 100644 --- a/tests/10apidoc/33room-members.pl +++ b/tests/10apidoc/33room-members.pl @@ -496,6 +496,8 @@ sub matrix_create_and_join_room } @local_members, ) })->then( sub { + log_if_fail "Created room_id=$room_id"; + Future->done( $room_id, ( $with_alias ? ( $room_alias_fullname ) : () ) ); @@ -660,7 +662,7 @@ sub matrix_join_room_synced matrix_do_and_wait_for_sync( $user, do => sub { - matrix_join_room( $user, $room_id_or_alias, %params ); + retry_until_success { matrix_join_room( $user, $room_id_or_alias, %params ) } }, check => sub { exists $_[0]->{rooms}{join}{$_[1]} }, ); diff --git a/tests/10apidoc/36room-levels.pl b/tests/10apidoc/36room-levels.pl index 8ffed904c..3c98a4f08 100644 --- a/tests/10apidoc/36room-levels.pl +++ b/tests/10apidoc/36room-levels.pl @@ -139,7 +139,7 @@ sub matrix_change_room_power_levels my ( $levels ) = @_; $func->( $levels ); - matrix_put_room_state( $user, $room_id, type => "m.room.power_levels", + matrix_put_room_state_synced( $user, $room_id, type => "m.room.power_levels", content => $levels, ); }); diff --git a/tests/30rooms/05aliases.pl b/tests/30rooms/05aliases.pl index 2f3cb5bf2..e3efcab3b 100644 --- a/tests/30rooms/05aliases.pl +++ b/tests/30rooms/05aliases.pl @@ -219,15 +219,19 @@ $_[0]->{events}->{'m.room.aliases'} = 50; }, )->then( sub { - matrix_get_room_state( $creator, $room_id, - type => "m.room.power_levels", - ); - })->then( sub { - my ( $body ) = @_; - log_if_fail "power levels", $body; + retry_until_success { + matrix_get_room_state( $creator, $room_id, + type => "m.room.power_levels", + )->then( sub { + my ( $body ) = @_; + log_if_fail "power levels", $body; - assert_eq( $body->{events}->{'m.room.aliases'}, 50 ); + assert_eq( $body->{events}->{'m.room.aliases'}, 50 ); + Future->done( 1 ) + }) + } + })->then( sub { do_request_json_for( $other_user, method => "PUT", uri => "/r0/directory/room/$alias", @@ -364,7 +368,7 @@ sub _test_can_create_and_delete_alias { content => { room_id => $room_id }, ) })->then( sub { - matrix_put_room_state( $creator, $room_id, + matrix_put_room_state_synced( $creator, $room_id, type => "m.room.canonical_alias", content => { alias => $room_alias } ) @@ -376,15 +380,17 @@ sub _test_can_create_and_delete_alias { content => {}, ) })->then( sub { - matrix_get_room_state( $creator, $room_id, - type => "m.room.canonical_alias", - ) - })->then( sub { - my ( $body ) = @_; + retry_until_success { + matrix_get_room_state( $creator, $room_id, + type => "m.room.canonical_alias", + )->then( sub { + my ( $body ) = @_; - not defined $body->{alias} or die "Expected canonical alias to be empty"; + not defined $body->{alias} or die "Expected canonical alias to be empty"; - Future->done( 1 ); + Future->done( 1 ); + }) + } }) }; diff --git a/tests/30rooms/06invite.pl b/tests/30rooms/06invite.pl index d5645fe00..798325abe 100644 --- a/tests/30rooms/06invite.pl +++ b/tests/30rooms/06invite.pl @@ -137,7 +137,7 @@ sub invited_user_can_reject_invite matrix_invite_user_to_room( $creator, $invitee, $room_id ) ->then( sub { - matrix_leave_room( $invitee, $room_id ) + matrix_leave_room_synced( $invitee, $room_id ) })->then( sub { matrix_get_room_state( $creator, $room_id, type => "m.room.member", diff --git a/tests/30rooms/07ban.pl b/tests/30rooms/07ban.pl index 515af2910..0bf7b9cb2 100644 --- a/tests/30rooms/07ban.pl +++ b/tests/30rooms/07ban.pl @@ -16,15 +16,19 @@ content => { user_id => $banned_user->user_id, reason => "testing" }, )->then( sub { - matrix_get_room_state( $creator, $room_id, - type => "m.room.member", - state_key => $banned_user->user_id, - ) + retry_until_success { + matrix_get_room_state( $creator, $room_id, + type => "m.room.member", + state_key => $banned_user->user_id, + )->then( sub { + my ( $body ) = @_; + $body->{membership} eq "ban" or + die "Expected banned user membership to be 'ban'"; + + Future->done( 1 ) + }) + } })->then( sub { - my ( $body ) = @_; - $body->{membership} eq "ban" or - die "Expected banned user membership to be 'ban'"; - matrix_join_room( $banned_user, $room_id ) ->main::expect_http_403; # Must be unbanned first })->then( sub { @@ -75,15 +79,19 @@ content => { user_id => $banned_user->user_id, reason => "testing" }, )->then( sub { - matrix_get_room_state( $creator, $room_id, - type => "m.room.member", - state_key => $banned_user->user_id, - ) + retry_until_success { + matrix_get_room_state( $creator, $room_id, + type => "m.room.member", + state_key => $banned_user->user_id, + )->then( sub { + my ( $body ) = @_; + $body->{membership} eq "ban" or + die "Expected banned user membership to be 'ban'"; + + Future->done( 1 ) + }) + } })->then( sub { - my ( $body ) = @_; - $body->{membership} eq "ban" or - die "Expected banned user membership to be 'ban'"; - repeat_until_true { matrix_get_room_state( $banned_user, $room_id, type => "m.room.member", diff --git a/tests/30rooms/08levels.pl b/tests/30rooms/08levels.pl index cc18ff15a..9a14769ff 100644 --- a/tests/30rooms/08levels.pl +++ b/tests/30rooms/08levels.pl @@ -7,6 +7,8 @@ sub lockeddown_room_fixture { + my ( %options ) = @_; + fixture( requires => [ $creator_fixture, $user_fixture, qw( can_change_power_levels ) ], @@ -14,7 +16,7 @@ sub lockeddown_room_fixture setup => sub { my ( $creator, $test_user ) = @_; - matrix_create_and_join_room( [ $creator, $test_user ] ) + matrix_create_and_join_room( [ $creator, $test_user ], %options ) ->then( sub { my ( $room_id ) = @_; @@ -154,6 +156,27 @@ sub test_powerlevel $levels->{$levelname} = 10000000; })->main::expect_http_403 - })->SyTest::pass_on_done( "Fails at setting 75" ); + })->SyTest::pass_on_done( "Fails at setting 10000000" ); }; } + +multi_test "Users cannot set notifications powerlevel higher than their own", + requires => [ $creator_fixture, $user_fixture, lockeddown_room_fixture( room_version => "6" ), + qw( can_change_power_levels )], + + do => sub { + my ( $user, undef, $room_id ) = @_; + + matrix_change_room_power_levels( $user, $room_id, sub { + my ( $levels ) = @_; + + $levels->{notifications}{room} = 25; + })->SyTest::pass_on_done( "Succeeds at setting 25" ) + ->then( sub { + matrix_change_room_power_levels( $user, $room_id, sub { + my ( $levels ) = @_; + + $levels->{notifications}{room} = 10000000; + })->main::expect_http_403 + })->SyTest::pass_on_done( "Fails at setting 10000000" ); + }; diff --git a/tests/30rooms/12thirdpartyinvite.pl b/tests/30rooms/12thirdpartyinvite.pl index 2b68b672c..19a5db28a 100644 --- a/tests/30rooms/12thirdpartyinvite.pl +++ b/tests/30rooms/12thirdpartyinvite.pl @@ -78,16 +78,18 @@ }, ); })->then( sub { - matrix_get_room_state( $inviter, $room_id, - type => "m.room.member", - state_key => $invitee_mxid, - ); - })->on_done( sub { - my ( $body ) = @_; - - log_if_fail "Body", $body; - assert_eq( $body->{membership}, "invite", - 'invited user membership' ); + retry_until_success { + matrix_get_room_state( $inviter, $room_id, + type => "m.room.member", + state_key => $invitee_mxid, + )->on_done( sub { + my ( $body ) = @_; + + log_if_fail "Body", $body; + assert_eq( $body->{membership}, "invite", + 'invited user membership' ); + }) + } }); }; @@ -236,13 +238,17 @@ sub can_invite_unbound_3pid log_if_fail "m.room.member invite", $body; assert_eq( $body->{third_party_invite}{display_name}, 'Bob', 'invite display name' ); - matrix_join_room( $invitee, $room_id ) + retry_until_success { + matrix_join_room( $invitee, $room_id ) + } })->then( sub { - matrix_get_room_state( $inviter, $room_id, - type => "m.room.member", - state_key => $invitee->user_id, - ) - })->followed_by( assert_membership( "join" ) ); + retry_until_success { + matrix_get_room_state( $inviter, $room_id, + type => "m.room.member", + state_key => $invitee->user_id, + )->followed_by( assert_membership( "join" ) ) + } + }) } test "Can invite unbound 3pid over federation with users from both servers", @@ -289,7 +295,9 @@ sub can_invite_unbound_3pid log_if_fail "m.room.member invite", $body; assert_eq( $body->{third_party_invite}{display_name}, 'Bob', 'invite display name' ); - matrix_join_room( $invitee, $room_id ) + retry_until_success { + matrix_join_room( $invitee, $room_id ) + } })->then( sub { await_event_for( $inviter, filter => sub { my ( $event ) = @_; @@ -301,11 +309,13 @@ sub can_invite_unbound_3pid return 1; }) })->then( sub { - matrix_get_room_state( $inviter, $room_id, - type => "m.room.member", - state_key => $invitee->user_id, - ) - })->followed_by( assert_membership( "join" ) ); + retry_until_success { + matrix_get_room_state( $inviter, $room_id, + type => "m.room.member", + state_key => $invitee->user_id, + )->followed_by( assert_membership( "join" ) ) + } + }); }; test "Can accept unbound 3pid invite after inviter leaves", diff --git a/tests/30rooms/60version_upgrade.pl b/tests/30rooms/60version_upgrade.pl index 6fa88605a..8f4c58ee7 100644 --- a/tests/30rooms/60version_upgrade.pl +++ b/tests/30rooms/60version_upgrade.pl @@ -674,7 +674,7 @@ sub upgrade_room_synced { ); })->then( sub { # alias 1 is the canonical alias. - matrix_put_room_state( $creator, $room_id, + matrix_put_room_state_synced( $creator, $room_id, type => "m.room.canonical_alias", content => { alias => $room_alias_1, @@ -757,7 +757,7 @@ sub upgrade_room_synced { $creator, $remote_user, $room_id )->then( sub { # Have the remote user join the room - matrix_join_room( $remote_user, $room_id ); + matrix_join_room_synced( $remote_user, $room_id ); })->then( sub { # Have the remote user add an alias do_request_json_for( @@ -781,20 +781,24 @@ sub upgrade_room_synced { ); })->then( sub { # Have the remote user join the upgraded room - matrix_join_room( $remote_user, $new_room_id ); + matrix_join_room_synced( $remote_user, $new_room_id ); })->then( sub { # Check that the remote alias points to the new room id - do_request_json_for( - $remote_user, - method => "GET", - uri => "/r0/directory/room/$remote_room_alias", - ); - })->then( sub { - my ( $body ) = @_; + retry_until_success { + do_request_json_for( + $remote_user, + method => "GET", + uri => "/r0/directory/room/$remote_room_alias", + )->then( sub { + my ( $body ) = @_; - assert_eq( $body->{room_id}, $new_room_id, "room_id for remote alias" ); + log_if_fail "Got room ID for alias", $body->{room_id}; - Future->done(1); + assert_eq( $body->{room_id}, $new_room_id, "room_id for remote alias" ); + + Future->done(1); + }) + } }); }; diff --git a/tests/31sync/06state.pl b/tests/31sync/06state.pl index 5d508ec27..821ffbfaa 100644 --- a/tests/31sync/06state.pl +++ b/tests/31sync/06state.pl @@ -774,7 +774,7 @@ qw( can_sync ) ], # sending 50 messages can take a while - timeout => 20000, + timeout => 20, check => sub { my ( $creator, $syncer, $invitee ) = @_; @@ -834,7 +834,7 @@ qw( can_sync ) ], # sending 50 messages can take a while - timeout => 20000, + timeout => 20, check => sub { my ( $creator, $syncer, $invitee ) = @_; diff --git a/tests/31sync/15lazy-members.pl b/tests/31sync/15lazy-members.pl index 3c9925e55..bbcf7c50a 100644 --- a/tests/31sync/15lazy-members.pl +++ b/tests/31sync/15lazy-members.pl @@ -286,7 +286,7 @@ test "Gapped incremental syncs include all state changes", # sending 50 messages can take a while - timeout => 20000, + timeout => 20, requires => [ local_user_fixtures( 4 ), qw( can_sync ) ], diff --git a/tests/32room-versions.pl b/tests/32room-versions.pl index 5ea451f5e..0aa66a914 100644 --- a/tests/32room-versions.pl +++ b/tests/32room-versions.pl @@ -1,7 +1,7 @@ use URI::Escape qw( uri_escape ); # We test that some basic functionality works across all room versions -foreach my $version ( qw ( 1 2 3 4 5 ) ) { +foreach my $version ( qw ( 1 2 3 4 5 6 ) ) { multi_test "User can create and send/receive messages in a room with version $version", requires => [ local_user_fixture() ], diff --git a/tests/41end-to-end-keys/06-device-lists.pl b/tests/41end-to-end-keys/06-device-lists.pl index b47ee5902..b7995f7d6 100644 --- a/tests/41end-to-end-keys/06-device-lists.pl +++ b/tests/41end-to-end-keys/06-device-lists.pl @@ -180,12 +180,12 @@ sub sync_until_user_in_device_list my $room_id; - matrix_create_room( $user1 )->then( sub { + matrix_create_room_synced( $user1 )->then( sub { ( $room_id ) = @_; - matrix_invite_user_to_room( $user1, $user2, $room_id ) + matrix_invite_user_to_room_synced( $user1, $user2, $room_id ) })->then( sub { - matrix_join_room( $user2, $room_id ); + matrix_join_room_synced( $user2, $room_id ); })->then( sub { matrix_sync( $user1 ); })->then( sub { @@ -236,12 +236,12 @@ sub sync_until_user_in_device_list my $room_id; - matrix_create_room( $user1 )->then( sub { + matrix_create_room_synced( $user1 )->then( sub { ( $room_id ) = @_; - matrix_invite_user_to_room( $user1, $user2, $room_id ) + matrix_invite_user_to_room_synced( $user1, $user2, $room_id ) })->then( sub { - matrix_join_room( $user2, $room_id ); + matrix_join_room_synced( $user2, $room_id ); })->then( sub { matrix_sync( $user1 ); })->then( sub { @@ -295,12 +295,12 @@ sub sync_until_user_in_device_list my $room_id; - matrix_create_room( $creator, + matrix_create_room_synced( $creator, invite => [ $remote_leaver->user_id ], )->then( sub { ( $room_id ) = @_; - matrix_join_room( $remote_leaver, $room_id ); + matrix_join_room_synced( $remote_leaver, $room_id ); })->then( sub { matrix_sync( $creator ); })->then( sub { @@ -344,18 +344,18 @@ sub sync_until_user_in_device_list my $room_id; - matrix_create_room( $creator )->then( sub { + matrix_create_room_synced( $creator )->then( sub { ( $room_id ) = @_; matrix_sync( $creator ); })->then( sub { - matrix_invite_user_to_room( $creator, $remote_leaver, $room_id ) + matrix_invite_user_to_room_synced( $creator, $remote_leaver, $room_id ) })->then( sub { - matrix_join_room( $remote_leaver, $room_id ); + matrix_join_room_synced( $remote_leaver, $room_id ); })->then( sub { - matrix_invite_user_to_room( $creator, $remote2, $room_id ) + matrix_invite_user_to_room_synced( $creator, $remote2, $room_id ) })->then( sub { - matrix_join_room( $remote2, $room_id ); + matrix_join_room_synced( $remote2, $room_id ); })->then( sub { log_if_fail "Created and joined room"; @@ -442,10 +442,10 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token, $to_token ); - matrix_create_room( $user1 )->then( sub { + matrix_create_room_synced( $user1 )->then( sub { ( $room_id ) = @_; - matrix_join_room( $user2, $room_id ); + matrix_join_room_synced( $user2, $room_id ); })->then( sub { matrix_sync( $user1 ); })->then( sub { @@ -504,7 +504,7 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token, $to_token ); - matrix_create_room( $user1 )->then( sub { + matrix_create_room_synced( $user1 )->then( sub { ( $room_id ) = @_; matrix_sync( $user1 ); @@ -556,12 +556,12 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token, $to_token ); - matrix_create_room( $creator, + matrix_create_room_synced( $creator, invite => [ $remote_leaver->user_id ], )->then( sub { ( $room_id ) = @_; - matrix_join_room( $remote_leaver, $room_id ); + matrix_join_room_synced( $remote_leaver, $room_id ); })->then( sub { matrix_sync( $creator ); })->then( sub { @@ -618,12 +618,12 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token ); - matrix_create_room( $creator, + matrix_create_room_synced( $creator, invite => [ $other_user->user_id ], )->then( sub { ( $room_id ) = @_; - matrix_join_room( $other_user, $room_id ); + matrix_join_room_synced( $other_user, $room_id ); })->then( sub { matrix_sync( $creator ); })->then( sub { @@ -674,12 +674,12 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token ); - matrix_create_room( $creator, + matrix_create_room_synced( $creator, invite => [ $other_user->user_id ], )->then( sub { ( $room_id ) = @_; - matrix_join_room( $other_user, $room_id ); + matrix_join_room_synced( $other_user, $room_id ); })->then( sub { matrix_sync( $creator ); })->then( sub { @@ -729,13 +729,13 @@ sub sync_until_user_in_device_list my ( $room_id, $from_token, $to_token ); - matrix_create_room( $creator, + matrix_create_room_synced( $creator, invite => [ $remote_user->user_id ], preset => "private_chat", # Allow default PL users to invite others )->then( sub { ( $room_id ) = @_; - matrix_join_room( $remote_user, $room_id ); + matrix_join_room_synced( $remote_user, $room_id ); })->then( sub { matrix_sync( $creator ); })->then( sub { @@ -757,7 +757,7 @@ sub sync_until_user_in_device_list matrix_invite_user_to_room( $remote_user, $creator, $room_id ) } })->then( sub { - matrix_join_room( $creator, $room_id ); + matrix_join_room_synced( $creator, $room_id ) })->then( sub { sync_until_user_in_device_list( $creator, $remote_user, msg => 'Second body', diff --git a/tests/48admin.pl b/tests/48admin.pl index 838db834e..268dcd7cc 100644 --- a/tests/48admin.pl +++ b/tests/48admin.pl @@ -387,6 +387,8 @@ sub await_purge_complete { $new_room_id = $body->{new_room_id}; + log_if_fail "Shutdown room, new room ID", $new_room_id; + matrix_send_room_text_message( $user, $room_id, body => "Hello" ) ->main::expect_http_403; })->SyTest::pass_on_done( "User cannot post in room" ) @@ -412,12 +414,13 @@ sub await_purge_complete { pass( "Aliases were repointed" ); - matrix_get_room_state( $user, $new_room_id, - type => "m.room.name", - state_key => "", - ); - })->SyTest::pass_on_done( "User was added to new room" ) - ->then( sub { + retry_until_success { + matrix_get_room_state( $user, $new_room_id, + type => "m.room.name", + state_key => "", + )->SyTest::pass_on_done( "User was added to new room" ) + } + })->then( sub { matrix_send_room_text_message( $user, $new_room_id, body => "Hello" ) ->main::expect_http_403; })->SyTest::pass_on_done( "User cannot send into new room" ); diff --git a/tests/50federation/30room-join.pl b/tests/50federation/30room-join.pl index a6dab2d28..b98461e99 100644 --- a/tests/50federation/30room-join.pl +++ b/tests/50federation/30room-join.pl @@ -1061,3 +1061,62 @@ sub assert_is_valid_pdu { }), ) }; + +# A homeserver receiving a `send_join` request for a room version 6 room with +# a bad JSON value (e.g. a float) should reject the request. +# +# To test this we need to: +# * Send a successful `make_join` request. +# * Add a "bad" value into the returned prototype event. +# * Make a request to `send_join`. +# * Check that the response is M_BAD_JSON. +test "Inbound: send_join rejects invalid JSON for room version 6", + requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, + local_user_and_room_fixtures( room_opts => { room_version => "6" } ), + federation_user_id_fixture() ], + + do => sub { + my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; + my $first_home_server = $creator->server_name; + + my $local_server_name = $outbound_client->server_name; + my $datastore = $inbound_server->datastore; + + $outbound_client->do_request_json( + method => "GET", + hostname => $first_home_server, + uri => "/v1/make_join/$room_id/$user_id", + params => { + ver => ["6"], + }, + )->then( sub { + my ( $body ) = @_; + + log_if_fail "make_join body", $body; + + my $protoevent = $body->{event}; + + # It is assumed that the make_join response is sane, other tests ensure + # this behavior. + + my %event = ( + ( map { $_ => $protoevent->{$_} } qw( + auth_events content depth prev_events room_id sender + state_key type ) ), + + origin => $local_server_name, + origin_server_ts => $inbound_server->time_ms, + ); + # Insert a "bad" value into the send join, in this case a float. + ${event}{content}{bad_val} = 1.1; + + $datastore->sign_event( \%event ); + + $outbound_client->do_request_json( + method => "PUT", + hostname => $first_home_server, + uri => "/v2/send_join/$room_id/xxx", + content => \%event, + ) + })->main::expect_m_bad_json; + }; diff --git a/tests/50federation/31room-send.pl b/tests/50federation/31room-send.pl index 945b16d1e..fe2d528f1 100644 --- a/tests/50federation/31room-send.pl +++ b/tests/50federation/31room-send.pl @@ -154,8 +154,8 @@ my $filter = '{"types":["m.room.message"]}'; - matrix_invite_user_to_room( $local_user, $federated_user, $room_id )->then( sub { - matrix_join_room( $federated_user, $room_id ) + matrix_invite_user_to_room_synced( $local_user, $federated_user, $room_id )->then( sub { + matrix_join_room_synced( $federated_user, $room_id ) })->then( sub { my $now_ms = int( time() * 1000 ); diff --git a/tests/50federation/33room-get-missing-events.pl b/tests/50federation/33room-get-missing-events.pl index dcaff1de6..0d476c1fe 100644 --- a/tests/50federation/33room-get-missing-events.pl +++ b/tests/50federation/33room-get-missing-events.pl @@ -1,7 +1,6 @@ test "Outbound federation can request missing events", requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, local_user_and_room_fixtures( - user_opts => { with_events => 1 }, room_opts => { room_version => "1" }, ), federation_user_id_fixture() ], @@ -10,10 +9,9 @@ my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id ) = @_; my $first_home_server = $creator->server_name; - my $local_server_name = $inbound_server->server_name; my $datastore = $inbound_server->datastore; - my $missing_event; + my $missing_event_id; $outbound_client->join_room( server_name => $first_home_server, @@ -27,7 +25,7 @@ my $latest_event = $room->get_current_state_event( "m.room.member", $user_id ); # Generate but don't send an event - $missing_event = $room->create_and_insert_event( + my $missing_event = $room->create_and_insert_event( type => "m.room.message", sender => $user_id, @@ -35,6 +33,7 @@ body => "Message 1", }, ); + $missing_event_id = $room->id_for_event( $missing_event ); # Generate another one and do send it so it will refer to the # previous in its prev_events field @@ -43,9 +42,7 @@ # This would be done by $room->create_and_insert_event anyway but lets be # sure for this test - prev_events => [ - [ $missing_event->{event_id}, $missing_event->{hashes} ], - ], + prev_events => $room->make_event_refs( $missing_event ), sender => $user_id, content => { @@ -65,13 +62,13 @@ assert_json_list( my $earliest = $body->{earliest_events} ); @$earliest == 1 or die "Expected a single 'earliest_event' ID"; - assert_eq( $earliest->[0], $latest_event->{event_id}, + assert_eq( $earliest->[0], $room->id_for_event( $latest_event ), 'earliest_events[0]' ); assert_json_list( my $latest = $body->{latest_events} ); @$latest == 1 or die "Expected a single 'latest_events' ID"; - assert_eq( $latest->[0], $sent_event->{event_id}, + assert_eq( $latest->[0], $room->id_for_event( $sent_event ), 'latest_events[0]' ); my @events = $datastore->get_backfill_events( @@ -99,7 +96,7 @@ check => sub { my ( $event ) = @_; $event->{type} eq "m.room.message" && - $event->{event_id} eq $missing_event->{event_id}; + $event->{event_id} eq $missing_event_id; }, ); }); @@ -413,3 +410,123 @@ sub sytest_user_and_room_fixture { Future->done; }); }; + +# A homeserver receiving a response from `get_missing_events` for a version 6 +# room with a bad JSON value (e.g. a float) should discard the bad data. +# +# To test this we need to: +# * Add an event with "bad" data into the room history, but don't send it. +# * Add a "good" event into the room history and send it. +# * The homeserver attempts to get the missing event (with the bad data). +# * Ensure that fetching the event results in an error. +test "Outbound federation will ignore a missing event with bad JSON for room version 6", + requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, + federated_rooms_fixture( room_opts => { room_version => "6" } ) ], + + do => sub { + my ( $outbound_client, $inbound_server, $creator, $user_id, @rooms ) = @_; + + my $room = @rooms[0]; + my $room_id = $room->{room_id}; + my $first_home_server = $creator->server_name; + + my $datastore = $inbound_server->datastore; + + # TODO: We happen to know the latest event in the server should be my + # m.room.member state event, but that's a bit fragile + my $latest_event = $room->get_current_state_event( "m.room.member", $user_id ); + + log_if_fail "Latest event", $latest_event; + + # Generate but don't send an event + my $missing_event = $room->create_and_insert_event( + type => "m.room.message", + + sender => $user_id, + content => { + body => "Message 1", + # Insert a bad value here so that this event cannot be fetched. + bad_val => 1.1, + }, + ); + + log_if_fail "Missing event", $missing_event; + + # Generate another one and do send it so it will refer to the + # previous in its prev_events field + my $sent_event = $room->create_and_insert_event( + type => "m.room.message", + + # This would be done by $room->create_and_insert_event anyway but lets be + # sure for this test + prev_events => $room->make_event_refs( $missing_event ), + + sender => $user_id, + content => { + body => "Message 2", + }, + ); + my $sent_event_id = $room->id_for_event( $sent_event ); + + log_if_fail "Sent event", $sent_event; + + Future->needs_all( + $inbound_server->await_request_get_missing_events( $room_id ) + ->then( sub { + my ( $req ) = @_; + my $body = $req->body_from_json; + + log_if_fail "Body", $body; + + assert_json_keys( $body, qw( earliest_events latest_events limit )); + # TODO: min_depth but I have no idea what it does + + assert_json_list( my $earliest = $body->{earliest_events} ); + @$earliest == 1 or + die "Expected a single 'earliest_event' ID"; + assert_eq( $earliest->[0], $room->id_for_event( $latest_event ), + 'earliest_events[0]' ); + + assert_json_list( my $latest = $body->{latest_events} ); + @$latest == 1 or + die "Expected a single 'latest_events' ID"; + assert_eq( $latest->[0], $sent_event_id, + 'latest_events[0]' ); + + my @events = $datastore->get_backfill_events( + start_at => $latest, + stop_before => $earliest, + limit => $body->{limit}, + ); + + log_if_fail "Backfilling", @events; + + $req->respond_json( { + events => \@events, + } ); + + Future->done; + }), + + # Can't use send_event here because that checks none were rejected. + $outbound_client->send_transaction( + destination => $first_home_server, + pdus => [ $sent_event ], + )->then( sub { + my ( $body ) = @_; + + log_if_fail "Send response", $body; + + assert_json_keys( $body, 'pdus' ); + # 'pdus' is a map from event id to error details. + my $pdus = $body->{pdus}; + + # Sending the event fails since fetching the event results in + # invalid JSON, thus we expect an error for the sent PDU. + assert_json_keys( $pdus, $sent_event_id ); + assert_json_keys( $pdus->{$sent_event_id}, qw( error ) ); + + Future->done; + }), + ); + }; diff --git a/tests/50federation/34room-backfill.pl b/tests/50federation/34room-backfill.pl index 1ee3e28a8..fd3b24d99 100644 --- a/tests/50federation/34room-backfill.pl +++ b/tests/50federation/34room-backfill.pl @@ -11,7 +11,7 @@ my $local_server_name = $inbound_server->server_name; my $datastore = $inbound_server->datastore; - my $room_alias = "#50fed-31backfill:$local_server_name"; + my $room_alias = "#50fed-34backfill:$local_server_name"; my $room = $datastore->create_room( creator => $creator_id, @@ -447,3 +447,103 @@ }); }); }; + +# A homeserver receiving a response from `backfill` for a version 6 room with a +# bad JSON value (e.g. a float) should discard the bad data. +# +# To test this we need to: +# * Create a room. +# * Add "bad" data into the room history. +# * Join the room. +# * Attempt to backfill the room history. +# * Ensure that the "bad" event will be discarded. +test "Outbound federation rejects backfill containing invalid JSON for events in room version 6", + requires => [ local_user_fixture(), $main::INBOUND_SERVER, federation_user_id_fixture() ], + + do => sub { + my ( $user, $inbound_server, $creator_id ) = @_; + + my $local_server_name = $inbound_server->server_name; + my $datastore = $inbound_server->datastore; + + my $room_alias = "#50fed-34backfill-bad-json:$local_server_name"; + + my $room = $datastore->create_room( + creator => $creator_id, + alias => $room_alias, + room_version => "6", + ); + + # Create some past messages to backfill from + $room->create_and_insert_event( + type => "m.room.message", + + sender => $creator_id, + content => { + msgtype => "m.text", + body => "Message here", + # Insert a "bad" value into the event, in this case a float. + bad_val => 1.1, + }, + ); + + Future->needs_all( + $inbound_server->await_request_backfill( $room->room_id )->then( sub { + my ( $req ) = @_; + + # The helpfully-named 'v' parameter gives the "versions", i.e. the + # event IDs to start the backfill walk from. This can just be used + # in the 'start_at' list for $datastore->get_backfill_events. + # This would typically be an event ID the requesting server is + # aware exists but has not yet seen, such as one listed in a + # prev_events or auth_events list. + my $v = $req->query_param( 'v' ); + + my $limit = $req->query_param( 'limit' ); + + my @events = $datastore->get_backfill_events( + start_at => [ $v ], + limit => $limit, + ); + + log_if_fail "Responding with JSON", @events; + + $req->respond_json( { + origin => $inbound_server->server_name, + origin_server_ts => $inbound_server->time_ms, + pdus => \@events, + } ); + + Future->done; + }), + + matrix_join_room_synced( $user, $room_alias )->then( sub { + my ($room_id) = @_; + + matrix_get_room_messages($user, $room_id, + limit => 10, # Something larger than 1. + ); + })->then( sub { + my ( $body ) = @_; + my @events = $body->{chunk}; + + log_if_fail "Body", $body; + + # Theoretically this is 1 m.room.message events + our own + # m.room.member, but since the message event will be rejected + # only the member event will come back. + assert_eq( scalar @events , 1 ); + + my $member_event = $body->{chunk}[0]; + + # Ensure the only event is the m.room.member event (not the + # m.room.message event). + assert_json_keys( $member_event, + qw( type event_id room_id sender state_key content )); + assert_eq( $member_event->{type}, "m.room.member", + 'events[0] type' ); + + Future->done; + }), + ) + }; diff --git a/tests/50federation/35room-invite.pl b/tests/50federation/35room-invite.pl index 175df965c..b88885dc1 100644 --- a/tests/50federation/35room-invite.pl +++ b/tests/50federation/35room-invite.pl @@ -408,7 +408,6 @@ sub do_v2_invite_request my ( $outbound_client, $user, $sytest_user_id ) = @_; my $server_name = $user->server_name; - my $sytest_server_name = $outbound_client->server_name; my $datastore = $outbound_client->datastore; my $room = $datastore->create_room( @@ -755,3 +754,155 @@ sub do_v2_invite_request # this test is a bit slooow timeout => 20; + +test "Inbound federation rejects invites which include invalid JSON for room version 6", + requires => [ + $main::OUTBOUND_CLIENT, local_user_fixture(), federation_user_id_fixture(), + ], + + do => sub { + my ( $outbound_client, $user, $sytest_user_id ) = @_; + + my $server_name = $user->server_name; + my $datastore = $outbound_client->datastore; + + my $room = $datastore->create_room( + creator => $sytest_user_id, + room_version => "6", + ); + + my $invite = $room->create_event( + type => "m.room.member", + content => { + membership => "invite", + bad_val => 1.1, + }, + sender => $sytest_user_id, + state_key => $user->user_id, + ); + + # Note that only v2 supports providing different room versions. + do_v2_invite_request( $room, $server_name, $outbound_client, $invite ) + ->main::expect_m_bad_json; + }; + +test "Outbound federation rejects invite response which include invalid JSON for room version 6", + requires => [ + local_user_and_room_fixtures( room_opts => { room_version => "6" } ), + $main::INBOUND_SERVER, + $main::OUTBOUND_CLIENT, + federation_user_id_fixture(), + ], + + do => sub { + my ($user, $room_id, $inbound_server, $outbound_client, $invitee_id) = @_; + + Future->needs_all( + matrix_invite_user_to_room($user, $invitee_id, $room_id), + + $inbound_server->await_request_v2_invite($room_id)->then(sub { + my ($req, undef) = @_; + + my $body = $req->body_from_json; + log_if_fail "Invitation", $body; + + my $invite = $body->{event}; + # Add a bad value into the response. + $invite->{bad_val} = 1.1; + + log_if_fail "Invitation 2", $invite; + + # accept the invite event and send it back + $inbound_server->datastore->sign_event($invite); + + $req->respond_json( + { event => $invite } + ); + + Future->done; + }), + )->main::expect_m_bad_json; + }; + +# A homeserver should reject an invite rejection for a version 6 room if it +# contains bad JSON data. +# +# To test this we need to: +# * Send a successful invite to a room (via `invite`). +# * Send a successful `make_leave` for the room. +# * Add a "bad" value into the returned prototype event. +# * Make a request to `send_leave`. +# * Check that the response is M_BAD_JSON. +test "Inbound federation rejects invite rejections which include invalid JSON for room version 6", + requires => [ + local_user_and_room_fixtures( room_opts => { room_version => "6" } ), + $main::INBOUND_SERVER, + $main::OUTBOUND_CLIENT, + federation_user_id_fixture(), + ], + + do => sub { + my ( $user, $room_id, $inbound_server, $outbound_client, $invitee_id ) = @_; + + Future->needs_all( + matrix_invite_user_to_room( $user, $invitee_id, $room_id ), + + $inbound_server->await_request_v2_invite( $room_id )->then( sub { + my ( $req, undef ) = @_; + + my $body = $req->body_from_json; + log_if_fail "Invitation", $body; + + my $invite = $body->{event}; + + # accept the invite event and send it back + $inbound_server->datastore->sign_event( $invite ); + + $req->respond_json( + { event => $invite } + ); + + Future->done; + }), + )->then( sub { + # Initiate a rejection of the invite: ask the server to build us a + # leave event. + # + # Note that it doesn't make sense to try to use a bad JSON value here + # since the endpoint doesn't accept any JSON anyway. + $outbound_client->do_request_json( + method => "GET", + hostname => $user->server_name, + uri => "/v1/make_leave/$room_id/$invitee_id", + ); + })->then( sub { + my ( $resp ) = @_; + + log_if_fail "/make_leave response", $resp; + + my $protoevent = $resp->{event}; + + # It is assumed that the make_leave response is sane, other tests + # ensure this behavior. + + my %event = ( + (map {$_ => $protoevent->{$_}} qw( + auth_events content depth prev_events room_id sender + state_key type)), + + origin => $outbound_client->server_name, + origin_server_ts => $inbound_server->time_ms, + ); + # Insert a "bad" value into the send leave, in this case a float. + ${event}{contents}{bad_val} = 1.1; + + $inbound_server->datastore->sign_event( \%event ); + + $outbound_client->do_request_json( + method => "PUT", + hostname => $user->server_name, + uri => "/v2/send_leave/$room_id/xxx", + content => \%event, + ) + })->main::expect_m_bad_json; + }; diff --git a/tests/50federation/40devicelists.pl b/tests/50federation/40devicelists.pl index d2b31b433..f25d51de6 100644 --- a/tests/50federation/40devicelists.pl +++ b/tests/50federation/40devicelists.pl @@ -325,9 +325,11 @@ matrix_leave_room( $user, $room->room_id ) })->then( sub { - matrix_join_room( $user, $room->room_id, - server_name => $inbound_server->server_name, - ) + retry_until_success { + matrix_join_room( $user, $room->room_id, + server_name => $inbound_server->server_name, + ) + } })->then( sub { Future->needs_all( $inbound_server->await_request_user_devices( $federated_user_id ) diff --git a/tests/50federation/51transactions.pl b/tests/50federation/51transactions.pl index 7ba252faa..4723f25ea 100644 --- a/tests/50federation/51transactions.pl +++ b/tests/50federation/51transactions.pl @@ -1,14 +1,10 @@ test "Server correctly handles transactions that break edu limits", - requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER, + requires => [ $main::OUTBOUND_CLIENT, local_user_and_room_fixtures(), - federation_user_id_fixture(), room_alias_name_fixture() ], + federation_user_id_fixture() ], do => sub { - my ( $outbound_client, $inbound_server, $creator, $room_id, $user_id, $room_alias_name ) = @_; - - my $remote_server_name = $inbound_server->server_name; - - my $room_alias = "#$room_alias_name:$remote_server_name"; + my ( $outbound_client, $creator, $room_id, $user_id ) = @_; $outbound_client->join_room( server_name => $creator->server_name, @@ -50,3 +46,40 @@ ); }); }; + +# Room version 6 states that homeservers should strictly enforce canonical JSON +# on PDUs. Test that a transaction to `send` with a PDU that has bad data will +# be handled properly. +# +# This enforces that the entire transaction is rejected if a single bad PDU is +# sent. It is unclear if this is the correct behavior or not. +# +# See https://github.com/matrix-org/synapse/issues/7543 +test "Server rejects invalid JSON in a version 6 room", + requires => [ $main::OUTBOUND_CLIENT, + federated_rooms_fixture( room_opts => { room_version => "6" } ) ], + + do => sub { + my ( $outbound_client, $creator, $user_id, @rooms ) = @_; + + my $room = $rooms[0]; + + my $bad_event = $room->create_and_insert_event( + type => "m.room.message", + + sender => $user_id, + content => { + body => "Message 1", + # Insert a "bad" value into the PDU, in this case a float. + bad_val => 1.1, + }, + ); + + my @pdus = ( $bad_event ); + + # Send the transaction to the client and expect a fail + $outbound_client->send_transaction( + pdus => \@pdus, + destination => $creator->server_name, + )->main::expect_m_bad_json; + }; diff --git a/tests/52user-directory/01public.pl b/tests/52user-directory/01public.pl index 641b962b9..a7420c0af 100644 --- a/tests/52user-directory/01public.pl +++ b/tests/52user-directory/01public.pl @@ -152,7 +152,7 @@ requires => [ local_user_fixtures( 2 ) ], # matrix_get_user_dir_synced creates two new users and a room, which is kinda slow. - timeout => 20000, + timeout => 20, check => sub { my ( $creator, $user ) = @_; @@ -250,7 +250,7 @@ requires => [ local_user_fixtures( 2 ) ], # matrix_get_user_dir_synced creates two new users and a room, which is kinda slow. - timeout => 20000, + timeout => 20, check => sub { my ( $creator, $user ) = @_; diff --git a/tests/80torture/20json.pl b/tests/80torture/20json.pl new file mode 100644 index 000000000..8e08f7ca7 --- /dev/null +++ b/tests/80torture/20json.pl @@ -0,0 +1,126 @@ +# Test integers that are outside of the range of [-2 ^ 53 + 1, 2 ^ 53 - 1]. +test "Invalid JSON integers", + requires => [ local_user_and_room_fixtures( + room_opts => { room_version => "6" } + ), ], + + do => sub { + my ( $user, $room_id ) = @_; + + Future->needs_all( + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => 9007199254740992, # 2 ** 53 + }, + )->main::expect_m_bad_json, + + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => -9007199254740992, # -2 ** 53 + }, + )->main::expect_m_bad_json, + ); + }; + +# Floats should be rejected. +test "Invalid JSON floats", + requires => [ local_user_and_room_fixtures( + room_opts => { room_version => "6" } + ), ], + + do => sub { + my ( $user, $room_id ) = @_; + + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => 1.1, + }, + )->main::expect_m_bad_json; + }; + +# Special values (like inf/nan) should be rejected. Note that these values are +# not technically valid JSON, but extensions that some programming languages +# support automatically. +# +# Note that these tests don't explictely check for M_BAD_JSON since the +# homeserver's parser might not even be able to parse the string. +test "Invalid JSON special values", + requires => [ local_user_and_room_fixtures( + room_opts => { room_version => "6" } + ), ], + + do => sub { + my ( $user, $room_id ) = @_; + + my $http = $user->http; + + Future->needs_all( + # Try some Perl magic values. + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => "NaN" + 0, + }, + )->main::expect_http_400, + + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => "inf" + 0, + }, + )->main::expect_http_400, + + do_request_json_for( $user, + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + content => { + msgtype => "sytest.dummy", + body => "-inf" + 0, + }, + )->main::expect_http_400, + + # Try some Python magic values. + $user->http->do_request( + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + params => { + access_token => $user->access_token, + }, + content => '{"msgtype": "sytest.dummy", "body": Infinity}', + content_type => "application/json", + )->main::expect_http_400, + + $user->http->do_request( + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + params => { + access_token => $user->access_token, + }, + content => '{"msgtype": "sytest.dummy", "body": -Infinity}', + content_type => "application/json", + )->main::expect_http_400, + + $user->http->do_request( + method => "POST", + uri => "/r0/rooms/$room_id/send/sytest.dummy", + params => { + access_token => $user->access_token, + }, + content => '{"msgtype": "sytest.dummy", "body": NaN}', + content_type => "application/json", + )->main::expect_http_400, + ); + }; diff --git a/tests/90jira/SYN-442.pl b/tests/90jira/SYN-442.pl index 779a958fe..9573b8b79 100644 --- a/tests/90jira/SYN-442.pl +++ b/tests/90jira/SYN-442.pl @@ -68,7 +68,8 @@ })->SyTest::pass_on_done( "User A received the invite from user B" ) })->then( sub { - matrix_join_room( $user_1, $room_id ) - ->SyTest::pass_on_done( "User A joined the room" ) + retry_until_success { + matrix_join_room( $user_1, $room_id ) + }->SyTest::pass_on_done( "User A joined the room" ) })->then_done(1); };