Skip to content

Commit

Permalink
Add tests for MSC3391
Browse files Browse the repository at this point in the history
  • Loading branch information
anoadragon453 committed Dec 20, 2022
1 parent 166f2da commit 8717497
Showing 1 changed file with 274 additions and 0 deletions.
274 changes: 274 additions & 0 deletions tests/msc3391_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
//go:build msc3391
// +build msc3391

// This file contains tests for deleting account data as
// defined by MSC3391, which you can read here:
// https://github.com/matrix-org/matrix-doc/pull/3391

package tests

import (
"fmt"
"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"
)

const testAccountDataType = "org.example.test"

var testAccountDataContent = map[string]interface{}{"test_data": 1}

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

// Create a user to manipulate the account data of
aliceUserID := "@alice:hs1"
alice := deployment.Client(t, "hs1", aliceUserID)

// And create a room with that user where we can store some room account data
roomID := alice.CreateRoom(t, map[string]interface{}{})

for _, httpMethod := range []string{"PUT", "DELETE"} {
t.Run(fmt.Sprintf("Deleting a user's account data via %s works", httpMethod), func(t *testing.T) {
createAndDeleteAccountData(t, alice, httpMethod == "DELETE", nil)
})
t.Run("Deleting a user's room account data via ", func(t *testing.T) {
createAndDeleteAccountData(t, alice, httpMethod == "DELETE", &roomID)
})
}
}

func createAndDeleteAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID *string) {
// Create the account data and check that it has been created successfully
createAccountData(t, c, roomID)

// Delete the account data and check that it was deleted successfully
deleteAccountData(t, c, viaDelete, roomID)
}

// createAccountData creates some account data for a user or a room, and checks that it was
// created successfully by both querying the data afterwards, and ensuring it appears down /sync.
func createAccountData(t *testing.T, c *client.CSAPI, roomID *string) {
// a function to check that the content of a user or account data object
// matches our test content.
checkAccountData := func(r gjson.Result) bool {
// Only listen for our test type
if r.Get("type").Str != testAccountDataType {
return false
}
content := r.Get("content")

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

// Retrieve a sync token for this user
_, nextBatchToken := c.MustSync(
t,
client.SyncReq{},
)

// Set and check the account data
if roomID != nil {
// Create room account data
c.SetRoomAccountData(t, *roomID, testAccountDataType, testAccountDataContent)

// Wait for the account data to appear down /sync
c.MustSyncUntil(
t,
client.SyncReq{
Since: nextBatchToken,
},
client.SyncRoomAccountDataHas(*roomID, checkAccountData),
)

// Also check the account data content by querying the appropriate endpoint
res := c.GetRoomAccountData(t, *roomID, testAccountDataType)
must.MatchResponse(t, res, match.HTTPResponse{
JSON: []match.JSON{
func(body []byte) error {
if !match.JSONDeepEqual(body, testAccountDataContent) {
return fmt.Errorf(
"Expected %s for room account data content when, got '%s'",
testAccountDataType,
string(body),
)
}

return nil
},
},
})
} else {
// Create user account data
c.SetGlobalAccountData(t, testAccountDataType, testAccountDataContent)

// Wait for the account data to appear down /sync
c.MustSyncUntil(
t,
client.SyncReq{
Since: nextBatchToken,
},
client.SyncGlobalAccountDataHas(checkAccountData),
)

// Also check the account data content by querying the appropriate endpoint
res := c.GetGlobalAccountData(t, testAccountDataType)
must.MatchResponse(t, res, match.HTTPResponse{
JSON: []match.JSON{
func(body []byte) error {
if !match.JSONDeepEqual(body, testAccountDataContent) {
return fmt.Errorf(
"Expected %s for room account data content when, got '%s'",
testAccountDataType,
string(body),
)
}

return nil
},
},
})
}
}

// deleteAccountData removes account data for a user or room.
//
// If viaDelete is true, a request is made to the DELETE endpoint for user or
// room account data. Otherwise, the PUT method is used with an empty content
// dictionary instead. MSC3391 specifies that a PUT with an empty content body
// is functionally equivalent to deleting an account data type directly.
//
// If roomID is not nil, room account data for the given room ID will be removed.
// Otherwise, account data from the user will be removed instead.
func deleteAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID *string) {
// a function to check that the content of a user or account data object
// matches our test content.
checkEmptyAccountData := func(r gjson.Result) bool {
// Only listen for our test type
if r.Get("type").Str != testAccountDataType {
return false
}
content := r.Get("content")

// Ensure the content of this account data type is an empty map.
// This means that it has been deleted.
return match.JSONDeepEqual([]byte(content.Raw), map[string]interface{}{})
}

// a function that checks that a given account data event type is not present
checkAccountDataTypeNotPresent := func(r gjson.Result) error {
// If we see our test type, return a failure
if r.Get("type").Str == testAccountDataType {
return fmt.Errorf(
"Found unexpected account data type '%s' in sync response",
testAccountDataType,
)
}

// We did not see our test type.
return nil
}

// Retrieve a sync token for this user
_, nextBatchToken := c.MustSync(
t,
client.SyncReq{},
)

if roomID != nil {
// Delete room account data
if viaDelete {
// Delete via the DELETE method
c.MustDoFunc(
t,
"DELETE",
[]string{"_matrix", "client", "unstable", "org.matrix.msc3391", "user", c.UserID, "rooms", *roomID, "account_data", testAccountDataType},
)
} else {
// Delete via the PUT method. PUT'ing with an empty dictionary will delete
// the account data type for this room.
c.SetRoomAccountData(t, *roomID, testAccountDataType, map[string]interface{}{})
}

// Check that the content of the room account data for this type
// has been set to an empty dictionary.
c.MustSyncUntil(
t,
client.SyncReq{
Since: nextBatchToken,
},
client.SyncRoomAccountDataHas(*roomID, checkEmptyAccountData),
)

// Also check the account data item is no longer found
res := c.DoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "room", *roomID, "account_data", testAccountDataType})
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 404,
})

// Finally, check that the account data item does not appear at all in an initial sync
initialSyncResponse, _ := c.MustSync(
t,
client.SyncReq{},
)
must.MatchGJSON(
t,
initialSyncResponse,
match.JSONArrayEach(
fmt.Sprintf("rooms.join.%s.account_data.events", *roomID),
checkAccountDataTypeNotPresent,
),
)
} else {
// Delete user account data
if viaDelete {
// Delete via the DELETE method
c.MustDoFunc(
t,
"DELETE",
[]string{"_matrix", "client", "unstable", "org.matrix.msc3391", "user", c.UserID, "account_data", testAccountDataType},
)
} else {
// Delete via the PUT method. PUT'ing with an empty dictionary will delete
// the account data type for this user.
c.SetGlobalAccountData(t, testAccountDataType, map[string]interface{}{})
}

// Check that the content of the user account data for this type
// has been set to an empty dictionary.
c.MustSyncUntil(
t,
client.SyncReq{
Since: nextBatchToken,
},
client.SyncGlobalAccountDataHas(checkEmptyAccountData),
)

// Also check the account data item is no longer found
res := c.DoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", testAccountDataType})
must.MatchResponse(t, res, match.HTTPResponse{
StatusCode: 404,
})

// Finally, check that the account data item does not appear at all in an initial sync
initialSyncResponse, _ := c.MustSync(
t,
client.SyncReq{},
)
must.MatchGJSON(
t,
initialSyncResponse,
match.JSONArrayEach(
"account_data.events",
checkAccountDataTypeNotPresent,
),
)
}
}

0 comments on commit 8717497

Please sign in to comment.