Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TestDelayedInviteResponse #98

Merged
merged 2 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions internal/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Client interface {
// IsRoomEncrypted returns true if the room is encrypted. May return an error e.g if you
// provide a bogus room ID.
IsRoomEncrypted(t ct.TestLike, roomID string) (bool, error)
// InviteUser attempts to invite the given user into the given room.
InviteUser(t ct.TestLike, roomID, userID string) error
// SendMessage sends the given text as an m.room.message with msgtype:m.text into the given
// room. Returns the event ID of the sent event, so MUST BLOCK until the event has been sent.
SendMessage(t ct.TestLike, roomID, text string) (eventID string)
Expand Down
7 changes: 7 additions & 0 deletions internal/api/js/js.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,13 @@ func (c *JSClient) Opts() api.ClientCreationOpts {
return c.opts
}

func (c *JSClient) InviteUser(t ct.TestLike, roomID, userID string) error {
_, err := chrome.RunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprint(`
await window.__client.invite("`, roomID, `","`, userID, `");
`))
return err
}

func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event {
t.Helper()
// serialised output (if encrypted):
Expand Down
6 changes: 6 additions & 0 deletions internal/api/rust/rust.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,12 @@ func (c *RustClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID
}
}

func (c *RustClient) InviteUser(t ct.TestLike, roomID, userID string) error {
t.Helper()
r := c.findRoom(t, roomID)
return r.InviteUserById(userID)
}

func (c *RustClient) MustBackpaginate(t ct.TestLike, roomID string, count int) {
t.Helper()
r := c.findRoom(t, roomID)
Expand Down
4 changes: 4 additions & 0 deletions internal/deploy/rpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ func (c *RPCClient) ListenForVerificationRequests(t ct.TestLike) chan api.Verifi
panic("unimplemented")
}

func (c *RPCClient) InviteUser(t ct.TestLike, roomID, userID string) error {
panic("unimplemented")
}

// Remove any persistent storage, if it was enabled.
func (c *RPCClient) DeletePersistentStorage(t ct.TestLike) {
var void int
Expand Down
90 changes: 90 additions & 0 deletions tests/delayed_requests_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package tests

import (
"strings"
"testing"
"time"

"github.com/matrix-org/complement-crypto/internal/api"
"github.com/matrix-org/complement-crypto/internal/deploy"
"github.com/matrix-org/complement/ct"
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/complement/must"
)

// Test that clients wait for invites to be processed before sending encrypted messages.
// Consider:
// - Alice is in a E2EE room and invites Bob, the request has yet to 200 OK.
// - Alice tries to send a message in the room. This should be queued behind the invite.
// If it is not, the message will not be encrypted for Bob.
//
// It is valid for SDKs to simply document that you shouldn't call Invite and SendMessage concurrently.
// Therefore, we will not test this.
//
// However, consider:
// - Alice is in a E2EE room and invites Bob. The request 200 OKs but has yet to come down /sync.
// - Alice tries to send a message in the room.
// - Alice should encrypt for Bob.
//
// This is much more realistic, as servers are typically asynchronous internally so /invite can 200 OK
// _before_ it comes down /sync.
func TestDelayedInviteResponse(t *testing.T) {
Instance().ForEachClientType(t, func(t *testing.T, clientType api.ClientType) {
tc := Instance().CreateTestContext(t, clientType, clientType)
roomID := tc.CreateNewEncryptedRoom(t, tc.Alice)
tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) {
// we send a message first so clients which lazily call /members can do so now.
// if we don't do this, the client won't rely on /sync for the member list so won't fail.
alice.SendMessage(t, roomID, "dummy message to make /members call")

config := tc.Deployment.MITM().Configure(t)
serverHasInvite := helpers.NewWaiter()
config.ForPath("/sync").AccessToken(alice.CurrentAccessToken(t)).Listen(func(cd deploy.CallbackData) {
if strings.Contains(
strings.ReplaceAll(string(cd.ResponseBody), " ", ""),
`"membership":"invite"`,
) {
t.Logf("/sync => %v", string(cd.ResponseBody))
delayTime := 3 * time.Second
t.Logf("intercepted /sync response which has the invite, tarpitting for %v - %v", delayTime, cd)
serverHasInvite.Finish()
time.Sleep(delayTime)
}
})
config.Execute(func() {
t.Logf("Alice about to /invite Bob")
if err := alice.InviteUser(t, roomID, bob.UserID()); err != nil {
ct.Errorf(t, "failed to invite user: %s", err)
}
t.Logf("Alice /invited Bob")
// once the server got the invite, send a message
serverHasInvite.Waitf(t, 3*time.Second, "did not intercept invite")
t.Logf("intercepted invite; sending message")
eventID := alice.SendMessage(t, roomID, "hello world!")

// bob joins, ensure he can decrypt the message.
tc.Bob.JoinRoom(t, roomID, []string{clientType.HS})
bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(tc.Bob.UserID, "join")).Waitf(t, 7*time.Second, "did not see own join")
bob.MustBackpaginate(t, roomID, 3)

time.Sleep(time.Second) // let things settle / decrypt

ev := bob.MustGetEvent(t, roomID, eventID)

// TODO: FIXME fix this issue in the SDK
// -
//
if ev.FailedToDecrypt || ev.Text != "hello world!" {
if clientType.Lang == api.ClientTypeRust {
t.Skipf("known broken: see https://github.com/matrix-org/matrix-rust-sdk/issues/3622")
}
if clientType.Lang == api.ClientTypeJS {
t.Skipf("known broken: see https://github.com/matrix-org/matrix-js-sdk/issues/4291")
}
}
must.Equal(t, ev.FailedToDecrypt, false, "failed to decrypt event")
must.Equal(t, ev.Text, "hello world!", "failed to decrypt plaintext")
})
})
})
}
Loading