From 7a106235976fa07a87d08592eeabdfe7f0855da3 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 23 Nov 2022 21:14:49 -0600 Subject: [PATCH 1/2] Test to make sure a remote event over federation makes it to application service --- internal/b/hs_with_application_service.go | 7 + tests/federation_rooms_invite_test.go | 156 ++++++++++++---------- 2 files changed, 92 insertions(+), 71 deletions(-) diff --git a/internal/b/hs_with_application_service.go b/internal/b/hs_with_application_service.go index 2b7652af..dff91b98 100644 --- a/internal/b/hs_with_application_service.go +++ b/internal/b/hs_with_application_service.go @@ -32,6 +32,13 @@ var BlueprintHSWithApplicationService = MustValidate(Blueprint{ DisplayName: "Charlie", }, }, + ApplicationServices: []ApplicationService{ + { + ID: "my_as_on_hs2_id", + SenderLocalpart: "the-bridge-user", + RateLimited: false, + }, + }, }, }, }) diff --git a/tests/federation_rooms_invite_test.go b/tests/federation_rooms_invite_test.go index 0bdd688d..0ed73a07 100644 --- a/tests/federation_rooms_invite_test.go +++ b/tests/federation_rooms_invite_test.go @@ -1,9 +1,15 @@ package tests import ( + "context" + "fmt" + "net/http" + "regexp" "testing" + "time" - "github.com/matrix-org/gomatrixserverlib" + "github.com/gorilla/mux" + "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "github.com/matrix-org/complement/internal/b" @@ -13,63 +19,90 @@ import ( ) func TestFederationRoomsInvite(t *testing.T) { - deployment := Deploy(t, b.BlueprintFederationOneToOneRoom) + // deployment := Deploy(t, b.BlueprintFederationOneToOneRoom) + deployment := Deploy(t, b.BlueprintHSWithApplicationService) defer deployment.Destroy(t) alice := deployment.Client(t, "hs1", "@alice:hs1") - bob := deployment.Client(t, "hs2", "@bob:hs2") + // bob := deployment.Client(t, "hs2", "@bob:hs2") + remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") t.Run("Parallel", func(t *testing.T) { - // sytest: Invited user can reject invite over federation - t.Run("Invited user can reject invite over federation", func(t *testing.T) { + t.Run("invite event over federation is seen by application service", func(t *testing.T) { t.Parallel() - roomID := alice.CreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - "invite": []string{bob.UserID}, - }) - bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) - bob.LeaveRoom(t, roomID) - alice.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(bob.UserID, roomID)) - }) - // sytest: Invited user can reject invite over federation several times - t.Run("Invited user can reject invite over federation several times", func(t *testing.T) { - t.Parallel() - roomID := alice.CreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - }) - for i := 0; i < 3; i++ { - alice.InviteRoom(t, roomID, bob.UserID) - bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) - bob.LeaveRoom(t, roomID) - alice.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(bob.UserID, roomID)) + // Find the URL and port of the application service in some registration yaml text + var asURLRegexp = regexp.MustCompile(`url: '(.+):(\d+)'`) + + // Find the application service port defined in the registration file + asRegistration := deployment.HS["hs2"].ApplicationServices["my_as_on_hs2_id"] + asURLMatches := asURLRegexp.FindStringSubmatch(asRegistration) + if asURLMatches == nil { + t.Fatalf("Unable to find application service `url` in registration=%s", asRegistration) } - }) + asPort := asURLMatches[2] - // sytest: Invited user can reject invite over federation for empty room - t.Run("Invited user can reject invite over federation for empty room", func(t *testing.T) { - t.Parallel() - roomID := alice.CreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - "invite": []string{bob.UserID}, - }) - aliceSince := alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) - bobSince := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) - alice.LeaveRoom(t, roomID) - alice.MustSyncUntil(t, client.SyncReq{Since: aliceSince}, client.SyncLeftFrom(alice.UserID, roomID)) - bob.LeaveRoom(t, roomID) - bob.MustSyncUntil(t, client.SyncReq{Since: bobSince}, client.SyncLeftFrom(bob.UserID, roomID)) - }) + // Create a listener and handler to stub an application service listening + // for transactions from a homeserver. + handler := mux.NewRouter() + // Application Service API: /_matrix/app/v1/transactions/{txnId} + waiter := NewWaiter() + var eventIDsWeSawOverTransactions []string + handler.HandleFunc("/transactions/{txnId}", func(w http.ResponseWriter, req *http.Request) { + must.MatchRequest(t, req, match.HTTPRequest{ + JSON: []match.JSON{ + match.JSONArrayEach("events", func(r gjson.Result) error { + // Add to our running list of events + eventIDsWeSawOverTransactions = append(eventIDsWeSawOverTransactions, r.Get("event_id").Str) + + logrus.WithFields(logrus.Fields{ + "event_id": r.Get("type").Str, + "state_key": r.Get("state_key").Str, + "content": r.Get("content").Raw, + }).Error("Saw event on application service") + + // If we found the event that occurs after our batch send. we can + // probably safely assume the historical events won't come later. + if r.Get("type").Str == "m.room.member" && r.Get("state_key").Str == remoteCharlie.UserID && r.Get("content").Get("membership").Str == "invite" { + defer waiter.Finish() + } + + return nil + }), + }, + }) + + // Acknowledge that we've seen the transaction + w.WriteHeader(200) + w.Write([]byte("{}")) + }).Methods("PUT") + + srv := &http.Server{ + Addr: fmt.Sprintf(":%s", asPort), + Handler: handler, + } + go func() { + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + // Note that running s.t.FailNow is not allowed in a separate goroutine + // Tests will likely fail if the server is not listening anyways + t.Logf("Failed to listen and serve our fake application service: %s", err) + } + }() + defer func() { + err := srv.Shutdown(context.Background()) + if err != nil { + t.Fatalf("Failed to shutdown our fake application service: %s", err) + } + }() + // ---------------------------------------------------------- - // sytest: Remote invited user can see room metadata - t.Run("Remote invited user can see room metadata", func(t *testing.T) { - t.Parallel() roomID := alice.CreateRoom(t, map[string]interface{}{ "preset": "private_chat", "name": "Invites room", - "invite": []string{bob.UserID}, }) + alice.InviteRoom(t, roomID, remoteCharlie.UserID) + wantFields := map[string]string{ "m.room.join_rules": "join_rule", "m.room.name": "name", @@ -79,36 +112,17 @@ func TestFederationRoomsInvite(t *testing.T) { "m.room.name": "Invites room", } - bob.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(bob.UserID, roomID)) - res, _ := bob.MustSync(t, client.SyncReq{}) + remoteCharlie.MustSyncUntil(t, client.SyncReq{}, client.SyncInvitedTo(remoteCharlie.UserID, roomID)) + res, _ := remoteCharlie.MustSync(t, client.SyncReq{}) verifyState(t, res, wantFields, wantValues, roomID, alice) - }) - t.Run("Invited user has 'is_direct' flag in prev_content after joining", func(t *testing.T) { - roomID := alice.CreateRoom(t, map[string]interface{}{ - "preset": "private_chat", - "name": "Invites room", - // invite Bob and make the room a DM, so we can verify m.direct flag is in the prev_content after joining - "invite": []string{bob.UserID}, - "is_direct": true, - }) - bob.JoinRoom(t, roomID, []string{}) - bob.MustSyncUntil(t, client.SyncReq{}, - client.SyncTimelineHas(roomID, func(result gjson.Result) bool { - // We expect a membership event .. - if result.Get("type").Str != gomatrixserverlib.MRoomMember { - return false - } - // .. for Bob - if result.Get("state_key").Str != bob.UserID { - return false - } - // Check that we've got tbe expected is_idrect flag - return result.Get("unsigned.prev_content.membership").Str == "invite" && - result.Get("unsigned.prev_content.is_direct").Bool() == true && - result.Get("unsigned.prev_sender").Str == alice.UserID - }), - ) + // If not, wait 5 seconds for to see if it happens. The waiter will only + // resolve if we see the invite event, otherwise timeout + waiter.Waitf(t, 5*time.Second, "waiting for invite, eventIDsWeSawOverTransactions=%s", eventIDsWeSawOverTransactions) + + logrus.WithFields(logrus.Fields{ + "eventIDsWeSawOverTransactions": eventIDsWeSawOverTransactions, + }).Error("afewfeew") }) }) } From 4d3efb5987d759c709ec36011f1efcd8e5e17d27 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Mon, 28 Nov 2022 13:58:06 -0600 Subject: [PATCH 2/2] Try inviting more than 1 --- internal/b/hs_with_application_service.go | 4 ++++ tests/federation_rooms_invite_test.go | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/b/hs_with_application_service.go b/internal/b/hs_with_application_service.go index dff91b98..44515d4c 100644 --- a/internal/b/hs_with_application_service.go +++ b/internal/b/hs_with_application_service.go @@ -31,6 +31,10 @@ var BlueprintHSWithApplicationService = MustValidate(Blueprint{ Localpart: "@charlie", DisplayName: "Charlie", }, + { + Localpart: "@frank", + DisplayName: "Frank", + }, }, ApplicationServices: []ApplicationService{ { diff --git a/tests/federation_rooms_invite_test.go b/tests/federation_rooms_invite_test.go index 0ed73a07..4debdb16 100644 --- a/tests/federation_rooms_invite_test.go +++ b/tests/federation_rooms_invite_test.go @@ -24,8 +24,9 @@ func TestFederationRoomsInvite(t *testing.T) { defer deployment.Destroy(t) alice := deployment.Client(t, "hs1", "@alice:hs1") - // bob := deployment.Client(t, "hs2", "@bob:hs2") + // bob := deployment.Client(t, "hs1", "@bob:hs1") remoteCharlie := deployment.Client(t, "hs2", "@charlie:hs2") + // remoteFrank := deployment.Client(t, "hs2", "@frank:hs2") t.Run("Parallel", func(t *testing.T) { t.Run("invite event over federation is seen by application service", func(t *testing.T) { @@ -99,9 +100,15 @@ func TestFederationRoomsInvite(t *testing.T) { roomID := alice.CreateRoom(t, map[string]interface{}{ "preset": "private_chat", "name": "Invites room", + // "invite": []string{bob.UserID}, }) + // Invite another local user + // alice.InviteRoom(t, roomID, bob.UserID) + + // Invite some remote users alice.InviteRoom(t, roomID, remoteCharlie.UserID) + // alice.InviteRoom(t, roomID, remoteFrank.UserID) wantFields := map[string]string{ "m.room.join_rules": "join_rule",