-
Notifications
You must be signed in to change notification settings - Fork 69
Add some tests for push rule behavior on room upgrade #819
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
Changes from 12 commits
07387a0
3fdbd02
cc58ec9
1167cee
d53a307
e00c1c0
fd90c90
ad2a7ea
5ebcc9f
e71b03e
4fb255c
369250a
3b4ca0b
6e2c6f2
10755c2
a7da686
7a601fa
201a93f
126cc9b
32aff46
bae2b74
87d6ef0
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,281 @@ | ||
| package csapi_tests | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/matrix-org/complement" | ||
| "github.com/matrix-org/complement/client" | ||
| "github.com/matrix-org/complement/helpers" | ||
| "github.com/matrix-org/complement/match" | ||
| "github.com/matrix-org/complement/must" | ||
| "github.com/matrix-org/complement/runtime" | ||
| "github.com/matrix-org/gomatrixserverlib/spec" | ||
| "github.com/tidwall/gjson" | ||
| ) | ||
|
|
||
| func TestPushRuleRoomUpgrade(t *testing.T) { | ||
| deployment := complement.Deploy(t, 2) | ||
| defer deployment.Destroy(t) | ||
|
|
||
| alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{ | ||
| LocalpartSuffix: "alice", | ||
| }) | ||
| alice2 := deployment.Register(t, "hs1", helpers.RegistrationOpts{ | ||
| LocalpartSuffix: "alice2", | ||
| }) | ||
| bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{ | ||
| LocalpartSuffix: "bob", | ||
| }) | ||
| bob2 := deployment.Register(t, "hs2", helpers.RegistrationOpts{ | ||
| LocalpartSuffix: "bob2", | ||
| }) | ||
|
|
||
| t.Run("parallel", func(t *testing.T) { | ||
| for _, useManualRoomUpgrade := range []bool{true, false} { | ||
| upgradeDescritorPrefix := "" | ||
| if useManualRoomUpgrade { | ||
| upgradeDescritorPrefix = "manually " | ||
| } | ||
|
|
||
| // When a homeserver becomes aware of a room upgrade (upgrade is done on local | ||
| // homeserver), it should copy over any existing push rules for all of its local users | ||
| // from the old room to the new room at the time of upgrade. | ||
| t.Run(upgradeDescritorPrefix+"upgrading a room carries over existing push rules for local users", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // FIXME: We have to skip this test on Synapse until | ||
| // https://github.com/element-hq/synapse/issues/19199 is resolved. | ||
| if useManualRoomUpgrade { | ||
| runtime.SkipIf(t, runtime.Synapse) | ||
| } | ||
|
Comment on lines
+57
to
+61
Collaborator
Author
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. We have to skip with Synapse until element-hq/synapse#19199 is resolved ⏩ |
||
|
|
||
| // Create a room | ||
| roomID := alice.MustCreateRoom(t, map[string]interface{}{ | ||
| "preset": "public_chat", | ||
| "room_version": "10", | ||
| }) | ||
| // Have alice2 join the room | ||
| // | ||
| // We use two users to ensure that all users get taken care of (not just the person | ||
| // upgrading the room). | ||
| alice2.MustJoinRoom(t, roomID, nil) | ||
|
|
||
| // Add some push rules | ||
| alice.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "pushrules", "global", "room", roomID}, | ||
| client.WithJSONBody(t, map[string]interface{}{ | ||
| "actions": []string{"dont_notify"}, | ||
| }), | ||
| ) | ||
| alice2.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "pushrules", "global", "room", roomID}, | ||
| client.WithJSONBody(t, map[string]interface{}{ | ||
| "actions": []string{"dont_notify"}, | ||
| }), | ||
| ) | ||
|
|
||
| // Sanity check the push rules are in the expected state before the upgrade | ||
| for _, client := range []*client.CSAPI{alice, alice2} { | ||
| t.Logf("Checking push rules (before upgrade) for %s", client.UserID) | ||
| pushRulesBefore := client.GetAllPushRules(t) | ||
| must.MatchGJSON(t, pushRulesBefore, | ||
| match.JSONCheckOff("global.room", | ||
| []interface{}{ | ||
| roomID, | ||
| }, | ||
| match.CheckOffAllowUnwanted(), | ||
| match.CheckOffMapper(func(r gjson.Result) interface{} { return r.Get("rule_id").Str }), | ||
| match.CheckOffForEach(func(roomIDFromPushRule interface{}, result gjson.Result) error { | ||
| return match.JSONKeyEqual("actions.0", "dont_notify")(result) | ||
| }), | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| // Upgrade the room | ||
| var newRoomID string | ||
| if useManualRoomUpgrade { | ||
| newRoomID = mustManualUpgradeRoom(t, alice, roomID, "11") | ||
| } else { | ||
| newRoomID = alice.MustUpgradeRoom(t, roomID, "11") | ||
| } | ||
| t.Logf("Upgraded room %s to %s", roomID, newRoomID) | ||
|
|
||
| // Alice2 joins the new room | ||
| alice2.MustJoinRoom(t, newRoomID, nil) | ||
|
|
||
| // Sanity check the push rules are in the expected state after the upgrade | ||
| for _, client := range []*client.CSAPI{alice, alice2} { | ||
| t.Logf("Checking push rules (after upgrade) for %s", client.UserID) | ||
| pushRulesAfter := client.GetAllPushRules(t) | ||
| must.MatchGJSON(t, pushRulesAfter, | ||
| match.JSONCheckOff("global.room", | ||
| []interface{}{ | ||
| roomID, | ||
| newRoomID, | ||
| }, | ||
| match.CheckOffAllowUnwanted(), | ||
| match.CheckOffMapper(func(r gjson.Result) interface{} { return r.Get("rule_id").Str }), | ||
| match.CheckOffForEach(func(roomIDFromPushRule interface{}, result gjson.Result) error { | ||
| return match.JSONKeyEqual("actions.0", "dont_notify")(result) | ||
| }), | ||
| ), | ||
| ) | ||
| } | ||
| }) | ||
|
|
||
| // When a homeserver becomes aware of a room upgrade (upgrade is done on remote | ||
| // homeserver), it should copy over any existing push rules for all of its local users | ||
| // from the old room to the new room at the time of upgrade. | ||
| t.Run("joining a remote "+upgradeDescritorPrefix+"upgraded room carries over existing push rules", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| // Start a sync loop | ||
| _, bobSince := bob.MustSync(t, client.SyncReq{TimeoutMillis: "0"}) | ||
|
|
||
| // Alice create a room | ||
| roomID := alice.MustCreateRoom(t, map[string]interface{}{ | ||
| "preset": "public_chat", | ||
| "room_version": "10", | ||
| }) | ||
| // Remote bob joins the room | ||
| bob.MustJoinRoom(t, roomID, []spec.ServerName{ | ||
| deployment.GetFullyQualifiedHomeserverName(t, "hs1"), | ||
| }) | ||
| // Wait until the homeserver is fully participating in the room so that we can | ||
| // double-check the subsequent joins also work (sanity check participating vs | ||
| // non-participating logic in the homeserver) | ||
| bob.MustAwaitPartialStateJoinCompletion(t, roomID) | ||
| // Wait until we know the first bob is joined for sure. We want to make sure bob2 | ||
| // doesn't also race us to remotely join the room as bob2 should be able to | ||
| // locally join and then send a join over federation (because the first bob is | ||
| // already joined to the room). | ||
| bobSince = bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncJoinedTo(bob.UserID, roomID)) | ||
| // Remote bob2 joins the room | ||
| // | ||
| // We use two users to ensure that all users get taken care of (not just the first | ||
| // user on the homeserver). | ||
| bob2.MustJoinRoom(t, roomID, | ||
| // bob2 can do a local join since bob is already in the room. No need to specify | ||
| // via servers here. | ||
| // | ||
| // []spec.ServerName{ | ||
| // deployment.GetFullyQualifiedHomeserverName(t, "hs1"), | ||
| // }, | ||
| nil, | ||
| ) | ||
|
|
||
| // Add some push rules | ||
| bob.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "pushrules", "global", "room", roomID}, | ||
| client.WithJSONBody(t, map[string]interface{}{ | ||
| "actions": []string{"dont_notify"}, | ||
| }), | ||
| ) | ||
| bob2.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "pushrules", "global", "room", roomID}, | ||
| client.WithJSONBody(t, map[string]interface{}{ | ||
| "actions": []string{"dont_notify"}, | ||
| }), | ||
| ) | ||
|
|
||
| // Sanity check the push rules are in the expected state before the upgrade | ||
| for _, client := range []*client.CSAPI{bob, bob2} { | ||
| t.Logf("Checking push rules (before upgrade) for %s", client.UserID) | ||
| pushRulesBefore := client.GetAllPushRules(t) | ||
| must.MatchGJSON(t, pushRulesBefore, | ||
| match.JSONCheckOff("global.room", | ||
| []interface{}{ | ||
| roomID, | ||
| }, | ||
| match.CheckOffAllowUnwanted(), | ||
| match.CheckOffMapper(func(r gjson.Result) interface{} { return r.Get("rule_id").Str }), | ||
| match.CheckOffForEach(func(roomIDFromPushRule interface{}, result gjson.Result) error { | ||
| return match.JSONKeyEqual("actions.0", "dont_notify")(result) | ||
| }), | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| // Upgrade the room | ||
| var newRoomID string | ||
| if useManualRoomUpgrade { | ||
| newRoomID = mustManualUpgradeRoom(t, alice, roomID, "11") | ||
| } else { | ||
| newRoomID = alice.MustUpgradeRoom(t, roomID, "11") | ||
| } | ||
| t.Logf("Upgraded room %s to %s", roomID, newRoomID) | ||
|
|
||
| // Ensure that the remote server sees the tombstone in the old room before | ||
| // joining the new room (avoid races and the client woudn't know where to go | ||
| // without this hint anyway) | ||
| bobSince = bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncTimelineHas(roomID, func(ev gjson.Result) bool { | ||
| return ev.Get("type").Str == "m.room.tombstone" && ev.Get("state_key").Str == "" | ||
| })) | ||
|
|
||
| // Remote bob joins the new room | ||
| bob.MustJoinRoom(t, newRoomID, []spec.ServerName{ | ||
| deployment.GetFullyQualifiedHomeserverName(t, "hs1"), | ||
| }) | ||
| // Wait until the homeserver is fully participating in the room so that we can | ||
| // double-check the subsequent joins also work (sanity check participating vs | ||
| // non-participating logic in the homeserver) | ||
| bob.MustAwaitPartialStateJoinCompletion(t, newRoomID) | ||
| // Wait until we know the first bob is joined for sure. We want to make sure bob2 | ||
| // doesn't also race us to remotely join the room as bob2 should be able to | ||
| // locally join and then send a join over federation (because the first bob is | ||
| // already joined to the room). | ||
| bobSince = bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncJoinedTo(bob.UserID, newRoomID)) | ||
| // Remote bob2 joins the new room | ||
| bob2.MustJoinRoom(t, newRoomID, | ||
| // bob2 can do a local join since bob is already in the room. No need to specify | ||
| // via servers here. | ||
| // | ||
| // []spec.ServerName{ | ||
| // deployment.GetFullyQualifiedHomeserverName(t, "hs1"), | ||
| // }, | ||
| nil, | ||
| ) | ||
|
|
||
| // Sanity check the push rules are in the expected state after the upgrade | ||
| for _, client := range []*client.CSAPI{bob, bob2} { | ||
| pushRulesAfter := client.GetAllPushRules(t) | ||
| t.Logf("Checking push rules (after upgrade) for %s", client.UserID) | ||
| must.MatchGJSON(t, pushRulesAfter, | ||
|
Collaborator
Author
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 tests appear flaky with Dendrite so we may need to sync until the push rules appear.
Collaborator
Author
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. Making the tests wait for push rules from Seems like the push rules aren't being returned from
Collaborator
Author
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. Skipping tests on Dendrite for now ⏩
Collaborator
Author
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. For the future: @S7evinK (former Dendrite dev) mentioned,
The relevant code is probably in Or I'm looking in the wrong place 🤷 Given that Dendrite doesn't have a convenient way to run Complement locally like we have with Synapse's
Contributor
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. Would be the right place, but I didn't have time to check if we need to/should
Member
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. element-hq/dendrite#3668 may fix this.
Collaborator
Author
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. Exploring this further in #822 |
||
| match.JSONCheckOff("global.room", | ||
| []interface{}{ | ||
| roomID, | ||
| newRoomID, | ||
| }, | ||
| match.CheckOffAllowUnwanted(), | ||
| match.CheckOffMapper(func(r gjson.Result) interface{} { return r.Get("rule_id").Str }), | ||
| match.CheckOffForEach(func(roomIDFromPushRule interface{}, result gjson.Result) error { | ||
| return match.JSONKeyEqual("actions.0", "dont_notify")(result) | ||
| }), | ||
| ), | ||
| ) | ||
| } | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| func mustManualUpgradeRoom(t *testing.T, c *client.CSAPI, oldRoomID string, newRoomVersion string) string { | ||
| t.Helper() | ||
|
|
||
| // Create a new room | ||
| newRoomID := c.MustCreateRoom(t, map[string]interface{}{ | ||
| "preset": "public_chat", | ||
| "room_version": newRoomVersion, | ||
| "creation_content": map[string]interface{}{ | ||
| // Specify the old room as the predecessor | ||
| "predecessor": map[string]interface{}{ | ||
| "room_id": oldRoomID, | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| // Send the m.room.tombstone event to the old room | ||
| c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "rooms", oldRoomID, "state", "m.room.tombstone", ""}, client.WithJSONBody(t, map[string]interface{}{ | ||
| "body": "This room has been replaced", | ||
| "replacement_room": newRoomID, | ||
| })) | ||
|
|
||
| return newRoomID | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.