Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 103 additions & 32 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,67 +90,106 @@ func (c *CSAPI) DownloadContent(t TestLike, mxcUri string) ([]byte, string) {
return b, contentType
}

// CreateRoom creates a room with an optional HTTP request body. Fails the test on error. Returns the room ID.
func (c *CSAPI) CreateRoom(t TestLike, creationContent interface{}) string {
// MustCreateRoom creates a room with an optional HTTP request body. Fails the test on error. Returns the room ID.
func (c *CSAPI) MustCreateRoom(t TestLike, creationContent map[string]interface{}) string {
t.Helper()
res := c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "createRoom"}, WithJSONBody(t, creationContent))
res := c.CreateRoom(t, creationContent)
mustRespond2xx(t, res)
body := ParseJSON(t, res)
return GetJSONFieldStr(t, body, "room_id")
}

// JoinRoom joins the room ID or alias given, else fails the test. Returns the room ID.
func (c *CSAPI) JoinRoom(t TestLike, roomIDOrAlias string, serverNames []string) string {
// CreateRoom creates a room with an optional HTTP request body.
func (c *CSAPI) CreateRoom(t TestLike, creationContent map[string]interface{}) *http.Response {
t.Helper()
return c.Do(t, "POST", []string{"_matrix", "client", "v3", "createRoom"}, WithJSONBody(t, creationContent))
}

// MustJoinRoom joins the room ID or alias given, else fails the test. Returns the room ID.
func (c *CSAPI) MustJoinRoom(t TestLike, roomIDOrAlias string, serverNames []string) string {
t.Helper()
res := c.JoinRoom(t, roomIDOrAlias, serverNames)
mustRespond2xx(t, res)
// return the room ID if we joined with it
if roomIDOrAlias[0] == '!' {
return roomIDOrAlias
}
// otherwise we should be told the room ID if we joined via an alias
body := ParseJSON(t, res)
return GetJSONFieldStr(t, body, "room_id")
}

// JoinRoom joins the room ID or alias given. Returns the raw http response
func (c *CSAPI) JoinRoom(t TestLike, roomIDOrAlias string, serverNames []string) *http.Response {
t.Helper()
// construct URL query parameters
query := make(url.Values, len(serverNames))
for _, serverName := range serverNames {
query.Add("server_name", serverName)
}
// join the room
res := c.MustDo(
return c.Do(
t, "POST", []string{"_matrix", "client", "v3", "join", roomIDOrAlias},
WithQueries(query), WithJSONBody(t, map[string]interface{}{}),
)
// return the room ID if we joined with it
if roomIDOrAlias[0] == '!' {
return roomIDOrAlias
}
// otherwise we should be told the room ID if we joined via an alias
body := ParseJSON(t, res)
return GetJSONFieldStr(t, body, "room_id")
}

// LeaveRoom leaves the room ID, else fails the test.
func (c *CSAPI) LeaveRoom(t TestLike, roomID string) {
// MustLeaveRoom leaves the room ID, else fails the test.
func (c *CSAPI) MustLeaveRoom(t TestLike, roomID string) {
res := c.LeaveRoom(t, roomID)
mustRespond2xx(t, res)
}

// LeaveRoom leaves the room ID.
func (c *CSAPI) LeaveRoom(t TestLike, roomID string) *http.Response {
t.Helper()
// leave the room
body := map[string]interface{}{}
c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "leave"}, WithJSONBody(t, body))
return c.Do(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "leave"}, WithJSONBody(t, body))
}

// InviteRoom invites userID to the room ID, else fails the test.
func (c *CSAPI) MustInviteRoom(t TestLike, roomID string, userID string) {
t.Helper()
res := c.InviteRoom(t, roomID, userID)
mustRespond2xx(t, res)
}

// InviteRoom invites userID to the room ID, else fails the test.
func (c *CSAPI) InviteRoom(t TestLike, roomID string, userID string) {
func (c *CSAPI) InviteRoom(t TestLike, roomID string, userID string) *http.Response {
t.Helper()
// Invite the user to the room
body := map[string]interface{}{
"user_id": userID,
}
c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "invite"}, WithJSONBody(t, body))
return c.Do(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "invite"}, WithJSONBody(t, body))
}

