-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Convert more tests to Complement #353
Changes from 5 commits
517076c
e95787d
bced719
7d5b661
abd890e
6e6b895
31a00b1
f5fcf4a
fd714b5
4cd926c
a5b7bd6
acea6ce
d6d98fe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package csapi_tests | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/tidwall/gjson" | ||
"github.com/tidwall/sjson" | ||
|
||
"github.com/matrix-org/complement/internal/b" | ||
"github.com/matrix-org/complement/internal/client" | ||
"github.com/matrix-org/complement/internal/match" | ||
"github.com/matrix-org/complement/internal/must" | ||
) | ||
|
||
func TestRoomLevels(t *testing.T) { | ||
deployment := Deploy(t, b.BlueprintAlice) | ||
defer deployment.Destroy(t) | ||
alice := deployment.Client(t, "hs1", "@alice:hs1") | ||
|
||
successCount := 0 | ||
defer func() { | ||
// sytest: Both GET and PUT work | ||
if successCount != 2 { | ||
t.Fatalf("expected GET and PUT to work") | ||
} | ||
}() | ||
t.Run("Parallel", func(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) { | ||
t.Parallel() | ||
roomID := alice.CreateRoom(t, map[string]interface{}{}) | ||
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) | ||
res := alice.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}) | ||
|
||
body := gjson.ParseBytes(must.ParseJSON(t, res.Body)) | ||
requiredFields := []string{"ban", "kick", "redact", "state_default", "events_default", "events", "users"} | ||
for i := range requiredFields { | ||
if !body.Get(requiredFields[i]).Exists() { | ||
t.Fatalf("expected json field %s, but it does not exist", requiredFields[i]) | ||
} | ||
} | ||
users := body.Get("users").Map() | ||
alicePowerLevel, ok := users[alice.UserID] | ||
if !ok { | ||
t.Fatalf("Expected room creator (%s) to exist in user powerlevel list", alice.UserID) | ||
} | ||
|
||
userDefaults := body.Get("user_defaults").Int() | ||
|
||
if userDefaults > alicePowerLevel.Int() { | ||
t.Fatalf("Expected room creator to have a higher-than-default powerlevel") | ||
} | ||
successCount++ | ||
}) | ||
// sytest: PUT /rooms/:room_id/state/m.room.power_levels can set levels | ||
t.Run("PUT /rooms/:room_id/state/m.room.power_levels can set levels", func(t *testing.T) { | ||
t.Parallel() | ||
roomID := alice.CreateRoom(t, map[string]interface{}{}) | ||
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) | ||
res := alice.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}) | ||
|
||
powerLevels := gjson.ParseBytes(must.ParseJSON(t, res.Body)) | ||
changedUser := client.GjsonEscape("@random-other-user:their.home") | ||
alicePowerLevel := powerLevels.Get("users." + alice.UserID).Int() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically |
||
pl := map[string]int64{ | ||
alice.UserID: alicePowerLevel, | ||
"@random-other-user:their.home": 20, | ||
} | ||
newPowerlevels, err := sjson.Set(powerLevels.Str, "users", pl) | ||
if err != nil { | ||
t.Fatalf("unable to update powerlevel JSON") | ||
} | ||
reqBody := client.WithRawBody([]byte(newPowerlevels)) | ||
alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}, reqBody) | ||
res = alice.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}) | ||
powerLevels = gjson.ParseBytes(must.ParseJSON(t, res.Body)) | ||
if powerLevels.Get("users."+changedUser).Int() != 20 { | ||
t.Fatal("Expected to have set other user's level to 20") | ||
} | ||
successCount++ | ||
}) | ||
// sytest: PUT power_levels should not explode if the old power levels were empty | ||
t.Run("PUT power_levels should not explode if the old power levels were empty", func(t *testing.T) { | ||
t.Parallel() | ||
roomID := alice.CreateRoom(t, map[string]interface{}{}) | ||
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) | ||
|
||
reqBody := client.WithJSONBody(t, map[string]interface{}{ | ||
"users": map[string]int64{ | ||
alice.UserID: 100, | ||
}, | ||
}) | ||
alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}, reqBody) | ||
// absence of a 'users' key | ||
reqBody = client.WithJSONBody(t, map[string]interface{}{}) | ||
alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}, reqBody) | ||
// this should now give a 403 (not a 500) | ||
res := alice.DoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "state", "m.room.power_levels"}, reqBody) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't what the sytest is doing. # absence of an 'events' key
matrix_put_room_state(
$user,
$room_id,
type => "m.room.power_levels",
state_key => "",
content => {
users => {
$user->user_id => 100,
},
},
)->then( sub {
# absence of a 'users' key
matrix_put_room_state(
$user,
$room_id,
type => "m.room.power_levels",
state_key => "",
content => {
},
);
})->then( sub {
# this should now give a 403 (not a 500)
matrix_put_room_state(
$user,
$room_id,
type => "m.room.power_levels",
state_key => "",
content => {
users => {},
},
) -> main::expect_http_403;
}) So the order is:
|
||
must.MatchResponse(t, res, match.HTTPResponse{ | ||
StatusCode: http.StatusForbidden, | ||
}) | ||
}) | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package csapi_tests | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
"testing" | ||
|
||
"github.com/matrix-org/gomatrixserverlib" | ||
"github.com/tidwall/gjson" | ||
|
||
"github.com/matrix-org/complement/internal/b" | ||
"github.com/matrix-org/complement/internal/client" | ||
"github.com/matrix-org/complement/internal/must" | ||
) | ||
|
||
func TestRoomVersions(t *testing.T) { | ||
deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) | ||
defer deployment.Destroy(t) | ||
|
||
alice := deployment.Client(t, "hs1", "@alice:hs1") | ||
bob := deployment.Client(t, "hs1", "@bob:hs1") | ||
charlie := deployment.Client(t, "hs2", "@charlie:hs2") | ||
|
||
roomVersions := gomatrixserverlib.RoomVersions() | ||
|
||
t.Run("Parallel", func(t *testing.T) { | ||
// iterate over all room versions | ||
for v := range roomVersions { | ||
roomVersion := v | ||
// sytest: User can create and send/receive messages in a room with version $version | ||
t.Run(fmt.Sprintf("User can create and send/receive messages in a room with version %s", roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
}) | ||
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't the whole point of the |
||
|
||
res, _ := alice.MustSync(t, client.SyncReq{}) | ||
room := res.Get("rooms.join." + client.GjsonEscape(roomID)) | ||
ev0 := room.Get("timeline.events").Array()[0] | ||
must.EqualStr(t, ev0.Get("type").Str, "m.room.create", "not a m.room.create event") | ||
sendMessageSynced(t, alice, roomID) | ||
}) | ||
|
||
userTypes := map[string]*client.CSAPI{ | ||
"local": bob, | ||
"remote": charlie, | ||
} | ||
for typ, joiner := range userTypes { | ||
typ := typ | ||
joiner := joiner | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need comments to explain why you do this (else it'll take the last value only). |
||
|
||
// sytest: $user_type user can join room with version $version | ||
t.Run(fmt.Sprintf("%s user can join room with version %s", typ, roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomAlias := fmt.Sprintf("roomAlias_V%s%s", typ, roomVersion) | ||
t.Logf("RoomAlias: %s", roomAlias) | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
"room_alias_name": roomAlias, | ||
"preset": "public_chat", | ||
}) | ||
joinRoomSynced(t, joiner, roomID, fmt.Sprintf("#%s:%s", roomAlias, "hs1")) | ||
_, nextBatch := joiner.MustSync(t, client.SyncReq{}) | ||
eventID := sendMessageSynced(t, alice, roomID) | ||
joiner.MustSyncUntil(t, client.SyncReq{Since: nextBatch}, client.SyncTimelineHas(roomID, func(result gjson.Result) bool { | ||
if len(result.Array()) > 1 { | ||
t.Fatal("Expected a single timeline event") | ||
} | ||
must.EqualStr(t, result.Array()[0].Get("event_id").Str, eventID, "wrong event id") | ||
return true | ||
})) | ||
}) | ||
|
||
// sytest: User can invite $user_type user to room with version $version | ||
t.Run(fmt.Sprintf("User can invite %s user to room with version %s", typ, roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
"preset": "private_chat", | ||
}) | ||
alice.InviteRoom(t, roomID, joiner.UserID) | ||
joiner.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(joiner.UserID, roomID)) | ||
joinRoomSynced(t, joiner, roomID, "") | ||
_, nextBatch := joiner.MustSync(t, client.SyncReq{}) | ||
eventID := sendMessageSynced(t, alice, roomID) | ||
joiner.MustSyncUntil(t, client.SyncReq{Since: nextBatch}, client.SyncTimelineHas(roomID, func(result gjson.Result) bool { | ||
if len(result.Array()) > 1 { | ||
t.Fatal("Expected a single timeline event") | ||
} | ||
must.EqualStr(t, result.Array()[0].Get("event_id").Str, eventID, "wrong event id") | ||
return true | ||
})) | ||
}) | ||
|
||
} | ||
|
||
// sytest: Remote user can backfill in a room with version $version | ||
t.Run(fmt.Sprintf("Remote user can backfill in a room with version %s", roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
"invite": []string{charlie.UserID}, | ||
}) | ||
for i := 0; i < 20; i++ { | ||
sendMessageSynced(t, alice, roomID) | ||
} | ||
charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(charlie.UserID, roomID)) | ||
joinRoomSynced(t, charlie, roomID, "") | ||
|
||
queryParams := url.Values{} | ||
queryParams.Set("dir", "b") | ||
queryParams.Set("limit", "6") | ||
res := charlie.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(queryParams)) | ||
body := gjson.ParseBytes(must.ParseJSON(t, res.Body)) | ||
defer res.Body.Close() | ||
if len(body.Get("chunk").Array()) != 6 { | ||
t.Fatal("Expected 6 messages") | ||
} | ||
}) | ||
|
||
// sytest: Can reject invites over federation for rooms with version $version | ||
t.Run(fmt.Sprintf("Can reject invites over federation for rooms with version %s", roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
"invite": []string{charlie.UserID}, | ||
}) | ||
charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(charlie.UserID, roomID)) | ||
charlie.LeaveRoom(t, roomID) | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No check for alice to see that the invite was rejected? |
||
|
||
// sytest: Can receive redactions from regular users over federation in room version $version | ||
t.Run(fmt.Sprintf("Can receive redactions from regular users over federation in room version %s", roomVersion), func(t *testing.T) { | ||
t.Parallel() | ||
roomID := createRoomSynced(t, alice, map[string]interface{}{ | ||
"room_version": roomVersion, | ||
"invite": []string{charlie.UserID}, | ||
}) | ||
charlie.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(charlie.UserID, roomID)) | ||
joinRoomSynced(t, charlie, roomID, "") | ||
eventID := sendMessageSynced(t, charlie, roomID) | ||
// redact the message | ||
res := charlie.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "redact", eventID}, client.WithRawBody([]byte("{}"))) | ||
js := must.ParseJSON(t, res.Body) | ||
defer res.Body.Close() | ||
redactID := must.GetJSONFieldStr(t, js, "event_id") | ||
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHas(roomID, func(result gjson.Result) bool { | ||
return redactID == result.Get("event_id").Str | ||
})) | ||
// query messages | ||
queryParams := url.Values{} | ||
queryParams.Set("dir", "b") | ||
res = alice.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(queryParams)) | ||
body := gjson.ParseBytes(must.ParseJSON(t, res.Body)) | ||
defer res.Body.Close() | ||
events := body.Get("chunk").Array() | ||
// first event should be the redaction | ||
must.EqualStr(t, events[0].Get("event_id").Str, redactID, "wrong event") | ||
must.EqualStr(t, events[0].Get("redacts").Str, eventID, "wrong event") | ||
// second event should be the original event | ||
must.EqualStr(t, events[1].Get("event_id").Str, eventID, "wrong event") | ||
must.EqualStr(t, events[1].Get("unsigned.redacted_by").Str, redactID, "wrong event") | ||
}) | ||
} | ||
}) | ||
} | ||
|
||
func sendMessageSynced(t *testing.T, cl *client.CSAPI, roomID string) (eventID string) { | ||
return cl.SendEventSynced(t, roomID, b.Event{ | ||
Type: "m.room.message", | ||
Content: map[string]interface{}{ | ||
"msgtype": "m.text", | ||
"body": "hello world", | ||
}, | ||
}) | ||
} | ||
|
||
func joinRoomSynced(t *testing.T, cl *client.CSAPI, roomID, alias string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These should probably go into the Client impl to be honest. For now leave them here though. |
||
joinRoom := roomID | ||
if alias != "" { | ||
joinRoom = alias | ||
} | ||
cl.JoinRoom(t, joinRoom, []string{}) | ||
cl.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(cl.UserID, roomID)) | ||
} | ||
|
||
func createRoomSynced(t *testing.T, c *client.CSAPI, content map[string]interface{}) (roomID string) { | ||
t.Helper() | ||
roomID = c.CreateRoom(t, content) | ||
c.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(c.UserID, roomID)) | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this necessary?
t.Run("Parallel", ...
will block until the tests complete. Each test can fail the subtest which will have a knock-on effect and fail the outer test, so this seems entirely redundant? We never do this anywhere in Complement AFAICT.