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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ complement
# For direnv users
/.envrc
.direnv/

# devenv users
.devenv/
35 changes: 34 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/url"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

Expand Down Expand Up @@ -48,6 +49,19 @@ type retryUntilParams struct {
// See functions starting with `With...` in this package for more info.
type RequestOpt func(req *http.Request)

type CSAPIOpts struct {
UserID string
AccessToken string
DeviceID string
Password string // if provided
BaseURL string
Client *http.Client
// how long are we willing to wait for MustSyncUntil.... calls
SyncUntilTimeout time.Duration
// True to enable verbose logging
Debug bool
}

type CSAPI struct {
UserID string
AccessToken string
Expand All @@ -60,7 +74,22 @@ type CSAPI struct {
// True to enable verbose logging
Debug bool

txnID int64
txnID int64
createRoomMutex *sync.Mutex
}

func NewCSAPI(opts CSAPIOpts) *CSAPI {
return &CSAPI{
UserID: opts.UserID,
AccessToken: opts.AccessToken,
DeviceID: opts.DeviceID,
Password: opts.Password,
BaseURL: opts.BaseURL,
Client: opts.Client,
SyncUntilTimeout: opts.SyncUntilTimeout,
Debug: opts.Debug,
createRoomMutex: &sync.Mutex{},
}
}

// CreateMedia creates an MXC URI for asynchronous media uploads.
Expand Down Expand Up @@ -172,6 +201,10 @@ func (c *CSAPI) MustCreateRoom(t ct.TestLike, reqBody map[string]interface{}) st
// CreateRoom creates a room with an optional HTTP request body.
func (c *CSAPI) CreateRoom(t ct.TestLike, body map[string]interface{}) *http.Response {
t.Helper()
// Ensure we don't call /createRoom from the same user in parallel, else we might try to make
// 2 rooms in the same millisecond (same `origin_server_ts`), causing v12 rooms to get the same room ID thus failing the test.
c.createRoomMutex.Lock()
defer c.createRoomMutex.Unlock()
return c.Do(t, "POST", []string{"_matrix", "client", "v3", "createRoom"}, WithJSONBody(t, body))
}

Expand Down
16 changes: 8 additions & 8 deletions internal/docker/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ func (d *Deployment) Register(t ct.TestLike, hsName string, opts helpers.Registr
ct.Fatalf(t, "Deployment.Register - HS name '%s' not found", hsName)
return nil
}
client := &client.CSAPI{
client := client.NewCSAPI(client.CSAPIOpts{
BaseURL: dep.BaseURL,
Client: client.NewLoggedClient(t, hsName, nil),
SyncUntilTimeout: 5 * time.Second,
Debug: d.Deployer.debugLogging,
Password: opts.Password,
}
})
// Appending a slice is not thread-safe. Protect the write with a mutex.
dep.CSAPIClientsMutex.Lock()
dep.CSAPIClients = append(dep.CSAPIClients, client)
Expand Down Expand Up @@ -155,13 +155,13 @@ func (d *Deployment) Login(t ct.TestLike, hsName string, existing *client.CSAPI,
if err != nil {
ct.Fatalf(t, "Deployment.Login: existing CSAPI client has invalid user ID '%s', cannot login as this user: %s", existing.UserID, err)
}
c := &client.CSAPI{
c := client.NewCSAPI(client.CSAPIOpts{
BaseURL: dep.BaseURL,
Client: client.NewLoggedClient(t, hsName, nil),
SyncUntilTimeout: 5 * time.Second,
Debug: d.Deployer.debugLogging,
Password: existing.Password,
}
})
if opts.Password != "" {
c.Password = opts.Password
}
Expand Down Expand Up @@ -197,12 +197,12 @@ func (d *Deployment) UnauthenticatedClient(t ct.TestLike, hsName string) *client
ct.Fatalf(t, "Deployment.Client - HS name '%s' not found", hsName)
return nil
}
client := &client.CSAPI{
client := client.NewCSAPI(client.CSAPIOpts{
BaseURL: dep.BaseURL,
Client: client.NewLoggedClient(t, hsName, nil),
SyncUntilTimeout: 5 * time.Second,
Debug: d.Deployer.debugLogging,
}
})
// Appending a slice is not thread-safe. Protect the write with a mutex.
dep.CSAPIClientsMutex.Lock()
dep.CSAPIClients = append(dep.CSAPIClients, client)
Expand Down Expand Up @@ -230,15 +230,15 @@ func (d *Deployment) AppServiceUser(t ct.TestLike, hsName, appServiceUserID stri
if deviceID == "" && appServiceUserID != "" {
t.Logf("WARNING: Deployment.Client - HS name '%s' - user ID '%s' - deviceID not found", hsName, appServiceUserID)
}
client := &client.CSAPI{
client := client.NewCSAPI(client.CSAPIOpts{
UserID: appServiceUserID,
AccessToken: token,
DeviceID: deviceID,
BaseURL: dep.BaseURL,
Client: client.NewLoggedClient(t, hsName, nil),
SyncUntilTimeout: 5 * time.Second,
Debug: d.Deployer.debugLogging,
}
})
// Appending a slice is not thread-safe. Protect the write with a mutex.
dep.CSAPIClientsMutex.Lock()
dep.CSAPIClients = append(dep.CSAPIClients, client)
Expand Down
46 changes: 42 additions & 4 deletions tests/csapi/apidoc_room_receipts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@ func createRoomForReadReceipts(t *testing.T, c *client.CSAPI) (string, string) {

c.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(c.UserID, roomID))

eventID := c.SendEventSynced(t, roomID, b.Event{
eventID := sendMessageIntoRoom(t, c, roomID)

return roomID, eventID
}

func sendMessageIntoRoom(t *testing.T, c *client.CSAPI, roomID string) string {
return c.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "Hello world!",
},
})

return roomID, eventID
}

func syncHasReadReceipt(roomID, userID, eventID string) client.SyncCheckOpt {
Expand All @@ -45,7 +49,41 @@ func TestRoomReceipts(t *testing.T) {
alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", eventID}, client.WithJSONBody(t, struct{}{}))

// Make sure the read receipt shows up in sync.
alice.MustSyncUntil(t, client.SyncReq{}, syncHasReadReceipt(roomID, alice.UserID, eventID))
sinceToken := alice.MustSyncUntil(t, client.SyncReq{}, syncHasReadReceipt(roomID, alice.UserID, eventID))

// Receipt events include a `room_id` field over federation, but they should
// not do so down `/sync` to clients. Ensure homeservers strip that field out.
t.Run("Receipts DO NOT include a `room_id` field", func(t *testing.T) {
// Send another event to read.
eventID2 := sendMessageIntoRoom(t, alice, roomID)

// Send a read receipt for the event.
alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", eventID2}, client.WithJSONBody(t, struct{}{}))

alice.MustSyncUntil(
t,
client.SyncReq{Since: sinceToken},
client.SyncEphemeralHas(roomID, func(r gjson.Result) bool {
// Check that this is a m.receipt ephemeral event.
if r.Get("type").Str != "m.receipt" {
return false
}

// Check that the receipt type is "m.read".
if !r.Get(`content.*.m\.read`).Exists() {
t.Fatalf("Receipt was not of type 'm.read'")
}

// Ensure that the `room_id` field does NOT exist.
if r.Get("room_id").Exists() {
t.Fatalf("Read receipt should not contain 'room_id' field when syncing but saw: %s", r.Raw)
}

// Exit the /sync loop.
return true;
}),
)
})
}

// sytest: POST /rooms/:room_id/read_markers can create read marker
Expand Down
1 change: 1 addition & 0 deletions tests/csapi/device_lists_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestDeviceListUpdates(t *testing.T) {
ed25519KeyID: ed25519Key,
curve25519KeyID: curve25519Key,
},
"signatures": map[string]interface{}{},
},
}),
)
Expand Down
25 changes: 25 additions & 0 deletions tests/csapi/room_typing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/matrix-org/complement"
"github.com/matrix-org/complement/client"
"github.com/matrix-org/complement/helpers"
"github.com/tidwall/gjson"
)

// sytest: PUT /rooms/:room_id/typing/:user_id sets typing notification
Expand Down Expand Up @@ -33,6 +34,30 @@ func TestTyping(t *testing.T) {
alice.SendTyping(t, roomID, false, 0)
bob.MustSyncUntil(t, client.SyncReq{Since: token}, client.SyncUsersTyping(roomID, []string{}))
})

// Typing events include a `room_id` field over federation, but they should
// not do so down `/sync` to clients. Ensure homeservers strip that field out.
t.Run("Typing events DO NOT include a `room_id` field", func(t *testing.T) {
alice.SendTyping(t, roomID, true, 0)

bob.MustSyncUntil(
t,
client.SyncReq{Since: token},
client.SyncEphemeralHas(roomID, func(r gjson.Result) bool {
if r.Get("type").Str != "m.typing" {
return false
}

// Ensure that the `room_id` field does NOT exist.
if r.Get("room_id").Exists() {
t.Fatalf("Typing event should not contain `room_id` field when syncing but saw: %s", r.Raw)
}

// Exit the /sync loop.
return true;
}),
)
})
}

// sytest: Typing notifications don't leak
Expand Down
8 changes: 6 additions & 2 deletions tests/federation_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ func TestACLs(t *testing.T) {
Content: map[string]interface{}{
"allow": []string{"*"},
"allow_ip_literals": true,
"deny": []string{"hs2"},
"deny": []string{
string(deployment.GetFullyQualifiedHomeserverName(t, "hs2")),
},
},
})
// wait for the ACL to show up on hs2
Expand Down Expand Up @@ -111,7 +113,9 @@ func TestACLs(t *testing.T) {
content := user.MustGetStateEventContent(t, roomID, "m.room.server_acl", "")
must.MatchGJSON(t, content,
match.JSONKeyEqual("allow", []string{"*"}),
match.JSONKeyEqual("deny", []string{"hs2"}),
match.JSONKeyEqual("deny", []string{
string(deployment.GetFullyQualifiedHomeserverName(t, "hs2")),
}),
match.JSONKeyEqual("allow_ip_literals", true),
)
}
Expand Down
14 changes: 13 additions & 1 deletion tests/federation_unreject_rejected_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package tests

import (
"context"
"encoding/json"
"testing"
"time"

"github.com/matrix-org/complement"
"github.com/matrix-org/complement/client"
"github.com/matrix-org/complement/federation"
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
)

Expand Down Expand Up @@ -65,7 +68,16 @@ func TestUnrejectRejectedEvents(t *testing.T) {
// Send event B into the room. Event A at this point is unknown
// to the homeserver and we're not going to respond to the events
// request for it, so it should get rejected.
srv.MustSendTransaction(t, deployment, deployment.GetFullyQualifiedHomeserverName(t, "hs1"), []json.RawMessage{eventB.JSON()}, nil)
fedClient := srv.FederationClient(deployment)
fedClient.SendTransaction(context.Background(), gomatrixserverlib.Transaction{
TransactionID: "complement1",
Origin: srv.ServerName(),
Destination: deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
OriginServerTS: spec.AsTimestamp(time.Now()),
PDUs: []json.RawMessage{
eventB.JSON(),
},
})

// Now we're going to send Event A into the room, which should give
// the server the prerequisite event to pass Event B later. This one
Expand Down