From 2553ffcedfaa1e27cf4943520f13545baa3e862e Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:43:43 +0000 Subject: [PATCH 1/2] Test for https://github.com/element-hq/synapse/issues/16940 with /members --- tests/csapi/room_members_test.go | 135 +++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/tests/csapi/room_members_test.go b/tests/csapi/room_members_test.go index c017b64c..e9465c25 100644 --- a/tests/csapi/room_members_test.go +++ b/tests/csapi/room_members_test.go @@ -1,6 +1,8 @@ package csapi_tests import ( + "encoding/json" + "fmt" "net/url" "strings" "testing" @@ -10,9 +12,11 @@ import ( "github.com/matrix-org/complement" "github.com/matrix-org/complement/b" "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" + "github.com/matrix-org/complement/should" ) // Maps every object by extracting `type` and `state_key` into a "$type|$state_key" string. @@ -214,3 +218,134 @@ func TestGetFilteredRoomMembers(t *testing.T) { }) }) } + +// Same as TestGetRoomMembersAtPoint but we will inject a dangling join event for a remote user. +// Regression test for https://github.com/element-hq/synapse/issues/16940 +// +// E1 +// ↗ ↖ +// | JOIN EVENT (charlie) +// | +// -----|--- +// | +// E2 +// | +// E3 <- /members?at=THIS_POINT +func TestGetRoomMembersAtPointWithStateFork(t *testing.T) { + deployment := complement.Deploy(t, 1) + defer deployment.Destroy(t) + srv := federation.NewServer(t, deployment, + federation.HandleKeyRequests(), + federation.HandleMakeSendJoinRequests(), + federation.HandleTransactionRequests(nil, nil), + ) + srv.UnexpectedRequestsAreErrors = false + cancel := srv.Listen() + defer cancel() + + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + bob := srv.UserID("bob") + ver := alice.GetDefaultRoomVersion(t) + serverRoom := srv.MustMakeRoom(t, ver, federation.InitialRoomEvents(ver, bob)) + + // Join Alice to the new room on the federation server and send E1. + alice.MustJoinRoom(t, serverRoom.RoomID, []string{srv.ServerName()}) + alice.SendEventSynced(t, serverRoom.RoomID, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "E1", + }, + }) + + // create JOIN EVENT but don't send it yet, prev_events will be set to [e1] + charlie := srv.UserID("charlie") + joinEvent := srv.MustCreateEvent(t, serverRoom, federation.Event{ + Type: "m.room.member", + StateKey: b.Ptr(charlie), + Sender: charlie, + Content: map[string]interface{}{ + "membership": "join", + }, + }) + + // send E2 and E3 + alice.SendEventSynced(t, serverRoom.RoomID, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "E2", + }, + }) + alice.SendEventSynced(t, serverRoom.RoomID, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "E3", + }, + }) + + // fork the dag earlier at e1 and send JOIN EVENT + srv.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{joinEvent.JSON()}, nil) + + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(serverRoom.RoomID, joinEvent.EventID())) + + // now do a sync request with limit=1. + // Note we don't need to SyncUntil here as we have all the data in the right places already. + res, since := alice.MustSync(t, client.SyncReq{ + Filter: `{ + "room": { + "timeline": { + "limit": 1 + } + } + }`, + }) + err := should.MatchGJSON(res, match.JSONCheckOff( + // look in this array + fmt.Sprintf("rooms.join.%s.state.events", client.GjsonEscape(serverRoom.RoomID)), + // for these items + []interface{}{joinEvent.EventID()}, + // and map them first into this format + match.CheckOffMapper(func(r gjson.Result) interface{} { + return r.Get("event_id").Str + }), match.CheckOffAllowUnwanted(), + )) + if err != nil { + t.Logf("did not find charlie's join event in 'state' block: %s", err) + } + // now hit /members?at=$since and check it has the join + httpRes := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", serverRoom.RoomID, "members"}, client.WithQueries(map[string][]string{ + "at": {since}, + })) + must.MatchResponse(t, httpRes, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONCheckOff("chunk", + []interface{}{ + "m.room.member|" + alice.UserID, + "m.room.member|" + bob, + "m.room.member|" + charlie, + }, match.CheckOffMapper(typeToStateKeyMapper)), + }, + StatusCode: 200, + }) + // now hit /members?at=$prev_batch and check it has the join + prevBatch := res.Get(fmt.Sprintf("rooms.join.%s.timeline.prev_batch", client.GjsonEscape(serverRoom.RoomID))).Str + t.Logf("since=%s prev_batch=%s", since, prevBatch) + httpRes = alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", serverRoom.RoomID, "members"}, client.WithQueries(map[string][]string{ + "at": { + prevBatch, + }, + })) + must.MatchResponse(t, httpRes, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONCheckOff("chunk", + []interface{}{ + "m.room.member|" + alice.UserID, + "m.room.member|" + bob, + "m.room.member|" + charlie, + }, match.CheckOffMapper(typeToStateKeyMapper)), + }, + StatusCode: 200, + }) +} From 122981eb15735039b36666f8e699ea5b76afbf84 Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:54:50 +0000 Subject: [PATCH 2/2] Correct teminology --- tests/csapi/room_members_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/csapi/room_members_test.go b/tests/csapi/room_members_test.go index e9465c25..9308eb4f 100644 --- a/tests/csapi/room_members_test.go +++ b/tests/csapi/room_members_test.go @@ -292,7 +292,7 @@ func TestGetRoomMembersAtPointWithStateFork(t *testing.T) { // now do a sync request with limit=1. // Note we don't need to SyncUntil here as we have all the data in the right places already. - res, since := alice.MustSync(t, client.SyncReq{ + res, nextBatch := alice.MustSync(t, client.SyncReq{ Filter: `{ "room": { "timeline": { @@ -314,9 +314,9 @@ func TestGetRoomMembersAtPointWithStateFork(t *testing.T) { if err != nil { t.Logf("did not find charlie's join event in 'state' block: %s", err) } - // now hit /members?at=$since and check it has the join + // now hit /members?at=$nextBatch and check it has the join httpRes := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", serverRoom.RoomID, "members"}, client.WithQueries(map[string][]string{ - "at": {since}, + "at": {nextBatch}, })) must.MatchResponse(t, httpRes, match.HTTPResponse{ JSON: []match.JSON{ @@ -331,7 +331,7 @@ func TestGetRoomMembersAtPointWithStateFork(t *testing.T) { }) // now hit /members?at=$prev_batch and check it has the join prevBatch := res.Get(fmt.Sprintf("rooms.join.%s.timeline.prev_batch", client.GjsonEscape(serverRoom.RoomID))).Str - t.Logf("since=%s prev_batch=%s", since, prevBatch) + t.Logf("next_batch=%s prev_batch=%s", nextBatch, prevBatch) httpRes = alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", serverRoom.RoomID, "members"}, client.WithQueries(map[string][]string{ "at": { prevBatch,