Skip to content

Commit bb8d0f0

Browse files
TestJumpToDateEndpoint: Add test for seamlessly paginating from /context start token (#853)
Follow-up to #852
1 parent 9b87d55 commit bb8d0f0

1 file changed

Lines changed: 76 additions & 46 deletions

File tree

tests/room_timestamp_to_event_test.go

Lines changed: 76 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package tests
99

1010
import (
1111
"fmt"
12+
"net/http"
1213
"net/url"
1314
"strconv"
1415
"testing"
@@ -204,19 +205,88 @@ func TestJumpToDateEndpoint(t *testing.T) {
204205
mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, timeBeforeRoomCreation, "b", importedEventID)
205206
})
206207

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

215-
// Get a pagination token from eventB
219+
// And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a
220+
// pagination token to retrieve the events around the returned event." (per the
221+
// spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event).
222+
//
223+
// Get a pagination token that represents the position just *before* eventB
216224
contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID},
217225
client.WithContentType("application/json"), client.WithQueries(url.Values{
218226
"limit": []string{"0"},
219227
}),
228+
// Retry as the worker backfilling and persisting the event isn't necessarily
229+
// the same as the worker serving `/context`
230+
client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool {
231+
return res.StatusCode == 200
232+
}),
233+
)
234+
contextResResBody := client.ParseJSON(t, contextRes)
235+
// Remember: Tokens are positions between events.
236+
//
237+
// start end
238+
// | |
239+
// [A] <-- ▼ [B] ▼ <--- [remoteCharlie join]
240+
//
241+
// "start" is the token that represents the position just *before* eventB
242+
paginationToken := client.GetJSONFieldStr(t, contextResResBody, "start")
243+
244+
// Paginate backwards seamlessly from the `/context` request (start, point
245+
// before eventB)
246+
messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"},
247+
client.WithContentType("application/json"),
248+
client.WithQueries(url.Values{
249+
"dir": []string{"b"},
250+
"limit": []string{"100"},
251+
"from": []string{paginationToken},
252+
}),
253+
)
254+
255+
// Make sure A is visible
256+
must.MatchResponse(t, messagesRes, match.HTTPResponse{
257+
JSON: []match.JSON{
258+
match.JSONCheckOff("chunk", []interface{}{eventA.EventID}, match.CheckOffMapper(func(r gjson.Result) interface{} {
259+
return r.Get("event_id").Str
260+
}), match.CheckOffAllowUnwanted()),
261+
},
262+
})
263+
})
264+
265+
t.Run("can paginate backwards after getting remote event from timestamp to event endpoint (end)", func(t *testing.T) {
266+
t.Parallel()
267+
roomID, eventA, eventB := createTestRoom(t, alice)
268+
remoteCharlie.MustJoinRoom(t, roomID, []spec.ServerName{
269+
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
270+
})
271+
// After Charlie's homeserver finds the event, it "should try to backfill this
272+
// event" (per the spec,
273+
// https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1timestamp_to_eventroomid)
274+
mustCheckEventisReturnedForTime(t, remoteCharlie, roomID, eventB.AfterTimestamp, "b", eventB.EventID)
275+
276+
// And then "clients can call /rooms/{roomId}/context/{eventId} to obtain a
277+
// pagination token to retrieve the events around the returned event." (per the
278+
// spec, https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event).
279+
//
280+
// Get a pagination token that represents the position just *after* eventB
281+
contextRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "context", eventB.EventID},
282+
client.WithContentType("application/json"), client.WithQueries(url.Values{
283+
"limit": []string{"0"},
284+
}),
285+
// Retry as the worker backfilling and persisting the event isn't necessarily
286+
// the same as the worker serving `/context`
287+
client.WithRetryUntil(remoteCharlie.SyncUntilTimeout, func(res *http.Response) bool {
288+
return res.StatusCode == 200
289+
}),
220290
)
221291
contextResResBody := client.ParseJSON(t, contextRes)
222292
// Remember: Tokens are positions between events. Normally, you would use the
@@ -227,16 +297,12 @@ func TestJumpToDateEndpoint(t *testing.T) {
227297
// start end
228298
// | |
229299
// [A] <-- ▼ [B] ▼ <--- [remoteCharlie join]
300+
//
301+
// "end" is the token that represents the position just *after* eventB
230302
paginationToken := client.GetJSONFieldStr(t, contextResResBody, "end")
231303

232-
// Hit `/messages` until `eventA` has been backfilled and replicated across
233-
// workers (the worker persisting events isn't necessarily the same as the worker
234-
// serving `/messages`)
235-
fetchUntilMessagesResponseHas(t, remoteCharlie, roomID, func(ev gjson.Result) bool {
236-
return ev.Get("event_id").Str == eventA.EventID
237-
})
238-
239-
// Paginate backwards from the point after eventB
304+
// Paginate backwards seamlessly from the `/context` request (end, point after
305+
// eventB)
240306
messagesRes := remoteCharlie.MustDo(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"},
241307
client.WithContentType("application/json"),
242308
client.WithQueries(url.Values{
@@ -357,42 +423,6 @@ func mustCheckEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID strin
357423
}
358424
}
359425

360-
func fetchUntilMessagesResponseHas(t *testing.T, c *client.CSAPI, roomID string, check func(gjson.Result) bool) {
361-
t.Helper()
362-
start := time.Now()
363-
checkCounter := 0
364-
for {
365-
if time.Since(start) > c.SyncUntilTimeout {
366-
t.Fatalf("fetchUntilMessagesResponseHas timed out. Called check function %d times", checkCounter)
367-
}
368-
369-
messagesRes := c.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{
370-
"dir": []string{"b"},
371-
"limit": []string{"100"},
372-
}))
373-
messsageResBody := client.ParseJSON(t, messagesRes)
374-
wantKey := "chunk"
375-
keyRes := gjson.GetBytes(messsageResBody, wantKey)
376-
if !keyRes.Exists() {
377-
t.Fatalf("missing key '%s'", wantKey)
378-
}
379-
if !keyRes.IsArray() {
380-
t.Fatalf("key '%s' is not an array (was %s)", wantKey, keyRes.Type)
381-
}
382-
383-
events := keyRes.Array()
384-
for _, ev := range events {
385-
if check(ev) {
386-
return
387-
}
388-
}
389-
390-
checkCounter++
391-
// Add a slight delay so we don't hammmer the messages endpoint
392-
time.Sleep(500 * time.Millisecond)
393-
}
394-
}
395-
396426
func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, roomID string, expectedEventId string, actualEventId string, givenTimestamp int64) string {
397427
t.Helper()
398428

0 commit comments

Comments
 (0)