func (c *CSAPI) MustGetGlobalAccountData(t TestLike, eventType string) *http.Response {
res := c.GetGlobalAccountData(t, eventType)
mustRespond2xx(t, res)
return res
}

func (c *CSAPI) GetGlobalAccountData(t TestLike, eventType string) *http.Response {
return c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType})
return c.Do(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType})
}

func (c *CSAPI) SetGlobalAccountData(t TestLike, eventType string, content map[string]interface{}) *http.Response {
func (c *CSAPI) MustSetGlobalAccountData(t TestLike, eventType string, content map[string]interface{}) *http.Response {
return c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType}, WithJSONBody(t, content))
}

func (c *CSAPI) MustGetRoomAccountData(t TestLike, roomID string, eventType string) *http.Response {
res := c.GetRoomAccountData(t, roomID, eventType)
mustRespond2xx(t, res)
return res
}

func (c *CSAPI) GetRoomAccountData(t TestLike, roomID string, eventType string) *http.Response {
return c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType})
return c.Do(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType})
}

func (c *CSAPI) SetRoomAccountData(t TestLike, roomID string, eventType string, content map[string]interface{}) *http.Response {
func (c *CSAPI) MustSetRoomAccountData(t TestLike, roomID string, eventType string, content map[string]interface{}) *http.Response {
return c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType}, WithJSONBody(t, content))
}

Expand Down Expand Up @@ -266,25 +305,38 @@ func (c *CSAPI) SendEventSynced(t TestLike, roomID string, e b.Event) string {
return eventID
}

// SendRedaction sends a redaction request. Will fail if the returned HTTP request code is not 200
func (c *CSAPI) SendRedaction(t TestLike, roomID string, e b.Event, eventID string) string {
// SendRedaction sends a redaction request. Will fail if the returned HTTP request code is not 200. Returns the
// event ID of the redaction event.
func (c *CSAPI) MustSendRedaction(t TestLike, roomID string, content map[string]interface{}, eventID string) string {
res := c.SendRedaction(t, roomID, content, eventID)
mustRespond2xx(t, res)
body := ParseJSON(t, res)
return GetJSONFieldStr(t, body, "event_id")
}

// SendRedaction sends a redaction request.
func (c *CSAPI) SendRedaction(t TestLike, roomID string, content map[string]interface{}, eventID string) *http.Response {
t.Helper()
txnID := int(atomic.AddInt64(&c.txnID, 1))
paths := []string{"_matrix", "client", "v3", "rooms", roomID, "redact", eventID, strconv.Itoa(txnID)}
res := c.MustDo(t, "PUT", paths, WithJSONBody(t, e.Content))
body := ParseJSON(t, res)
return GetJSONFieldStr(t, body, "event_id")
return c.Do(t, "PUT", paths, WithJSONBody(t, content))
}

// MustSendTyping marks this user as typing until the timeout is reached. If isTyping is false, timeout is ignored.
func (c *CSAPI) MustSendTyping(t TestLike, roomID string, isTyping bool, timeoutMillis int) {
res := c.SendTyping(t, roomID, isTyping, timeoutMillis)
mustRespond2xx(t, res)
}

// SendTyping marks this user as typing until the timeout is reached. If isTyping is false, timeout is ignored.
func (c *CSAPI) SendTyping(t TestLike, roomID string, isTyping bool, timeoutMillis int) {
func (c *CSAPI) SendTyping(t TestLike, roomID string, isTyping bool, timeoutMillis int) *http.Response {
content := map[string]interface{}{
"typing": isTyping,
}
if isTyping {
content["timeout"] = timeoutMillis
}
c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "typing", c.UserID}, WithJSONBody(t, content))
return c.Do(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "typing", c.UserID}, WithJSONBody(t, content))
}

// GetCapbabilities queries the server's capabilities
Expand All @@ -311,7 +363,7 @@ func (c *CSAPI) GetDefaultRoomVersion(t TestLike) gomatrixserverlib.RoomVersion
return gomatrixserverlib.RoomVersion(defaultVersion.Str)
}

