Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 75 additions & 46 deletions tests/room_timestamp_to_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,88 @@ func TestJumpToDateEndpoint(t *testing.T) {
mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, timeBeforeRoomCreation, "b", importedEventID)
})

t.Run("can paginate after getting remote event from timestamp to event endpoint", func(t *testing.T) {
t.Run("can paginate backwards after getting remote event from timestamp to event endpoint (start)", func(t *testing.T) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test will fail until element-hq/synapse#19611 is merged

Copy link
Copy Markdown
Collaborator Author

@MadLittleMods MadLittleMods Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to have a test for paginating from the start and end tokens from /context after getting an event from /timestamp_to_event.

Previously, we only had a test for end

t.Parallel()
roomID, eventA, eventB := createTestRoom(t, alice)
remoteCharlie.MustJoinRoom(t, roomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
// After Charlie's homeserver finds the event, it "should try to backfill this
// event" (per the spec,
// https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid)
mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID)

// Get a pagination token from eventB
// And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a
// pagination token to retrieve the events around the returned event." (per the
// spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event).
//
// Get a pagination token that represents the position just *before* eventB
contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID},
client.WithContentType("application/json"), client.WithQueries(url.Values{
"limit": []string{"0"},
}),
// Retry as the worker backfilling and persisting the event isn't necessarily
// the same as the worker serving `/context`
client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool {
return res.StatusCode == 200
}))
)
contextResResBody := client.ParseJSON(t, contextRes)
// Remember: Tokens are positions between events.
//
// start end
// | |
// [A] <-- ▼ [B] ▼ <--- [remoteCharlie join]
//
// "start" is the token that represents the position just *before* eventB
paginationToken := client.GetJSONFieldStr(t, contextResResBody, "start")

// Paginate backwards seamlessly from the `/context` request (start, point
// before eventB)
messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"},
client.WithContentType("application/json"),
client.WithQueries(url.Values{
"dir": []string{"b"},
"limit": []string{"100"},
"from": []string{paginationToken},
}),
)

// Make sure A is visible
must.MatchResponse(t, messagesRes, match.HTTPResponse{
JSON: []match.JSON{
match.JSONCheckOff("chunk", []interface{}{eventA.EventID}, match.CheckOffMapper(func(r gjson.Result) interface{} {
return r.Get("event_id").Str
}), match.CheckOffAllowUnwanted()),
},
})
})

t.Run("can paginate backwards after getting remote event from timestamp to event endpoint (end)", func(t *testing.T) {
t.Parallel()
roomID, eventA, eventB := createTestRoom(t, alice)
remoteCharlie.MustJoinRoom(t, roomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
})
// After Charlie's homeserver finds the event, it "should try to backfill this
// event" (per the spec,
// https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid)
mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID)

// And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a
// pagination token to retrieve the events around the returned event." (per the
// spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event).
//
// Get a pagination token that represents the position just *after* eventB
contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID},
client.WithContentType("application/json"), client.WithQueries(url.Values{
"limit": []string{"0"},
}),
// Retry as the worker backfilling and persisting the event isn't necessarily
// the same as the worker serving `/context`
client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool {
return res.StatusCode == 200
}))
)
contextResResBody := client.ParseJSON(t, contextRes)
// Remember: Tokens are positions between events. Normally, you would use the
Expand All @@ -227,16 +296,12 @@ func TestJumpToDateEndpoint(t *testing.T) {
// start end
// | |
// [A] <-- ▼ [B] ▼ <--- [remoteCharlie join]
//
// "end" is the token that represents the position just *after* eventB
paginationToken := client.GetJSONFieldStr(t, contextResResBody, "end")

// Hit `/messages` until `eventA` has been backfilled and replicated across
// workers (the worker persisting events isn't necessarily the same as the worker
// serving `/messages`)
fetchUntilMessagesResponseHas(t, remoteCharlie, roomID, func(ev gjson.Result) bool {
return ev.Get("event_id").Str == eventA.EventID
})

// Paginate backwards from the point after eventB
// Paginate backwards seamlessly from the `/context` request (end, point after
// eventB)
messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"},
client.WithContentType("application/json"),
client.WithQueries(url.Values{
Expand Down Expand Up @@ -357,42 +422,6 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin
}
}

func fetchUntilMessagesResponseHas(t *testing.T, c *client.CSAPI, roomID string, check func(gjson.Result) bool) {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove fetchUntilMessagesResponseHas as it's now unused

t.Helper()
start := time.Now()
checkCounter := 0
for {
if time.Since(start) > c.SyncUntilTimeout {
t.Fatalf("fetchUntilMessagesResponseHas timed out. Called check function %d times", checkCounter)
}

messagesRes := c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{
"dir": []string{"b"},
"limit": []string{"100"},
}))
messsageResBody := client.ParseJSON(t, messagesRes)
wantKey := "chunk"
keyRes := gjson.GetBytes(messsageResBody, wantKey)
if !keyRes.Exists() {
t.Fatalf("missing key '%s'", wantKey)
}
if !keyRes.IsArray() {
t.Fatalf("key '%s' is not an array (was %s)", wantKey, keyRes.Type)
}

events := keyRes.Array()
for _, ev := range events {
if check(ev) {
return
}
}

checkCounter++
// Add a slight delay so we don't hammmer the messages endpoint
time.Sleep(500 * time.Millisecond)
}
}

func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, roomID string, expectedEventId string, actualEventId string, givenTimestamp int64) string {
t.Helper()

Expand Down
Loading