<commit_before>use List::UtilsBy qw( partition_by extract_by );

use SyTest::Federation::Room;

my %STATE_EVENT_TYPES = map {$_=>1} qw (
   m.room.create
   m.room.member
   m.room.power_levels
   m.room.join_levels
   m.room.history_visibility
);

# check that the given event is valid
# (ideally would check that it is correctly signed and hashed, but that is TODO)
sub assert_is_valid_pdu {
   my ( $event ) = @_;

   assert_json_keys( $event, qw(
      auth_events content depth event_id hashes origin origin_server_ts
      prev_events room_id sender signatures type
   ));

   assert_json_list( $event->{auth_events} );
   assert_json_number( $event->{depth} );
   assert_json_string( $event->{event_id} );
   assert_json_object( $event->{hashes} );

   assert_json_string( $event->{origin} );

   assert_json_number( $event->{origin_server_ts} );
   assert_json_list( $event->{prev_events} );

   assert_json_string( $event->{room_id} );
   assert_json_string( $event->{sender} );
   assert_json_object( $event->{signatures} );
   assert_json_string( $event->{type} );

   # for event types which are known to be state events, check that they
   # have the relevant keys
   if ( $STATE_EVENT_TYPES{ $event->{type} }) {
      # XXX richvdh: I'm unconvinced prev_state is required here - I think
      # it's deprecated. It's certainly not mentioned in the spec.
      assert_json_keys( $event, qw(
         state_key prev_state
      ));

      assert_json_string( $event->{state_key} );
      assert_json_list( $event->{prev_state} );
   }

   # TODO: Check signatures and hashes
}
push our @EXPORT, qw( assert_is_valid_pdu );


test "Outbound federation can send room-join requests",
   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 = SyTest::Federation::Room->new(
         datastore => $datastore,
      );

      $room->create_initial_events(
         server  => $inbound_server,
         creator => $creator_id,
      );

      my $room_id = $room->room_id;

      # We'll have to jump through the extra hoop of using the directory
      # service first, because we can't join a remote room by room ID alone

      my $room_alias = "#50fed-room-alias:$local_server_name";
      require_stub $inbound_server->await_request_query_directory( $room_alias )
         ->on_done( sub {
            my ( $req ) = @_;

            $req->respond_json( {
               room_id => $room_id,
               servers => [
                  $local_server_name,
               ]
            } );
         });

      Future->needs_all(
         # Await PDU?

         $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub {
            my ( $req, $room_id, $user_id ) = @_;

            my $proto = $room->make_join_protoevent(
               user_id => $user_id,
            );

            $proto->{origin}           = $inbound_server->server_name;
            $proto->{origin_server_ts} = $inbound_server->time_ms;

            $req->respond_json( {
               event => $proto,
            } );

            Future->done;
         }),

         $inbound_server->await_request_send_join( $room_id )->then( sub {
            my ( $req, $room_id, $event_id ) = @_;

            $req->method eq "PUT" or
               die "Expected send_join method to be PUT";

            my $event = $req->body_from_json;
            log_if_fail "send_join event", $event;

            my @auth_chain = $datastore->get_auth_chain_events(
               map { $_->[0] } @{ $event->{auth_events} }
            );

            $req->respond_json(
               # TODO(paul): This workaround is for SYN-490
               my $response = [ 200, {
                  auth_chain => \@auth_chain,
                  state      => [ $room->current_state_events ],
               } ]
            );

            log_if_fail "send_join response", $response;

            Future->done;
         }),

         do_request_json_for( $user,
            method => "POST",
            uri    => "/r0/join/$room_alias",

            content => {},
         )->then( sub {
            my ( $body ) = @_;
            log_if_fail "Join response", $body;

            assert_json_keys( $body, qw( room_id ));

            $body->{room_id} eq $room_id or
               die "Expected room_id to be $room_id";

            matrix_get_my_member_event( $user, $room_id )
         })->then( sub {
            my ( $event ) = @_;

            # The joining HS (i.e. the SUT) should have invented the event ID
            # for my membership event.

            # TODO - sanity check the $event

            Future->done(1);
         }),
      )
   };

