Skip to content

Commit 056812f

Browse files
committed
Add tests for edge cases of multiple membership changes in sync period
Addresses element-hq/synapse#14683 case seen in the real world.
1 parent 5da410e commit 056812f

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

tests/csapi/sync_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,145 @@ func TestRoomSummary(t *testing.T) {
731731
alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncJoinedTo(bob.UserID, roomID), joinedCheck)
732732
}
733733

734+
// TestLeaveReinviteSync tests that when a user is kicked and then re-invited,
735+
// they only see an invite in their sync response, not both an invite and leave event.
736+
func TestLeaveReinviteSync(t *testing.T) {
737+
deployment := complement.Deploy(t, 1)
738+
defer deployment.Destroy(t)
739+
740+
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
741+
bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
742+
743+
// 1. Alice creates a room and Bob joins
744+
roomID := alice.MustCreateRoom(t, map[string]any{
745+
"preset": "public_chat",
746+
})
747+
bob.MustJoinRoom(t, roomID, nil)
748+
749+
// 2. Bob does a sync and verifies they see the join
750+
bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID))
751+
752+
// 3. Alice kicks Bob from the room and then re-invites them.
753+
// For kicking, we need to use the POST /rooms/{roomId}/kick endpoint
754+
alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "kick"},
755+
client.WithJSONBody(t, map[string]any{
756+
"user_id": bob.UserID,
757+
}),
758+
)
759+
760+
// Wait until Bob is kicked.
761+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(bob.UserID, roomID))
762+
763+
// Alice re-invites Bob
764+
alice.MustInviteRoom(t, roomID, bob.UserID)
765+
766+
// 4. Bob does a sync
767+
jsonRes, _ := bob.MustSync(t, client.SyncReq{Since: bobSince})
768+
769+
// Bob should only see an invite, not both an invite and a leave event
770+
if !jsonRes.Get("rooms.invite." + client.GjsonEscape(roomID)).Exists() {
771+
t.Errorf("Expected to see the room in the invite section of the sync response")
772+
}
773+
774+
// Make sure there's no leave event for the room
775+
if jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
776+
t.Errorf("Room should not appear in the leave section of the sync response")
777+
}
778+
}
779+
780+
// TestLeaveJoinLeaveSync tests that when a user leaves, rejoins, and leaves again,
781+
// they only see a leave event in their sync response, not both a join and a leave event.
782+
func TestLeaveJoinLeaveSync(t *testing.T) {
783+
deployment := complement.Deploy(t, 1)
784+
defer deployment.Destroy(t)
785+
786+
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
787+
bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
788+
789+
// 1. Alice creates a room and Bob joins
790+
roomID := alice.MustCreateRoom(t, map[string]any{
791+
"preset": "public_chat",
792+
})
793+
bob.MustJoinRoom(t, roomID, nil)
794+
795+
// 2. Bob does a sync and verifies they see the join
796+
bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID))
797+
798+
// 3. Bob leaves the room
799+
bob.MustLeaveRoom(t, roomID)
800+
801+
// 4. Bob rejoins the room
802+
bob.MustJoinRoom(t, roomID, nil)
803+
804+
// 5. Bob leaves the room again
805+
bob.MustLeaveRoom(t, roomID)
806+
807+
// 6. Bob does a sync
808+
jsonRes, _ := bob.MustSync(t, client.SyncReq{Since: bobSince})
809+
810+
// Bob should only see a leave event, not both a join and a leave event
811+
if !jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
812+
t.Errorf("Expected to see the room in the leave section of the sync response")
813+
}
814+
815+
// Make sure there's no join event for the room
816+
if jsonRes.Get("rooms.join." + client.GjsonEscape(roomID)).Exists() {
817+
t.Errorf("Room should not appear in the join section of the sync response")
818+
}
819+
}
820+
821+
// TestLeaveReinviteSyncFederated tests that when a user is kicked and then re-invited over federation,
822+
// they only see an invite in their sync response, not both an invite and leave event.
823+
func TestLeaveReinviteSyncFederated(t *testing.T) {
824+
deployment := complement.Deploy(t, 2)
825+
defer deployment.Destroy(t)
826+
827+
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
828+
bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
829+
830+
// 1. Alice creates a room and Bob joins
831+
roomID := alice.MustCreateRoom(t, map[string]any{
832+
"preset": "public_chat",
833+
})
834+
835+
// Bob needs to join via federation, so we need to specify the server name
836+
alice.MustInviteRoom(t, roomID, bob.UserID)
837+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID))
838+
bob.MustJoinRoom(t, roomID, []spec.ServerName{
839+
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
840+
})
841+
842+
// 2. Bob does a sync and verifies they see the join
843+
bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID))
844+
845+
// 3. Alice kicks Bob from the room and then re-invites them.
846+
// For kicking, we need to use the POST /rooms/{roomId}/kick endpoint
847+
alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "kick"},
848+
client.WithJSONBody(t, map[string]any{
849+
"user_id": bob.UserID,
850+
}),
851+
)
852+
853+
// Wait until Bob is kicked.
854+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(bob.UserID, roomID))
855+
856+
// Alice re-invites Bob
857+
alice.MustInviteRoom(t, roomID, bob.UserID)
858+
859+
// 4. Bob does a sync
860+
jsonRes, _ := bob.MustSync(t, client.SyncReq{Since: bobSince})
861+
862+
// Bob should only see an invite, not both an invite and a leave event
863+
if !jsonRes.Get("rooms.invite." + client.GjsonEscape(roomID)).Exists() {
864+
t.Errorf("Expected to see the room in the invite section of the sync response")
865+
}
866+
867+
// Make sure there's no leave event for the room
868+
if jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
869+
t.Errorf("Room should not appear in the leave section of the sync response")
870+
}
871+
}
872+
734873
func sendMessages(t *testing.T, client *client.CSAPI, roomID string, prefix string, count int) {
735874
t.Helper()
736875
for i := 0; i < count; i++ {

0 commit comments

Comments
 (0)