Skip to content

Commit 4d6f00d

Browse files
* address matrix-org/dendrite#1394 bugs * praise be the eternal linter * address feedback * convert other manual sync calls * lint
1 parent 07e2ef4 commit 4d6f00d

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

internal/client/client.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ func SyncInvitedTo(userID, roomID string) SyncCheckOpt {
592592
}
593593
}
594594

595-
// Check that `userID` gets joined to `roomID` by inspecting the join timeline for a membership event.
595+
// Check that `userID` gets joined to `roomID` by inspecting the join timeline for a membership event
596596
func SyncJoinedTo(userID, roomID string) SyncCheckOpt {
597597
return func(clientUserID string, topLevelSyncJSON gjson.Result) error {
598598
// awkward wrapping to get the error message correct at the start :/
@@ -606,6 +606,29 @@ func SyncJoinedTo(userID, roomID string) SyncCheckOpt {
606606
}
607607
}
608608

609+
// Check that `userID` is leaving `roomID` by inspecting the timeline for a membership event, or witnessing `roomID` in `rooms.leave`
610+
// Note: This will not work properly with initial syncs, see https://github.com/matrix-org/matrix-doc/issues/3537
611+
func SyncLeftFrom(userID, roomID string) SyncCheckOpt {
612+
return func(clientUserID string, topLevelSyncJSON gjson.Result) error {
613+
// two forms which depend on what the client user is:
614+
// - passively viewing a membership for a room you're joined in
615+
// - actively leaving the room
616+
if clientUserID == userID {
617+
// active
618+
events := topLevelSyncJSON.Get("rooms.leave." + GjsonEscape(roomID))
619+
if !events.Exists() {
620+
return fmt.Errorf("no leave section for room %s", roomID)
621+
} else {
622+
return nil
623+
}
624+
}
625+
// passive
626+
return SyncTimelineHas(roomID, func(ev gjson.Result) bool {
627+
return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == userID && ev.Get("content.membership").Str == "leave"
628+
})(clientUserID, topLevelSyncJSON)
629+
}
630+
}
631+
609632
// Calls the `check` function for each global account data event, and returns with success if the
610633
// `check` function returns true for at least one event.
611634
func SyncGlobalAccountDataHas(check func(gjson.Result) bool) SyncCheckOpt {

tests/csapi/sync_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package csapi_tests
2+
3+
import (
4+
"testing"
5+
6+
"github.com/matrix-org/complement/internal/b"
7+
"github.com/matrix-org/complement/internal/client"
8+
)
9+
10+
// Observes "first bug" from https://github.com/matrix-org/dendrite/pull/1394#issuecomment-687056673
11+
func TestCumulativeJoinLeaveJoinSync(t *testing.T) {
12+
deployment := Deploy(t, b.BlueprintOneToOneRoom)
13+
defer deployment.Destroy(t)
14+
15+
alice := deployment.Client(t, "hs1", "@alice:hs1")
16+
bob := deployment.Client(t, "hs1", "@bob:hs1")
17+
18+
roomID := bob.CreateRoom(t, map[string]interface{}{
19+
"preset": "public_chat",
20+
})
21+
22+
var since string
23+
24+
// Get floating next_batch from before joining at all
25+
_, since = alice.MustSync(t, client.SyncReq{TimeoutMillis: "0"})
26+
27+
alice.JoinRoom(t, roomID, nil)
28+
29+
// This assumes that sync does not have side-effects in servers.
30+
//
31+
// The alternative would be to sleep, but that is not acceptable here.
32+
sinceJoin := alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID))
33+
34+
alice.LeaveRoom(t, roomID)
35+
36+
sinceLeave := alice.MustSyncUntil(t, client.SyncReq{Since: sinceJoin}, client.SyncLeftFrom(alice.UserID, roomID))
37+
38+
alice.JoinRoom(t, roomID, nil)
39+
40+
alice.MustSyncUntil(t, client.SyncReq{Since: sinceLeave}, client.SyncJoinedTo(alice.UserID, roomID))
41+
42+
jsonRes, _ := alice.MustSync(t, client.SyncReq{TimeoutMillis: "0", Since: since})
43+
if jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
44+
t.Errorf("Incremental sync has joined-left-joined room showing up in leave section, this shouldnt be the case.")
45+
}
46+
}
47+
48+
// Observes "second bug" from https://github.com/matrix-org/dendrite/pull/1394#issuecomment-687056673
49+
func TestTentativeEventualJoiningAfterRejecting(t *testing.T) {
50+
deployment := Deploy(t, b.BlueprintOneToOneRoom)
51+
defer deployment.Destroy(t)
52+
53+
alice := deployment.Client(t, "hs1", "@alice:hs1")
54+
bob := deployment.Client(t, "hs1", "@bob:hs1")
55+
56+
roomID := alice.CreateRoom(t, map[string]interface{}{
57+
"preset": "public_chat",
58+
})
59+
60+
var since string
61+
62+
// Get floating current next_batch
63+
_, since = alice.MustSync(t, client.SyncReq{TimeoutMillis: "0"})
64+
65+
alice.InviteRoom(t, roomID, bob.UserID)
66+
67+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID))
68+
69+
// This rejects the invite
70+
bob.LeaveRoom(t, roomID)
71+
72+
// Full sync
73+
jsonRes, since := bob.MustSync(t, client.SyncReq{TimeoutMillis: "0", FullState: true, Since: since})
74+
if !jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
75+
t.Errorf("Bob just rejected an invite, it should show up under 'leave' in a full sync")
76+
}
77+
78+
bob.JoinRoom(t, roomID, nil)
79+
80+
jsonRes, since = bob.MustSync(t, client.SyncReq{TimeoutMillis: "0", FullState: true, Since: since})
81+
if jsonRes.Get("rooms.leave." + client.GjsonEscape(roomID)).Exists() {
82+
t.Errorf("Bob has rejected an invite, but then just joined the public room anyways, it should not show up under 'leave' in a full sync %s", since)
83+
}
84+
}

0 commit comments

Comments
 (0)