Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
07387a0
Add some tests for push rule behavior on room upgrade
MadLittleMods Nov 17, 2025
3fdbd02
Make the test generic with multiple push rules types
MadLittleMods Nov 17, 2025
cc58ec9
Revert "Make the test generic with multiple push rules types"
MadLittleMods Nov 17, 2025
1167cee
Add `MustUpgradeRoom`
MadLittleMods Nov 17, 2025
d53a307
Test multiple users
MadLittleMods Nov 17, 2025
e00c1c0
Better remote tests
MadLittleMods Nov 17, 2025
fd90c90
Run tests in parallel
MadLittleMods Nov 17, 2025
ad2a7ea
More robust test (`MustAwaitPartialStateJoinCompletion`)
MadLittleMods Nov 17, 2025
5ebcc9f
Even better test
MadLittleMods Nov 17, 2025
e71b03e
Add manually upgraded variant and more robust
MadLittleMods Nov 18, 2025
4fb255c
Specify predecessor when manually upgrading room
MadLittleMods Nov 18, 2025
369250a
Skip test until Synapse is fixed
MadLittleMods Nov 18, 2025
3b4ca0b
Use `SetPushRule`
MadLittleMods Nov 18, 2025
6e2c6f2
Wait/sync until push rules show up
MadLittleMods Nov 18, 2025
10755c2
Fix waiting since the correct spot
MadLittleMods Nov 18, 2025
a7da686
Merge branch 'main' into madlittlemods/room-upgrade-push-rules
MadLittleMods Nov 18, 2025
7a601fa
Skip earlier
MadLittleMods Nov 18, 2025
201a93f
Skip on Dendrite
MadLittleMods Nov 18, 2025
126cc9b
This does sometimes pass but skip since not reliable
MadLittleMods Nov 18, 2025
32aff46
Accurate plural language
MadLittleMods Nov 20, 2025
bae2b74
`upgradeDescriptorPrefix` typo
MadLittleMods Nov 20, 2025
87d6ef0
Explain state of the spec
MadLittleMods Nov 20, 2025
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
25 changes: 25 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,31 @@ func (c *CSAPI) CreateRoom(t ct.TestLike, body map[string]interface{}) *http.Res
return c.Do(t, "POST", []string{"_matrix", "client", "v3", "createRoom"}, WithJSONBody(t, body))
}

// MustUpgradeRoom upgrades a room to the newVersion. Fails the test on error. Returns the new room ID.
func (c *CSAPI) MustUpgradeRoom(t ct.TestLike, roomID string, newVersion string) string {
t.Helper()
res := c.UpgradeRoom(t, roomID, newVersion)
mustRespond2xx(t, res)
resBody := ParseJSON(t, res)
return GetJSONFieldStr(t, resBody, "replacement_room")
}

// UpgradeRoom upgrades a room to the newVersion
func (c *CSAPI) UpgradeRoom(t ct.TestLike, roomID string, newVersion string) *http.Response {
t.Helper()
// Ensure we don't call create a room (upgrade creates a new room) 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", "rooms", roomID, "upgrade"},
WithJSONBody(t, map[string]string{
"new_version": newVersion,
}),
)
}

// MustJoinRoom joins the room ID or alias given, else fails the test. Returns the room ID.
//
// Args:
Expand Down
171 changes: 171 additions & 0 deletions tests/csapi/room_upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
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/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{})
alice2 := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
bob2 := deployment.Register(t, "hs2", helpers.RegistrationOpts{})

t.Run("parallel", func(t *testing.T) {
// 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("upgrading a room carries over existing push rules for local users", func(t *testing.T) {
t.Parallel()

// 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} {
pushRulesBefore := client.GetAllPushRules(t)
must.MatchGJSON(t, pushRulesBefore,
match.JSONCheckOff("global.room", []interface{}{
roomID,
},
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
newRoomID := alice.MustUpgradeRoom(t, roomID, "11")

// 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} {
pushRulesAfter := client.GetAllPushRules(t)
must.MatchGJSON(t, pushRulesAfter,
match.JSONCheckOff("global.room", []interface{}{
roomID,
newRoomID,
},
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 upgraded room carries over existing push rules", func(t *testing.T) {
t.Parallel()

// 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"),
})
// 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, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
Comment thread
MadLittleMods marked this conversation as resolved.
Outdated

// 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} {
pushRulesBefore := client.GetAllPushRules(t)
must.MatchGJSON(t, pushRulesBefore,
match.JSONCheckOff("global.room", []interface{}{
roomID,
},
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
newRoomID := alice.MustUpgradeRoom(t, roomID, "11")

// Remote bob joins the new room
bob.MustJoinRoom(t, newRoomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
// Remote bob2 joins the new room
bob2.MustJoinRoom(t, newRoomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})

// Sanity check the push rules are in the expected state after the upgrade
for _, client := range []*client.CSAPI{bob, bob2} {
pushRulesAfter := client.GetAllPushRules(t)
must.MatchGJSON(t, pushRulesAfter,
match.JSONCheckOff("global.room", []interface{}{
roomID,
newRoomID,
},
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)
}),
),
)
}
})
})
}
Loading