diff --git a/federation/server.go b/federation/server.go index 27f3c3a1..ee9cd2c4 100644 --- a/federation/server.go +++ b/federation/server.go @@ -357,6 +357,57 @@ func (s *Server) MustCreateEvent(t ct.TestLike, room *ServerRoom, ev Event) goma return signedEvent } +// Invite someone a room. +func (s *Server) MustInviteRoom( + t ct.TestLike, + deployment FederationDeployment, + remoteServer spec.ServerName, + roomID string, + inviterUserID string, + inviteeUserID string, +) { + t.Helper() + fedClient := s.FederationClient(deployment) + + room := s.rooms[roomID] + if room == nil { + ct.Fatalf(t, "MustInviteRoom: room %s not found", roomID) + } + roomVersionImplementation, err := gomatrixserverlib.GetRoomVersion(room.Version) + + rawInviteEvent := s.MustCreateEvent(t, room, Event{ + Type: "m.room.member", + StateKey: &inviterUserID, + Sender: inviteeUserID, + Content: map[string]interface{}{ + "membership": "invite", + }, + }) + inviteReq, err := fclient.NewInviteV2Request(rawInviteEvent, []gomatrixserverlib.InviteStrippedState{}) + if err != nil { + t.Fatalf("failed to make invite request: %s", err) + } + sendInviteResp, err := fedClient.SendInviteV2( + context.Background(), + spec.ServerName(s.ServerName()), + remoteServer, + inviteReq, + ) + if err != nil { + t.Fatalf("MustInviteRoom: failed to send invite v2: %s", err) + } + + inviteEvent, err := roomVersionImplementation.NewEventFromUntrustedJSON(sendInviteResp.Event) + if err != nil { + t.Fatalf("MustInviteRoom: failed to decode event response: %w", err) + } + + room.AddEvent(inviteEvent) + s.rooms[roomID] = room + + t.Logf("Server.MustInviteRoom %s invited %s to room ID %s", inviterUserID, inviteeUserID, roomID) +} + // MustJoinRoom will make the server send a make_join and a send_join to join a room // It returns the resultant room. func (s *Server) MustJoinRoom(t ct.TestLike, deployment FederationDeployment, remoteServer spec.ServerName, roomID string, userID string, partialState ...bool) *ServerRoom { diff --git a/tests/federation_rooms_invite_test.go b/tests/federation_rooms_invite_test.go index 896bf9f7..12ab64ba 100644 --- a/tests/federation_rooms_invite_test.go +++ b/tests/federation_rooms_invite_test.go @@ -13,6 +13,7 @@ import ( "github.com/tidwall/gjson" "github.com/matrix-org/complement/client" + "github.com/matrix-org/complement/federation" "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" @@ -99,7 +100,7 @@ func TestFederationRoomsInvite(t *testing.T) { verifyState(t, res, wantFields, wantValues, roomID, alice) }) - t.Run("Remote invited user can join the room when homeserver is already participating in the room", func(t *testing.T) { + t.Run("Remote invited user can join the room when homeserver is already participating in the room (e2e)", func(t *testing.T) { t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", @@ -121,7 +122,7 @@ func TestFederationRoomsInvite(t *testing.T) { alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob2.UserID, roomID)) }) - t.Run("Remote invited user can reject invite when homeserver is already participating in the room", func(t *testing.T) { + t.Run("Remote invited user can reject invite when homeserver is already participating in the room (e2e)", func(t *testing.T) { t.Parallel() roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", @@ -143,6 +144,44 @@ func TestFederationRoomsInvite(t *testing.T) { alice.MustSyncUntil(t, client.SyncReq{Filter: includeLeaveSyncFilter}, client.SyncLeftFrom(bob2.UserID, roomID)) }) + // Engineered condition where we only send `/invite` requests over federation and do + // not send the event in a `/send` transaction. Regression tests for + // https://github.com/element-hq/synapse/pull/18075 + t.Run("Remote invited user can join the room when homeserver is already participating in the room (engineered)", func(t *testing.T) { + t.Parallel() + + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + // bob1 will use this to join the remote room + federation.HandleMakeSendJoinRequests(), + ) + cancel := srv.Listen() + defer cancel() + + engineeredAliceUserId := srv.UserID("alice") + + roomVersion := alice.GetDefaultRoomVersion(t) + initalEvents := federation.InitialRoomEvents(roomVersion, engineeredAliceUserId) + serverRoom := srv.MustMakeRoom(t, roomVersion, initalEvents) + roomID := serverRoom.RoomID + + // bob1 is invited and can join the room (hs2 is now participating of the room) + // + // alice.MustInviteRoom(t, roomID, bob.UserID) + srv.MustInviteRoom(t, deployment, "hs1", roomID, engineeredAliceUserId, bob.UserID) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) + bob.MustJoinRoom(t, roomID, []string{srv.ServerName()}) + // Make sure alice can see bob in the room + // alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + + // bob2 is invited and can also join the room + // alice.MustInviteRoom(t, roomID, bob2.UserID) + // bob2.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob2.UserID, roomID)) + // bob2.MustJoinRoom(t, roomID, []string{srv.ServerName()}) + // // Make sure alice can see bob2 in the room + // alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob2.UserID, roomID)) + }) + t.Run("Invited user has 'is_direct' flag in prev_content after joining", func(t *testing.T) { roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat",