Skip to content
Closed
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
21 changes: 21 additions & 0 deletions internal/b/alice_and_bob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package b

// BlueprintAliceAndBob is a two user homeserver
var BlueprintAliceAndBob = MustValidate(Blueprint{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name: "alice_and_bob",
Homeservers: []Homeserver{
{
Name: "hs1",
Users: []User{
{
Localpart: "@alice",
DisplayName: "Alice",
},
{
Localpart: "@bob",
DisplayName: "Bob",
},
},
},
},
})
1 change: 1 addition & 0 deletions internal/b/blueprints.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
var KnownBlueprints = map[string]*Blueprint{
BlueprintCleanHS.Name: &BlueprintCleanHS,
BlueprintAlice.Name: &BlueprintAlice,
BlueprintAliceAndBob.Name: &BlueprintAliceAndBob,
BlueprintFederationOneToOneRoom.Name: &BlueprintFederationOneToOneRoom,
BlueprintFederationTwoLocalOneRemote.Name: &BlueprintFederationTwoLocalOneRemote,
BlueprintHSWithApplicationService.Name: &BlueprintHSWithApplicationService,
Expand Down
19 changes: 13 additions & 6 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,7 @@ func (c *CSAPI) InviteRoom(t *testing.T, roomID string, userID string) {
// Returns the event ID of the sent event.
func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string {
t.Helper()
c.txnID++
paths := []string{"_matrix", "client", "r0", "rooms", roomID, "send", e.Type, strconv.Itoa(c.txnID)}
if e.StateKey != nil {
paths = []string{"_matrix", "client", "r0", "rooms", roomID, "state", e.Type, *e.StateKey}
}
res := c.MustDo(t, "PUT", paths, e.Content)
res := c.SendEvent(t, roomID, e)
body := ParseJSON(t, res)
eventID := GetJSONFieldStr(t, body, "event_id")
t.Logf("SendEventSynced waiting for event ID %s", eventID)
Expand All @@ -127,6 +122,18 @@ func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string {
return eventID
}

// SendEventSynced sends `e` into the room.
func (c *CSAPI) SendEvent(t *testing.T, roomID string, e b.Event) *http.Response {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this into a helper function inside tests/client_power_levels_test.go and not inside the Complement CSAPI client struct.

The reason for this is because CSAPI should be reserved for functions which are commonly used across many tests, and this function is primarily only useful to check error conditions when sending events. In addition, by having both forms sit alongside each other in CSAPI, it confuses test authors who may prefer to use this function over SendEventSynced which would end up being disasterous as SendEventSynced is the only form which guards against race conditions when processing new events.

t.Helper()
c.txnID++
paths := []string{"_matrix", "client", "r0", "rooms", roomID, "send", e.Type, strconv.Itoa(c.txnID)}
if e.StateKey != nil {
paths = []string{"_matrix", "client", "r0", "rooms", roomID, "state", e.Type, *e.StateKey}
}
res := c.DoFunc(t, "PUT", paths, WithJSONBody(t, e.Content))
return res
}

// SyncUntilTimelineHas blocks and continually calls /sync until the `check` function returns true.
// If the `check` function fails the test, the failing event will be automatically logged.
// Will time out after CSAPI.SyncUntilTimeout.
Expand Down
10 changes: 5 additions & 5 deletions internal/must/must.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,30 +71,30 @@ func MatchResponse(t *testing.T, res *http.Response, m match.HTTPResponse) []byt
t.Helper()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("MatchResponse: Failed to read response body: %s", err)
t.Errorf("MatchResponse: Failed to read response body: %s", err)
}

contextStr := fmt.Sprintf("%s => %s", res.Request.URL.String(), string(body))

if m.StatusCode != 0 {
if res.StatusCode != m.StatusCode {
t.Fatalf("MatchResponse got status %d want %d - %s", res.StatusCode, m.StatusCode, contextStr)
t.Errorf("MatchResponse got status %d want %d - %s", res.StatusCode, m.StatusCode, contextStr)
}
}
if m.Headers != nil {
for name, val := range m.Headers {
if res.Header.Get(name) != val {
t.Fatalf("MatchResponse got %s: %s want %s - %s", name, res.Header.Get(name), val, contextStr)
t.Errorf("MatchResponse got %s: %s want %s - %s", name, res.Header.Get(name), val, contextStr)
}
}
}
if m.JSON != nil {
if !gjson.ValidBytes(body) {
t.Fatalf("MatchResponse response body is not valid JSON - %s", contextStr)
t.Errorf("MatchResponse response body is not valid JSON - %s", contextStr)
}
for _, jm := range m.JSON {
if err = jm(body); err != nil {
t.Fatalf("MatchResponse %s - %s", err, contextStr)
t.Errorf("MatchResponse %s - %s", err, contextStr)
Copy link
Copy Markdown
Member

@kegsay kegsay Jul 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not do this. It is intended for test assertions in the must package to fail the test immediately and not to continue executing.

}
}
}
Expand Down
124 changes: 124 additions & 0 deletions tests/client_power_levels_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package tests

import (
"math/big"
"testing"

"github.com/matrix-org/complement/internal/b"
"github.com/matrix-org/complement/internal/match"
"github.com/matrix-org/complement/internal/must"
)

func TestClientSendingPowerLevelsEvents(t *testing.T) {
deployment := Deploy(t, b.BlueprintAliceAndBob)
defer deployment.Destroy(t)

emptyStateKey := ""

// Create a client for one local user
aliceUserID := "@alice:hs1"
alice := deployment.Client(t, "hs1", aliceUserID)

// Create a client for another local user
bobUserID := "@bob:hs1"
bob := deployment.Client(t, "hs1", bobUserID)

t.Run("Attempt to set valid power levels event", func(t *testing.T) {
roomID := alice.CreateRoom(t, map[string]interface{}{
"preset": "public_chat",
})

alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.power_levels",
Sender: alice.UserID,
StateKey: &emptyStateKey,
Content: map[string]interface{}{
"ban": 50,
"events": map[string]interface{}{
"m.room.name": 100,
"m.room.power_levels": 100,
},
"events_default": 0,
"invite": 50,
"kick": 50,
"notifications": map[string]interface{}{
"room": 20,
},
"redact": 50,
"state_default": 50,
"users": map[string]interface{}{
alice.UserID: 100,
bob.UserID: 0,
},
"users_default": 0,
},
})
})

t.Run("Attempt to set power levels event with string power level fails", func(t *testing.T) {
roomID := alice.CreateRoom(t, map[string]interface{}{
"preset": "public_chat",
})

res := alice.SendEvent(t, roomID, b.Event{
Type: "m.room.power_levels",
Sender: alice.UserID,
StateKey: &emptyStateKey,
Content: map[string]interface{}{
"users_default": "30",
"users": map[string]interface{}{
alice.UserID: 100,
bob.UserID: 0,
},
"events": map[string]interface{}{
"m.room.power_levels": 100,
},
},
})
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 400,
JSON: []match.JSON{
match.JSONKeyEqual("errcode", "M_BAD_JSON"),
},
})
})

t.Run("Attempt to set power levels event with unsafe integer fails", func(t *testing.T) {
roomID := alice.CreateRoom(t, map[string]interface{}{
"preset": "public_chat",
"room_version": "5", // room version from before canonical JSON was enforced
})

var smallnum, _ = new(big.Int).SetString("-9007199254740992", 0)
res := alice.SendEvent(t, roomID, b.Event{
Type: "m.room.power_levels",
Sender: alice.UserID,
StateKey: &emptyStateKey,
Content: map[string]interface{}{
"users_default": smallnum,
},
})
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 400,
JSON: []match.JSON{
match.JSONKeyEqual("errcode", "M_BAD_JSON"),
},
})

var bignum, _ = new(big.Int).SetString("9007199254740992", 0)
res2 := alice.SendEvent(t, roomID, b.Event{
Type: "m.room.power_levels",
Sender: alice.UserID,
StateKey: &emptyStateKey,
Content: map[string]interface{}{
"users_default": bignum,
},
})
must.MatchResponse(t, res2, match.HTTPResponse{
StatusCode: 400,
JSON: []match.JSON{
match.JSONKeyEqual("errcode", "M_BAD_JSON"),
},
})
})
}