test "Inbound federation can receive room-join requests",
   requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER,
                 $main::HOMESERVER_INFO[0],
                 local_user_and_room_fixtures(),
                 federation_user_id_fixture() ],

   do => sub {
      my ( $outbound_client, $inbound_server, $info, undef, $room_id, $user_id ) = @_;
      my $first_home_server = $info->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      => "/make_join/$room_id/$user_id",
      )->then( sub {
         my ( $body ) = @_;
         log_if_fail "make_join body", $body;

         # TODO(paul): This is all entirely cargoculted guesswork based on
         #   observing what Synapse actually does, because the entire make_join
         #   API is entirely undocumented. See SPEC-241
<commit_msg>test make_join error passthrough<commit_after>use List::UtilsBy qw( partition_by extract_by );

use SyTest::Federation::Room;

my %STATE_EVENT_TYPES = map {$_=>1} qw (
   m.room.create
   m.room.member
   m.room.power_levels
   m.room.join_levels
   m.room.history_visibility
);

# check that the given event is valid
# (ideally would check that it is correctly signed and hashed, but that is TODO)
sub assert_is_valid_pdu {
   my ( $event ) = @_;

   assert_json_keys( $event, qw(
      auth_events content depth event_id hashes origin origin_server_ts
      prev_events room_id sender signatures type
   ));

   assert_json_list( $event->{auth_events} );
   assert_json_number( $event->{depth} );
   assert_json_string( $event->{event_id} );
   assert_json_object( $event->{hashes} );

   assert_json_string( $event->{origin} );

   assert_json_number( $event->{origin_server_ts} );
   assert_json_list( $event->{prev_events} );

   assert_json_string( $event->{room_id} );
   assert_json_string( $event->{sender} );
   assert_json_object( $event->{signatures} );
   assert_json_string( $event->{type} );

   # for event types which are known to be state events, check that they
   # have the relevant keys
   if ( $STATE_EVENT_TYPES{ $event->{type} }) {
      # XXX richvdh: I'm unconvinced prev_state is required here - I think
      # it's deprecated. It's certainly not mentioned in the spec.
      assert_json_keys( $event, qw(
         state_key prev_state
      ));

      assert_json_string( $event->{state_key} );
      assert_json_list( $event->{prev_state} );
   }

   # TODO: Check signatures and hashes
}
push our @EXPORT, qw( assert_is_valid_pdu );


test "Outbound federation can send room-join requests",
   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 = SyTest::Federation::Room->new(
         datastore => $datastore,
      );

      $room->create_initial_events(
         server  => $inbound_server,
         creator => $creator_id,
      );

      my $room_id = $room->room_id;

      # We'll have to jump through the extra hoop of using the directory
      # service first, because we can't join a remote room by room ID alone

      my $room_alias = "#50fed-room-alias:$local_server_name";
      require_stub $inbound_server->await_request_query_directory( $room_alias )
         ->on_done( sub {
            my ( $req ) = @_;

            $req->respond_json( {
               room_id => $room_id,
               servers => [
                  $local_server_name,
               ]
            } );
         });

      Future->needs_all(
         # Await PDU?

         $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub {
            my ( $req, $room_id, $user_id ) = @_;

            my $proto = $room->make_join_protoevent(
               user_id => $user_id,
            );

            $proto->{origin}           = $inbound_server->server_name;
            $proto->{origin_server_ts} = $inbound_server->time_ms;

            $req->respond_json( {
               event => $proto,
            } );

            Future->done;
         }),

         $inbound_server->await_request_send_join( $room_id )->then( sub {
            my ( $req, $room_id, $event_id ) = @_;

            $req->method eq "PUT" or
               die "Expected send_join method to be PUT";

            my $event = $req->body_from_json;
            log_if_fail "send_join event", $event;

            my @auth_chain = $datastore->get_auth_chain_events(
               map { $_->[0] } @{ $event->{auth_events} }
            );

            $req->respond_json(
               # TODO(paul): This workaround is for SYN-490
               my $response = [ 200, {
                  auth_chain => \@auth_chain,
                  state      => [ $room->current_state_events ],
               } ]
            );

            log_if_fail "send_join response", $response;

            Future->done;
         }),

         do_request_json_for( $user,
            method => "POST",
            uri    => "/r0/join/$room_alias",

            content => {},
         )->then( sub {
            my ( $body ) = @_;
            log_if_fail "Join response", $body;

            assert_json_keys( $body, qw( room_id ));

            $body->{room_id} eq $room_id or
               die "Expected room_id to be $room_id";

            matrix_get_my_member_event( $user, $room_id )
         })->then( sub {
            my ( $event ) = @_;

            # The joining HS (i.e. the SUT) should have invented the event ID
            # for my membership event.

            # TODO - sanity check the $event

            Future->done(1);
         }),
      )
   };


test "Outbound federation passes make_join failures through to the client",
   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_id           = $datastore->next_room_id;

      my $test_room_version = 'sytest-room-ver';

      # We'll have to jump through the extra hoop of using the directory
      # service first, because we can't join a remote room by room ID alone
      my $room_alias = "#unsupported-room-ver:$local_server_name";
      require_stub $inbound_server->await_request_query_directory( $room_alias )
         ->on_done( sub {
            my ( $req ) = @_;

            $req->respond_json( {
               room_id => $room_id,
               servers => [
                  $local_server_name,
               ]
            } );
         });

      Future->needs_all(
         $inbound_server->await_request_make_join( $room_id, $user->user_id )->then( sub {
            my ( $req, $room_id, $user_id ) = @_;
            $req->respond_json(
               {
                  errcode => "M_TEST_ERROR_CODE",
                  error => "denied!",
               },
               code => 400,
            );
            Future->done;
         }),

         do_request_json_for( $user,
            method => "POST",
            uri    => "/r0/join/$room_alias",

            content => {},
         )->main::expect_http_400
         ->then( sub {
            my ( $response ) = @_;
            my $body = decode_json( $response->content );
            log_if_fail "Join error response", $body;

            assert_eq( $body->{errcode}, "M_TEST_ERROR_CODE", 'responsecode' );
            Future->done(1);
         }),
      )
   };



test "Inbound federation can receive room-join requests",
   requires => [ $main::OUTBOUND_CLIENT, $main::INBOUND_SERVER,
                 $main::HOMESERVER_INFO[0],
                 local_user_and_room_fixtures(),
                 federation_user_id_fixture() ],

   do => sub {
      my ( $outbound_client, $inbound_server, $info, undef, $room_id, $user_id ) = @_;
      my $first_home_server = $info->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      => "/make_join/$room_id/$user_id",
      )->then( sub {
         my ( $body ) = @_;
         log_if_fail "make_join body", $body;

         # TODO(paul): This is all entirely cargoculted guesswork based on
         #   observing what Synapse actually does, because the entire make_join
         #   API is entirely undocumented. See SPEC-241

{'fraction_non_alphanumeric': 0.12074842265574008, 'fraction_numerical': 0.0029008630067445065, 'mean_word_length': 2.0267778753292363, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 174, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '502851', 'n_tokens_mistral': 4409, 'n_tokens_neox': 4257, 'n_words': 953}