From 166f2da9fd9809f6b057ef740de304e02619c976 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 20 Dec 2022 13:01:27 +0000 Subject: [PATCH 1/5] Add Get and Set helper methods for room account data --- internal/client/client.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/client/client.go b/internal/client/client.go index feb2b0ed..cd5a5b80 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -180,6 +180,14 @@ func (c *CSAPI) SetGlobalAccountData(t *testing.T, eventType string, content map return c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType}, WithJSONBody(t, content)) } +func (c *CSAPI) GetRoomAccountData(t *testing.T, roomID string, eventType string) *http.Response { + return c.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType}) +} + +func (c *CSAPI) SetRoomAccountData(t *testing.T, roomID string, eventType string, content map[string]interface{}) *http.Response { + return c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType}, WithJSONBody(t, content)) +} + // SendEventSynced sends `e` into the room and waits for its event ID to come down /sync. // Returns the event ID of the sent event. func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e b.Event) string { From 8717497084f71596a2fa3d7053ea807f55bd9a30 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Tue, 20 Dec 2022 13:01:47 +0000 Subject: [PATCH 2/5] Add tests for MSC3391 --- tests/msc3391_test.go | 274 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 tests/msc3391_test.go diff --git a/tests/msc3391_test.go b/tests/msc3391_test.go new file mode 100644 index 00000000..e3613e95 --- /dev/null +++ b/tests/msc3391_test.go @@ -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, + ), + ) + } +} From a5a06e12703f4b81ff1e8cbc0f5bb8bf105b9071 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Fri, 30 Dec 2022 17:28:02 +0000 Subject: [PATCH 3/5] Update tests/msc3391_test.go Co-authored-by: Patrick Cloke --- tests/msc3391_test.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/msc3391_test.go b/tests/msc3391_test.go index e3613e95..d10d10e7 100644 --- a/tests/msc3391_test.go +++ b/tests/msc3391_test.go @@ -34,14 +34,21 @@ func TestRemovingAccountData(t *testing.T) { // 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) - }) - } + // Test deleting global account data. + t.Run("Deleting a user's account data via DELETE works", func(t *testing.T) { + createAndDeleteAccountData(t, alice, true, nil) + }) + t.Run("Deleting a user's account data via PUT works", func(t *testing.T) { + createAndDeleteAccountData(t, alice, false, nil) + }) + + // Test deleting room account data. + t.Run("Deleting a user's room data via DELETE works", func(t *testing.T) { + createAndDeleteAccountData(t, alice, true, &roomID) + }) + t.Run("Deleting a user's room account data via PUT works", func(t *testing.T) { + createAndDeleteAccountData(t, alice, false, &roomID) + }) } func createAndDeleteAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID *string) { From 58913da25133a6362d6e4f2786619b84af573804 Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Fri, 30 Dec 2022 17:43:18 +0000 Subject: [PATCH 4/5] Seperate test methods into user and room variants This simplifies the bodies of each from large conditionals into separate methods --- tests/msc3391_test.go | 407 ++++++++++++++++++++++-------------------- 1 file changed, 216 insertions(+), 191 deletions(-) diff --git a/tests/msc3391_test.go b/tests/msc3391_test.go index d10d10e7..c8ba24e4 100644 --- a/tests/msc3391_test.go +++ b/tests/msc3391_test.go @@ -36,45 +36,40 @@ func TestRemovingAccountData(t *testing.T) { // Test deleting global account data. t.Run("Deleting a user's account data via DELETE works", func(t *testing.T) { - createAndDeleteAccountData(t, alice, true, nil) + createAndDeleteUserAccountData(t, alice, true) }) t.Run("Deleting a user's account data via PUT works", func(t *testing.T) { - createAndDeleteAccountData(t, alice, false, nil) + createAndDeleteUserAccountData(t, alice, false) }) // Test deleting room account data. t.Run("Deleting a user's room data via DELETE works", func(t *testing.T) { - createAndDeleteAccountData(t, alice, true, &roomID) + createAndDeleteRoomAccountData(t, alice, true, roomID) }) t.Run("Deleting a user's room account data via PUT works", func(t *testing.T) { - createAndDeleteAccountData(t, alice, false, &roomID) + createAndDeleteRoomAccountData(t, alice, false, roomID) }) } -func createAndDeleteAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID *string) { +func createAndDeleteUserAccountData(t *testing.T, c *client.CSAPI, viaDelete bool) { // Create the account data and check that it has been created successfully - createAccountData(t, c, roomID) + createUserAccountData(t, c) // Delete the account data and check that it was deleted successfully - deleteAccountData(t, c, viaDelete, roomID) + deleteUserAccountData(t, c, viaDelete) } -// 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) - } +func createAndDeleteRoomAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID string) { + // Create the account data and check that it has been created successfully + createRoomAccountData(t, c, roomID) + + // Delete the account data and check that it was deleted successfully + deleteRoomAccountData(t, c, viaDelete, roomID) +} +// createUserAccountData creates some account data for a user and checks that it was +// created successfully by both querying the data afterwards, and ensuring it appears down /sync. +func createUserAccountData(t *testing.T, c *client.CSAPI) { // Retrieve a sync token for this user _, nextBatchToken := c.MustSync( t, @@ -82,200 +77,230 @@ func createAccountData(t *testing.T, c *client.CSAPI, roomID *string) { ) // Set and check the account data - if roomID != nil { - // Create room account data - c.SetRoomAccountData(t, *roomID, testAccountDataType, testAccountDataContent) + // 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.SyncRoomAccountDataHas(*roomID, checkAccountData), - ) + // Wait for the account data to appear down /sync + c.MustSyncUntil( + t, + client.SyncReq{ + Since: nextBatchToken, + }, + client.SyncGlobalAccountDataHas(checkAccountDataContent), + ) - // 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 - }, + // 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 }, - }) - } 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), - ) +// createUserAccountData creates some account data for a room and checks that it was +// created successfully by both querying the data afterwards, and ensuring it appears down /sync. +func createRoomAccountData(t *testing.T, c *client.CSAPI, roomID string) { + // Retrieve a sync token for this user + _, nextBatchToken := c.MustSync( + t, + client.SyncReq{}, + ) + + // 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, checkAccountDataContent), + ) - // 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 - }, + // 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 }, - }) - } + }, + }) } -// deleteAccountData removes account data for a user or room. +// deleteUserAccountData removes account data for a user. // -// 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 +// If viaDelete is true, a request is made to the DELETE endpoint for user +// 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 - } - +func deleteUserAccountData(t *testing.T, c *client.CSAPI, viaDelete bool) { // 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( + // Delete user account data + if viaDelete { + // Delete via the DELETE method + c.MustDoFunc( t, - client.SyncReq{ - Since: nextBatchToken, - }, - client.SyncRoomAccountDataHas(*roomID, checkEmptyAccountData), + "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{}{}) + } - // 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, - }) + // 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), + ) - // Finally, check that the account data item does not appear at all in an initial sync - initialSyncResponse, _ := c.MustSync( - t, - client.SyncReq{}, - ) - must.MatchGJSON( + // 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, + ), + ) +} + +// deleteRoomAccountData removes account data for a user. +// +// If viaDelete is true, a request is made to the DELETE endpoint for 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. +func deleteRoomAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID string) { + // Retrieve a sync token for this user + _, nextBatchToken := c.MustSync( + t, + client.SyncReq{}, + ) + + // Delete room account data + if viaDelete { + // Delete via the DELETE method + c.MustDoFunc( t, - initialSyncResponse, - match.JSONArrayEach( - fmt.Sprintf("rooms.join.%s.account_data.events", *roomID), - checkAccountDataTypeNotPresent, - ), + "DELETE", + []string{"_matrix", "client", "unstable", "org.matrix.msc3391", "user", c.UserID, "rooms", roomID, "account_data", testAccountDataType}, ) } 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), - ) + // 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, "account_data", testAccountDataType}) - must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 404, - }) + // 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( - "account_data.events", - checkAccountDataTypeNotPresent, - ), - ) + // 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, + ), + ) +} + +// checkAccountDataContent checks that the content of a user or account data object +// matches our test content. +func checkAccountDataContent(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) } + +// checkEmptyAccountData checks that the content of a user or account data object +// matches our test content. +func checkEmptyAccountData(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{}{}) +} + +// checkAccountDataTypeNotPresent checks that a given account data event type is not present +func checkAccountDataTypeNotPresent(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 +} \ No newline at end of file From 4e17e8258fef75f8c499e56ba5fe625f0a2659df Mon Sep 17 00:00:00 2001 From: Andrew Morgan Date: Sun, 1 Jan 2023 02:54:29 +0000 Subject: [PATCH 5/5] Remove unnecessary helper functions, fix typo --- tests/msc3391_test.go | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/tests/msc3391_test.go b/tests/msc3391_test.go index c8ba24e4..c797bc50 100644 --- a/tests/msc3391_test.go +++ b/tests/msc3391_test.go @@ -36,37 +36,25 @@ func TestRemovingAccountData(t *testing.T) { // Test deleting global account data. t.Run("Deleting a user's account data via DELETE works", func(t *testing.T) { - createAndDeleteUserAccountData(t, alice, true) + createUserAccountData(t, alice) + deleteUserAccountData(t, alice, true) }) t.Run("Deleting a user's account data via PUT works", func(t *testing.T) { - createAndDeleteUserAccountData(t, alice, false) + createUserAccountData(t, alice) + deleteUserAccountData(t, alice, false) }) // Test deleting room account data. t.Run("Deleting a user's room data via DELETE works", func(t *testing.T) { - createAndDeleteRoomAccountData(t, alice, true, roomID) + createRoomAccountData(t, alice, roomID) + deleteRoomAccountData(t, alice, true, roomID) }) t.Run("Deleting a user's room account data via PUT works", func(t *testing.T) { - createAndDeleteRoomAccountData(t, alice, false, roomID) + createRoomAccountData(t, alice, roomID) + deleteRoomAccountData(t, alice, false, roomID) }) } -func createAndDeleteUserAccountData(t *testing.T, c *client.CSAPI, viaDelete bool) { - // Create the account data and check that it has been created successfully - createUserAccountData(t, c) - - // Delete the account data and check that it was deleted successfully - deleteUserAccountData(t, c, viaDelete) -} - -func createAndDeleteRoomAccountData(t *testing.T, c *client.CSAPI, viaDelete bool, roomID string) { - // Create the account data and check that it has been created successfully - createRoomAccountData(t, c, roomID) - - // Delete the account data and check that it was deleted successfully - deleteRoomAccountData(t, c, viaDelete, roomID) -} - // createUserAccountData creates some account data for a user and checks that it was // created successfully by both querying the data afterwards, and ensuring it appears down /sync. func createUserAccountData(t *testing.T, c *client.CSAPI) { @@ -108,7 +96,7 @@ func createUserAccountData(t *testing.T, c *client.CSAPI) { }) } -// createUserAccountData creates some account data for a room and checks that it was +// createRoomAccountData creates some account data for a room and checks that it was // created successfully by both querying the data afterwards, and ensuring it appears down /sync. func createRoomAccountData(t *testing.T, c *client.CSAPI, roomID string) { // Retrieve a sync token for this user