Skip to content

Commit cb4e3c5

Browse files
committed
Add working soft-failure test; add failing newly joined servers test
1 parent 4b00d1e commit cb4e3c5

1 file changed

Lines changed: 111 additions & 64 deletions

File tree

tests/msc4354/sticky_events_test.go

Lines changed: 111 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package tests
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"net/url"
76
"strconv"
@@ -13,13 +12,11 @@ import (
1312
"github.com/matrix-org/complement/b"
1413
"github.com/matrix-org/complement/client"
1514
"github.com/matrix-org/complement/ct"
16-
"github.com/matrix-org/complement/federation"
1715
"github.com/matrix-org/complement/helpers"
1816
"github.com/matrix-org/complement/match"
1917
"github.com/matrix-org/complement/must"
2018
"github.com/matrix-org/gomatrixserverlib/spec"
2119
"github.com/tidwall/gjson"
22-
"github.com/tidwall/sjson"
2320
)
2421

2522
var txnID int64 = 10000
@@ -53,6 +50,7 @@ func sendStickyEvent(t ct.TestLike, c *client.CSAPI, roomID string, e b.Event, o
5350
}
5451

5552
func MustDoSlidingSync(t ct.TestLike, user *client.CSAPI, pos string) (gjson.Result, string) {
53+
t.Helper()
5654
body := map[string]interface{}{
5755
"lists": map[string]any{
5856
"any-key": map[string]any{
@@ -111,8 +109,9 @@ var stopMsg = b.Event{
111109

112110
// Helper function to do /sync or SSS requests. Does a single /sync request.
113111
// Returns the sticky/timeline events for the provided room ID, if any.
114-
// Returns `true` if the timeline included stopAtEventID.
112+
// Returns `true` if the timeline or sticky section included stopAtEventID.
115113
func performSync(t ct.TestLike, cli *client.CSAPI, useSimplifiedSlidingSync bool, since, roomID, stopAtEventID string) (syncResp syncResponse, nextSince string, stop bool) {
114+
t.Helper()
116115
var timeline []gjson.Result
117116
var sticky []gjson.Result
118117
var resp gjson.Result
@@ -126,7 +125,7 @@ func performSync(t ct.TestLike, cli *client.CSAPI, useSimplifiedSlidingSync bool
126125
sticky = resp.Get("rooms.join." + client.GjsonEscape(roomID) + ".msc4354_sticky.events").Array()
127126
// t.Logf("%s\b", resp.Raw)
128127
}
129-
for _, ev := range timeline {
128+
for _, ev := range append(append([]gjson.Result{}, timeline...), sticky...) {
130129
if ev.Get("event_id").Str == stopAtEventID {
131130
stop = true
132131
break
@@ -407,79 +406,127 @@ func TestStickyEventsIgnoreHistoryVisibility(t *testing.T) {
407406
})
408407
}
409408

410-
func xTestSoftFailedStickyEvents(t *testing.T) {
411-
deployment := complement.Deploy(t, 1)
409+
func xTestStickyEventsSentToNewlyJoinedServers(t *testing.T) {
410+
deployment := complement.Deploy(t, 3)
412411
defer deployment.Destroy(t)
413412

414-
srv := federation.NewServer(t, deployment,
415-
federation.HandleKeyRequests(),
416-
federation.HandleMakeSendJoinRequests(),
417-
federation.HandleTransactionRequests(
418-
nil, nil,
419-
),
420-
)
421-
cancel := srv.Listen()
422-
defer cancel()
413+
// newJoiner will join via alice (hs1).
414+
// we include bob as a bystander server. hs2 will not process the /send_join response
415+
// but should receive the join event and realise it needs to send its own sticky events
416+
// to hs3.
417+
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
418+
bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
419+
newJoiner := deployment.Register(t, "hs3", helpers.RegistrationOpts{})
420+
421+
forEachSync(t, func(t *testing.T, useSimplifiedSlidingSync bool) {
422+
roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"})
423+
bob.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"})
424+
// Make a timeline like
425+
// [ STICKY, MSG1, MSG2, ... MSG25, STICKY ]
426+
// and ensure newly joined servers see both sticky events
427+
duration := 30000
428+
aliceStickyEventIDNotInTimeline := sendStickyEvent(t, alice, roomID, b.Event{
429+
Type: "m.room.message",
430+
Content: map[string]interface{}{
431+
"msgtype": "m.text",
432+
"body": "ALICE This is a sticky event which is beyond the timeline limit",
433+
},
434+
}, withStickyDuration(duration))
435+
bobStickyEventIDNotInTimeline := sendStickyEvent(t, bob, roomID, b.Event{
436+
Type: "m.room.message",
437+
Content: map[string]interface{}{
438+
"msgtype": "m.text",
439+
"body": "BOB This is a sticky event which is beyond the timeline limit",
440+
},
441+
}, withStickyDuration(duration))
442+
for i := 0; i < 25; i++ {
443+
alice.Unsafe_SendEventUnsynced(t, roomID, b.Event{
444+
Type: "m.room.message",
445+
Content: map[string]interface{}{
446+
"msgtype": "m.text",
447+
"body": fmt.Sprintf("msg %d", i),
448+
},
449+
})
450+
}
451+
aliceStickyEventIDInTimeline := sendStickyEvent(t, alice, roomID, b.Event{
452+
Type: "m.room.message",
453+
Content: map[string]interface{}{
454+
"msgtype": "m.text",
455+
"body": "ALICE This is a sticky event which is inside the timeline limit",
456+
},
457+
}, withStickyDuration(duration))
458+
bobStickyEventIDInTimeline := sendStickyEvent(t, bob, roomID, b.Event{
459+
Type: "m.room.message",
460+
Content: map[string]interface{}{
461+
"msgtype": "m.text",
462+
"body": "BOB This is a sticky event which is inside the timeline limit",
463+
},
464+
}, withStickyDuration(duration))
465+
466+
newJoiner.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"})
467+
468+
// wait until hs1 and hs2 see the join, as this will trigger the sending of sticky events
469+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(newJoiner.UserID, roomID))
470+
bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(newJoiner.UserID, roomID))
471+
472+
stopEventID := alice.Unsafe_SendEventUnsynced(t, roomID, stopMsg)
473+
474+
syncResp := gatherSyncResults(t, newJoiner, useSimplifiedSlidingSync, roomID, stopEventID)
475+
mustHaveStickyEventID(t, aliceStickyEventIDInTimeline, syncResp.stickyEvents)
476+
mustHaveStickyEventID(t, aliceStickyEventIDNotInTimeline, syncResp.stickyEvents)
477+
mustHaveStickyEventID(t, bobStickyEventIDInTimeline, syncResp.stickyEvents)
478+
mustHaveStickyEventID(t, bobStickyEventIDNotInTimeline, syncResp.stickyEvents)
479+
})
480+
}
481+
482+
func TestSoftFailedStickyEvents(t *testing.T) {
483+
deployment := complement.Deploy(t, 2)
484+
defer deployment.Destroy(t)
423485

424486
alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{})
425-
bob := srv.UserID("bob")
487+
bob := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
488+
sentinel := deployment.Register(t, "hs2", helpers.RegistrationOpts{})
426489

427490
roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"})
428-
srvRoom := srv.MustJoinRoom(t, deployment, "hs1", roomID, bob)
429-
latestEventID := srvRoom.ForwardExtremities[0]
430-
t.Logf("latestEventID = %s", latestEventID)
431-
432-
// Alice kicks Bob. Concurrently, Bob sends a sticky event. The sticky event is soft-failed.
491+
bob.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"})
492+
sentinel.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"})
493+
494+
// We want to concurrently:
495+
// - Alice kicks Bob
496+
// - Bob sends a sticky event.
497+
// To do this, we will pause each server so they can't communicate their events with each other.
498+
deployment.PauseServer(t, "hs2")
433499
alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "kick"}, client.WithJSONBody(t, map[string]string{
434-
"user_id": bob,
500+
"user_id": bob.UserID,
435501
"reason": "Testing",
436502
}))
437-
stickyPDU := srv.MustCreateEvent(t, srvRoom, federation.Event{
438-
Type: "m.room.message",
439-
Sender: bob,
503+
deployment.PauseServer(t, "hs1")
504+
deployment.UnpauseServer(t, "hs2")
505+
stickyEventID := sendStickyEvent(t, bob, roomID, b.Event{
506+
Type: "m.room.message",
440507
Content: map[string]interface{}{
441508
"msgtype": "m.text",
442-
"body": "Bob's sticky event",
443-
},
444-
PrevEvents: []string{latestEventID},
445-
AuthEvents: []string{
446-
srvRoom.CurrentState(spec.MRoomCreate, "").EventID(),
447-
srvRoom.CurrentState(spec.MRoomPowerLevels, "").EventID(),
448-
latestEventID, // bob's join
509+
"body": "This is a sticky message sent whilst HS1 is offline",
449510
},
450511
})
451-
// XXX: this doesn't work as it trips the content hash check
452-
stickyJSON := stickyPDU.JSON()
453-
stickyJSON, err := sjson.SetBytes(stickyJSON, "msc4354_sticky.duration_ms", 600000)
454-
must.NotError(t, "failed to set sticky field", err)
455-
srv.MustSendTransaction(t, deployment, "hs1", []json.RawMessage{stickyJSON}, nil)
456-
t.Logf("sticky event ID: %s", stickyPDU.EventID())
457-
458-
// TODO: Check that the sticky event was soft-failed and did not appear in the timeline.
459-
460-
// now send 25 timeline events to shift the timeline.
461-
// TODO: test without this as well, as it shouldn't matter (it'll always go to sticky even if <25 events)
462-
for i := 0; i < 25; i++ {
463-
alice.Unsafe_SendEventUnsynced(t, roomID, b.Event{
464-
Type: "m.room.message",
465-
Content: map[string]interface{}{
466-
"msgtype": "m.text",
467-
"body": fmt.Sprintf("msg %d", i),
468-
},
469-
})
512+
deployment.UnpauseServer(t, "hs1")
513+
514+
// we want to check that the sticky event was in fact soft-failed. This is hard to do since it won't
515+
// come down /sync. Instead, we send a sentinel message from a different user and assert that we see
516+
// the sentinel event but not the sticky event.
517+
sentinelEventID := sentinel.Unsafe_SendEventUnsynced(t, roomID, stopMsg)
518+
syncResp := gatherSyncResults(t, alice, false, roomID, sentinelEventID)
519+
for _, ev := range append(syncResp.timelineEvents, syncResp.stickyEvents...) {
520+
if ev.Get("event_id").Str == stickyEventID {
521+
ct.Fatalf(t, "sticky event %s was not soft failed!", stickyEventID)
522+
}
470523
}
471-
// now Bob rejoins. We should see the sticky event in the sticky section.
472-
srv.MustJoinRoom(t, deployment, "hs1", roomID, bob)
473524

474-
stopEventID := alice.Unsafe_SendEventUnsynced(t, roomID, b.Event{
475-
Type: "m.room.message",
476-
Content: map[string]interface{}{
477-
"msgtype": "m.text",
478-
"body": "STOP",
479-
},
525+
// now we rejoin bob.
526+
// This should cause soft-failure of sticky events to be re-evaluated, causing it to appear in the 'sticky' section.
527+
bob.MustJoinRoom(t, roomID, []spec.ServerName{"hs1"})
528+
forEachSync(t, func(t *testing.T, useSimplifiedSlidingSync bool) {
529+
syncResp := gatherSyncResults(t, alice, useSimplifiedSlidingSync, roomID, stickyEventID)
530+
mustHaveStickyEventID(t, stickyEventID, syncResp.stickyEvents)
480531
})
481-
482-
syncResp := gatherSyncResults(t, alice, false, roomID, stopEventID)
483-
mustHaveStickyEventID(t, stickyPDU.EventID(), syncResp.stickyEvents)
484-
485532
}

0 commit comments

Comments
 (0)