From 3041a46ac78759a4c9d231dcfef025e1c7b6fc38 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 02:30:32 -0400 Subject: [PATCH 01/17] Start adding tests for MSC4140 --- tests/msc4140/delayed_event_test.go | 76 +++++++++++++++++++++++++++++ tests/msc4140/main_test.go | 11 +++++ 2 files changed, 87 insertions(+) create mode 100644 tests/msc4140/delayed_event_test.go create mode 100644 tests/msc4140/main_test.go diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go new file mode 100644 index 00000000..0466f91c --- /dev/null +++ b/tests/msc4140/delayed_event_test.go @@ -0,0 +1,76 @@ +package tests + +import ( + "net/http" + "net/url" + "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" +) + +func TestDelayedEvents(t *testing.T) { + deployment := complement.Deploy(t, 1) + defer deployment.Destroy(t) + + user := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + + roomID := user.MustCreateRoom(t, map[string]interface{}{ + "preset": "trusted_private_chat", + }) + + t.Run("delayed state events are cancelled by a more recent state event", func(t *testing.T) { + var res *http.Response + + eventType := "com.example.test" + stateKey := "to_be_cancelled" + + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + "setter": "on_timeout", + }), + getDelayQueryParam("1000"), + ) + + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + "setter": "manual", + }), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 200, + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + }) +} + +func getPathForSend(roomID string, eventType string) []string { + return []string{"_matrix", "client", "v3", "rooms", roomID, "send", eventType} +} + +func getPathForState(roomID string, eventType string, stateKey string) []string { + return []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey} +} + +func getDelayQueryParam(delayStr string) client.RequestOpt { + return client.WithQueries(url.Values{ + "org.matrix.msc4140.delay": []string{delayStr}, + }) +} + +func getDelayedEvents(t *testing.T, user *client.CSAPI) *http.Response { + t.Helper() + return user.MustDo(t, "GET", []string{"_matrix", "client", "unstable", "org.matrix.msc4140", "delayed_events"}) +} diff --git a/tests/msc4140/main_test.go b/tests/msc4140/main_test.go new file mode 100644 index 00000000..f1404d3a --- /dev/null +++ b/tests/msc4140/main_test.go @@ -0,0 +1,11 @@ +package tests + +import ( + "testing" + + "github.com/matrix-org/complement" +) + +func TestMain(m *testing.M) { + complement.TestMain(m, "msc4140") +} From f08663987eb7a4de7a3059cbcaaf9a10ea56978f Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 09:30:25 -0400 Subject: [PATCH 02/17] Remove redundant check for 200 from a "Must" req --- tests/msc4140/delayed_event_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 0466f91c..ad5c488c 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -48,7 +48,6 @@ func TestDelayedEvents(t *testing.T) { ) res = getDelayedEvents(t, user) must.MatchResponse(t, res, match.HTTPResponse{ - StatusCode: 200, JSON: []match.JSON{ match.JSONKeyArrayOfSize("delayed_events", 0), }, From 1a33f1aa5bd1f5b49fc772d4f30e8427f3d48de2 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 09:40:39 -0400 Subject: [PATCH 03/17] Confirm that cancelled delayed state is not sent --- tests/msc4140/delayed_event_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index ad5c488c..5d97368c 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/url" "testing" + "time" "github.com/matrix-org/complement" "github.com/matrix-org/complement/client" @@ -35,15 +36,22 @@ func TestDelayedEvents(t *testing.T) { client.WithJSONBody(t, map[string]interface{}{ "setter": "on_timeout", }), - getDelayQueryParam("1000"), + getDelayQueryParam("900"), ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + setterExpected := "manual" user.MustDo( t, "PUT", getPathForState(roomID, eventType, stateKey), client.WithJSONBody(t, map[string]interface{}{ - "setter": "manual", + "setter": setterExpected, }), ) res = getDelayedEvents(t, user) @@ -52,6 +60,14 @@ func TestDelayedEvents(t *testing.T) { match.JSONKeyArrayOfSize("delayed_events", 0), }, }) + + time.Sleep(1 * time.Second) + res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual("setter", setterExpected), + }, + }) }) } From 0e32834e4e7f972dd488e923faacc0e16e961c00 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 09:43:15 -0400 Subject: [PATCH 04/17] Test sending uncancelled delayed state events --- tests/msc4140/delayed_event_test.go | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 5d97368c..d39e8abf 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -1,6 +1,7 @@ package tests import ( + "fmt" "net/http" "net/url" "testing" @@ -11,6 +12,7 @@ import ( "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/tidwall/gjson" ) func TestDelayedEvents(t *testing.T) { @@ -23,6 +25,54 @@ func TestDelayedEvents(t *testing.T) { "preset": "trusted_private_chat", }) + t.Run("delayed state events are sent on timeout", func(t *testing.T) { + var res *http.Response + + eventType := "com.example.test" + stateKey := "to_send_on_timeout" + + setterExpected := "on_timeout" + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + "setter": setterExpected, + }), + getDelayQueryParam("900"), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + match.JSONArrayEach("delayed_events", func(val gjson.Result) error { + if setterActual := val.Get("content").Get("setter").Str; setterActual != setterExpected { + return fmt.Errorf("wrong setter in delayed event content: expected %v, got %v", setterExpected, setterActual) + } + return nil + }), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual("setter", setterExpected), + }, + }) + }) + t.Run("delayed state events are cancelled by a more recent state event", func(t *testing.T) { var res *http.Response From 63b5788b863dd1a0cc6d4402993c0f6b252a37f1 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 11:22:50 -0400 Subject: [PATCH 05/17] Use consts for unchanging variables --- tests/msc4140/delayed_event_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index d39e8abf..bdda09f5 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -15,11 +15,14 @@ import ( "github.com/tidwall/gjson" ) +const hsName = "hs1" +const eventType = "com.example.test" + func TestDelayedEvents(t *testing.T) { deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) - user := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + user := deployment.Register(t, hsName, helpers.RegistrationOpts{}) roomID := user.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", @@ -28,7 +31,6 @@ func TestDelayedEvents(t *testing.T) { t.Run("delayed state events are sent on timeout", func(t *testing.T) { var res *http.Response - eventType := "com.example.test" stateKey := "to_send_on_timeout" setterExpected := "on_timeout" @@ -76,7 +78,6 @@ func TestDelayedEvents(t *testing.T) { t.Run("delayed state events are cancelled by a more recent state event", func(t *testing.T) { var res *http.Response - eventType := "com.example.test" stateKey := "to_be_cancelled" user.MustDo( From 68fe42eab96d7906f0844e34dc05da489456bf71 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 11:23:17 -0400 Subject: [PATCH 06/17] Test restoring delayed events after server restart --- tests/msc4140/delayed_event_test.go | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index bdda09f5..498ad6de 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -120,6 +120,55 @@ func TestDelayedEvents(t *testing.T) { }, }) }) + + t.Run("delayed state events are kept on server restart", func(t *testing.T) { + var res *http.Response + + stateKey1 := "1" + stateKey2 := "2" + + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey1), + client.WithJSONBody(t, map[string]interface{}{}), + getDelayQueryParam("900"), + ) + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey2), + client.WithJSONBody(t, map[string]interface{}{}), + getDelayQueryParam("9900"), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 2), + }, + }) + + deployment.StopServer(t, hsName) + time.Sleep(1 * time.Second) + deployment.StartServer(t, hsName) + + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey1)) + + time.Sleep(9 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey2)) + }) } func getPathForSend(roomID string, eventType string) []string { From a197342f90e256427456b4d0b9dced7cb4c9c4a9 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Fri, 13 Sep 2024 01:25:43 -0400 Subject: [PATCH 07/17] Test that delayed events are empty on startup --- tests/msc4140/delayed_event_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 498ad6de..d8118f50 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -28,6 +28,15 @@ func TestDelayedEvents(t *testing.T) { "preset": "trusted_private_chat", }) + t.Run("delayed events are empty on startup", func(t *testing.T) { + res := getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + }) + t.Run("delayed state events are sent on timeout", func(t *testing.T) { var res *http.Response From 0101b7ce76c723be1c12eb05c16b5f22b850d84c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 16 Sep 2024 15:24:38 -0400 Subject: [PATCH 08/17] Use variable for content key --- tests/msc4140/delayed_event_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index d8118f50..845cbf4e 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -42,13 +42,14 @@ func TestDelayedEvents(t *testing.T) { stateKey := "to_send_on_timeout" + setterKey := "setter" setterExpected := "on_timeout" user.MustDo( t, "PUT", getPathForState(roomID, eventType, stateKey), client.WithJSONBody(t, map[string]interface{}{ - "setter": setterExpected, + setterKey: setterExpected, }), getDelayQueryParam("900"), ) @@ -79,7 +80,7 @@ func TestDelayedEvents(t *testing.T) { res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ - match.JSONKeyEqual("setter", setterExpected), + match.JSONKeyEqual(setterKey, setterExpected), }, }) }) @@ -89,12 +90,13 @@ func TestDelayedEvents(t *testing.T) { stateKey := "to_be_cancelled" + setterKey := "setter" user.MustDo( t, "PUT", getPathForState(roomID, eventType, stateKey), client.WithJSONBody(t, map[string]interface{}{ - "setter": "on_timeout", + setterKey: "on_timeout", }), getDelayQueryParam("900"), ) @@ -111,7 +113,7 @@ func TestDelayedEvents(t *testing.T) { "PUT", getPathForState(roomID, eventType, stateKey), client.WithJSONBody(t, map[string]interface{}{ - "setter": setterExpected, + setterKey: setterExpected, }), ) res = getDelayedEvents(t, user) @@ -125,7 +127,7 @@ func TestDelayedEvents(t *testing.T) { res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) must.MatchResponse(t, res, match.HTTPResponse{ JSON: []match.JSON{ - match.JSONKeyEqual("setter", setterExpected), + match.JSONKeyEqual(setterKey, setterExpected), }, }) }) From 8323f5125ef1a12b432a71fc7f05bd5d28d1e85a Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 16 Sep 2024 15:25:10 -0400 Subject: [PATCH 09/17] Assert absence of unexpected content fields --- tests/msc4140/delayed_event_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 845cbf4e..56bf5742 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -58,7 +58,11 @@ func TestDelayedEvents(t *testing.T) { JSON: []match.JSON{ match.JSONKeyArrayOfSize("delayed_events", 1), match.JSONArrayEach("delayed_events", func(val gjson.Result) error { - if setterActual := val.Get("content").Get("setter").Str; setterActual != setterExpected { + content := val.Get("content").Map() + if l := len(content); l != 1 { + return fmt.Errorf("wrong number of content fields: expected 1, got %d", l) + } + if setterActual := content[setterKey].Str; setterActual != setterExpected { return fmt.Errorf("wrong setter in delayed event content: expected %v, got %v", setterExpected, setterActual) } return nil From 64d724ca57403e3f4eb6c77d72415df47e50d55b Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 16 Sep 2024 16:18:23 -0400 Subject: [PATCH 10/17] Remove helper function for delayed message events as it is currently unused --- tests/msc4140/delayed_event_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 56bf5742..2ad4fffb 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -186,10 +186,6 @@ func TestDelayedEvents(t *testing.T) { }) } -func getPathForSend(roomID string, eventType string) []string { - return []string{"_matrix", "client", "v3", "rooms", roomID, "send", eventType} -} - func getPathForState(roomID string, eventType string, stateKey string) []string { return []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey} } From 129272c4f0988f90a1091f23470e9b13bc55b4a8 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 16 Sep 2024 16:18:49 -0400 Subject: [PATCH 11/17] Add many more test cases --- tests/msc4140/delayed_event_test.go | 244 +++++++++++++++++++++++++++- 1 file changed, 243 insertions(+), 1 deletion(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 2ad4fffb..a8e42cb1 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -89,6 +89,244 @@ func TestDelayedEvents(t *testing.T) { }) }) + t.Run("cannot update a delayed event without a delay ID", func(t *testing.T) { + res := user.Do(t, "POST", append(getPathForUpdateDelayedEvents(), "")) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + }) + + t.Run("cannot update a delayed event without a request body", func(t *testing.T) { + res := user.Do(t, "POST", append(getPathForUpdateDelayedEvents(), "abc")) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 400, + JSON: []match.JSON{ + match.JSONKeyEqual("errcode", "M_NOT_JSON"), + }, + }) + }) + + t.Run("cannot update a delayed event without an action", func(t *testing.T) { + res := user.Do( + t, + "POST", + append(getPathForUpdateDelayedEvents(), "abc"), + client.WithJSONBody(t, map[string]interface{}{}), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 400, + JSON: []match.JSON{ + match.JSONKeyEqual("errcode", "M_MISSING_PARAM"), + }, + }) + }) + + t.Run("cannot update a delayed event with an invalid action", func(t *testing.T) { + res := user.Do( + t, + "POST", + append(getPathForUpdateDelayedEvents(), "abc"), + client.WithJSONBody(t, map[string]interface{}{ + "action": "oops", + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 400, + JSON: []match.JSON{ + match.JSONKeyEqual("errcode", "M_INVALID_PARAM"), + }, + }) + }) + + t.Run("parallel", func(t *testing.T) { + for _, action := range []string{"cancel", "restart", "send"} { + t.Run(fmt.Sprintf("cannot %s a delayed event without a matching delay ID", action), func(t *testing.T) { + t.Parallel() + res := user.Do( + t, + "POST", + append(getPathForUpdateDelayedEvents(), "abc"), + client.WithJSONBody(t, map[string]interface{}{ + "action": action, + }), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + }) + } + }) + + t.Run("delayed state events can be cancelled", func(t *testing.T) { + var res *http.Response + + stateKey := "to_never_send" + + setterKey := "setter" + setterExpected := "none" + res = user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + setterKey: setterExpected, + }), + getDelayQueryParam("1500"), + ) + delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + + user.MustDo( + t, + "POST", + append(getPathForUpdateDelayedEvents(), delayID), + client.WithJSONBody(t, map[string]interface{}{ + "action": "cancel", + }), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + + time.Sleep(1 * time.Second) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + }) + + t.Run("delayed state events can be sent on request", func(t *testing.T) { + var res *http.Response + + stateKey := "to_send_on_request" + + setterKey := "setter" + setterExpected := "on_send" + res = user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + setterKey: setterExpected, + }), + getDelayQueryParam("100000"), + ) + delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + + user.MustDo( + t, + "POST", + append(getPathForUpdateDelayedEvents(), delayID), + client.WithJSONBody(t, map[string]interface{}{ + "action": "send", + }), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual(setterKey, setterExpected), + }, + }) + }) + + t.Run("delayed state events can be restarted", func(t *testing.T) { + var res *http.Response + + stateKey := "to_send_on_restarted_timeout" + + setterKey := "setter" + setterExpected := "on_timeout" + res = user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + setterKey: setterExpected, + }), + getDelayQueryParam("1500"), + ) + delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + + user.MustDo( + t, + "POST", + append(getPathForUpdateDelayedEvents(), delayID), + client.WithJSONBody(t, map[string]interface{}{ + "action": "restart", + }), + ) + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + res = user.Do(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + StatusCode: 404, + }) + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual(setterKey, setterExpected), + }, + }) + }) + t.Run("delayed state events are cancelled by a more recent state event", func(t *testing.T) { var res *http.Response @@ -186,6 +424,10 @@ func TestDelayedEvents(t *testing.T) { }) } +func getPathForUpdateDelayedEvents() []string { + return []string{"_matrix", "client", "unstable", "org.matrix.msc4140", "delayed_events"} +} + func getPathForState(roomID string, eventType string, stateKey string) []string { return []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey} } @@ -198,5 +440,5 @@ func getDelayQueryParam(delayStr string) client.RequestOpt { func getDelayedEvents(t *testing.T, user *client.CSAPI) *http.Response { t.Helper() - return user.MustDo(t, "GET", []string{"_matrix", "client", "unstable", "org.matrix.msc4140", "delayed_events"}) + return user.MustDo(t, "GET", getPathForUpdateDelayedEvents()) } From 2e4902155b2e3215513353aa792f0e4f00a9a6e9 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 17 Sep 2024 12:17:44 -0400 Subject: [PATCH 12/17] Test delayed state event cancelled by other user --- tests/msc4140/delayed_event_test.go | 60 +++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index a8e42cb1..5e5d87f5 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -23,10 +23,17 @@ func TestDelayedEvents(t *testing.T) { defer deployment.Destroy(t) user := deployment.Register(t, hsName, helpers.RegistrationOpts{}) + user2 := deployment.Register(t, hsName, helpers.RegistrationOpts{}) roomID := user.MustCreateRoom(t, map[string]interface{}{ - "preset": "trusted_private_chat", + "preset": "public_chat", + "power_level_content_override": map[string]interface{}{ + "events": map[string]int{ + eventType: 0, + }, + }, }) + user2.MustJoinRoom(t, roomID, nil) t.Run("delayed events are empty on startup", func(t *testing.T) { res := getDelayedEvents(t, user) @@ -327,10 +334,10 @@ func TestDelayedEvents(t *testing.T) { }) }) - t.Run("delayed state events are cancelled by a more recent state event", func(t *testing.T) { + t.Run("delayed state events are cancelled by a more recent state event from the same user", func(t *testing.T) { var res *http.Response - stateKey := "to_be_cancelled" + stateKey := "to_be_cancelled_by_same_user" setterKey := "setter" user.MustDo( @@ -374,6 +381,53 @@ func TestDelayedEvents(t *testing.T) { }) }) + t.Run("delayed state events are cancelled by a more recent state event from another user", func(t *testing.T) { + var res *http.Response + + stateKey := "to_be_cancelled_by_other_user" + + setterKey := "setter" + user.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + setterKey: "on_timeout", + }), + getDelayQueryParam("900"), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 1), + }, + }) + + setterExpected := "manual" + user2.MustDo( + t, + "PUT", + getPathForState(roomID, eventType, stateKey), + client.WithJSONBody(t, map[string]interface{}{ + setterKey: setterExpected, + }), + ) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + + time.Sleep(1 * time.Second) + res = user.MustDo(t, "GET", getPathForState(roomID, eventType, stateKey)) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual(setterKey, setterExpected), + }, + }) + }) + t.Run("delayed state events are kept on server restart", func(t *testing.T) { var res *http.Response From cd42448c27c7b6671c191e3b79641e988da0cf0d Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 17 Sep 2024 12:21:59 -0400 Subject: [PATCH 13/17] Skip server restart test if not Synapse --- tests/msc4140/delayed_event_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 5e5d87f5..dce09abf 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -12,6 +12,7 @@ import ( "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/tidwall/gjson" ) @@ -429,6 +430,9 @@ func TestDelayedEvents(t *testing.T) { }) t.Run("delayed state events are kept on server restart", func(t *testing.T) { + // Spec cannot enforce server restart behaviour + runtime.SkipIf(t, runtime.Dendrite, runtime.Conduit, runtime.Conduwuit) + var res *http.Response stateKey1 := "1" From a3ea116a0fc7443c4e4d57477f92ef4ec7b8bf66 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 17 Sep 2024 14:55:27 -0400 Subject: [PATCH 14/17] Test delayed message events --- tests/msc4140/delayed_event_test.go | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index dce09abf..6b1f3af0 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -45,6 +45,81 @@ func TestDelayedEvents(t *testing.T) { }) }) + t.Run("delayed message events are sent on timeout", func(t *testing.T) { + var res *http.Response + var countExpected uint64 + + _, token := user.MustSync(t, client.SyncReq{}) + + txnIdBase := "txn-delayed-msg-timeout-%d" + + countKey := "count" + numEvents := 3 + for i, delayStr := range []string{"700", "800", "900"} { + res = user.MustDo( + t, + "PUT", + getPathForSend(roomID, eventType, fmt.Sprintf(txnIdBase, i)), + client.WithJSONBody(t, map[string]interface{}{ + countKey: i + 1, + }), + getDelayQueryParam(delayStr), + ) + delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") + // Requesting the same path with the same txnID should have the same response + res = user.MustDo( + t, + "PUT", + getPathForSend(roomID, eventType, fmt.Sprintf(txnIdBase, i)), + getDelayQueryParam(delayStr), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual("delay_id", delayID), + }, + }) + } + + checkContent := func(val gjson.Result) error { + content := val.Get("content").Map() + if l := len(content); l != 1 { + return fmt.Errorf("wrong number of content fields: expected 1, got %d", l) + } + countExpected++ + if countActual := content[countKey].Uint(); countActual != countExpected { + return fmt.Errorf("wrong count in delayed event content: expected %v, got %v", countExpected, countActual) + } + return nil + } + + res = getDelayedEvents(t, user) + countExpected = 0 + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", numEvents), + match.JSONArrayEach("delayed_events", checkContent), + }, + }) + + time.Sleep(1 * time.Second) + res = getDelayedEvents(t, user) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + queryParams := url.Values{} + queryParams.Set("dir", "f") + queryParams.Set("from", token) + res = user.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(queryParams)) + countExpected = 0 + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONArrayEach("chunk", checkContent), + }, + }) + }) + t.Run("delayed state events are sent on timeout", func(t *testing.T) { var res *http.Response @@ -486,6 +561,10 @@ func getPathForUpdateDelayedEvents() []string { return []string{"_matrix", "client", "unstable", "org.matrix.msc4140", "delayed_events"} } +func getPathForSend(roomID string, eventType string, txnId string) []string { + return []string{"_matrix", "client", "v3", "rooms", roomID, "send", eventType, txnId} +} + func getPathForState(roomID string, eventType string, stateKey string) []string { return []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey} } From fc931931bf59fb7a04524177016f82e05874dbef Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 17 Sep 2024 15:07:05 -0400 Subject: [PATCH 15/17] Move txnID rerequest to subtest --- tests/msc4140/delayed_event_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 6b1f3af0..aa79027f 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -66,17 +66,19 @@ func TestDelayedEvents(t *testing.T) { getDelayQueryParam(delayStr), ) delayID := client.GetJSONFieldStr(t, client.ParseJSON(t, res), "delay_id") - // Requesting the same path with the same txnID should have the same response - res = user.MustDo( - t, - "PUT", - getPathForSend(roomID, eventType, fmt.Sprintf(txnIdBase, i)), - getDelayQueryParam(delayStr), - ) - must.MatchResponse(t, res, match.HTTPResponse{ - JSON: []match.JSON{ - match.JSONKeyEqual("delay_id", delayID), - }, + + t.Run("rerequesting delayed event path with the same txnID should have the same response", func(t *testing.T) { + res := user.MustDo( + t, + "PUT", + getPathForSend(roomID, eventType, fmt.Sprintf(txnIdBase, i)), + getDelayQueryParam(delayStr), + ) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyEqual("delay_id", delayID), + }, + }) }) } From f73508f2aa4d99eaa1a8f85d65023c1a0b11967e Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 17 Sep 2024 15:07:18 -0400 Subject: [PATCH 16/17] Test that only own delayed events are returned --- tests/msc4140/delayed_event_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index aa79027f..210fafa5 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -103,6 +103,15 @@ func TestDelayedEvents(t *testing.T) { }, }) + t.Run("cannot get delayed events of another user", func(t *testing.T) { + res := getDelayedEvents(t, user2) + must.MatchResponse(t, res, match.HTTPResponse{ + JSON: []match.JSON{ + match.JSONKeyArrayOfSize("delayed_events", 0), + }, + }) + }) + time.Sleep(1 * time.Second) res = getDelayedEvents(t, user) must.MatchResponse(t, res, match.HTTPResponse{ From 5ad5d6272f868fd62516eb482e5341a9a4175cae Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:20:57 +0100 Subject: [PATCH 17/17] Add TODO for testing pagination --- tests/msc4140/delayed_event_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/msc4140/delayed_event_test.go b/tests/msc4140/delayed_event_test.go index 210fafa5..cf81bf83 100644 --- a/tests/msc4140/delayed_event_test.go +++ b/tests/msc4140/delayed_event_test.go @@ -19,6 +19,9 @@ import ( const hsName = "hs1" const eventType = "com.example.test" +// TODO: Test pagination of `GET /_matrix/client/v1/delayed_events` once +// it is implemented in a homeserver. + func TestDelayedEvents(t *testing.T) { deployment := complement.Deploy(t, 1) defer deployment.Destroy(t)