func (c *CSAPI) GenerateOneTimeKeys(t TestLike, otkCount uint) (deviceKeys map[string]interface{}, oneTimeKeys map[string]interface{}) {
func (c *CSAPI) MustGenerateOneTimeKeys(t TestLike, otkCount uint) (deviceKeys map[string]interface{}, oneTimeKeys map[string]interface{}) {
t.Helper()
account := olm.NewAccount()
ed25519Key, curveKey := account.IdentityKeys()
Expand Down Expand Up @@ -641,10 +693,20 @@ func SplitMxc(mxcUri string) (string, string) {
//
// The messages parameter is nested as follows:
// user_id -> device_id -> content (map[string]interface{})
func (c *CSAPI) SendToDeviceMessages(t TestLike, evType string, messages map[string]map[string]map[string]interface{}) {
func (c *CSAPI) MustSendToDeviceMessages(t TestLike, evType string, messages map[string]map[string]map[string]interface{}) {
t.Helper()
res := c.SendToDeviceMessages(t, evType, messages)
mustRespond2xx(t, res)
}

// SendToDeviceMessages sends to-device messages over /sendToDevice/.
//
// The messages parameter is nested as follows:
// user_id -> device_id -> content (map[string]interface{})
func (c *CSAPI) SendToDeviceMessages(t TestLike, evType string, messages map[string]map[string]map[string]interface{}) (errRes *http.Response) {
t.Helper()
txnID := int(atomic.AddInt64(&c.txnID, 1))
c.MustDo(
return c.Do(
t,
"PUT",
[]string{"_matrix", "client", "v3", "sendToDevice", evType, strconv.Itoa(txnID)},
Expand All @@ -656,3 +718,12 @@ func (c *CSAPI) SendToDeviceMessages(t TestLike, evType string, messages map[str
),
)
}

func mustRespond2xx(t TestLike, res *http.Response) {
if res.StatusCode >= 200 && res.StatusCode < 300 {
return // 2xx
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
t.Fatalf("CSAPI.Must: %s %s returned non-2xx code: %s - body: %s", res.Request.Method, res.Request.URL.String(), res.Status, string(body))
}
21 changes: 18 additions & 3 deletions client/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package client

import (
"fmt"
"net/http"
"net/url"
"reflect"
"sort"
Expand Down Expand Up @@ -141,6 +142,18 @@ func (c *CSAPI) MustSyncUntil(t TestLike, syncReq SyncReq, checks ...SyncCheckOp
// Fails the test if the /sync request does not return 200 OK.
// Returns the top-level parsed /sync response JSON as well as the next_batch token from the response.
func (c *CSAPI) MustSync(t TestLike, syncReq SyncReq) (gjson.Result, string) {
t.Helper()
jsonBody, res := c.Sync(t, syncReq)
mustRespond2xx(t, res)
return jsonBody, jsonBody.Get("next_batch").Str
}

// Perform a single /sync request with the given request options. To sync until something happens,
// see `MustSyncUntil`.
//
// Always returns the HTTP response, even on non-2xx.
// Returns the top-level parsed /sync response JSON on 2xx.
func (c *CSAPI) Sync(t TestLike, syncReq SyncReq) (gjson.Result, *http.Response) {
t.Helper()
query := url.Values{
"timeout": []string{"1000"},
Expand All @@ -161,11 +174,13 @@ func (c *CSAPI) MustSync(t TestLike, syncReq SyncReq) (gjson.Result, string) {
if syncReq.SetPresence != "" {
query["set_presence"] = []string{syncReq.SetPresence}
}
res := c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "sync"}, WithQueries(query))
res := c.Do(t, "GET", []string{"_matrix", "client", "v3", "sync"}, WithQueries(query))
if res.StatusCode < 200 || res.StatusCode >= 300 {
return gjson.Result{}, res
}
body := ParseJSON(t, res)
result := gjson.ParseBytes(body)
nextBatch := GetJSONFieldStr(t, body, "next_batch")
return result, nextBatch
return result, res
}

// Check that the timeline for `roomID` has an event which passes the check function.
Expand Down
18 changes: 9 additions & 9 deletions tests/csapi/account_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ func TestAddAccountData(t *testing.T) {
// sytest: Can get account data without syncing
t.Run("Can add global account data", func(t *testing.T) {
// Set the account data entry
alice.SetGlobalAccountData(t, "test.key", map[string]interface{}{"value": "first"})
alice.MustSetGlobalAccountData(t, "test.key", map[string]interface{}{"value": "first"})

// check that getting the account data returns the correct value
must.MatchResponse(t, alice.GetGlobalAccountData(t, "test.key"), match.HTTPResponse{
must.MatchResponse(t, alice.MustGetGlobalAccountData(t, "test.key"), match.HTTPResponse{
JSON: []match.JSON{
match.JSONKeyEqual("value", "first"),
},
})

// Set it to something else
alice.SetGlobalAccountData(t, "test.key", map[string]interface{}{"value": "second"})
alice.MustSetGlobalAccountData(t, "test.key", map[string]interface{}{"value": "second"})

// check that getting the account data returns the updated value
must.MatchResponse(t, alice.GetGlobalAccountData(t, "test.key"), match.HTTPResponse{
must.MatchResponse(t, alice.MustGetGlobalAccountData(t, "test.key"), match.HTTPResponse{
JSON: []match.JSON{
match.JSONKeyEqual("value", "second"),
},
Expand All @@ -42,23 +42,23 @@ func TestAddAccountData(t *testing.T) {
// sytest: Can get room account data without syncing
t.Run("Can add room account data", func(t *testing.T) {
// Create a room
roomID := alice.CreateRoom(t, map[string]interface{}{})
roomID := alice.MustCreateRoom(t, map[string]interface{}{})

// Set the room account data entry
alice.SetRoomAccountData(t, roomID, "test.key", map[string]interface{}{"value": "room first"})
alice.MustSetRoomAccountData(t, roomID, "test.key", map[string]interface{}{"value": "room first"})

// check that getting the account data returns the correct value
must.MatchResponse(t, alice.GetRoomAccountData(t, roomID, "test.key"), match.HTTPResponse{
must.MatchResponse(t, alice.MustGetRoomAccountData(t, roomID, "test.key"), match.HTTPResponse{
JSON: []match.JSON{
match.JSONKeyEqual("value", "room first"),
},
})

// Set it to something else
alice.SetRoomAccountData(t, roomID, "test.key", map[string]interface{}{"value": "room second"})
alice.MustSetRoomAccountData(t, roomID, "test.key", map[string]interface{}{"value": "room second"})

// check that getting the account data returns the updated value
must.MatchResponse(t, alice.GetRoomAccountData(t, roomID, "test.key"), match.HTTPResponse{
must.MatchResponse(t, alice.MustGetRoomAccountData(t, roomID, "test.key"), match.HTTPResponse{
JSON: []match.JSON{
match.JSONKeyEqual("value", "room second"),
},
Expand Down
8 changes: 4 additions & 4 deletions tests/csapi/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (

"github.com/tidwall/gjson"

"github.com/matrix-org/complement/client"
"github.com/matrix-org/complement/b"
"github.com/matrix-org/complement/client"
"github.com/matrix-org/complement/match"
"github.com/matrix-org/complement/must"
)
Expand Down Expand Up @@ -55,7 +55,7 @@ func TestServerNotices(t *testing.T) {
roomID = syncUntilInvite(t, alice)
})
t.Run("Alice cannot reject the invite", func(t *testing.T) {
res := alice.Do(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "leave"})
res := alice.LeaveRoom(t, roomID)
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: http.StatusForbidden,
JSON: []match.JSON{
Expand All @@ -64,11 +64,11 @@ func TestServerNotices(t *testing.T) {
})
})
t.Run("Alice can join the alert room", func(t *testing.T) {
alice.JoinRoom(t, roomID, []string{})
alice.MustJoinRoom(t, roomID, []string{})
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(roomID, eventID))
})
t.Run("Alice can leave the alert room, after joining it", func(t *testing.T) {
alice.LeaveRoom(t, roomID)
alice.MustLeaveRoom(t, roomID)
})
t.Run("After leaving the alert room and on re-invitation, no new room is created", func(t *testing.T) {
sendServerNotice(t, admin, reqBody, nil)
Expand Down
Loading