@@ -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+
734873func 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