From 1889d0a6380f1ee32a7761addd049225658a4d3d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 28 Jul 2021 00:40:17 -0500 Subject: [PATCH 01/19] Add tests to ensure MSC3030 functionality MSC3030: https://github.com/matrix-org/matrix-doc/pull/3030 Synapse implementation: https://github.com/matrix-org/synapse/pull/9445 --- internal/client/client.go | 41 +++++++++++++++++++ tests/msc3030_test.go | 86 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/msc3030_test.go diff --git a/internal/client/client.go b/internal/client/client.go index b61728b8..cc835a55 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -212,6 +212,27 @@ func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, return userID, accessToken } +// GetEvent fetches the given event from the specified room and returns a typed Event +func (c *CSAPI) GetEvent(t *testing.T, roomID, eventId string) b.Event { + t.Helper() + res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "event", eventId}) + body := ParseJSON(t, res) + + statKeyRes := gjson.GetBytes(body, "state_key") + var stateKey *string = nil + if statKeyRes.Exists() { + stateKey = b.Ptr(statKeyRes.Str) + } + + return b.Event{ + Type: GetJSONFieldStr(t, body, "type"), + Sender: GetJSONFieldStr(t, body, "sender"), + StateKey: stateKey, + Content: GetJSONFieldStringMap(t, body, "content"), + Unsigned: GetJSONFieldStringMap(t, body, "unsigned"), + } +} + // MustDo will do the HTTP request and fail the test if the response is not 2xx func (c *CSAPI) MustDo(t *testing.T, method string, paths []string, jsonBody interface{}) *http.Response { t.Helper() @@ -406,6 +427,26 @@ func GetJSONFieldStringArray(t *testing.T, body []byte, wantKey string) []string return arr } +func GetJSONFieldStringMap(t *testing.T, body []byte, wantKey string) map[string]interface{} { + t.Helper() + + res := gjson.GetBytes(body, wantKey) + + if !res.Exists() { + t.Fatalf("GetJSONFieldStringMap: key '%s' missing from %s", wantKey, string(body)) + } + + jsonMap := map[string]interface{}{} + res.ForEach(func(key, value gjson.Result) bool { + jsonMap[key.Str] = value.Str + + // Keep iterating + return true + }) + + return jsonMap +} + // ParseJSON parses a JSON-encoded HTTP Response body into a byte slice func ParseJSON(t *testing.T, res *http.Response) []byte { t.Helper() diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go new file mode 100644 index 00000000..efb974f7 --- /dev/null +++ b/tests/msc3030_test.go @@ -0,0 +1,86 @@ +// +build msc3030 + +// This file contains tests for a jump to date API endpoint, +// currently experimental feature defined by MSC3030, which you can read here: +// https://github.com/matrix-org/matrix-doc/pull/3030 + +package tests + +import ( + "net/url" + "strconv" + "testing" + "time" + + "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/client" + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" +) + +func TestJumpToDateEndpoint(t *testing.T) { + deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote) + defer deployment.Destroy(t) + + // Create the normal user which will send messages in the room + userID := "@alice:hs1" + alice := deployment.Client(t, "hs1", userID) + + roomID := alice.CreateRoom(t, map[string]interface{}{}) + alice.JoinRoom(t, roomID, nil) + + timeBeforeEventA := time.Now() + eventAID := alice.SendEventSynced(t, roomID, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "Message A", + }, + }) + eventBID := alice.SendEventSynced(t, roomID, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "Message A", + }, + }) + timeAfterEventB := time.Now() + + logrus.WithFields(logrus.Fields{ + "eventAID": eventAID, + "eventBID": eventBID, + }).Error("see messages") + + t.Run("parallel", func(t *testing.T) { + t.Run("should find event after given timestmap", func(t *testing.T) { + checkEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID) + }) + + t.Run("should find event before given timestmap", func(t *testing.T) { + checkEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID) + }) + + }) +} + +func makeTimestampFromTime(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} + +func checkEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) { + t.Helper() + + timestampString := strconv.FormatInt(makeTimestampFromTime(givenTime), 10) + timestampToEventRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + "ts": []string{timestampString}, + })) + timestampToEventResBody := client.ParseJSON(t, timestampToEventRes) + + actualEventIdRes := gjson.GetBytes(timestampToEventResBody, "event_id") + actualEventId := actualEventIdRes.Str + + if actualEventId != expectedEventId { + actualEvent := c.GetEvent(t, roomID, actualEventId) + t.Fatalf("Expected to see %s but received %s\n%+v", expectedEventId, actualEventId, actualEvent) + } +} From 55385ec46098c7cf0f70d15fa1059de8c15ca6de Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 28 Jul 2021 02:15:34 -0500 Subject: [PATCH 02/19] Add color coding debug string when the test fails --- tests/msc3030_test.go | 91 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index efb974f7..1fac45f2 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -7,6 +7,7 @@ package tests import ( + "fmt" "net/url" "strconv" "testing" @@ -14,7 +15,6 @@ import ( "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/client" - "github.com/sirupsen/logrus" "github.com/tidwall/gjson" ) @@ -46,11 +46,6 @@ func TestJumpToDateEndpoint(t *testing.T) { }) timeAfterEventB := time.Now() - logrus.WithFields(logrus.Fields{ - "eventAID": eventAID, - "eventBID": eventBID, - }).Error("see messages") - t.Run("parallel", func(t *testing.T) { t.Run("should find event after given timestmap", func(t *testing.T) { checkEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID) @@ -59,28 +54,94 @@ func TestJumpToDateEndpoint(t *testing.T) { t.Run("should find event before given timestmap", func(t *testing.T) { checkEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID) }) - }) } -func makeTimestampFromTime(t time.Time) int64 { - return t.UnixNano() / int64(time.Millisecond) -} - func checkEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) { t.Helper() - timestampString := strconv.FormatInt(makeTimestampFromTime(givenTime), 10) + givenTimestamp := makeTimestampFromTime(givenTime) + timestampString := strconv.FormatInt(givenTimestamp, 10) timestampToEventRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ "ts": []string{timestampString}, })) timestampToEventResBody := client.ParseJSON(t, timestampToEventRes) actualEventIdRes := gjson.GetBytes(timestampToEventResBody, "event_id") - actualEventId := actualEventIdRes.Str + actualEventId := actualEventIdRes.String() if actualEventId != expectedEventId { - actualEvent := c.GetEvent(t, roomID, actualEventId) - t.Fatalf("Expected to see %s but received %s\n%+v", expectedEventId, actualEventId, actualEvent) + debugMessageList := getDebugMessageListFromMessagesResponse(t, c, roomID, expectedEventId, actualEventId, givenTimestamp) + t.Fatalf( + "Expected to see %s given %s but received %s\n%s", + decorateStringWithAnsiColor(expectedEventId, AnsiColorGreen), + decorateStringWithAnsiColor(timestampString, AnsiColorYellow), + decorateStringWithAnsiColor(actualEventId, AnsiColorRed), + debugMessageList, + ) + } +} + +func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, roomID string, expectedEventId string, actualEventId string, givenTimestamp int64) string { + t.Helper() + + messagesRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + "dir": []string{"b"}, + "limit": []string{"100"}, + })) + messsageResBody := client.ParseJSON(t, messagesRes) + + wantKey := "chunk" + res := gjson.GetBytes(messsageResBody, wantKey) + if !res.Exists() { + t.Fatalf("missing key '%s'", wantKey) + } + if !res.IsArray() { + t.Fatalf("key '%s' is not an array (was %s)", wantKey, res.Type) } + + givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("-- givenTimestamp=%s --\n", strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow) + + resultantString := "" + givenTimestampAlreadyInserted := false + res.ForEach(func(key, r gjson.Result) bool { + // The timestmap could be after-in-time of any of the events + if r.Get("origin_server_ts").Int() < givenTimestamp && !givenTimestampAlreadyInserted { + resultantString += givenTimestampMarker + givenTimestampAlreadyInserted = true + } + + event_id := r.Get("event_id").String() + event_id_string := event_id + if event_id == expectedEventId { + event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorGreen) + } else if event_id == actualEventId { + event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorRed) + } + + resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(r.Get("origin_server_ts").Int(), 10), r.Get("type").String()) + + // The timestmap could be before-in-time of any of the events + if r.Get("origin_server_ts").Int() > givenTimestamp && !givenTimestampAlreadyInserted { + resultantString += givenTimestampMarker + givenTimestampAlreadyInserted = true + } + + // keep iterating + return true + }) + + return resultantString +} + +func makeTimestampFromTime(t time.Time) int64 { + return t.UnixNano() / int64(time.Millisecond) +} + +const AnsiColorRed string = "31" +const AnsiColorGreen string = "32" +const AnsiColorYellow string = "33" + +func decorateStringWithAnsiColor(inputString, decorationColor string) string { + return fmt.Sprintf("\033[%sm%s\033[0m", decorationColor, inputString) } From 2a360a00248073a228b01f44f6205a8c7e6d5e6b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 28 Jul 2021 02:21:53 -0500 Subject: [PATCH 03/19] Remove unused GetEvent and GetJSONFieldStringMap functions --- internal/client/client.go | 41 --------------------------------------- 1 file changed, 41 deletions(-) diff --git a/internal/client/client.go b/internal/client/client.go index cc835a55..b61728b8 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -212,27 +212,6 @@ func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, return userID, accessToken } -// GetEvent fetches the given event from the specified room and returns a typed Event -func (c *CSAPI) GetEvent(t *testing.T, roomID, eventId string) b.Event { - t.Helper() - res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "event", eventId}) - body := ParseJSON(t, res) - - statKeyRes := gjson.GetBytes(body, "state_key") - var stateKey *string = nil - if statKeyRes.Exists() { - stateKey = b.Ptr(statKeyRes.Str) - } - - return b.Event{ - Type: GetJSONFieldStr(t, body, "type"), - Sender: GetJSONFieldStr(t, body, "sender"), - StateKey: stateKey, - Content: GetJSONFieldStringMap(t, body, "content"), - Unsigned: GetJSONFieldStringMap(t, body, "unsigned"), - } -} - // MustDo will do the HTTP request and fail the test if the response is not 2xx func (c *CSAPI) MustDo(t *testing.T, method string, paths []string, jsonBody interface{}) *http.Response { t.Helper() @@ -427,26 +406,6 @@ func GetJSONFieldStringArray(t *testing.T, body []byte, wantKey string) []string return arr } -func GetJSONFieldStringMap(t *testing.T, body []byte, wantKey string) map[string]interface{} { - t.Helper() - - res := gjson.GetBytes(body, wantKey) - - if !res.Exists() { - t.Fatalf("GetJSONFieldStringMap: key '%s' missing from %s", wantKey, string(body)) - } - - jsonMap := map[string]interface{}{} - res.ForEach(func(key, value gjson.Result) bool { - jsonMap[key.Str] = value.Str - - // Keep iterating - return true - }) - - return jsonMap -} - // ParseJSON parses a JSON-encoded HTTP Response body into a byte slice func ParseJSON(t *testing.T, res *http.Response) []byte { t.Helper() From 5990efe37906d835c5c0b6ee04a30842362ee85f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 11 Nov 2021 19:01:18 -0600 Subject: [PATCH 04/19] Fix timestamp insertion in debug string --- tests/msc3030_test.go | 52 ++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 1fac45f2..0c13ab8b 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -48,16 +48,16 @@ func TestJumpToDateEndpoint(t *testing.T) { t.Run("parallel", func(t *testing.T) { t.Run("should find event after given timestmap", func(t *testing.T) { - checkEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID) + mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID) }) t.Run("should find event before given timestmap", func(t *testing.T) { - checkEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID) + mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID) }) }) } -func checkEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) { +func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) { t.Helper() givenTimestamp := makeTimestampFromTime(givenTime) @@ -86,32 +86,43 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room t.Helper() messagesRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + // The events returned will be from the newest -> oldest since we're going backwards "dir": []string{"b"}, "limit": []string{"100"}, })) messsageResBody := client.ParseJSON(t, messagesRes) wantKey := "chunk" - res := gjson.GetBytes(messsageResBody, wantKey) - if !res.Exists() { + keyRes := gjson.GetBytes(messsageResBody, wantKey) + if !keyRes.Exists() { t.Fatalf("missing key '%s'", wantKey) } - if !res.IsArray() { - t.Fatalf("key '%s' is not an array (was %s)", wantKey, res.Type) + if !keyRes.IsArray() { + t.Fatalf("key '%s' is not an array (was %s)", wantKey, keyRes.Type) + } + + events := keyRes.Array() + if len(events) == 0 { + t.Fatalf( + "getDebugMessageListFromMessagesResponse found no messages in the room(%s).", + roomID, + ) } givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("-- givenTimestamp=%s --\n", strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow) - resultantString := "" + resultantString := "(newest)\n" givenTimestampAlreadyInserted := false - res.ForEach(func(key, r gjson.Result) bool { - // The timestmap could be after-in-time of any of the events - if r.Get("origin_server_ts").Int() < givenTimestamp && !givenTimestampAlreadyInserted { + // Given the `/messages?dir=b` request above, we're iterating over the events + // from newest-in-time to oldest-in-time. + for _, ev := range events { + // Check whether the givenTimestamp is newer(after-in-time) than the current event + if givenTimestamp > ev.Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { resultantString += givenTimestampMarker givenTimestampAlreadyInserted = true } - event_id := r.Get("event_id").String() + event_id := ev.Get("event_id").String() event_id_string := event_id if event_id == expectedEventId { event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorGreen) @@ -119,17 +130,16 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorRed) } - resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(r.Get("origin_server_ts").Int(), 10), r.Get("type").String()) + resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(ev.Get("origin_server_ts").Int(), 10), ev.Get("type").String()) + } - // The timestmap could be before-in-time of any of the events - if r.Get("origin_server_ts").Int() > givenTimestamp && !givenTimestampAlreadyInserted { - resultantString += givenTimestampMarker - givenTimestampAlreadyInserted = true - } + // The givenTimestamp could be older(before-in-time) than any of the other events + if givenTimestamp < events[len(events)-1].Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { + resultantString += givenTimestampMarker + givenTimestampAlreadyInserted = true + } - // keep iterating - return true - }) + resultantString += "(oldest)\n" return resultantString } From f8b88a9d5e7df26674c8042492cc6ccd818d8e44 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 11 Nov 2021 19:16:09 -0600 Subject: [PATCH 05/19] Show debug message list in scrollback order how you would see it in a chat --- tests/msc3030_test.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 0c13ab8b..a8d7520b 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -101,23 +101,22 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room t.Fatalf("key '%s' is not an array (was %s)", wantKey, keyRes.Type) } - events := keyRes.Array() + // Make the events go from oldest-in-time -> newest-in-time + events := reverseGjsonArray(keyRes.Array()) if len(events) == 0 { t.Fatalf( "getDebugMessageListFromMessagesResponse found no messages in the room(%s).", roomID, ) } - + resultantString := "(oldest)\n" + givenTimestampAlreadyInserted := false givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("-- givenTimestamp=%s --\n", strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow) - resultantString := "(newest)\n" - givenTimestampAlreadyInserted := false - // Given the `/messages?dir=b` request above, we're iterating over the events - // from newest-in-time to oldest-in-time. + // We're iterating over the events from oldest-in-time -> newest-in-time for _, ev := range events { - // Check whether the givenTimestamp is newer(after-in-time) than the current event - if givenTimestamp > ev.Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { + // Check whether the givenTimestamp is older(before-in-time) than the current event + if givenTimestamp < ev.Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { resultantString += givenTimestampMarker givenTimestampAlreadyInserted = true } @@ -133,13 +132,13 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(ev.Get("origin_server_ts").Int(), 10), ev.Get("type").String()) } - // The givenTimestamp could be older(before-in-time) than any of the other events - if givenTimestamp < events[len(events)-1].Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { + // The givenTimestamp could be newer(after-in-time) than any of the other events + if givenTimestamp > events[len(events)-1].Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { resultantString += givenTimestampMarker givenTimestampAlreadyInserted = true } - resultantString += "(oldest)\n" + resultantString += "(newest)\n" return resultantString } @@ -155,3 +154,11 @@ const AnsiColorYellow string = "33" func decorateStringWithAnsiColor(inputString, decorationColor string) string { return fmt.Sprintf("\033[%sm%s\033[0m", decorationColor, inputString) } + +func reverseGjsonArray(in []gjson.Result) []gjson.Result { + out := make([]gjson.Result, len(in)) + for i := 0; i < len(in); i++ { + out[i] = in[len(in)-i-1] + } + return out +} From 42af51af5d32635b32cdb0baa8aaf54653636944 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 11 Nov 2021 20:38:37 -0600 Subject: [PATCH 06/19] Update to use direction paremeter Also as side-effect we also get less flakey tests because it will always pick the event we expect instead of the one behind it because it happened to be created closer to the given timestamp. --- tests/msc3030_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index a8d7520b..761ccf2f 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -48,22 +48,23 @@ func TestJumpToDateEndpoint(t *testing.T) { t.Run("parallel", func(t *testing.T) { t.Run("should find event after given timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID) + mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, "f", eventAID) }) t.Run("should find event before given timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID) + mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, "b", eventBID) }) }) } -func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) { +func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, direction string, expectedEventId string) { t.Helper() givenTimestamp := makeTimestampFromTime(givenTime) timestampString := strconv.FormatInt(givenTimestamp, 10) timestampToEventRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ - "ts": []string{timestampString}, + "ts": []string{timestampString}, + "dir": []string{direction}, })) timestampToEventResBody := client.ParseJSON(t, timestampToEventRes) @@ -115,7 +116,9 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room // We're iterating over the events from oldest-in-time -> newest-in-time for _, ev := range events { - // Check whether the givenTimestamp is older(before-in-time) than the current event + // As we go, keep checking whether the givenTimestamp is + // older(before-in-time) than the current event and insert a timestamp + // marker as soon as we find the spot if givenTimestamp < ev.Get("origin_server_ts").Int() && !givenTimestampAlreadyInserted { resultantString += givenTimestampMarker givenTimestampAlreadyInserted = true From 52333830278ce8ac9dae149a1732f6ecbec5e3ed Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 12 Nov 2021 01:34:01 -0600 Subject: [PATCH 07/19] Add tests for finding nothing before and after the event timeline --- tests/msc3030_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 761ccf2f..4bdd3edb 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -26,6 +26,7 @@ func TestJumpToDateEndpoint(t *testing.T) { userID := "@alice:hs1" alice := deployment.Client(t, "hs1", userID) + timeBeforeRoomCreation := time.Now() roomID := alice.CreateRoom(t, map[string]interface{}{}) alice.JoinRoom(t, roomID, nil) @@ -54,6 +55,14 @@ func TestJumpToDateEndpoint(t *testing.T) { t.Run("should find event before given timestmap", func(t *testing.T) { mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, "b", eventBID) }) + + t.Run("should find nothing before the earliest timestmap", func(t *testing.T) { + mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeRoomCreation, "b", "") + }) + + t.Run("should find nothing after the latest timestmap", func(t *testing.T) { + mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, "f", "") + }) }) } @@ -62,14 +71,18 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin givenTimestamp := makeTimestampFromTime(givenTime) timestampString := strconv.FormatInt(givenTimestamp, 10) - timestampToEventRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + timestampToEventRes := c.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ "ts": []string{timestampString}, "dir": []string{direction}, })) timestampToEventResBody := client.ParseJSON(t, timestampToEventRes) - actualEventIdRes := gjson.GetBytes(timestampToEventResBody, "event_id") - actualEventId := actualEventIdRes.String() + actualEventId := "" + if timestampToEventRes.StatusCode == 200 { + actualEventId = client.GetJSONFieldStr(t, timestampToEventResBody, "event_id") + } else if timestampToEventRes.StatusCode != 404 { + t.Fatalf("mustCheckEventisReturnedForTime: /timestamp_to_event request failed with status=%d", timestampToEventRes.StatusCode) + } if actualEventId != expectedEventId { debugMessageList := getDebugMessageListFromMessagesResponse(t, c, roomID, expectedEventId, actualEventId, givenTimestamp) From 4348803a8100cb4bcb6f5175b1349042a0f7d7ce Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 12 Nov 2021 01:39:47 -0600 Subject: [PATCH 08/19] Add some comments --- tests/msc3030_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 4bdd3edb..5d6fbfca 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -66,6 +66,7 @@ func TestJumpToDateEndpoint(t *testing.T) { }) } +// Fetch event from /timestamp_to_event and ensure it matches the expectedEventId func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, direction string, expectedEventId string) { t.Helper() @@ -77,6 +78,8 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin })) timestampToEventResBody := client.ParseJSON(t, timestampToEventRes) + // Only allow a 200 response meaning we found an event or a 404 meaning we didn't. + // Other status codes will throw and assumed to be application errors. actualEventId := "" if timestampToEventRes.StatusCode == 200 { actualEventId = client.GetJSONFieldStr(t, timestampToEventResBody, "event_id") From 83f993c6a08ddbf62aef9083f1cfca2cc04888d4 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 15 Nov 2021 23:59:42 -0600 Subject: [PATCH 09/19] Make tests parallel and add federation tests --- tests/msc3030_test.go | 87 ++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 5d6fbfca..93dc02b0 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -26,19 +26,77 @@ func TestJumpToDateEndpoint(t *testing.T) { userID := "@alice:hs1" alice := deployment.Client(t, "hs1", userID) - timeBeforeRoomCreation := time.Now() - roomID := alice.CreateRoom(t, map[string]interface{}{}) - alice.JoinRoom(t, roomID, nil) + // Create the federated user which will fetch the messages from a remote homeserver + remoteUserID := "@charlie:hs2" + remoteCharlie := deployment.Client(t, "hs2", remoteUserID) + + t.Run("parallel", func(t *testing.T) { + t.Run("should find event after given timestmap", func(t *testing.T) { + t.Parallel() + roomID, eventA, _ := createTestRoom(t, alice) + mustCheckEventisReturnedForTime(t, alice, roomID, eventA.BeforeTimestamp, "f", eventA.EventID) + }) + + t.Run("should find event before given timestmap", func(t *testing.T) { + t.Parallel() + roomID, _, eventB := createTestRoom(t, alice) + mustCheckEventisReturnedForTime(t, alice, roomID, eventB.AfterTimestamp, "b", eventB.EventID) + }) + + t.Run("should find nothing before the earliest timestmap", func(t *testing.T) { + t.Parallel() + timeBeforeRoomCreation := time.Now() + roomID, _, _ := createTestRoom(t, alice) + mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeRoomCreation, "b", "") + }) + + t.Run("should find nothing after the latest timestmap", func(t *testing.T) { + t.Parallel() + roomID, _, eventB := createTestRoom(t, alice) + mustCheckEventisReturnedForTime(t, alice, roomID, eventB.AfterTimestamp, "f", "") + }) + + t.Run("federation", func(t *testing.T) { + t.Run("looking forwards, should be able to find event that was sent before we joined", func(t *testing.T) { + t.Parallel() + roomID, eventA, _ := createTestRoom(t, alice) + remoteCharlie.JoinRoom(t, roomID, []string{"hs1"}) + mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventA.BeforeTimestamp, "f", eventA.EventID) + }) + + t.Run("looking backwards, should be able to find event that was sent before we joined", func(t *testing.T) { + t.Parallel() + roomID, _, eventB := createTestRoom(t, alice) + remoteCharlie.JoinRoom(t, roomID, []string{"hs1"}) + mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID) + }) + }) + }) +} + +type eventTime struct { + EventID string + BeforeTimestamp time.Time + AfterTimestamp time.Time +} + +func createTestRoom(t *testing.T, c *client.CSAPI) (roomID string, eventA, eventB *eventTime) { + t.Helper() + + roomID = c.CreateRoom(t, map[string]interface{}{}) + //c.JoinRoom(t, roomID, nil) timeBeforeEventA := time.Now() - eventAID := alice.SendEventSynced(t, roomID, b.Event{ + eventAID := c.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", "body": "Message A", }, }) - eventBID := alice.SendEventSynced(t, roomID, b.Event{ + timeAfterEventA := time.Now() + + eventBID := c.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -47,23 +105,10 @@ func TestJumpToDateEndpoint(t *testing.T) { }) timeAfterEventB := time.Now() - t.Run("parallel", func(t *testing.T) { - t.Run("should find event after given timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, "f", eventAID) - }) - - t.Run("should find event before given timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, "b", eventBID) - }) + eventA = &eventTime{EventID: eventAID, BeforeTimestamp: timeBeforeEventA, AfterTimestamp: timeAfterEventA} + eventB = &eventTime{EventID: eventBID, BeforeTimestamp: timeAfterEventA, AfterTimestamp: timeAfterEventB} - t.Run("should find nothing before the earliest timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeBeforeRoomCreation, "b", "") - }) - - t.Run("should find nothing after the latest timestmap", func(t *testing.T) { - mustCheckEventisReturnedForTime(t, alice, roomID, timeAfterEventB, "f", "") - }) - }) + return roomID, eventA, eventB } // Fetch event from /timestamp_to_event and ensure it matches the expectedEventId From 7d3a691fa5bafc3f11130dda9d72a5d9a31cdd9f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 17 Nov 2021 03:06:10 -0600 Subject: [PATCH 10/19] Add experimental feature flag and use unstable endpoint --- dockerfiles/synapse/homeserver.yaml | 2 ++ dockerfiles/synapse/workers-shared.yaml | 2 ++ tests/msc3030_test.go | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dockerfiles/synapse/homeserver.yaml b/dockerfiles/synapse/homeserver.yaml index fab6922e..b85d1aef 100644 --- a/dockerfiles/synapse/homeserver.yaml +++ b/dockerfiles/synapse/homeserver.yaml @@ -109,3 +109,5 @@ experimental_features: spaces_enabled: true # Enable history backfilling support msc2716_enabled: true + # Enable jump to date endpoint + msc3030_enabled: true diff --git a/dockerfiles/synapse/workers-shared.yaml b/dockerfiles/synapse/workers-shared.yaml index b824557a..6b392b81 100644 --- a/dockerfiles/synapse/workers-shared.yaml +++ b/dockerfiles/synapse/workers-shared.yaml @@ -68,3 +68,5 @@ experimental_features: msc2716_enabled: true # Enable spaces support spaces_enabled: true + # Enable jump to date endpoint + msc3030_enabled: true diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 93dc02b0..2a12c363 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -117,7 +117,7 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin givenTimestamp := makeTimestampFromTime(givenTime) timestampString := strconv.FormatInt(givenTimestamp, 10) - timestampToEventRes := c.DoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + timestampToEventRes := c.DoFunc(t, "GET", []string{"_matrix", "client", "unstable", "org.matrix.msc3030", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ "ts": []string{timestampString}, "dir": []string{direction}, })) From 97a287f16833ccac77c98f3c59a9d9b74820fc50 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 29 Nov 2021 23:45:21 -0600 Subject: [PATCH 11/19] Add potential history visibility test This test does not work because redacted versions of the events are backfilled which include the event_id and origin_server_ts necessary to find it. We just can't see the contents of the message. So it's ok that this test fails. See https://github.com/matrix-org/synapse/pull/9445#discussion_r756013164 --- tests/msc3030_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 2a12c363..e37058e6 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -70,6 +70,43 @@ func TestJumpToDateEndpoint(t *testing.T) { remoteCharlie.JoinRoom(t, roomID, []string{"hs1"}) mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID) }) + + t.Run("looking backwards, before user was invited with restricted history_visibility, should find nothing", func(t *testing.T) { + t.Parallel() + + privateRoomId := alice.CreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + "name": "private spot", + "initial_state": []map[string]interface{}{ + { + "type": "m.room.history_visibility", + "state_key": "", + "content": map[string]string{ + // Events are only accessible to newly joined members from the + // point they were invited onwards. + "history_visibility": "invited", + }, + }, + }, + }) + + alice.SendEventSynced(t, privateRoomId, b.Event{ + Type: "m.room.message", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "Message A", + }, + }) + + timeBeforeRemoteCharlieInvited := time.Now() + alice.InviteRoom(t, privateRoomId, remoteCharlie.UserID) + remoteCharlie.JoinRoom(t, privateRoomId, []string{"hs1"}) + + // TODO: This test currently fails because the homeserver can see a + // redacted version of the hidden event. This is because of the behavior + // of `/backfilled` returning redacted version of hidden events. + mustCheckEventisReturnedForTime(t, remoteCharlie, privateRoomId, timeBeforeRemoteCharlieInvited, "b", "") + }) }) }) } From d96622b816ad2aebc632066e3d48e22f40af16c5 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 29 Nov 2021 23:49:14 -0600 Subject: [PATCH 12/19] Remove superfluous history visibility test The history visiblity of the room does not matter. We still return redacted versions of the events regardless of if you can see the event according to the history visiblity. --- tests/msc3030_test.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index e37058e6..2a12c363 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -70,43 +70,6 @@ func TestJumpToDateEndpoint(t *testing.T) { remoteCharlie.JoinRoom(t, roomID, []string{"hs1"}) mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID) }) - - t.Run("looking backwards, before user was invited with restricted history_visibility, should find nothing", func(t *testing.T) { - t.Parallel() - - privateRoomId := alice.CreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - "name": "private spot", - "initial_state": []map[string]interface{}{ - { - "type": "m.room.history_visibility", - "state_key": "", - "content": map[string]string{ - // Events are only accessible to newly joined members from the - // point they were invited onwards. - "history_visibility": "invited", - }, - }, - }, - }) - - alice.SendEventSynced(t, privateRoomId, b.Event{ - Type: "m.room.message", - Content: map[string]interface{}{ - "msgtype": "m.text", - "body": "Message A", - }, - }) - - timeBeforeRemoteCharlieInvited := time.Now() - alice.InviteRoom(t, privateRoomId, remoteCharlie.UserID) - remoteCharlie.JoinRoom(t, privateRoomId, []string{"hs1"}) - - // TODO: This test currently fails because the homeserver can see a - // redacted version of the hidden event. This is because of the behavior - // of `/backfilled` returning redacted version of hidden events. - mustCheckEventisReturnedForTime(t, remoteCharlie, privateRoomId, timeBeforeRemoteCharlieInvited, "b", "") - }) }) }) } From e8787c13d75fcf5922919d36d9dd7fff98972f08 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 2 Dec 2021 23:27:06 -0600 Subject: [PATCH 13/19] Add actual/expected text labels so it's usable with ascii colors --- tests/msc3030_test.go | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 2a12c363..45c5dbb7 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -171,9 +171,15 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room roomID, ) } - resultantString := "(oldest)\n" + + // We need some padding for some lines to make them all align with the label. + // Pad this out so it equals whatever the longest label is. + paddingString := " " + + resultantString := fmt.Sprintf("%s-- oldest events --\n", paddingString) + givenTimestampAlreadyInserted := false - givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("-- givenTimestamp=%s --\n", strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow) + givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("%s-- givenTimestamp=%s --\n", paddingString, strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow) // We're iterating over the events from oldest-in-time -> newest-in-time for _, ev := range events { @@ -185,15 +191,24 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room givenTimestampAlreadyInserted = true } - event_id := ev.Get("event_id").String() - event_id_string := event_id - if event_id == expectedEventId { - event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorGreen) - } else if event_id == actualEventId { - event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorRed) + eventID := ev.Get("event_id").String() + eventIDString := eventID + labelString := paddingString + if eventID == expectedEventId { + eventIDString = decorateStringWithAnsiColor(eventID, AnsiColorGreen) + labelString = "(expected) " + } else if eventID == actualEventId { + eventIDString = decorateStringWithAnsiColor(eventID, AnsiColorRed) + labelString = " (actual) " } - resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(ev.Get("origin_server_ts").Int(), 10), ev.Get("type").String()) + resultantString += fmt.Sprintf( + "%s%s (%s) - %s\n", + labelString, + eventIDString, + strconv.FormatInt(ev.Get("origin_server_ts").Int(), 10), + ev.Get("type").String(), + ) } // The givenTimestamp could be newer(after-in-time) than any of the other events @@ -202,7 +217,7 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room givenTimestampAlreadyInserted = true } - resultantString += "(newest)\n" + resultantString += fmt.Sprintf("%s-- newest events --\n", paddingString) return resultantString } From f71f88f5096b0344c0ffa6998057d773b4a31ba9 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 2 Dec 2021 23:31:44 -0600 Subject: [PATCH 14/19] Remove commented out code --- tests/msc3030_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 45c5dbb7..c4b79fde 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -84,7 +84,6 @@ func createTestRoom(t *testing.T, c *client.CSAPI) (roomID string, eventA, event t.Helper() roomID = c.CreateRoom(t, map[string]interface{}{}) - //c.JoinRoom(t, roomID, nil) timeBeforeEventA := time.Now() eventAID := c.SendEventSynced(t, roomID, b.Event{ From 94957f430b45d47868ffcaf4bc05b1a1afba6ee8 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 2 Dec 2021 23:50:08 -0600 Subject: [PATCH 15/19] Add test to make sure we're not leaking events from private rooms --- tests/msc3030_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index c4b79fde..b45c9384 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -56,6 +56,34 @@ func TestJumpToDateEndpoint(t *testing.T) { mustCheckEventisReturnedForTime(t, alice, roomID, eventB.AfterTimestamp, "f", "") }) + // Just a sanity check that we're not leaking anything from the `/timestamp_to_event` endpoint + t.Run("should not be able to query a private room you are not a member of", func(t *testing.T) { + t.Parallel() + timeBeforeRoomCreation := time.Now() + + // Alice will create the private room + roomID := alice.CreateRoom(t, map[string]interface{}{ + "preset": "private_chat", + }) + + // We will use Bob to query the room they're not a member of + nonMemberUser := deployment.Client(t, "hs1", "@bob:hs1") + + // Make the `/timestamp_to_event` request from Bob's perspective (non room member) + timestamp := makeTimestampFromTime(timeBeforeRoomCreation) + timestampString := strconv.FormatInt(timestamp, 10) + timestampToEventRes := nonMemberUser.DoFunc(t, "GET", []string{"_matrix", "client", "unstable", "org.matrix.msc3030", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + "ts": []string{timestampString}, + "dir": []string{"f"}, + })) + + // A random user is not allowed to query for events in a private room + // they're not a member of (forbidden). + if timestampToEventRes.StatusCode != 403 { + t.Fatalf("/timestamp_to_event returned %d HTTP status code but expected %d", timestampToEventRes.StatusCode, 403) + } + }) + t.Run("federation", func(t *testing.T) { t.Run("looking forwards, should be able to find event that was sent before we joined", func(t *testing.T) { t.Parallel() From 39cb82c9b6ab42536705da248c056734a51b9c07 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 17 Dec 2021 18:01:35 -0600 Subject: [PATCH 16/19] Refactor language to want/got See https://github.com/matrix-org/complement/pull/178#discussion_r771481500 --- tests/msc3030_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index b45c9384..69513171 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -162,7 +162,7 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin if actualEventId != expectedEventId { debugMessageList := getDebugMessageListFromMessagesResponse(t, c, roomID, expectedEventId, actualEventId, givenTimestamp) t.Fatalf( - "Expected to see %s given %s but received %s\n%s", + "Want %s given %s but got %s\n%s", decorateStringWithAnsiColor(expectedEventId, AnsiColorGreen), decorateStringWithAnsiColor(timestampString, AnsiColorYellow), decorateStringWithAnsiColor(actualEventId, AnsiColorRed), @@ -201,7 +201,7 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room // We need some padding for some lines to make them all align with the label. // Pad this out so it equals whatever the longest label is. - paddingString := " " + paddingString := " " resultantString := fmt.Sprintf("%s-- oldest events --\n", paddingString) @@ -223,10 +223,10 @@ func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, room labelString := paddingString if eventID == expectedEventId { eventIDString = decorateStringWithAnsiColor(eventID, AnsiColorGreen) - labelString = "(expected) " + labelString = "(want) " } else if eventID == actualEventId { eventIDString = decorateStringWithAnsiColor(eventID, AnsiColorRed) - labelString = " (actual) " + labelString = " (got) " } resultantString += fmt.Sprintf( From 148387eec3b7bb234ef3b23e9ed688e8cdf8f9c1 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 2 Mar 2022 20:32:20 -0600 Subject: [PATCH 17/19] Always set preset since Synapse/Dendrite disagree on what the default is See https://github.com/matrix-org/complement/pull/178#discussion_r817870441 --- tests/msc3030_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 69513171..621edbe7 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -111,7 +111,9 @@ type eventTime struct { func createTestRoom(t *testing.T, c *client.CSAPI) (roomID string, eventA, eventB *eventTime) { t.Helper() - roomID = c.CreateRoom(t, map[string]interface{}{}) + roomID = c.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + }) timeBeforeEventA := time.Now() eventAID := c.SendEventSynced(t, roomID, b.Event{ From ccb51a945d37c5ab6bbcb32526cb8424d2362cb9 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 2 Mar 2022 20:33:48 -0600 Subject: [PATCH 18/19] Fix message A vs B typo As discovered in https://github.com/matrix-org/complement/pull/178#discussion_r817871029 --- tests/msc3030_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 621edbe7..7572921b 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -129,7 +129,7 @@ func createTestRoom(t *testing.T, c *client.CSAPI) (roomID string, eventA, event Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", - "body": "Message A", + "body": "Message B", }, }) timeAfterEventB := time.Now() From a383a08a198a139ca222c50142380bb51816b5f6 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 2 Mar 2022 20:42:55 -0600 Subject: [PATCH 19/19] Also add test to make sure we don't leak from public room either See https://github.com/matrix-org/complement/pull/178#discussion_r817876733 --- tests/msc3030_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/msc3030_test.go b/tests/msc3030_test.go index 7572921b..4608474a 100644 --- a/tests/msc3030_test.go +++ b/tests/msc3030_test.go @@ -84,6 +84,34 @@ func TestJumpToDateEndpoint(t *testing.T) { } }) + // Just a sanity check that we're not leaking anything from the `/timestamp_to_event` endpoint + t.Run("should not be able to query a public room you are not a member of", func(t *testing.T) { + t.Parallel() + timeBeforeRoomCreation := time.Now() + + // Alice will create the public room + roomID := alice.CreateRoom(t, map[string]interface{}{ + "preset": "public_chat", + }) + + // We will use Bob to query the room they're not a member of + nonMemberUser := deployment.Client(t, "hs1", "@bob:hs1") + + // Make the `/timestamp_to_event` request from Bob's perspective (non room member) + timestamp := makeTimestampFromTime(timeBeforeRoomCreation) + timestampString := strconv.FormatInt(timestamp, 10) + timestampToEventRes := nonMemberUser.DoFunc(t, "GET", []string{"_matrix", "client", "unstable", "org.matrix.msc3030", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{ + "ts": []string{timestampString}, + "dir": []string{"f"}, + })) + + // A random user is not allowed to query for events in a public room + // they're not a member of (forbidden). + if timestampToEventRes.StatusCode != 403 { + t.Fatalf("/timestamp_to_event returned %d HTTP status code but expected %d", timestampToEventRes.StatusCode, 403) + } + }) + t.Run("federation", func(t *testing.T) { t.Run("looking forwards, should be able to find event that was sent before we joined", func(t *testing.T) { t.Parallel()