From 57edac79bf64a9a0125375539dc4264334956c26 Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Fri, 6 Jun 2025 09:24:53 +0100 Subject: [PATCH] Add (Must)GetStateEventContent It's used in enough places to warrant being a helper function. --- client/client.go | 16 +++++ tests/csapi/apidoc_room_create_test.go | 27 ++------ tests/csapi/apidoc_room_members_test.go | 45 +++----------- tests/csapi/power_levels_test.go | 83 +++++++++++-------------- tests/csapi/room_leave_test.go | 48 +++----------- tests/csapi/rooms_invite_test.go | 9 +-- tests/federation_acl_test.go | 15 ++--- tests/federation_room_join_test.go | 13 ++-- 8 files changed, 88 insertions(+), 168 deletions(-) diff --git a/client/client.go b/client/client.go index 7f281de0..d4d16581 100644 --- a/client/client.go +++ b/client/client.go @@ -355,6 +355,21 @@ func (c *CSAPI) SendRedaction(t ct.TestLike, roomID string, content map[string]i return c.Do(t, "PUT", paths, WithJSONBody(t, content)) } +// MustGetStateEvent returns the event content for the given state event. Fails the test if the state event does not exist. +func (c *CSAPI) MustGetStateEventContent(t ct.TestLike, roomID, eventType, stateKey string) (content gjson.Result) { + t.Helper() + res := c.GetStateEventContent(t, roomID, eventType, stateKey) + mustRespond2xx(t, res) + body := ParseJSON(t, res) + return gjson.ParseBytes(body) +} + +// GetStateEvent returns the event content for the given state event. Use this form to detect absence via 404. +func (c *CSAPI) GetStateEventContent(t ct.TestLike, roomID, eventType, stateKey string) *http.Response { + t.Helper() + return c.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey}) +} + // MustSendTyping marks this user as typing until the timeout is reached. If isTyping is false, timeout is ignored. func (c *CSAPI) MustSendTyping(t ct.TestLike, roomID string, isTyping bool, timeoutMillis int) { res := c.SendTyping(t, roomID, isTyping, timeoutMillis) @@ -822,6 +837,7 @@ func (c *CSAPI) SendToDeviceMessages(t ct.TestLike, evType string, messages map[ } func mustRespond2xx(t ct.TestLike, res *http.Response) { + t.Helper() if res.StatusCode >= 200 && res.StatusCode < 300 { return // 2xx } diff --git a/tests/csapi/apidoc_room_create_test.go b/tests/csapi/apidoc_room_create_test.go index 2fea3dbc..7abc9b51 100644 --- a/tests/csapi/apidoc_room_create_test.go +++ b/tests/csapi/apidoc_room_create_test.go @@ -59,13 +59,8 @@ func TestRoomCreate(t *testing.T) { "topic": "Test Room", "preset": "public_chat", }) - res := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.topic"}) - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyEqual("topic", "Test Room"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.topic", "") + must.MatchGJSON(t, content, match.JSONKeyEqual("topic", "Test Room")) }) // sytest: POST /createRoom makes a room with a name t.Run("POST /createRoom makes a room with a name", func(t *testing.T) { @@ -74,13 +69,8 @@ func TestRoomCreate(t *testing.T) { "name": "Test Room", "preset": "public_chat", }) - res := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.name"}) - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyEqual("name", "Test Room"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.name", "") + must.MatchGJSON(t, content, match.JSONKeyEqual("name", "Test Room")) }) // sytest: POST /createRoom creates a room with the given version t.Run("POST /createRoom creates a room with the given version", func(t *testing.T) { @@ -89,13 +79,8 @@ func TestRoomCreate(t *testing.T) { "room_version": "2", "preset": "public_chat", }) - res := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.create"}) - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyEqual("room_version", "2"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.create", "") + must.MatchGJSON(t, content, match.JSONKeyEqual("room_version", "2")) }) // sytest: POST /createRoom makes a private room with invites t.Run("POST /createRoom makes a private room with invites", func(t *testing.T) { diff --git a/tests/csapi/apidoc_room_members_test.go b/tests/csapi/apidoc_room_members_test.go index b62ef843..4d0bc1d3 100644 --- a/tests/csapi/apidoc_room_members_test.go +++ b/tests/csapi/apidoc_room_members_test.go @@ -157,14 +157,8 @@ func TestRoomMembers(t *testing.T) { }) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) - res = alice.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", bob.UserID}) - - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("foo", "bar"), - match.JSONKeyEqual("membership", "join"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", bob.UserID) + must.MatchGJSON(t, content, match.JSONKeyEqual("membership", "join"), match.JSONKeyEqual("foo", "bar")) }) // sytest: POST /join/:room_alias can join a room with custom content t.Run("POST /join/:room_alias can join a room with custom content", func(t *testing.T) { @@ -186,14 +180,8 @@ func TestRoomMembers(t *testing.T) { }) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) - res = alice.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", bob.UserID}) - - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("foo", "bar"), - match.JSONKeyEqual("membership", "join"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", bob.UserID) + must.MatchGJSON(t, content, match.JSONKeyEqual("membership", "join"), match.JSONKeyEqual("foo", "bar")) }) // sytest: POST /rooms/:room_id/ban can ban a user @@ -221,12 +209,8 @@ func TestRoomMembers(t *testing.T) { return ev.Get("content.membership").Str == "ban" })) // verify bob is banned - res = alice.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", bob.UserID}) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("membership", "ban"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", bob.UserID) + must.MatchGJSON(t, content, match.JSONKeyEqual("membership", "ban")) }) // sytest: POST /rooms/:room_id/invite can send an invite @@ -235,12 +219,8 @@ func TestRoomMembers(t *testing.T) { roomID := alice.MustCreateRoom(t, map[string]interface{}{}) alice.MustInviteRoom(t, roomID, bob.UserID) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) - res := alice.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", bob.UserID}) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("membership", "invite"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", bob.UserID) + must.MatchGJSON(t, content, match.JSONKeyEqual("membership", "invite")) }) // sytest: POST /rooms/:room_id/leave can leave a room @@ -253,13 +233,8 @@ func TestRoomMembers(t *testing.T) { alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) bob.MustLeaveRoom(t, roomID) alice.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(bob.UserID, roomID)) - - res := alice.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", bob.UserID}) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("membership", "leave"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", bob.UserID) + must.MatchGJSON(t, content, match.JSONKeyEqual("membership", "leave")) }) }) } diff --git a/tests/csapi/power_levels_test.go b/tests/csapi/power_levels_test.go index 15a76056..9ed2bd90 100644 --- a/tests/csapi/power_levels_test.go +++ b/tests/csapi/power_levels_test.go @@ -60,52 +60,47 @@ func TestPowerLevels(t *testing.T) { // sytest: GET /rooms/:room_id/state/m.room.power_levels can fetch levels t.Run("GET /rooms/:room_id/state/m.room.power_levels can fetch levels", func(t *testing.T) { // Test if the old state still exists - res := alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}) - // note: before v10 we technically cannot assume that powerlevel integers are json numbers, // as they can be both strings and numbers. // However, for this test, we control the test environment, // and we will assume the server is sane and give us powerlevels as numbers, // and if it doesn't, that's an offense worthy of a frown. + content := alice.MustGetStateEventContent(t, roomID, "m.room.power_levels", "") + must.MatchGJSON(t, content, + match.JSONKeyTypeEqual("ban", gjson.Number), + match.JSONKeyTypeEqual("kick", gjson.Number), + match.JSONKeyTypeEqual("redact", gjson.Number), + match.JSONKeyTypeEqual("state_default", gjson.Number), + match.JSONKeyTypeEqual("events_default", gjson.Number), + match.JSONKeyTypeEqual("users_default", gjson.Number), + + match.JSONMapEach("events", func(k, v gjson.Result) error { + if v.Type != gjson.Number { + return fmt.Errorf("key %s is not a number", k.Str) + } else { + return nil + } + }), - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyTypeEqual("ban", gjson.Number), - match.JSONKeyTypeEqual("kick", gjson.Number), - match.JSONKeyTypeEqual("redact", gjson.Number), - match.JSONKeyTypeEqual("state_default", gjson.Number), - match.JSONKeyTypeEqual("events_default", gjson.Number), - match.JSONKeyTypeEqual("users_default", gjson.Number), - - match.JSONMapEach("events", func(k, v gjson.Result) error { - if v.Type != gjson.Number { - return fmt.Errorf("key %s is not a number", k.Str) - } else { - return nil - } - }), - - match.JSONMapEach("users", func(k, v gjson.Result) error { - if v.Type != gjson.Number { - return fmt.Errorf("key %s is not a number", k.Str) - } else { - return nil - } - }), - - func(body gjson.Result) error { - userDefault := int(body.Get("users_default").Num) - thisUser := int(body.Get("users." + client.GjsonEscape(alice.UserID)).Num) - - if thisUser > userDefault { - return nil - } else { - return fmt.Errorf("expected room creator (%d) to have a higher-than-default powerlevel (which is %d)", thisUser, userDefault) - } - }, + match.JSONMapEach("users", func(k, v gjson.Result) error { + if v.Type != gjson.Number { + return fmt.Errorf("key %s is not a number", k.Str) + } else { + return nil + } + }), + + func(body gjson.Result) error { + userDefault := int(body.Get("users_default").Num) + thisUser := int(body.Get("users." + client.GjsonEscape(alice.UserID)).Num) + + if thisUser > userDefault { + return nil + } else { + return fmt.Errorf("expected room creator (%d) to have a higher-than-default powerlevel (which is %d)", thisUser, userDefault) + } }, - }) + ) }) // sytest: PUT /rooms/:room_id/state/m.room.power_levels can set levels @@ -172,13 +167,7 @@ func TestPowerLevels(t *testing.T) { }) // Test if the old state still exists - res = alice.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}) - - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyMissing("users"), - }, - }) + content := alice.MustGetStateEventContent(t, roomID, "m.room.power_levels", "") + must.MatchGJSON(t, content, match.JSONKeyMissing("users")) }) } diff --git a/tests/csapi/room_leave_test.go b/tests/csapi/room_leave_test.go index 1ea3e9a5..b56edc34 100644 --- a/tests/csapi/room_leave_test.go +++ b/tests/csapi/room_leave_test.go @@ -128,28 +128,12 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get rooms/{roomId}/state for a departed room (SPEC-216) t.Run("Can get rooms/{roomId}/state for a departed room", func(t *testing.T) { // Bob gets the old state - resp := bob.MustDo( - t, - "GET", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", madeUpStateKey}, - ) - must.MatchResponse(t, resp, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("body", beforeMadeUpState), - }, - }) + content := bob.MustGetStateEventContent(t, roomID, madeUpStateKey, "") + must.MatchGJSON(t, content, match.JSONKeyEqual("body", beforeMadeUpState)) // ...While Alice gets the new state - resp = alice.MustDo( - t, - "GET", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", madeUpStateKey}, - ) - must.MatchResponse(t, resp, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("body", afterMadeUpState), - }, - }) + content = alice.MustGetStateEventContent(t, roomID, madeUpStateKey, "") + must.MatchGJSON(t, content, match.JSONKeyEqual("body", afterMadeUpState)) }) // sytest: Can get rooms/{roomId}/members for a departed room (SPEC-216) @@ -205,28 +189,12 @@ func TestLeftRoomFixture(t *testing.T) { // sytest: Can get 'm.room.name' state for a departed room (SPEC-216) t.Run("Can get 'm.room.name' state for a departed room", func(t *testing.T) { // Bob gets the old name - resp := bob.MustDo( - t, - "GET", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.name"}, - ) - must.MatchResponse(t, resp, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("name", beforeRoomName), - }, - }) + content := bob.MustGetStateEventContent(t, roomID, "m.room.name", "") + must.MatchGJSON(t, content, match.JSONKeyEqual("name", beforeRoomName)) // ...While Alice gets the new name - resp = alice.MustDo( - t, - "GET", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.name"}, - ) - must.MatchResponse(t, resp, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("name", afterRoomName), - }, - }) + content = alice.MustGetStateEventContent(t, roomID, "m.room.name", "") + must.MatchGJSON(t, content, match.JSONKeyEqual("name", afterRoomName)) }) // sytest: Getting messages going forward is limited for a departed room (SPEC-216) diff --git a/tests/csapi/rooms_invite_test.go b/tests/csapi/rooms_invite_test.go index 6c0f1db0..2e0e5362 100644 --- a/tests/csapi/rooms_invite_test.go +++ b/tests/csapi/rooms_invite_test.go @@ -175,12 +175,7 @@ func verifyState(t *testing.T, res gjson.Result, roomID string, cl *client.CSAPI eventContent := event.Get("content." + field).Str eventStateKey := event.Get("state_key").Str - res := cl.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, eventStateKey}) - - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual(field, eventContent), - }, - }) + content := cl.MustGetStateEventContent(t, roomID, eventType, eventStateKey) + must.MatchGJSON(t, content, match.JSONKeyEqual(field, eventContent)) } } diff --git a/tests/federation_acl_test.go b/tests/federation_acl_test.go index 72b6094b..6e153952 100644 --- a/tests/federation_acl_test.go +++ b/tests/federation_acl_test.go @@ -99,14 +99,11 @@ func TestACLs(t *testing.T) { must.ContainSubset(t, events, []string{sentinelEventID}) // Validate the ACL event is actually in the rooms state - res := user.Do(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.server_acl"}) - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, - JSON: []match.JSON{ - match.JSONKeyEqual("allow", []string{"*"}), - match.JSONKeyEqual("deny", []string{"hs2"}), - match.JSONKeyEqual("allow_ip_literals", true), - }, - }) + content := user.MustGetStateEventContent(t, roomID, "m.room.server_acl", "") + must.MatchGJSON(t, content, + match.JSONKeyEqual("allow", []string{"*"}), + match.JSONKeyEqual("deny", []string{"hs2"}), + match.JSONKeyEqual("allow_ip_literals", true), + ) } } diff --git a/tests/federation_room_join_test.go b/tests/federation_room_join_test.go index 111bb028..fe62aafc 100644 --- a/tests/federation_room_join_test.go +++ b/tests/federation_room_join_test.go @@ -22,8 +22,8 @@ import ( "github.com/matrix-org/complement/b" "github.com/matrix-org/complement/client" - "github.com/matrix-org/complement/helpers" "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/runtime" @@ -352,15 +352,10 @@ func TestBannedUserCannotSendJoin(t *testing.T) { } // Alice checks the room state to check that charlie isn't a member - res := alice.MustDo( - t, - "GET", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.member", charlie}, + content := alice.MustGetStateEventContent(t, roomID, "m.room.member", charlie) + must.MatchGJSON(t, content, + match.JSONKeyEqual("membership", "ban"), ) - stateResp := must.ParseJSON(t, res.Body) - res.Body.Close() - membership := must.GetJSONFieldStr(t, stateResp, "membership") - must.Equal(t, membership, "ban", "membership of charlie") } // This test checks that we cannot submit anything via /v1/send_join except a join.