Skip to content
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

Add tests for MSC3890: Remotely silence local notifications #576

Merged
merged 8 commits into from
Jan 13, 2023
24 changes: 24 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,30 @@ func (c *CSAPI) MustSyncUntil(t *testing.T, syncReq SyncReq, checks ...SyncCheck
}
}

// LoginUser will log in to a homeserver and create a new device on an existing user.
func (c *CSAPI) LoginUser(t *testing.T, localpart, password string) (userID, accessToken, deviceID string) {
t.Helper()
reqBody := map[string]interface{}{
"identifier": map[string]interface{}{
"type": "m.id.user",
"user": localpart,
},
"password": password,
"type": "m.login.password",
}
res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "login"}, WithJSONBody(t, reqBody))

body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("unable to read response body: %v", err)
}

userID = gjson.GetBytes(body, "user_id").Str
accessToken = gjson.GetBytes(body, "access_token").Str
deviceID = gjson.GetBytes(body, "device_id").Str
return userID, accessToken, deviceID
}

//RegisterUser will register the user with given parameters and
// return user ID & access token, and fail the test on network error
func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, accessToken, deviceID string) {
Expand Down
24 changes: 24 additions & 0 deletions internal/docker/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ func (d *Deployment) RegisterUser(t *testing.T, hsName, localpart, password stri
return client
}

// LoginUser within a homeserver and return an authenticatedClient. Fails the test if the hsName is not found.
// Note that this will not change the access token of the client that is returned by `deployment.Client`.
func (d *Deployment) LoginUser(t *testing.T, hsName, localpart, password string) *client.CSAPI {
t.Helper()
dep, ok := d.HS[hsName]
if !ok {
t.Fatalf("Deployment.Client - HS name '%s' not found", hsName)
return nil
}
client := &client.CSAPI{
BaseURL: dep.BaseURL,
Client: client.NewLoggedClient(t, hsName, nil),
SyncUntilTimeout: 5 * time.Second,
Debug: d.Deployer.debugLogging,
}
dep.CSAPIClients = append(dep.CSAPIClients, client)
userID, accessToken, deviceID := client.LoginUser(t, localpart, password)

client.UserID = userID
client.AccessToken = accessToken
client.DeviceID = deviceID
return client
}

// Restart a deployment.
func (d *Deployment) Restart(t *testing.T) error {
t.Helper()
Expand Down
4 changes: 2 additions & 2 deletions tests/msc3391_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func createUserAccountData(t *testing.T, c *client.CSAPI) {
func(body []byte) error {
if !match.JSONDeepEqual(body, testAccountDataContent) {
return fmt.Errorf(
"Expected %s for room account data content when, got '%s'",
"Expected %s for room account data content, got '%s'",
testAccountDataType,
string(body),
)
Expand Down Expand Up @@ -124,7 +124,7 @@ func createRoomAccountData(t *testing.T, c *client.CSAPI, roomID string) {
func(body []byte) error {
if !match.JSONDeepEqual(body, testAccountDataContent) {
return fmt.Errorf(
"Expected %s for room account data content when, got '%s'",
"Expected %s for room account data content, got '%s'",
testAccountDataType,
string(body),
)
Expand Down
93 changes: 93 additions & 0 deletions tests/msc3890_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//go:build msc3890
// +build msc3890

// This file contains tests for local notification settings as
// defined by MSC3890, which you can read here:
// https://github.com/matrix-org/matrix-doc/pull/3890

package tests

import (
"testing"

"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"
"github.com/tidwall/gjson"
)

func TestDeletingDeviceRemovesDeviceLocalNotificationSettings(t *testing.T) {
// Create a deployment with a single user
deployment := Deploy(t, b.BlueprintCleanHS)
defer deployment.Destroy(t)

// Create a user which we can log in to multiple times
aliceLocalpart := "alice"
alicePassword := "hunter2"
aliceDeviceOne := deployment.RegisterUser(t, "hs1", aliceLocalpart, alicePassword, false)

// Log in to another device on this user
aliceDeviceTwo := deployment.LoginUser(t, "hs1", aliceLocalpart, alicePassword)

accountDataType := "org.matrix.msc3890.local_notification_settings." + aliceDeviceTwo.DeviceID
accountDataContent := map[string]interface{}{"is_silenced": true}

// Test deleting global account data.
t.Run("Deleting a user's device should delete any local notification settings entries from their account data", func(t *testing.T) {
// Retrieve a sync token for this user
_, nextBatchToken := aliceDeviceOne.MustSync(
t,
client.SyncReq{},
)

// Using the first device, create some local notification settings in the user's account data for the second device.
aliceDeviceOne.SetGlobalAccountData(
t,
accountDataType,
accountDataContent,
)

checkAccountDataContent := func(r gjson.Result) bool {
// Only listen for our test type
if r.Get("type").Str != accountDataType {
return false
}
content := r.Get("content")

// Ensure the content of this account data type is as we expect
return match.JSONDeepEqual([]byte(content.Raw), accountDataContent)
}

// Check that the content of the user account data for this type has been set successfully
aliceDeviceOne.MustSyncUntil(
t,
client.SyncReq{
Since: nextBatchToken,
},
client.SyncGlobalAccountDataHas(checkAccountDataContent),
)
// Also check via the dedicated account data endpoint to ensure the similar check later is not 404'ing for some other reason.
// Using `MustDoFunc` ensures that the response code is 2xx.
res := aliceDeviceOne.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", aliceDeviceOne.UserID, "account_data", accountDataType})
must.MatchResponse(t, res, match.HTTPResponse{
JSON: []match.JSON{
match.JSONKeyEqual("is_silenced", true),
},
})

// Log out the second device
aliceDeviceTwo.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "logout"})

// Using the first device, check that the local notification setting account data for the deleted device was removed.
res = aliceDeviceOne.DoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", aliceDeviceOne.UserID, "account_data", accountDataType})
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 404,
JSON: []match.JSON{
// A 404 can be generated for missing endpoints as well (which would have an errcode of `M_UNRECOGNIZED`).
// Ensure we're getting the error we expect.
match.JSONKeyEqual("errcode", "M_NOT_FOUND"),
},
})
})
}