From 264d458acf926cbeac9cf1547f98f4529cd1ebaf Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:45:26 +0100 Subject: [PATCH 1/4] Split the Client API in two We want the Client API to be as small as possible, to make it as easy as possible to implement clients which can work with complement-crypto. However, we also want to use that same API when writing tests, where it is desirable to have helper functions (typically of the form `MustXXX`). To satisfy both, split the API in two, where the SDK authors implement the simple `Client` and the test suite then wraps that in another API called `TestClient` which handles all the nice helper functions. For now, just convert `MustStartSyncing` to illustrate the design change. Next PR will move the remaining Must functions. --- internal/api/client.go | 46 +++++++++++++++++---------- internal/api/js/js.go | 7 ---- internal/api/rust/rust.go | 7 ---- internal/cc/test_context.go | 32 +++++++++---------- internal/deploy/rpc/client.go | 18 ----------- internal/deploy/rpc/server.go | 6 ---- internal/tests/client_test.go | 24 +++++++------- tests/delayed_requests_test.go | 2 +- tests/device_keys_test.go | 2 +- tests/federation_connectivity_test.go | 6 ++-- tests/key_backup_test.go | 4 +-- tests/membership_acls_test.go | 18 +++++------ tests/one_time_keys_test.go | 6 ++-- tests/room_keys_test.go | 18 +++++------ tests/rust/notification_test.go | 16 +++++----- tests/to_device_test.go | 18 +++++------ tests/verification_test.go | 4 +-- 17 files changed, 105 insertions(+), 129 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index 36797e6..71bb708 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -33,11 +33,6 @@ type Client interface { // uploaded to the server. Failure to block will result in flakey tests as other users may not // encrypt for this Client due to not detecting keys for the Client. Login(t ct.TestLike, opts ClientCreationOpts) error - // MustStartSyncing to begin syncing from sync v2 / sliding sync. - // Tests should call stopSyncing() at the end of the test. - // MUST BLOCK until the initial sync is complete. - // Fails the test if there was a problem syncing. - MustStartSyncing(t ct.TestLike) (stopSyncing func()) // StartSyncing to begin syncing from sync v2 / sliding sync. // Tests should call stopSyncing() at the end of the test. // MUST BLOCK until the initial sync is complete. @@ -96,9 +91,31 @@ type Client interface { Opts() ClientCreationOpts } -type Notification struct { - Event - HasMentions *bool +// TestClient is a Client with extra helper functions added to make writing tests easier. +// Client implementations are not expected to implement these helper functions, and are +// instead composed together by the test rig itself. See TestClientImpl. +type TestClient interface { + Client + MustStartSyncing(t ct.TestLike) (stopSyncing func()) +} + +func NewTestClient(c Client) TestClient { + return &testClientImpl{ + Client: c, + } +} + +type testClientImpl struct { + Client +} + +func (c *testClientImpl) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { + t.Helper() + stopSyncing, err := c.StartSyncing(t) + if err != nil { + ct.Fatalf(t, "MustStartSyncing: %s", err) + } + return stopSyncing } type LoggedClient struct { @@ -136,14 +153,6 @@ func (c *LoggedClient) MustGetEvent(t ct.TestLike, roomID, eventID string) Event return c.Client.MustGetEvent(t, roomID, eventID) } -func (c *LoggedClient) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { - t.Helper() - c.Logf(t, "%s MustStartSyncing starting to sync", c.logPrefix()) - stopSyncing = c.Client.MustStartSyncing(t) - c.Logf(t, "%s MustStartSyncing now syncing", c.logPrefix()) - return -} - func (c *LoggedClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) { t.Helper() c.Logf(t, "%s StartSyncing starting to sync", c.logPrefix()) @@ -216,6 +225,11 @@ func (c *LoggedClient) logPrefix() string { return fmt.Sprintf("[%s](%s)", c.UserID(), c.Type()) } +type Notification struct { + Event + HasMentions *bool +} + // ClientCreationOpts are options to use when creating crypto clients. // // This contains a mixture of generic options which can be used across any client, and specific diff --git a/internal/api/js/js.go b/internal/api/js/js.go index 5ae0294..b421d68 100644 --- a/internal/api/js/js.go +++ b/internal/api/js/js.go @@ -549,13 +549,6 @@ func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event return ev } -func (c *JSClient) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { - t.Helper() - stopSyncing, err := c.StartSyncing(t) - must.NotError(t, "StartSyncing", err) - return stopSyncing -} - // StartSyncing to begin syncing from sync v2 / sliding sync. // Tests should call stopSyncing() at the end of the test. func (c *JSClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) { diff --git a/internal/api/rust/rust.go b/internal/api/rust/rust.go index 44db503..bd3cf1f 100644 --- a/internal/api/rust/rust.go +++ b/internal/api/rust/rust.go @@ -323,13 +323,6 @@ func (c *RustClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Eve return *ev } -func (c *RustClient) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { - t.Helper() - stopSyncing, err := c.StartSyncing(t) - must.NotError(t, "StartSyncing", err) - return stopSyncing -} - // StartSyncing to begin syncing from sync v2 / sliding sync. // Tests should call stopSyncing() at the end of the test. func (c *RustClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) { diff --git a/internal/cc/test_context.go b/internal/cc/test_context.go index 169b356..8711215 100644 --- a/internal/cc/test_context.go +++ b/internal/cc/test_context.go @@ -70,9 +70,9 @@ func (c *TestContext) RegisterNewUser(t *testing.T, clientType api.ClientType, l // // The callback function is invoked after this, and cleanup functions are called on your behalf when the // callback function ends. -func (c *TestContext) WithClientSyncing(t *testing.T, req *ClientCreationRequest, callback func(cli api.Client)) { +func (c *TestContext) WithClientSyncing(t *testing.T, req *ClientCreationRequest, callback func(cli api.TestClient)) { t.Helper() - c.WithClientsSyncing(t, []*ClientCreationRequest{req}, func(clients []api.Client) { + c.WithClientsSyncing(t, []*ClientCreationRequest{req}, func(clients []api.TestClient) { callback(clients[0]) }) } @@ -87,9 +87,9 @@ func (c *TestContext) WithClientSyncing(t *testing.T, req *ClientCreationRequest // // The callback function is invoked after this, and cleanup functions are called on your behalf when the // callback function ends. -func (c *TestContext) WithClientsSyncing(t *testing.T, reqs []*ClientCreationRequest, callback func(clients []api.Client)) { +func (c *TestContext) WithClientsSyncing(t *testing.T, reqs []*ClientCreationRequest, callback func(clients []api.TestClient)) { t.Helper() - cryptoClients := make([]api.Client, len(reqs)) + cryptoClients := make([]api.TestClient, len(reqs)) // Login all clients BEFORE starting any of their sync loops. // We do this because Login will send device list updates and cause clients to upload OTKs/device keys. // We want to make sure ALL these keys are on the server before any test client syncs otherwise it @@ -107,18 +107,18 @@ func (c *TestContext) WithClientsSyncing(t *testing.T, reqs []*ClientCreationReq } // mustCreateMultiprocessClient creates a new RPC process and instructs it to create a client given by the client creation options. -func (c *TestContext) mustCreateMultiprocessClient(t *testing.T, req *ClientCreationRequest) api.Client { +func (c *TestContext) mustCreateMultiprocessClient(t *testing.T, req *ClientCreationRequest) api.TestClient { t.Helper() if c.RPCBinaryPath == "" { t.Skipf("RPC binary path not provided, skipping multiprocess test. To run this test, set COMPLEMENT_CRYPTO_RPC_BINARY") - return nil + return api.NewTestClient(nil) } ctxPrefix := fmt.Sprintf("%d", c.RPCInstance.Add(1)) remoteBindings, err := rpc.NewLanguageBindings(c.RPCBinaryPath, req.User.ClientType.Lang, ctxPrefix) if err != nil { t.Fatalf("Failed to create new RPC language bindings: %s", err) } - return remoteBindings.MustCreateClient(t, req.Opts) + return api.NewTestClient(remoteBindings.MustCreateClient(t, req.Opts)) } // WithAliceSyncing is a helper function which creates a rust/js client and automatically logs in Alice and starts @@ -126,7 +126,7 @@ func (c *TestContext) mustCreateMultiprocessClient(t *testing.T, req *ClientCrea // // The callback function is invoked after this, and cleanup functions are called on your behalf when the // callback function ends. -func (c *TestContext) WithAliceSyncing(t *testing.T, callback func(alice api.Client)) { +func (c *TestContext) WithAliceSyncing(t *testing.T, callback func(alice api.TestClient)) { t.Helper() must.NotEqual(t, c.Alice, nil, "No Alice defined. Call CreateTestContext() with at least 1 api.ClientType.") c.WithClientSyncing(t, &ClientCreationRequest{ @@ -139,7 +139,7 @@ func (c *TestContext) WithAliceSyncing(t *testing.T, callback func(alice api.Cli // // The callback function is invoked after this, and cleanup functions are called on your behalf when the // callback function ends. -func (c *TestContext) WithAliceAndBobSyncing(t *testing.T, callback func(alice, bob api.Client)) { +func (c *TestContext) WithAliceAndBobSyncing(t *testing.T, callback func(alice, bob api.TestClient)) { t.Helper() must.NotEqual(t, c.Bob, nil, "No Bob defined. Call CreateTestContext() with at least 2 api.ClientTypes.") c.WithClientsSyncing(t, []*ClientCreationRequest{ @@ -149,7 +149,7 @@ func (c *TestContext) WithAliceAndBobSyncing(t *testing.T, callback func(alice, { User: c.Bob, }, - }, func(clients []api.Client) { + }, func(clients []api.TestClient) { callback(clients[0], clients[1]) }) } @@ -159,7 +159,7 @@ func (c *TestContext) WithAliceAndBobSyncing(t *testing.T, callback func(alice, // // The callback function is invoked after this, and cleanup functions are called on your behalf when the // callback function ends. -func (c *TestContext) WithAliceBobAndCharlieSyncing(t *testing.T, callback func(alice, bob, charlie api.Client)) { +func (c *TestContext) WithAliceBobAndCharlieSyncing(t *testing.T, callback func(alice, bob, charlie api.TestClient)) { t.Helper() must.NotEqual(t, c.Charlie, nil, "No Charlie defined. Call CreateTestContext() with at least 3 api.ClientTypes.") c.WithClientsSyncing(t, []*ClientCreationRequest{ @@ -172,7 +172,7 @@ func (c *TestContext) WithAliceBobAndCharlieSyncing(t *testing.T, callback func( { User: c.Charlie, }, - }, func(clients []api.Client) { + }, func(clients []api.TestClient) { callback(clients[0], clients[1], clients[2]) }) } @@ -288,7 +288,7 @@ func (c *TestContext) MustRegisterNewDevice(t *testing.T, user *User, newDeviceI } // MustLoginClient is the same as MustCreateClient but also logs in the client. -func (c *TestContext) MustLoginClient(t *testing.T, req *ClientCreationRequest) api.Client { +func (c *TestContext) MustLoginClient(t *testing.T, req *ClientCreationRequest) api.TestClient { t.Helper() client := c.MustCreateClient(t, req) must.NotError(t, "failed to login client", client.Login(t, client.Opts())) @@ -297,7 +297,7 @@ func (c *TestContext) MustLoginClient(t *testing.T, req *ClientCreationRequest) // MustCreateClient creates an api.Client from an existing Complement client and the specified client type. Additional options // can be set to configure the client beyond that of the Complement client e.g to add persistent storage. -func (c *TestContext) MustCreateClient(t *testing.T, req *ClientCreationRequest) api.Client { +func (c *TestContext) MustCreateClient(t *testing.T, req *ClientCreationRequest) api.TestClient { t.Helper() if req.User == nil { ct.Fatalf(t, "MustCreateClient: ClientCreationRequest missing 'user', register one with RegisterNewUser or use an existing one.") @@ -318,11 +318,11 @@ func (c *TestContext) MustCreateClient(t *testing.T, req *ClientCreationRequest) // mustCreateClient creates an api.Client with the specified language/server, else fails the test. // // Options can be provided to configure clients, such as enabling persistent storage. -func mustCreateClient(t *testing.T, clientType api.ClientType, cfg api.ClientCreationOpts) api.Client { +func mustCreateClient(t *testing.T, clientType api.ClientType, cfg api.ClientCreationOpts) api.TestClient { bindings := langs.GetLanguageBindings(clientType.Lang) if bindings == nil { t.Fatalf("unknown language: %s", clientType.Lang) } c := bindings.MustCreateClient(t, cfg) - return c + return api.NewTestClient(c) } diff --git a/internal/deploy/rpc/client.go b/internal/deploy/rpc/client.go index 2f07061..02ab2fa 100644 --- a/internal/deploy/rpc/client.go +++ b/internal/deploy/rpc/client.go @@ -218,24 +218,6 @@ func (c *RPCClient) Login(t ct.TestLike, opts api.ClientCreationOpts) error { return err } -// MustStartSyncing to begin syncing from sync v2 / sliding sync. -// Tests should call stopSyncing() at the end of the test. -// MUST BLOCK until the initial sync is complete. -// Fails the test if there was a problem syncing. -func (c *RPCClient) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { - var void int - err := c.client.Call("Server.MustStartSyncing", t.Name(), &void) - if err != nil { - t.Fatalf("RPCClient.MustStartSyncing: %s", err) - } - return func() { - err := c.client.Call("Server.StopSyncing", t.Name(), &void) - if err != nil { - t.Fatalf("RPCClient.StopSyncing: %s", err) - } - } -} - // StartSyncing to begin syncing from sync v2 / sliding sync. // Tests should call stopSyncing() at the end of the test. // MUST BLOCK until the initial sync is complete. diff --git a/internal/deploy/rpc/server.go b/internal/deploy/rpc/server.go index 1c83d14..0e363d3 100644 --- a/internal/deploy/rpc/server.go +++ b/internal/deploy/rpc/server.go @@ -111,12 +111,6 @@ func (s *Server) Login(opts api.ClientCreationOpts, void *int) error { return s.activeClient.Login(&api.MockT{}, opts) } -func (s *Server) MustStartSyncing(testName string, void *int) error { - defer s.keepAlive() - s.stopSyncing = s.activeClient.MustStartSyncing(&api.MockT{TestName: testName}) - return nil -} - func (s *Server) StartSyncing(testName string, void *int) error { defer s.keepAlive() stopSyncing, err := s.activeClient.StartSyncing(&api.MockT{TestName: testName}) diff --git a/internal/tests/client_test.go b/internal/tests/client_test.go index 72502d9..c000301 100644 --- a/internal/tests/client_test.go +++ b/internal/tests/client_test.go @@ -29,7 +29,7 @@ var ( ssDeployment *deploy.SlidingSyncDeployment // aka functions which make clients, and we don't care about the language. // Tests just loop through this array for each client impl. - clientFactories []func(t *testing.T, cfg api.ClientCreationOpts) api.Client + clientFactories []func(t *testing.T, cfg api.ClientCreationOpts) api.TestClient ) func Deploy(t *testing.T) *deploy.SlidingSyncDeployment { @@ -47,30 +47,30 @@ func Deploy(t *testing.T) *deploy.SlidingSyncDeployment { } func TestMain(m *testing.M) { - rustClientCreator := func(t *testing.T, cfg api.ClientCreationOpts) api.Client { + rustClientCreator := func(t *testing.T, cfg api.ClientCreationOpts) api.TestClient { client, err := rust.NewRustClient(t, cfg) if err != nil { t.Fatalf("NewRustClient: %s", err) } - return client + return api.NewTestClient(client) } - jsClientCreator := func(t *testing.T, cfg api.ClientCreationOpts) api.Client { + jsClientCreator := func(t *testing.T, cfg api.ClientCreationOpts) api.TestClient { client, err := js.NewJSClient(t, cfg) if err != nil { t.Fatalf("NewJSClient: %s", err) } - return client + return api.NewTestClient(client) } clientFactories = append(clientFactories, rustClientCreator, jsClientCreator) rpcBinary := os.Getenv("COMPLEMENT_CRYPTO_RPC_BINARY") if rpcBinary != "" { - clientFactories = append(clientFactories, func(t *testing.T, cfg api.ClientCreationOpts) api.Client { + clientFactories = append(clientFactories, func(t *testing.T, cfg api.ClientCreationOpts) api.TestClient { remoteBindings, err := rpc.NewLanguageBindings(rpcBinary, api.ClientTypeRust, "") if err != nil { log.Fatal(err) } - return remoteBindings.MustCreateClient(t, cfg) + return api.NewTestClient(remoteBindings.MustCreateClient(t, cfg)) }) } rust.SetupLogs("rust_sdk_logs") @@ -104,7 +104,7 @@ func TestReceiveTimeline(t *testing.T) { } // test that if we start syncing with a room full of events, we see those events. - ForEachClient(t, "existing_events", deployment, func(t *testing.T, client api.Client, csapi *client.CSAPI) { + ForEachClient(t, "existing_events", deployment, func(t *testing.T, client api.TestClient, csapi *client.CSAPI) { must.NotError(t, "Failed to login", client.Login(t, client.Opts())) roomID, eventIDs := createAndSendEvents(t, csapi) time.Sleep(time.Second) // give time for everything to settle server-side e.g sliding sync proxy @@ -132,7 +132,7 @@ func TestReceiveTimeline(t *testing.T) { }) // test that if we are already syncing and then see a room live stream full of events, we see those events. - ForEachClient(t, "live_events", deployment, func(t *testing.T, client api.Client, csapi *client.CSAPI) { + ForEachClient(t, "live_events", deployment, func(t *testing.T, client api.TestClient, csapi *client.CSAPI) { must.NotError(t, "Failed to login", client.Login(t, client.Opts())) stopSyncing := client.MustStartSyncing(t) defer stopSyncing() @@ -171,7 +171,7 @@ func TestReceiveTimeline(t *testing.T) { func TestCanWaitUntilEventInRoomBeforeRoomIsKnown(t *testing.T) { deployment := Deploy(t) - ForEachClient(t, "", deployment, func(t *testing.T, client api.Client, csapi *client.CSAPI) { + ForEachClient(t, "", deployment, func(t *testing.T, client api.TestClient, csapi *client.CSAPI) { roomID := csapi.MustCreateRoom(t, map[string]interface{}{}) eventID := csapi.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", @@ -196,7 +196,7 @@ func TestCanWaitUntilEventInRoomBeforeRoomIsKnown(t *testing.T) { func TestSendingEvents(t *testing.T) { deployment := Deploy(t) - ForEachClient(t, "", deployment, func(t *testing.T, client api.Client, csapi *client.CSAPI) { + ForEachClient(t, "", deployment, func(t *testing.T, client api.TestClient, csapi *client.CSAPI) { must.NotError(t, "Failed to login", client.Login(t, client.Opts())) roomID := csapi.MustCreateRoom(t, map[string]interface{}{}) stopSyncing := client.MustStartSyncing(t) @@ -217,7 +217,7 @@ func TestSendingEvents(t *testing.T) { } // run a subtest for each client factory -func ForEachClient(t *testing.T, name string, deployment *deploy.SlidingSyncDeployment, fn func(t *testing.T, client api.Client, csapi *client.CSAPI)) { +func ForEachClient(t *testing.T, name string, deployment *deploy.SlidingSyncDeployment, fn func(t *testing.T, client api.TestClient, csapi *client.CSAPI)) { for _, createClient := range clientFactories { csapiAlice := deployment.Register(t, "hs1", helpers.RegistrationOpts{ LocalpartSuffix: "client", diff --git a/tests/delayed_requests_test.go b/tests/delayed_requests_test.go index 4ff5f26..4720084 100644 --- a/tests/delayed_requests_test.go +++ b/tests/delayed_requests_test.go @@ -33,7 +33,7 @@ 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) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // 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") diff --git a/tests/device_keys_test.go b/tests/device_keys_test.go index b18ad08..9ada72a 100644 --- a/tests/device_keys_test.go +++ b/tests/device_keys_test.go @@ -41,7 +41,7 @@ func TestFailedDeviceKeyDownloadRetries(t *testing.T) { roomID := tc.CreateNewEncryptedRoom(t, tc.Alice, cc.EncRoomOptions.Invite([]string{tc.Bob.UserID})) tc.Bob.MustJoinRoom(t, roomID, []string{"hs1"}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // When Alice sends a message alice.SendMessage(t, roomID, "checking whether we can send a message") diff --git a/tests/federation_connectivity_test.go b/tests/federation_connectivity_test.go index 4b6b624..e72958e 100644 --- a/tests/federation_connectivity_test.go +++ b/tests/federation_connectivity_test.go @@ -32,7 +32,7 @@ func TestNewUserCannotGetKeysForOfflineServer(t *testing.T) { t.Logf("%s joining room %s", tc.Bob.UserID, roomID) tc.Bob.MustJoinRoom(t, roomID, []string{"hs1"}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // let clients sync device keys time.Sleep(time.Second) @@ -50,7 +50,7 @@ func TestNewUserCannotGetKeysForOfflineServer(t *testing.T) { tc.Alice.MustInviteRoom(t, roomID, tc.Charlie.UserID) tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Charlie, - }, func(charlie api.Client) { + }, func(charlie api.TestClient) { tc.Charlie.MustJoinRoom(t, roomID, []string{"hs1"}) // let charlie sync device keys... and fail to get bob's keys! @@ -116,7 +116,7 @@ func TestExistingSessionCannotGetKeysForOfflineServer(t *testing.T) { tc.Bob.MustJoinRoom(t, roomIDab, []string{"hs1"}) tc.Bob.MustJoinRoom(t, roomIDbc, []string{"hs1"}) - tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.Client) { + tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.TestClient) { // let clients sync device keys time.Sleep(time.Second) diff --git a/tests/key_backup_test.go b/tests/key_backup_test.go index d267f3c..62c9c43 100644 --- a/tests/key_backup_test.go +++ b/tests/key_backup_test.go @@ -36,7 +36,7 @@ func TestCanBackupKeys(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceSyncing(t, func(backupCreator api.Client) { + tc.WithAliceSyncing(t, func(backupCreator api.TestClient) { body := "An encrypted message" waiter := backupCreator.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(body)) evID := backupCreator.SendMessage(t, roomID, body) @@ -98,7 +98,7 @@ func TestBackupWrongRecoveryKeyFails(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceSyncing(t, func(backupCreator api.Client) { + tc.WithAliceSyncing(t, func(backupCreator api.TestClient) { body := "An encrypted message" waiter := backupCreator.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(body)) evID := backupCreator.SendMessage(t, roomID, body) diff --git a/tests/membership_acls_test.go b/tests/membership_acls_test.go index 42a1cb9..a7dd54c 100644 --- a/tests/membership_acls_test.go +++ b/tests/membership_acls_test.go @@ -35,7 +35,7 @@ func TestAliceBobEncryptionWorks(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { wantMsgBody := "Hello world" // Check the room is in fact encrypted @@ -75,7 +75,7 @@ func TestCanDecryptMessagesAfterInviteButBeforeJoin(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { wantMsgBody := "Message sent when bob is invited not joined" // Check the room is in fact encrypted @@ -122,7 +122,7 @@ func TestBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // Alice sends a message which Bob should not be able to decrypt beforeJoinBody := "Before Bob joins" waiter := alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(beforeJoinBody)) @@ -163,7 +163,7 @@ func TestOnRejoinBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // Alice sends a message which Bob should be able to decrypt. bothJoinedBody := "Alice and Bob in a room" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(bothJoinedBody)) @@ -222,7 +222,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // SDK testing below // ----------------- - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // Alice sends a message which Bob should be able to decrypt. onlyFirstDeviceBody := "Alice and Bob in a room" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(onlyFirstDeviceBody)) @@ -234,7 +234,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { csapiBob2 := tc.MustRegisterNewDevice(t, tc.Bob, "NEW_DEVICE") tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: csapiBob2, - }, func(bob2 api.Client) { + }, func(bob2 api.TestClient) { time.Sleep(time.Second) // let device keys propagate to alice bob2.MustBackpaginate(t, roomID, 5) // ensure the older event is there time.Sleep(time.Second) @@ -265,7 +265,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // now bob logs in again tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: csapiBob2, - }, func(bob2 api.Client) { + }, func(bob2 api.TestClient) { time.Sleep(time.Second) // let device keys propagate to alice undecryptableEvent := bob2.MustGetEvent(t, roomID, evID) must.Equal(t, undecryptableEvent.FailedToDecrypt, true, "bob's new device was able to decrypt a message sent after he had logged out") @@ -282,7 +282,7 @@ func TestChangingDeviceAfterInviteReEncrypts(t *testing.T) { // shared history visibility roomID := tc.CreateNewEncryptedRoom(t, tc.Alice, cc.EncRoomOptions.PresetPublicChat()) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // Alice invites Bob and then she sends an event tc.Alice.MustInviteRoom(t, roomID, tc.Bob.UserID) time.Sleep(time.Second) // let device keys propagate @@ -293,7 +293,7 @@ func TestChangingDeviceAfterInviteReEncrypts(t *testing.T) { csapiBob2 := tc.MustRegisterNewDevice(t, tc.Bob, "NEW_DEVICE") tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: csapiBob2, - }, func(bob2 api.Client) { + }, func(bob2 api.TestClient) { time.Sleep(time.Second) // let device keys propagate tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) diff --git a/tests/one_time_keys_test.go b/tests/one_time_keys_test.go index 6e5605a..6bac656 100644 --- a/tests/one_time_keys_test.go +++ b/tests/one_time_keys_test.go @@ -95,7 +95,7 @@ func TestFallbackKeyIsUsedIfOneTimeKeysRunOut(t *testing.T) { // ================= // Upload OTKs and a fallback - tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.Client) { + tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.TestClient) { // we need to send _something_ to cause /sync v2 to return a long poll response, as fallback // keys don't wake up /sync v2. If we don't do this, rust SDK fails to realise it needs to upload a fallback // key because SS doesn't tell it, because Synapse doesn't tell SS that the fallback key was used. @@ -169,7 +169,7 @@ func TestFailedOneTimeKeyUploadRetries(t *testing.T) { }, RequestCallback: callback.SendError(2, http.StatusGatewayTimeout), }, func() { - tc.WithAliceSyncing(t, func(alice api.Client) { + tc.WithAliceSyncing(t, func(alice api.TestClient) { tc.Bob.MustDo(t, "POST", []string{ "_matrix", "client", "v3", "keys", "claim", }, client.WithJSONBody(t, map[string]any{ @@ -208,7 +208,7 @@ func TestFailedKeysClaimRetries(t *testing.T) { Instance().ForEachClientType(t, func(t *testing.T, clientType api.ClientType) { tc := Instance().CreateTestContext(t, clientType, clientType) // both clients start syncing to upload OTKs - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { var stopPoking atomic.Bool waiter := helpers.NewWaiter() // make a room which will link the 2 users together when diff --git a/tests/room_keys_test.go b/tests/room_keys_test.go index c7284b2..baa5f80 100644 --- a/tests/room_keys_test.go +++ b/tests/room_keys_test.go @@ -57,7 +57,7 @@ func TestRoomKeyIsCycledOnDeviceLogout(t *testing.T) { User: csapiAlice2, }) defer alice2.Close(t) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { alice2StopSyncing := alice2.MustStartSyncing(t) alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(tc.Bob.UserID, "join")).Waitf(t, 5*time.Second, "alice did not see own join") // check the room works @@ -117,7 +117,7 @@ func TestRoomKeyIsCycledAfterEnoughMessages(t *testing.T) { ) tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // And some messages were sent, but not enough to trigger resending for i := 0; i < 4; i++ { wantMsgBody := fmt.Sprintf("Before we hit the threshold %d", i) @@ -191,7 +191,7 @@ func TestRoomKeyIsCycledAfterEnoughTime(t *testing.T) { ) tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // Before we start, ensure some keys have already been sent, so we // don't get a false positive. wantMsgBody := "Before we start" @@ -226,7 +226,7 @@ func TestRoomKeyIsCycledOnMemberLeaving(t *testing.T) { Instance().ClientTypeMatrix(t, func(t *testing.T, clientTypeA, clientTypeB api.ClientType) { tc := Instance().CreateTestContext(t, clientTypeA, clientTypeB, clientTypeB) // Alice, Bob and Charlie are in a room. - tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.Client) { + tc.WithAliceBobAndCharlieSyncing(t, func(alice, bob, charlie api.TestClient) { // do setup code after all clients are syncing to ensure that if Alice asks for Charlie's keys on receipt of the // join event, then Charlie has already uploaded keys. roomID := tc.CreateNewEncryptedRoom( @@ -281,7 +281,7 @@ func TestRoomKeyIsNotCycled(t *testing.T) { tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) // Alice, Bob are in a room. - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // check the room works wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) @@ -407,7 +407,7 @@ func testRoomKeyIsNotCycledOnClientRestartRust(t *testing.T, clientType api.Clie tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { wantMsgBody := "test from another process" // send a message as Alice in a different process tc.WithClientSyncing(t, &cc.ClientCreationRequest{ @@ -416,7 +416,7 @@ func testRoomKeyIsNotCycledOnClientRestartRust(t *testing.T, clientType api.Clie PersistentStorage: true, }, Multiprocess: true, - }, func(remoteAlice api.Client) { + }, func(remoteAlice api.TestClient) { eventID := remoteAlice.SendMessage(t, roomID, wantMsgBody) waiter := remoteAlice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasEventID(eventID)) waiter.Waitf(t, 5*time.Second, "client did not see event %s", eventID) @@ -480,7 +480,7 @@ func testRoomKeyIsNotCycledOnClientRestartJS(t *testing.T, clientType api.Client // no alice.close here as we'll close it in the test mid-way tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // check the room works wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) @@ -496,7 +496,7 @@ func testRoomKeyIsNotCycledOnClientRestartJS(t *testing.T, clientType api.Client tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Alice, Opts: alice.Opts(), - }, func(alice api.Client) { + }, func(alice api.TestClient) { // now send another message from Alice, who should NOT send another new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) diff --git a/tests/rust/notification_test.go b/tests/rust/notification_test.go index f791322..47bf815 100644 --- a/tests/rust/notification_test.go +++ b/tests/rust/notification_test.go @@ -118,7 +118,7 @@ func TestNSEReceiveForNonPreKeyMessage(t *testing.T) { // Bob sends a message to alice tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // let bob realise alice exists and claims keys time.Sleep(time.Second) // Send a message as Bob, this will contain ensure an Olm session is set up already before we do NSE work @@ -175,7 +175,7 @@ func TestMultiprocessNSE(t *testing.T) { // Bob sends a message to alice tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // let bob realise alice exists and claims keys time.Sleep(time.Second) for i := 0; i < numPreBackgroundMsgs; i++ { @@ -336,7 +336,7 @@ func TestMultiprocessNSEBackupKeyMacError(t *testing.T) { // Bob sends a message to alice tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // let bob realise alice exists and claims keys time.Sleep(time.Second) @@ -452,7 +452,7 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) { // Bob sends a message to alice tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // let bob realise alice exists and claims keys time.Sleep(time.Second) msg := "pre message" @@ -599,7 +599,7 @@ func TestNotificationClientDupeOTKUpload(t *testing.T) { // The main app will see this in /sync and then try to upload another OTK, which we will tarpit. tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { eventID := bob.SendMessage(t, roomID, "Hello world!") // create a NotificationClient in the same process to fetch this "push notification". // It might make the NotificationClient upload a OTK as it would have seen 1 has been used. @@ -648,7 +648,7 @@ func TestMultiprocessInitialE2EESyncDoesntDropDeviceListUpdates(t *testing.T) { // Bob sends a message to Alice tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Alice, - }, func(alice api.Client) { + }, func(alice api.TestClient) { // ensure bob has queried keys from alice by sending a message. msg := "pre message" bob.SendMessage(t, roomID, msg) @@ -700,7 +700,7 @@ func TestMultiprocessInitialE2EESyncDoesntDropDeviceListUpdates(t *testing.T) { csapiAlice2 := tc.MustRegisterNewDevice(t, tc.Alice, "NEW_DEVICE") tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: csapiAlice2, - }, func(alice2 api.Client) { + }, func(alice2 api.TestClient) { // wait for device keys to sync up time.Sleep(time.Second) // alice[1] sends a message, this is unimportant other than to grab the event ID for the push process @@ -750,7 +750,7 @@ func bobSendsMessage(t *testing.T, tc *cc.TestContext, roomID, text string, msgs pushNotifEventID := "" tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, - }, func(bob api.Client) { + }, func(bob api.TestClient) { for i := 0; i < msgsBefore; i++ { bob.SendMessage(t, roomID, fmt.Sprintf("msg before %d", i)) } diff --git a/tests/to_device_test.go b/tests/to_device_test.go index 102b6d3..56a852d 100644 --- a/tests/to_device_test.go +++ b/tests/to_device_test.go @@ -22,7 +22,7 @@ func TestClientRetriesSendToDevice(t *testing.T) { tc := Instance().CreateTestContext(t, clientTypeA, clientTypeB) roomID := tc.CreateNewEncryptedRoom(t, tc.Alice, cc.EncRoomOptions.PresetPublicChat()) tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // lets device keys be exchanged time.Sleep(time.Second) @@ -85,7 +85,7 @@ func TestUnprocessedToDeviceMessagesArentLostOnRestart(t *testing.T) { PersistentStorage: true, }, }) - tc.WithAliceSyncing(t, func(alice api.Client) { + tc.WithAliceSyncing(t, func(alice api.TestClient) { // we will close this in the test, no defer bobStopSyncing := bob.MustStartSyncing(t) // check the room works @@ -178,7 +178,7 @@ func testUnprocessedToDeviceMessagesArentLostOnRestartRust(t *testing.T, tc *cc. Opts: api.ClientCreationOpts{ PersistentStorage: true, }, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // we can't rely on MustStartSyncing returning to know that the room key has been received, as // in rust we just wait for RoomListLoadingStateLoaded which is a separate connection to the // encryption loop. @@ -242,7 +242,7 @@ func testUnprocessedToDeviceMessagesArentLostOnRestartJS(t *testing.T, tc *cc.Te Opts: api.ClientCreationOpts{ PersistentStorage: true, }, - }, func(bob api.Client) { + }, func(bob api.TestClient) { // include a grace period like rust, no specific reason beyond consistency. time.Sleep(time.Second) ev := bob.MustGetEvent(t, roomID, eventID) @@ -282,7 +282,7 @@ func TestToDeviceMessagesAreBatched(t *testing.T) { clientUnderTest.Close(t) } waiter := helpers.NewWaiter() - tc.WithAliceSyncing(t, func(alice api.Client) { + tc.WithAliceSyncing(t, func(alice api.TestClient) { // intercept /sendToDevice and check we are sending 100 messages per request tc.Deployment.MITM().Configure(t).WithIntercept(mitm.InterceptOpts{ Filter: mitm.FilterParams{ @@ -345,7 +345,7 @@ func TestToDeviceMessagesArentLostWhenKeysQueryFails(t *testing.T) { // get a normal E2EE room set up roomID := tc.CreateNewEncryptedRoom(t, tc.Alice, cc.EncRoomOptions.Invite([]string{tc.Bob.UserID})) tc.Bob.MustJoinRoom(t, roomID, []string{clientType.HS}) - tc.WithAliceAndBobSyncing(t, func(alice, bob api.Client) { + tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { msg := "hello world" msg2 := "new device message from alice" alice.SendMessage(t, roomID, msg) @@ -370,7 +370,7 @@ func TestToDeviceMessagesArentLostWhenKeysQueryFails(t *testing.T) { csapiAlice2 := tc.MustRegisterNewDevice(t, tc.Alice, "OTHER_DEVICE") tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: csapiAlice2, - }, func(alice2 api.Client) { + }, func(alice2 api.TestClient) { // we don't know how long it will take for the device list update to be processed, so wait 1s time.Sleep(time.Second) @@ -429,7 +429,7 @@ func TestToDeviceMessagesAreProcessedInOrder(t *testing.T) { ID string Body string }{} - tc.WithAliceSyncing(t, func(alice api.Client) { + tc.WithAliceSyncing(t, func(alice api.TestClient) { callbackFn := func(cd callback.Data) *callback.Response { // try v2 sync then SS toDeviceEvents := gjson.ParseBytes(cd.ResponseBody).Get("to_device.events").Array() @@ -469,7 +469,7 @@ func TestToDeviceMessagesAreProcessedInOrder(t *testing.T) { creationReqs[i].User.MustJoinRoom(t, roomID, []string{clientType.HS}) } // send 30 messages as each user (interleaved) - tc.WithClientsSyncing(t, creationReqs, func(clients []api.Client) { + tc.WithClientsSyncing(t, creationReqs, func(clients []api.TestClient) { for i := 0; i < numMsgsPerClient; i++ { for _, c := range clients { body := fmt.Sprintf("Message %d", i+1) diff --git a/tests/verification_test.go b/tests/verification_test.go index d437795..e1d6f74 100644 --- a/tests/verification_test.go +++ b/tests/verification_test.go @@ -67,13 +67,13 @@ func TestVerificationSAS(t *testing.T) { ClientType: verifieeClientType, } - tc.WithAliceSyncing(t, func(verifier api.Client) { + tc.WithAliceSyncing(t, func(verifier api.TestClient) { tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: verifieeUser, Opts: api.ClientCreationOpts{ DeviceID: "OTHER_DEVICE", }, - }, func(verifiee api.Client) { + }, func(verifiee api.TestClient) { status := &verificationStatus{ mu: &sync.Mutex{}, } From 7ed40c682cdc7398fbef9a299308e1757fa693e5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:50:28 +0100 Subject: [PATCH 2/4] Better docs --- internal/api/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/api/client.go b/internal/api/client.go index 71bb708..7bfc99d 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -93,12 +93,13 @@ type Client interface { // TestClient is a Client with extra helper functions added to make writing tests easier. // Client implementations are not expected to implement these helper functions, and are -// instead composed together by the test rig itself. See TestClientImpl. +// instead composed together by the test rig itself. type TestClient interface { Client MustStartSyncing(t ct.TestLike) (stopSyncing func()) } +// NewTestClient wraps a Client implementation with helper functions which tests can use. func NewTestClient(c Client) TestClient { return &testClientImpl{ Client: c, From 9397df30228683f859aef6e6ae171df7f692966b Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:29:32 +0100 Subject: [PATCH 3/4] Convert remaining Must functions to TestClient --- internal/api/client.go | 118 +++++++++++++++++--------- internal/api/js/js.go | 43 ++++------ internal/api/rust/rust.go | 54 ++++++------ internal/deploy/rpc/client.go | 57 +++---------- internal/deploy/rpc/server.go | 41 ++++----- internal/tests/client_test.go | 14 +-- tests/delayed_requests_test.go | 4 +- tests/device_keys_test.go | 2 +- tests/federation_connectivity_test.go | 12 +-- tests/key_backup_test.go | 4 +- tests/membership_acls_test.go | 22 ++--- tests/one_time_keys_test.go | 6 +- tests/room_keys_test.go | 34 ++++---- tests/rust/notification_test.go | 40 ++++----- tests/to_device_test.go | 20 ++--- 15 files changed, 229 insertions(+), 242 deletions(-) diff --git a/internal/api/client.go b/internal/api/client.go index 7bfc99d..03e521d 100644 --- a/internal/api/client.go +++ b/internal/api/client.go @@ -45,20 +45,18 @@ type Client interface { InviteUser(t ct.TestLike, roomID, userID string) error // SendMessage sends the given text as an encrypted/unencrypted message in the room, depending // if the room is encrypted or not. 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) - // TrySendMessage tries to send the message, but can fail. - TrySendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) + // If the event cannot be sent, returns an error. + SendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) // Wait until an event is seen in the given room. The checker functions can be custom or you can use // a pre-defined one like api.CheckEventHasMembership, api.CheckEventHasBody, or api.CheckEventHasEventID. WaitUntilEventInRoom(t ct.TestLike, roomID string, checker func(e Event) bool) Waiter - // Backpaginate in this room by `count` events. - MustBackpaginate(t ct.TestLike, roomID string, count int) - // MustGetEvent will return the client's view of this event, or fail the test if the event cannot be found. - MustGetEvent(t ct.TestLike, roomID, eventID string) Event - // MustBackupKeys will backup E2EE keys, else fail the test. - MustBackupKeys(t ct.TestLike) (recoveryKey string) - // MustLoadBackup will recover E2EE keys from the latest backup, else fail the test. - MustLoadBackup(t ct.TestLike, recoveryKey string) + // Backpaginate in this room by `count` events. Returns an error if there was a problem backpaginating. + // Getting to the beginning of the room is not an error condition. + Backpaginate(t ct.TestLike, roomID string, count int) error + // GetEvent will return the client's view of this event, or returns an error if the event cannot be found. + GetEvent(t ct.TestLike, roomID, eventID string) (*Event, error) + // BackupKeys will backup E2EE keys, else return an error. + BackupKeys(t ct.TestLike) (recoveryKey string, err error) // LoadBackup will recover E2EE keys from the latest backup, else return an error. LoadBackup(t ct.TestLike, recoveryKey string) error // GetNotification gets push notification-like information for the given event. If there is a problem, an error is returned. @@ -96,7 +94,18 @@ type Client interface { // instead composed together by the test rig itself. type TestClient interface { Client + // MustStartSyncing is StartSyncing but fails the test on error. MustStartSyncing(t ct.TestLike) (stopSyncing func()) + // MustLoadBackup is LoadBackup but fails the test on error. + MustLoadBackup(t ct.TestLike, recoveryKey string) + // MustSendMessage is SendMessage but fails the test on error. + MustSendMessage(t ct.TestLike, roomID, text string) (eventID string) + // MustGetEvent is GetEvent but fails the test on error. + MustGetEvent(t ct.TestLike, roomID, eventID string) *Event + // MustBackupKeys is BackupKeys but fails the test on error. + MustBackupKeys(t ct.TestLike) (recoveryKey string) + // MustBackpaginate is Backpaginate but fails the test on error. + MustBackpaginate(t ct.TestLike, roomID string, count int) } // NewTestClient wraps a Client implementation with helper functions which tests can use. @@ -119,6 +128,49 @@ func (c *testClientImpl) MustStartSyncing(t ct.TestLike) (stopSyncing func()) { return stopSyncing } +func (c *testClientImpl) MustLoadBackup(t ct.TestLike, recoveryKey string) { + t.Helper() + err := c.LoadBackup(t, recoveryKey) + if err != nil { + ct.Fatalf(t, "MustLoadBackup: %s", err) + } +} + +func (c *testClientImpl) MustBackupKeys(t ct.TestLike) (recoveryKey string) { + t.Helper() + recoveryKey, err := c.BackupKeys(t) + if err != nil { + ct.Fatalf(t, "MustBackupKeys: %s", err) + } + return recoveryKey +} + +func (c *testClientImpl) MustBackpaginate(t ct.TestLike, roomID string, count int) { + t.Helper() + err := c.Backpaginate(t, roomID, count) + if err != nil { + ct.Fatalf(t, "MustBackpaginate: %s", err) + } +} + +func (c *testClientImpl) MustSendMessage(t ct.TestLike, roomID, text string) (eventID string) { + t.Helper() + eventID, err := c.SendMessage(t, roomID, text) + if err != nil { + ct.Fatalf(t, "MustSendMessage: %s", err) + } + return eventID +} + +func (c *testClientImpl) MustGetEvent(t ct.TestLike, roomID, eventID string) *Event { + t.Helper() + ev, err := c.GetEvent(t, roomID, eventID) + if err != nil { + ct.Fatalf(t, "MustGetEvent: %s", err) + } + return ev +} + type LoggedClient struct { Client } @@ -148,10 +200,10 @@ func (c *LoggedClient) ForceClose(t ct.TestLike) { c.Client.ForceClose(t) } -func (c *LoggedClient) MustGetEvent(t ct.TestLike, roomID, eventID string) Event { +func (c *LoggedClient) GetEvent(t ct.TestLike, roomID, eventID string) (*Event, error) { t.Helper() - c.Logf(t, "%s MustGetEvent(%s, %s)", c.logPrefix(), roomID, eventID) - return c.Client.MustGetEvent(t, roomID, eventID) + c.Logf(t, "%s GetEvent(%s, %s)", c.logPrefix(), roomID, eventID) + return c.Client.GetEvent(t, roomID, eventID) } func (c *LoggedClient) StartSyncing(t ct.TestLike) (stopSyncing func(), err error) { @@ -168,19 +220,11 @@ func (c *LoggedClient) IsRoomEncrypted(t ct.TestLike, roomID string) (bool, erro return c.Client.IsRoomEncrypted(t, roomID) } -func (c *LoggedClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { - t.Helper() - c.Logf(t, "%s TrySendMessage %s => %s", c.logPrefix(), roomID, text) - eventID, err = c.Client.TrySendMessage(t, roomID, text) - c.Logf(t, "%s TrySendMessage %s => %s", c.logPrefix(), roomID, eventID) - return -} - -func (c *LoggedClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string) { +func (c *LoggedClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { t.Helper() c.Logf(t, "%s SendMessage %s => %s", c.logPrefix(), roomID, text) - eventID = c.Client.SendMessage(t, roomID, text) - c.Logf(t, "%s SendMessage %s => %s", c.logPrefix(), roomID, eventID) + eventID, err = c.Client.SendMessage(t, roomID, text) + c.Logf(t, "%s SendMessage %s => %s %s", c.logPrefix(), roomID, eventID, err) return } @@ -190,24 +234,20 @@ func (c *LoggedClient) WaitUntilEventInRoom(t ct.TestLike, roomID string, checke return c.Client.WaitUntilEventInRoom(t, roomID, checker) } -func (c *LoggedClient) MustBackpaginate(t ct.TestLike, roomID string, count int) { +func (c *LoggedClient) Backpaginate(t ct.TestLike, roomID string, count int) error { t.Helper() - c.Logf(t, "%s MustBackpaginate %d %s", c.logPrefix(), count, roomID) - c.Client.MustBackpaginate(t, roomID, count) -} - -func (c *LoggedClient) MustBackupKeys(t ct.TestLike) (recoveryKey string) { - t.Helper() - c.Logf(t, "%s MustBackupKeys", c.logPrefix()) - recoveryKey = c.Client.MustBackupKeys(t) - c.Logf(t, "%s MustBackupKeys => %s", c.logPrefix(), recoveryKey) - return recoveryKey + c.Logf(t, "%s Backpaginate %d %s", c.logPrefix(), count, roomID) + err := c.Client.Backpaginate(t, roomID, count) + c.Logf(t, "%s Backpaginate %d %s => %s", c.logPrefix(), count, roomID, err) + return err } -func (c *LoggedClient) MustLoadBackup(t ct.TestLike, recoveryKey string) { +func (c *LoggedClient) BackupKeys(t ct.TestLike) (recoveryKey string, err error) { t.Helper() - c.Logf(t, "%s MustLoadBackup key=%s", c.logPrefix(), recoveryKey) - c.Client.MustLoadBackup(t, recoveryKey) + c.Logf(t, "%s BackupKeys", c.logPrefix()) + recoveryKey, err = c.Client.BackupKeys(t) + c.Logf(t, "%s BackupKeys => %s %s", c.logPrefix(), recoveryKey, err) + return recoveryKey, err } func (c *LoggedClient) LoadBackup(t ct.TestLike, recoveryKey string) error { diff --git a/internal/api/js/js.go b/internal/api/js/js.go index b421d68..0140843 100644 --- a/internal/api/js/js.go +++ b/internal/api/js/js.go @@ -14,7 +14,6 @@ import ( "github.com/matrix-org/complement-crypto/internal/api" "github.com/matrix-org/complement-crypto/internal/api/js/chrome" "github.com/matrix-org/complement/ct" - "github.com/matrix-org/complement/must" "github.com/tidwall/gjson" ) @@ -509,7 +508,7 @@ func (c *JSClient) InviteUser(t ct.TestLike, roomID, userID string) error { return err } -func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event { +func (c *JSClient) GetEvent(t ct.TestLike, roomID, eventID string) (*api.Event, error) { t.Helper() // serialised output (if encrypted): // { @@ -517,14 +516,17 @@ func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event // decrypted: { event } // } // else just returns { event } - evSerialised := chrome.MustRunAsyncFn[string](t, c.browser.Ctx, fmt.Sprintf(` + evSerialised, err := chrome.RunAsyncFn[string](t, c.browser.Ctx, fmt.Sprintf(` return JSON.stringify(window.__client.getRoom("%s")?.getLiveTimeline()?.getEvents().filter((ev, i) => { console.log("MustGetEvent["+i+"] => " + ev.getId()+ " " + JSON.stringify(ev.toJSON())); return ev.getId() === "%s"; })[0].toJSON()); `, roomID, eventID)) + if err != nil { + return nil, fmt.Errorf("failed to get event %s: %s", eventID, err) + } if !gjson.Valid(*evSerialised) { - ct.Fatalf(t, "MustGetEvent(%s, %s) %s (js): invalid event, got %s", roomID, eventID, c.userID, *evSerialised) + return nil, fmt.Errorf("invalid event %s, got %s", eventID, *evSerialised) } result := gjson.Parse(*evSerialised) decryptedEvent := result.Get("decrypted") @@ -533,7 +535,7 @@ func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event } encryptedEvent := result.Get("encrypted") //fmt.Printf("DECRYPTED: %s\nENCRYPTED: %s\n\n", decryptedEvent.Raw, encryptedEvent.Raw) - ev := api.Event{ + ev := &api.Event{ ID: decryptedEvent.Get("event_id").Str, Text: decryptedEvent.Get("content.body").Str, Sender: decryptedEvent.Get("sender").Str, @@ -546,7 +548,7 @@ func (c *JSClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event ev.FailedToDecrypt = true } - return ev + return ev, nil } // StartSyncing to begin syncing from sync v2 / sliding sync. @@ -602,16 +604,7 @@ func (c *JSClient) IsRoomEncrypted(t ct.TestLike, roomID string) (bool, error) { return *isEncrypted, nil } -// SendMessage sends the given text as an m.room.message with msgtype:m.text into the given -// room. -func (c *JSClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string) { - t.Helper() - eventID, err := c.TrySendMessage(t, roomID, text) - must.NotError(t, "failed to sendMessage", err) - return eventID -} - -func (c *JSClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { +func (c *JSClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { t.Helper() res, err := chrome.RunAsyncFn[map[string]interface{}](t, c.browser.Ctx, fmt.Sprintf(` return await window.__client.sendMessage("%s", { @@ -624,16 +617,17 @@ func (c *JSClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID s return (*res)["event_id"].(string), nil } -func (c *JSClient) MustBackpaginate(t ct.TestLike, roomID string, count int) { +func (c *JSClient) Backpaginate(t ct.TestLike, roomID string, count int) error { t.Helper() - chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf( + _, err := chrome.RunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf( `await window.__client.scrollback(window.__client.getRoom("%s"), %d);`, roomID, count, )) + return err } -func (c *JSClient) MustBackupKeys(t ct.TestLike) (recoveryKey string) { +func (c *JSClient) BackupKeys(t ct.TestLike) (recoveryKey string, err error) { t.Helper() - key := chrome.MustRunAsyncFn[string](t, c.browser.Ctx, ` + key, err := chrome.RunAsyncFn[string](t, c.browser.Ctx, ` // we need to ensure that we have a recovery key first, though we don't actually care about it..? const recoveryKey = await window.__client.getCrypto().createRecoveryKeyFromPassphrase(); // now use said key to make backups @@ -645,15 +639,14 @@ func (c *JSClient) MustBackupKeys(t ct.TestLike) (recoveryKey string) { // now we can enable key backups await window.__client.getCrypto().checkKeyBackupAndEnable(); return recoveryKey.encodedPrivateKey;`) + if err != nil { + return "", fmt.Errorf("error enabling key backup: %s", err) + } // the backup loop which sends keys will wait between 0-10s before uploading keys... // See https://github.com/matrix-org/matrix-js-sdk/blob/49624d5d7308e772ebee84322886a39d2e866869/src/rust-crypto/backup.ts#L319 // Ideally this would be configurable.. time.Sleep(11 * time.Second) - return *key -} - -func (c *JSClient) MustLoadBackup(t ct.TestLike, recoveryKey string) { - must.NotError(t, "failed to load backup", c.LoadBackup(t, recoveryKey)) + return *key, nil } func (c *JSClient) LoadBackup(t ct.TestLike, recoveryKey string) error { diff --git a/internal/api/rust/rust.go b/internal/api/rust/rust.go index bd3cf1f..b4ceea0 100644 --- a/internal/api/rust/rust.go +++ b/internal/api/rust/rust.go @@ -309,18 +309,18 @@ func (c *RustClient) Close(t ct.TestLike) { } } -func (c *RustClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event { +func (c *RustClient) GetEvent(t ct.TestLike, roomID, eventID string) (*api.Event, error) { t.Helper() room := c.findRoom(t, roomID) timelineItem, err := mustGetTimeline(t, room).GetEventTimelineItemByEventId(eventID) if err != nil { - ct.Fatalf(t, "MustGetEvent(rust) %s (%s, %s): %s", c.userID, roomID, eventID, err) + return nil, fmt.Errorf("failed to GetEventTimelineItemByEventId(%s): %s", eventID, err) } ev := eventTimelineItemToEvent(timelineItem) if ev == nil { - ct.Fatalf(t, "MustGetEvent(rust) %s (%s, %s): found timeline item but failed to convert it to an Event", c.userID, roomID, eventID) + return nil, fmt.Errorf("found timeline item %s but failed to convert it to an Event", eventID) } - return *ev + return ev, nil } // StartSyncing to begin syncing from sync v2 / sliding sync. @@ -453,33 +453,40 @@ func (c *RustClient) IsRoomEncrypted(t ct.TestLike, roomID string) (bool, error) return r.IsEncrypted() } -func (c *RustClient) MustBackupKeys(t ct.TestLike) (recoveryKey string) { +func (c *RustClient) BackupKeys(t ct.TestLike) (recoveryKey string, err error) { t.Helper() genericListener := newGenericStateListener[matrix_sdk_ffi.EnableRecoveryProgress]() var listener matrix_sdk_ffi.EnableRecoveryProgressListener = genericListener e := c.FFIClient.Encryption() defer e.Destroy() - recoveryKey, err := e.EnableRecovery(true, nil, listener) - must.NotError(t, "Encryption.EnableRecovery", err) + recoveryKey, err = e.EnableRecovery(true, nil, listener) + if err != nil { + return "", fmt.Errorf("EnableRecovery: %s", err) + } + var lastState string for !genericListener.isClosed.Load() { select { case s := <-genericListener.ch: switch x := s.(type) { case matrix_sdk_ffi.EnableRecoveryProgressCreatingBackup: t.Logf("MustBackupKeys: state=CreatingBackup") + lastState = "CreatingBackup" case matrix_sdk_ffi.EnableRecoveryProgressBackingUp: t.Logf("MustBackupKeys: state=BackingUp %v/%v", x.BackedUpCount, x.TotalCount) + lastState = fmt.Sprintf("BackingUp %v/%v", x.BackedUpCount, x.TotalCount) case matrix_sdk_ffi.EnableRecoveryProgressCreatingRecoveryKey: t.Logf("MustBackupKeys: state=CreatingRecoveryKey") + lastState = "CreatingRecoveryKey" case matrix_sdk_ffi.EnableRecoveryProgressDone: t.Logf("MustBackupKeys: state=Done") + lastState = "Done" genericListener.Close() // break the loop } case <-time.After(5 * time.Second): - ct.Fatalf(t, "timed out enabling backup keys") + return "", fmt.Errorf("timed out enabling backup keys: last state: %s", lastState) } } - return recoveryKey + return recoveryKey, nil } func (c *RustClient) LoadBackup(t ct.TestLike, recoveryKey string) error { @@ -489,11 +496,6 @@ func (c *RustClient) LoadBackup(t ct.TestLike, recoveryKey string) error { return e.Recover(recoveryKey) } -func (c *RustClient) MustLoadBackup(t ct.TestLike, recoveryKey string) { - t.Helper() - c.LoadBackup(t, recoveryKey) -} - func (c *RustClient) WaitUntilEventInRoom(t ct.TestLike, roomID string, checker func(api.Event) bool) api.Waiter { t.Helper() c.ensureListening(t, roomID) @@ -508,18 +510,7 @@ func (c *RustClient) Type() api.ClientTypeLang { return api.ClientTypeRust } -// 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. -func (c *RustClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string) { - t.Helper() - eventID, err := c.TrySendMessage(t, roomID, text) - if err != nil { - ct.Fatalf(t, err.Error()) - } - return eventID -} - -func (c *RustClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { +func (c *RustClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { t.Helper() var isChannelClosed atomic.Bool ch := make(chan bool) @@ -579,12 +570,17 @@ func (c *RustClient) InviteUser(t ct.TestLike, roomID, userID string) error { return r.InviteUserById(userID) } -func (c *RustClient) MustBackpaginate(t ct.TestLike, roomID string, count int) { +func (c *RustClient) Backpaginate(t ct.TestLike, roomID string, count int) error { t.Helper() r := c.findRoom(t, roomID) - must.NotEqual(t, r, nil, "unknown room") + if r == nil { + return fmt.Errorf("Backpaginate: cannot find room %s", roomID) + } _, err := mustGetTimeline(t, r).PaginateBackwards(uint16(count)) - must.NotError(t, "failed to backpaginate", err) + if err != nil { + return fmt.Errorf("cannot PaginateBackwards in %s: %s", roomID, err) + } + return nil } func (c *RustClient) UserID() string { diff --git a/internal/deploy/rpc/client.go b/internal/deploy/rpc/client.go index 02ab2fa..82f0da8 100644 --- a/internal/deploy/rpc/client.go +++ b/internal/deploy/rpc/client.go @@ -244,23 +244,9 @@ func (c *RPCClient) IsRoomEncrypted(t ct.TestLike, roomID string) (bool, error) return isEncrypted, err } -// 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. -func (c *RPCClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string) { - err := c.client.Call("Server.SendMessage", RPCSendMessage{ - TestName: t.Name(), - RoomID: roomID, - Text: text, - }, &eventID) - if err != nil { - t.Fatalf("RPCClient.SendMessage: %s", err) - } - return -} - -// TrySendMessage tries to send the message, but can fail. -func (c *RPCClient) TrySendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { - err = c.client.Call("Server.TrySendMessage", RPCSendMessage{ +// SendMessage tries to send the message, but can fail. +func (c *RPCClient) SendMessage(t ct.TestLike, roomID, text string) (eventID string, err error) { + err = c.client.Call("Server.SendMessage", RPCSendMessage{ TestName: t.Name(), RoomID: roomID, Text: text, @@ -287,50 +273,33 @@ func (c *RPCClient) WaitUntilEventInRoom(t ct.TestLike, roomID string, checker f } // Backpaginate in this room by `count` events. -func (c *RPCClient) MustBackpaginate(t ct.TestLike, roomID string, count int) { +func (c *RPCClient) Backpaginate(t ct.TestLike, roomID string, count int) error { var void int - err := c.client.Call("Server.MustBackpaginate", RPCBackpaginate{ + err := c.client.Call("Server.Backpaginate", RPCBackpaginate{ TestName: t.Name(), RoomID: roomID, Count: count, }, &void) - if err != nil { - t.Fatalf("RPCClient.MustBackpaginate: %s", err) - } + return err } -// MustGetEvent will return the client's view of this event, or fail the test if the event cannot be found. -func (c *RPCClient) MustGetEvent(t ct.TestLike, roomID, eventID string) api.Event { +// GetEvent will return the client's view of this event, or return an error if the event cannot be found. +func (c *RPCClient) GetEvent(t ct.TestLike, roomID, eventID string) (*api.Event, error) { var ev api.Event - err := c.client.Call("Server.MustGetEvent", RPCGetEvent{ + err := c.client.Call("Server.GetEvent", RPCGetEvent{ TestName: t.Name(), RoomID: roomID, EventID: eventID, }, &ev) - if err != nil { - t.Fatalf("RPCClient.MustGetEvent: %s", err) - } - return ev + return &ev, err } -// MustBackupKeys will backup E2EE keys, else fail the test. -func (c *RPCClient) MustBackupKeys(t ct.TestLike) (recoveryKey string) { - err := c.client.Call("Server.MustBackupKeys", 0, &recoveryKey) - if err != nil { - t.Fatalf("RPCClient.MustBackupKeys: %v", err) - } +// BackupKeys will backup E2EE keys, else return an error. +func (c *RPCClient) BackupKeys(t ct.TestLike) (recoveryKey string, err error) { + err = c.client.Call("Server.BackupKeys", 0, &recoveryKey) return } -// MustLoadBackup will recover E2EE keys from the latest backup, else fail the test. -func (c *RPCClient) MustLoadBackup(t ct.TestLike, recoveryKey string) { - var void int - err := c.client.Call("Server.MustLoadBackup", recoveryKey, &void) - if err != nil { - t.Fatalf("RPCClient.MustLoadBackup: %v", err) - } -} - // LoadBackup will recover E2EE keys from the latest backup, else return an error. func (c *RPCClient) LoadBackup(t ct.TestLike, recoveryKey string) error { var void int diff --git a/internal/deploy/rpc/server.go b/internal/deploy/rpc/server.go index 0e363d3..cc85e0c 100644 --- a/internal/deploy/rpc/server.go +++ b/internal/deploy/rpc/server.go @@ -145,15 +145,9 @@ type RPCSendMessage struct { } func (s *Server) SendMessage(msg RPCSendMessage, eventID *string) error { - defer s.keepAlive() - *eventID = s.activeClient.SendMessage(&api.MockT{TestName: msg.TestName}, msg.RoomID, msg.Text) - return nil -} - -func (s *Server) TrySendMessage(msg RPCSendMessage, eventID *string) error { defer s.keepAlive() var err error - *eventID, err = s.activeClient.TrySendMessage(&api.MockT{TestName: msg.TestName}, msg.RoomID, msg.Text) + *eventID, err = s.activeClient.SendMessage(&api.MockT{TestName: msg.TestName}, msg.RoomID, msg.Text) if err != nil { return err } @@ -228,10 +222,8 @@ func (s *Server) WaiterStart(input RPCWait, void *int) error { func (s *Server) WaiterPoll(waiterID int, eventsToCheck *[]api.Event) error { defer s.keepAlive() - fmt.Println("Acquiring lock") s.waitersMu.Lock() defer s.waitersMu.Unlock() - fmt.Println("Acquired!") w := s.waiters[waiterID] if w == nil { return fmt.Errorf("unknown waiter id %d", waiterID) @@ -255,10 +247,9 @@ type RPCBackpaginate struct { Count int } -func (s *Server) MustBackpaginate(input RPCBackpaginate, void *int) error { +func (s *Server) Backpaginate(input RPCBackpaginate, void *int) error { defer s.keepAlive() - s.activeClient.MustBackpaginate(&api.MockT{TestName: input.TestName}, input.RoomID, input.Count) - return nil + return s.activeClient.Backpaginate(&api.MockT{TestName: input.TestName}, input.RoomID, input.Count) } type RPCGetEvent struct { @@ -267,18 +258,23 @@ type RPCGetEvent struct { EventID string } -// MustGetEvent will return the client's view of this event, or fail the test if the event cannot be found. -func (s *Server) MustGetEvent(input RPCGetEvent, output *api.Event) error { +// GetEvent will return the client's view of this event, or returns an error if the event cannot be found. +func (s *Server) GetEvent(input RPCGetEvent, output *api.Event) error { defer s.keepAlive() - *output = s.activeClient.MustGetEvent(&api.MockT{TestName: input.TestName}, input.RoomID, input.EventID) + ev, err := s.activeClient.GetEvent(&api.MockT{TestName: input.TestName}, input.RoomID, input.EventID) + if err != nil { + return err + } + *output = *ev return nil } -// MustBackupKeys will backup E2EE keys, else fail the test. -func (s *Server) MustBackupKeys(testName string, recoveryKey *string) error { +// BackupKeys will backup E2EE keys, else fail the test. +func (s *Server) BackupKeys(testName string, recoveryKey *string) error { defer s.keepAlive() - *recoveryKey = s.activeClient.MustBackupKeys(&api.MockT{TestName: testName}) - return nil + var err error + *recoveryKey, err = s.activeClient.BackupKeys(&api.MockT{TestName: testName}) + return err } type RPCGetNotification struct { @@ -296,13 +292,6 @@ func (s *Server) GetNotification(input RPCGetNotification, output *api.Notificat return err } -// MustLoadBackup will recover E2EE keys from the latest backup, else fail the test. -func (s *Server) MustLoadBackup(recoveryKey string, void *int) error { - defer s.keepAlive() - s.activeClient.MustLoadBackup(&api.MockT{}, recoveryKey) - return nil -} - func (s *Server) LoadBackup(recoveryKey string, void *int) error { defer s.keepAlive() return s.activeClient.LoadBackup(&api.MockT{}, recoveryKey) diff --git a/internal/tests/client_test.go b/internal/tests/client_test.go index c000301..9441633 100644 --- a/internal/tests/client_test.go +++ b/internal/tests/client_test.go @@ -201,18 +201,18 @@ func TestSendingEvents(t *testing.T) { roomID := csapi.MustCreateRoom(t, map[string]interface{}{}) stopSyncing := client.MustStartSyncing(t) defer stopSyncing() - eventID := client.SendMessage(t, roomID, "Test Message") + eventID := client.MustSendMessage(t, roomID, "Test Message") event := client.MustGetEvent(t, roomID, eventID) must.Equal(t, event.Text, "Test Message", "event text mismatch") - eventID2, err := client.TrySendMessage(t, roomID, "Another Test Message") - must.NotError(t, "TrySendMessage failed", err) + eventID2, err := client.SendMessage(t, roomID, "Another Test Message") + must.NotError(t, "SendMessage failed", err) event2 := client.MustGetEvent(t, roomID, eventID2) must.Equal(t, event2.Text, "Another Test Message", "event text mismatch") // sending to a bogus room should error but not fail the test - invalidEventID, err := client.TrySendMessage(t, "!foo:hs1", "This should not work") - t.Logf("TrySendMessage -> %v", err) - must.NotEqual(t, err, nil, "TrySendMessage returned no error when it should have") - must.Equal(t, invalidEventID, "", "TrySendMessage returned an event ID when it should have returned an error") + invalidEventID, err := client.SendMessage(t, "!foo:hs1", "This should not work") + t.Logf("SendMessage -> %v", err) + must.NotEqual(t, err, nil, "SendMessage returned no error when it should have") + must.Equal(t, invalidEventID, "", "SendMessage returned an event ID when it should have returned an error") }) } diff --git a/tests/delayed_requests_test.go b/tests/delayed_requests_test.go index 4720084..167cb4a 100644 --- a/tests/delayed_requests_test.go +++ b/tests/delayed_requests_test.go @@ -36,7 +36,7 @@ func TestDelayedInviteResponse(t *testing.T) { tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // 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") + alice.MustSendMessage(t, roomID, "dummy message to make /members call") config := tc.Deployment.MITM().Configure(t) serverHasInvite := helpers.NewWaiter() @@ -67,7 +67,7 @@ func TestDelayedInviteResponse(t *testing.T) { // 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!") + eventID := alice.MustSendMessage(t, roomID, "hello world!") // bob joins, ensure he can decrypt the message. tc.Bob.JoinRoom(t, roomID, []string{clientType.HS}) diff --git a/tests/device_keys_test.go b/tests/device_keys_test.go index 9ada72a..e8d3717 100644 --- a/tests/device_keys_test.go +++ b/tests/device_keys_test.go @@ -43,7 +43,7 @@ func TestFailedDeviceKeyDownloadRetries(t *testing.T) { tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { // When Alice sends a message - alice.SendMessage(t, roomID, "checking whether we can send a message") + alice.MustSendMessage(t, roomID, "checking whether we can send a message") // Then Bob should receive it bob.WaitUntilEventInRoom( diff --git a/tests/federation_connectivity_test.go b/tests/federation_connectivity_test.go index e72958e..d961fda 100644 --- a/tests/federation_connectivity_test.go +++ b/tests/federation_connectivity_test.go @@ -39,7 +39,7 @@ func TestNewUserCannotGetKeysForOfflineServer(t *testing.T) { // ensure encrypted messaging works wantMsgBody := "Hello world" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - evID := alice.SendMessage(t, roomID, wantMsgBody) + evID := alice.MustSendMessage(t, roomID, wantMsgBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message '%s'", wantMsgBody) @@ -59,7 +59,7 @@ func TestNewUserCannotGetKeysForOfflineServer(t *testing.T) { // send a message: bob won't be able to decrypt this, but alice will. wantUndecryptableMsgBody := "Bob can't see this because his server is down" waiter = alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantUndecryptableMsgBody)) - undecryptableEventID := charlie.SendMessage(t, roomID, wantUndecryptableMsgBody) + undecryptableEventID := charlie.MustSendMessage(t, roomID, wantUndecryptableMsgBody) t.Logf("alice (%s) waiting for event %s", alice.Type(), undecryptableEventID) waiter.Waitf(t, 5*time.Second, "alice did not see charlie's messages '%s'", wantUndecryptableMsgBody) @@ -77,7 +77,7 @@ func TestNewUserCannotGetKeysForOfflineServer(t *testing.T) { // send another message, bob should be able to decrypt it. wantMsgBody = "Bob can see this because his server is now back online" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - evID = charlie.SendMessage(t, roomID, wantMsgBody) + evID = charlie.MustSendMessage(t, roomID, wantMsgBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 7*time.Second, "bob did not see charlie's message '%s'", wantMsgBody) @@ -123,11 +123,11 @@ func TestExistingSessionCannotGetKeysForOfflineServer(t *testing.T) { // ensure encrypted messaging works in rooms ab,bc wantMsgBody := "Hello world" waiter := bob.WaitUntilEventInRoom(t, roomIDab, api.CheckEventHasBody(wantMsgBody)) - evID := alice.SendMessage(t, roomIDab, wantMsgBody) + evID := alice.MustSendMessage(t, roomIDab, wantMsgBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message: '%s'", wantMsgBody) waiter = bob.WaitUntilEventInRoom(t, roomIDbc, api.CheckEventHasBody(wantMsgBody)) - evID = charlie.SendMessage(t, roomIDbc, wantMsgBody) + evID = charlie.MustSendMessage(t, roomIDbc, wantMsgBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see charlie's message: '%s'", wantMsgBody) @@ -145,7 +145,7 @@ func TestExistingSessionCannotGetKeysForOfflineServer(t *testing.T) { // are per-device, not per-room. wantDecryptableMsgBody := "Bob can see this even though his server is down as we had a session already" waiter = alice.WaitUntilEventInRoom(t, roomIDab, api.CheckEventHasBody(wantDecryptableMsgBody)) - decryptableEventID := charlie.SendMessage(t, roomIDab, wantDecryptableMsgBody) + decryptableEventID := charlie.MustSendMessage(t, roomIDab, wantDecryptableMsgBody) t.Logf("alice (%s) waiting for event %s", alice.Type(), decryptableEventID) waiter.Waitf(t, 5*time.Second, "alice did not see charlie's message: '%s'", wantDecryptableMsgBody) diff --git a/tests/key_backup_test.go b/tests/key_backup_test.go index 62c9c43..fce2bad 100644 --- a/tests/key_backup_test.go +++ b/tests/key_backup_test.go @@ -39,7 +39,7 @@ func TestCanBackupKeys(t *testing.T) { tc.WithAliceSyncing(t, func(backupCreator api.TestClient) { body := "An encrypted message" waiter := backupCreator.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(body)) - evID := backupCreator.SendMessage(t, roomID, body) + evID := backupCreator.MustSendMessage(t, roomID, body) t.Logf("backupCreator (%s) waiting for event %s", backupCreator.Type(), evID) waiter.Waitf(t, 5*time.Second, "backup creator did not see own message %s", evID) @@ -101,7 +101,7 @@ func TestBackupWrongRecoveryKeyFails(t *testing.T) { tc.WithAliceSyncing(t, func(backupCreator api.TestClient) { body := "An encrypted message" waiter := backupCreator.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(body)) - evID := backupCreator.SendMessage(t, roomID, body) + evID := backupCreator.MustSendMessage(t, roomID, body) t.Logf("backupCreator (%s) waiting for event %s", backupCreator.Type(), evID) waiter.Waitf(t, 5*time.Second, "backup creator did not see own message %s", evID) diff --git a/tests/membership_acls_test.go b/tests/membership_acls_test.go index a7dd54c..8d39edd 100644 --- a/tests/membership_acls_test.go +++ b/tests/membership_acls_test.go @@ -48,7 +48,7 @@ func TestAliceBobEncryptionWorks(t *testing.T) { t.Logf("bob room encrypted = %v", isEncrypted) waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - evID := alice.SendMessage(t, roomID, wantMsgBody) + evID := alice.MustSendMessage(t, roomID, wantMsgBody) // Bob receives the message t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) @@ -84,7 +84,7 @@ func TestCanDecryptMessagesAfterInviteButBeforeJoin(t *testing.T) { must.Equal(t, isEncrypted, true, "room is not encrypted when it should be") // Alice sends the message whilst Bob is still invited. - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) // Bob joins the room (via Complement, but it shouldn't matter) tc.Bob.MustJoinRoom(t, roomID, []string{clientTypeA.HS}) @@ -98,7 +98,7 @@ func TestCanDecryptMessagesAfterInviteButBeforeJoin(t *testing.T) { // This also checks that subsequent messages are decryptable. sentinelBody := "Sentinel" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(sentinelBody)) - alice.SendMessage(t, roomID, sentinelBody) + alice.MustSendMessage(t, roomID, sentinelBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") // Explicitly ask for a pagination, rather than assuming the SDK will return events @@ -126,7 +126,7 @@ func TestBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // Alice sends a message which Bob should not be able to decrypt beforeJoinBody := "Before Bob joins" waiter := alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(beforeJoinBody)) - evID := alice.SendMessage(t, roomID, beforeJoinBody) + evID := alice.MustSendMessage(t, roomID, beforeJoinBody) t.Logf("alice (%s) waiting for event %s", alice.Type(), evID) waiter.Waitf(t, 5*time.Second, "alice did not see own message") @@ -167,7 +167,7 @@ func TestOnRejoinBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // Alice sends a message which Bob should be able to decrypt. bothJoinedBody := "Alice and Bob in a room" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(bothJoinedBody)) - evID := alice.SendMessage(t, roomID, bothJoinedBody) + evID := alice.MustSendMessage(t, roomID, bothJoinedBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") @@ -179,7 +179,7 @@ func TestOnRejoinBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // now alice sends another message, which should use a key that bob does not have. Wait for the remote echo to come back. onlyAliceBody := "Only me on my lonesome" waiter = alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(onlyAliceBody)) - evID = alice.SendMessage(t, roomID, onlyAliceBody) + evID = alice.MustSendMessage(t, roomID, onlyAliceBody) t.Logf("alice (%s) waiting for event %s", alice.Type(), evID) waiter.Waitf(t, 5*time.Second, "alice did not see own message") @@ -201,7 +201,7 @@ func TestOnRejoinBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { time.Sleep(time.Second) // let alice realise bob is back in the room // bob should be able to decrypt subsequent messages bothJoinedBody = "Alice and Bob in a room again" - evID = alice.SendMessage(t, roomID, bothJoinedBody) + evID = alice.MustSendMessage(t, roomID, bothJoinedBody) time.Sleep(time.Second) // TODO: use a Waiter; currently this is broken as it seems like listeners get detached on leave? ev = bob.MustGetEvent(t, roomID, evID) must.Equal(t, ev.Text, bothJoinedBody, "event was not decrypted correctly") */ @@ -226,7 +226,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // Alice sends a message which Bob should be able to decrypt. onlyFirstDeviceBody := "Alice and Bob in a room" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(onlyFirstDeviceBody)) - evID := alice.SendMessage(t, roomID, onlyFirstDeviceBody) + evID := alice.MustSendMessage(t, roomID, onlyFirstDeviceBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") @@ -244,7 +244,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // now alice sends another message, which bob's new device should be able to decrypt. decryptableBody := "Bob's new device can decrypt this" waiter = bob2.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(decryptableBody)) - evID = alice.SendMessage(t, roomID, decryptableBody) + evID = alice.MustSendMessage(t, roomID, decryptableBody) t.Logf("bob2 (%s) waiting for event %s", bob2.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob2 did not see alice's message") }) @@ -258,7 +258,7 @@ func TestOnNewDeviceBobCanSeeButNotDecryptHistoryInPublicRoom(t *testing.T) { // by bob's other logged in device though. undecryptableBody := "Bob's logged out device won't be able to decrypt this" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(undecryptableBody)) - evID = alice.SendMessage(t, roomID, undecryptableBody) + evID = alice.MustSendMessage(t, roomID, undecryptableBody) t.Logf("bob (%s) waiting for event %s", bob.Type(), evID) waiter.Waitf(t, 5*time.Second, "bob did not see alice's event %s", evID) @@ -287,7 +287,7 @@ func TestChangingDeviceAfterInviteReEncrypts(t *testing.T) { tc.Alice.MustInviteRoom(t, roomID, tc.Bob.UserID) time.Sleep(time.Second) // let device keys propagate body := "Alice should re-encrypt this message for bob's new device" - evID := alice.SendMessage(t, roomID, body) + evID := alice.MustSendMessage(t, roomID, body) // now Bob logs in on a different device and accepts the invite. The different device should be able to decrypt the message. csapiBob2 := tc.MustRegisterNewDevice(t, tc.Bob, "NEW_DEVICE") diff --git a/tests/one_time_keys_test.go b/tests/one_time_keys_test.go index 6bac656..e51c919 100644 --- a/tests/one_time_keys_test.go +++ b/tests/one_time_keys_test.go @@ -136,8 +136,8 @@ func TestFallbackKeyIsUsedIfOneTimeKeysRunOut(t *testing.T) { charlie.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(alice.UserID(), "join")).Waitf(t, 5*time.Second, "charlie did not see alice's join") bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(alice.UserID(), "join")).Waitf(t, 5*time.Second, "bob did not see alice's join") alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(alice.UserID(), "join")).Waitf(t, 5*time.Second, "alice did not see own join") - bob.SendMessage(t, roomID, "Hello world!") - charlie.SendMessage(t, roomID, "Goodbye world!") + bob.MustSendMessage(t, roomID, "Hello world!") + charlie.MustSendMessage(t, roomID, "Goodbye world!") waiter = alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody("Hello world!")) // ensure that /keys/upload is actually blocked (OTK count should be 0) res, _ := tc.Alice.MustSync(t, client.SyncReq{}) @@ -239,7 +239,7 @@ func TestFailedKeysClaimRetries(t *testing.T) { // JS SDK won't retry the /keys/claim automatically. Try sending another event to kick it. counter := 0 for !stopPoking.Load() && counter < 10 { - bob.TrySendMessage(t, roomID, "poke msg") + bob.SendMessage(t, roomID, "poke msg") counter++ time.Sleep(100 * time.Millisecond * time.Duration(counter+1)) } diff --git a/tests/room_keys_test.go b/tests/room_keys_test.go index baa5f80..7e39c68 100644 --- a/tests/room_keys_test.go +++ b/tests/room_keys_test.go @@ -64,7 +64,7 @@ func TestRoomKeyIsCycledOnDeviceLogout(t *testing.T) { wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) waiter2 := alice2.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") waiter2.Waitf(t, 5*time.Second, "alice2 did not see alice's message") alice2StopSyncing() @@ -82,7 +82,7 @@ func TestRoomKeyIsCycledOnDeviceLogout(t *testing.T) { // now send another message from Alice, who should negotiate a new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's new message") // we should have seen a /sendToDevice call by now. If we didn't, this implies we didn't cycle @@ -122,7 +122,7 @@ func TestRoomKeyIsCycledAfterEnoughMessages(t *testing.T) { for i := 0; i < 4; i++ { wantMsgBody := fmt.Sprintf("Before we hit the threshold %d", i) waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message '%s'", wantMsgBody) } @@ -138,12 +138,12 @@ func TestRoomKeyIsCycledAfterEnoughMessages(t *testing.T) { // of these approaches will pass the test. wantMsgBody := "This one hits the threshold" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message '%s'", wantMsgBody) wantMsgBody = "After the threshold" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message '%s'", wantMsgBody) // Then we did send out new keys @@ -196,7 +196,7 @@ func TestRoomKeyIsCycledAfterEnoughTime(t *testing.T) { // don't get a false positive. wantMsgBody := "Before we start" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "Did not see 'before we start' event in the room") // Sniff calls to /sendToDevice to ensure we see the new room key being sent. @@ -204,7 +204,7 @@ func TestRoomKeyIsCycledAfterEnoughTime(t *testing.T) { // Send a message to ensure the room is working, and any timer is set up wantMsgBody := "Before the time expires" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "Did not see 'before the time expires' event in the room") // When we wait 1+period seconds @@ -213,7 +213,7 @@ func TestRoomKeyIsCycledAfterEnoughTime(t *testing.T) { // And send another message wantMsgBody = "After the time expires" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "Did not see 'after the time expires' event in the room") pc.Recv(t, "did not see /sendToDevice after waiting rotation_period_ms milliseconds") @@ -242,7 +242,7 @@ func TestRoomKeyIsCycledOnMemberLeaving(t *testing.T) { wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) waiter2 := charlie.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") waiter2.Waitf(t, 5*time.Second, "charlie did not see alice's message") @@ -258,7 +258,7 @@ func TestRoomKeyIsCycledOnMemberLeaving(t *testing.T) { // now send another message from Alice, who should negotiate a new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") // we should have seen a /sendToDevice call by now. If we didn't, this implies we didn't cycle @@ -285,7 +285,7 @@ func TestRoomKeyIsNotCycled(t *testing.T) { // check the room works wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") t.Run("on display name change", func(t *testing.T) { // we don't know when the new room key will be sent, it could be sent as soon as the device list update @@ -304,7 +304,7 @@ func TestRoomKeyIsNotCycled(t *testing.T) { // now send another message from Alice, who should negotiate a new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") got := pc.TryRecv(t) @@ -338,7 +338,7 @@ func TestRoomKeyIsNotCycled(t *testing.T) { // now send another message from Alice, who should negotiate a new room key wantMsgBody = "Yet Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") // we should have seen a /sendToDevice call by now. If we didn't, this implies we didn't cycle @@ -417,7 +417,7 @@ func testRoomKeyIsNotCycledOnClientRestartRust(t *testing.T, clientType api.Clie }, Multiprocess: true, }, func(remoteAlice api.TestClient) { - eventID := remoteAlice.SendMessage(t, roomID, wantMsgBody) + eventID := remoteAlice.MustSendMessage(t, roomID, wantMsgBody) waiter := remoteAlice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasEventID(eventID)) waiter.Waitf(t, 5*time.Second, "client did not see event %s", eventID) }) @@ -446,7 +446,7 @@ func testRoomKeyIsNotCycledOnClientRestartRust(t *testing.T, clientType api.Clie // now send another message from Alice, who should NOT negotiate a new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") // we should have seen a /sendToDevice call by now. If we didn't, this implies we didn't cycle @@ -484,7 +484,7 @@ func testRoomKeyIsNotCycledOnClientRestartJS(t *testing.T, clientType api.Client // check the room works wantMsgBody := "Test Message" waiter := bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") // we're going to sniff calls to /sendToDevice to ensure we do NOT see a new room key being sent. @@ -500,7 +500,7 @@ func testRoomKeyIsNotCycledOnClientRestartJS(t *testing.T, clientType api.Client // now send another message from Alice, who should NOT send another new room key wantMsgBody = "Another Test Message" waiter = bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsgBody)) - alice.SendMessage(t, roomID, wantMsgBody) + alice.MustSendMessage(t, roomID, wantMsgBody) waiter.Waitf(t, 5*time.Second, "bob did not see alice's message") }) diff --git a/tests/rust/notification_test.go b/tests/rust/notification_test.go index 47bf815..57dc33c 100644 --- a/tests/rust/notification_test.go +++ b/tests/rust/notification_test.go @@ -70,7 +70,7 @@ func testNSEReceive(t *testing.T, numMsgsBefore, numMsgsAfter int) { alice.Logf(t, "syncing and sending dummy message to ensure e2ee keys are uploaded") stopSyncing := alice.MustStartSyncing(t) alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasMembership(tc.Bob.UserID, "join")).Waitf(t, 5*time.Second, "did not see bob's join") - alice.SendMessage(t, roomID, "test message to ensure E2EE keys are uploaded") + alice.MustSendMessage(t, roomID, "test message to ensure E2EE keys are uploaded") accessToken := alice.Opts().AccessToken // app is "backgrounded" so we tidy things up @@ -122,14 +122,14 @@ func TestNSEReceiveForNonPreKeyMessage(t *testing.T) { // let bob realise alice exists and claims keys time.Sleep(time.Second) // Send a message as Bob, this will contain ensure an Olm session is set up already before we do NSE work - bob.SendMessage(t, roomID, "initial message") + bob.MustSendMessage(t, roomID, "initial message") alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody("initial message")).Waitf(t, 5*time.Second, "alice did not see bob's initial message") // Alice goes into the background accessToken := alice.Opts().AccessToken stopSyncing() alice.Close(t) // Bob sends another message which the NSE process will get - eventID := bob.SendMessage(t, roomID, "for nse") + eventID := bob.MustSendMessage(t, roomID, "for nse") bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasEventID(eventID)).Waitf(t, 5*time.Second, "bob did not see his own message") // now make the "NSE" process and get bob's message client := tc.MustCreateClient(t, &cc.ClientCreationRequest{ @@ -180,7 +180,7 @@ func TestMultiprocessNSE(t *testing.T) { time.Sleep(time.Second) for i := 0; i < numPreBackgroundMsgs; i++ { msg := fmt.Sprintf("numPreBackgroundMsgs %d", i) - bob.SendMessage(t, roomID, msg) + bob.MustSendMessage(t, roomID, msg) alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(msg)).Waitf(t, 5*time.Second, "alice did not see '%s'", msg) } @@ -251,7 +251,7 @@ func TestMultiprocessNSE(t *testing.T) { nseAlice.Close(t) } msg := fmt.Sprintf("numPostNSEMsgs %d", i) - eventID := bob.SendMessage(t, roomID, msg) + eventID := bob.MustSendMessage(t, roomID, msg) eventTimeline = append(eventTimeline, eventID) t.Logf("event %s => '%s'", eventID, msg) if restartNSE { // a new NSE process is created as a result of bob's message @@ -277,7 +277,7 @@ func TestMultiprocessNSE(t *testing.T) { startAliceSyncing() } if aliceSendsMsg { // this will cause the main app to update the crypto store - sentEventID := alice.SendMessage(t, roomID, "dummy") + sentEventID := alice.MustSendMessage(t, roomID, "dummy") eventTimeline = append(eventTimeline, sentEventID) } if !nseOpensFirst { @@ -384,7 +384,7 @@ func TestMultiprocessNSEBackupKeyMacError(t *testing.T) { }) // this should login already as we provided an access token msg := "first message" - eventID := bob.SendMessage(t, roomID, msg) + eventID := bob.MustSendMessage(t, roomID, msg) eventTimeline = append(eventTimeline, eventID) t.Logf("first event %s => '%s'", eventID, msg) checkNSECanDecryptEvent(nseAlice, roomID, eventID, msg) @@ -395,7 +395,7 @@ func TestMultiprocessNSEBackupKeyMacError(t *testing.T) { // send final message msg = "final message" - eventID = bob.SendMessage(t, roomID, msg) + eventID = bob.MustSendMessage(t, roomID, msg) eventTimeline = append(eventTimeline, eventID) t.Logf("final event %s => '%s'", eventID, msg) @@ -456,7 +456,7 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) { // let bob realise alice exists and claims keys time.Sleep(time.Second) msg := "pre message" - bob.SendMessage(t, roomID, msg) + bob.MustSendMessage(t, roomID, msg) alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(msg)).Waitf(t, 5*time.Second, "alice did not see '%s'", msg) stopAliceSyncing := func() { @@ -510,7 +510,7 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) { stopAliceSyncing() msg = fmt.Sprintf("test message %d", 1) - eventID := bob.SendMessage(t, roomID, msg) + eventID := bob.MustSendMessage(t, roomID, msg) t.Logf("event %s => '%s'", eventID, msg) // both the nse process and the app process should be able to decrypt the event. @@ -519,12 +519,12 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) { t.Logf("restarting alice") nseAlice.Logf(t, "post checkNSECanDecryptEvent") startAliceSyncing() - alice.SendMessage(t, roomID, "dummy") + alice.MustSendMessage(t, roomID, "dummy") // iteration 2 stopAliceSyncing() msg = fmt.Sprintf("test message %d", 2) - eventID = bob.SendMessage(t, roomID, msg) + eventID = bob.MustSendMessage(t, roomID, msg) t.Logf("event %s => '%s'", eventID, msg) // both the nse process and the app process should be able to decrypt the event. @@ -532,7 +532,7 @@ func TestMultiprocessNSEOlmSessionWedge(t *testing.T) { checkNSECanDecryptEvent(nseAlice, roomID, eventID, msg) t.Logf("restarting alice") startAliceSyncing() - alice.SendMessage(t, roomID, "dummy") + alice.MustSendMessage(t, roomID, "dummy") nseAlice.Close(t) stopAliceSyncing() @@ -600,7 +600,7 @@ func TestNotificationClientDupeOTKUpload(t *testing.T) { tc.WithClientSyncing(t, &cc.ClientCreationRequest{ User: tc.Bob, }, func(bob api.TestClient) { - eventID := bob.SendMessage(t, roomID, "Hello world!") + eventID := bob.MustSendMessage(t, roomID, "Hello world!") // create a NotificationClient in the same process to fetch this "push notification". // It might make the NotificationClient upload a OTK as it would have seen 1 has been used. // The NotificationClient and main Client must talk to each other to ensure they use the same key. @@ -651,7 +651,7 @@ func TestMultiprocessInitialE2EESyncDoesntDropDeviceListUpdates(t *testing.T) { }, func(alice api.TestClient) { // ensure bob has queried keys from alice by sending a message. msg := "pre message" - bob.SendMessage(t, roomID, msg) + bob.MustSendMessage(t, roomID, msg) alice.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(msg)).Waitf(t, 5*time.Second, "alice did not see '%s'", msg) stopBobSyncing := func() { @@ -704,7 +704,7 @@ func TestMultiprocessInitialE2EESyncDoesntDropDeviceListUpdates(t *testing.T) { // wait for device keys to sync up time.Sleep(time.Second) // alice[1] sends a message, this is unimportant other than to grab the event ID for the push process - pushEventID := alice.SendMessage(t, roomID, "pre message 2") + pushEventID := alice.MustSendMessage(t, roomID, "pre message 2") // Bob's push process receives Alice[1]'s message. // This /should/ make Bob aware of Alice[2]. notif, err := nseBob.GetNotification(t, roomID, pushEventID) @@ -718,7 +718,7 @@ func TestMultiprocessInitialE2EESyncDoesntDropDeviceListUpdates(t *testing.T) { time.Sleep(time.Second) // Bob sends a message. wantMsg := "can alice's new device decrypt this?" - bob.SendMessage(t, roomID, wantMsg) + bob.MustSendMessage(t, roomID, wantMsg) alice2.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(wantMsg)).Waitf(t, 5*time.Second, "alice[2] did not see '%s'", wantMsg) }) }) @@ -752,13 +752,13 @@ func bobSendsMessage(t *testing.T, tc *cc.TestContext, roomID, text string, msgs User: tc.Bob, }, func(bob api.TestClient) { for i := 0; i < msgsBefore; i++ { - bob.SendMessage(t, roomID, fmt.Sprintf("msg before %d", i)) + bob.MustSendMessage(t, roomID, fmt.Sprintf("msg before %d", i)) } bob.Logf(t, "sending push notification message as bob") - pushNotifEventID = bob.SendMessage(t, roomID, text) + pushNotifEventID = bob.MustSendMessage(t, roomID, text) bob.Logf(t, "sent push notification message as bob => %s", pushNotifEventID) for i := 0; i < msgsAfter; i++ { - bob.SendMessage(t, roomID, fmt.Sprintf("msg after %d", i)) + bob.MustSendMessage(t, roomID, fmt.Sprintf("msg after %d", i)) } }) return pushNotifEventID diff --git a/tests/to_device_test.go b/tests/to_device_test.go index 56a852d..bb9cff6 100644 --- a/tests/to_device_test.go +++ b/tests/to_device_test.go @@ -38,18 +38,18 @@ func TestClientRetriesSendToDevice(t *testing.T) { }, ResponseCallback: callback.SendError(0, http.StatusGatewayTimeout), }, func() { - evID, err = alice.TrySendMessage(t, roomID, wantMsgBody) + evID, err = alice.SendMessage(t, roomID, wantMsgBody) if err != nil { // we allow clients to fail the send if they cannot call /sendToDevice - t.Logf("TrySendMessage: %s", err) + t.Logf("SendMessage: %s", err) } if evID != "" { - t.Logf("TrySendMessage: => %s", evID) + t.Logf("SendMessage: => %s", evID) } }) if err != nil { // retry now we have connectivity - evID = alice.SendMessage(t, roomID, wantMsgBody) + evID = alice.MustSendMessage(t, roomID, wantMsgBody) } // Bob receives the message @@ -89,7 +89,7 @@ func TestUnprocessedToDeviceMessagesArentLostOnRestart(t *testing.T) { // we will close this in the test, no defer bobStopSyncing := bob.MustStartSyncing(t) // check the room works - alice.SendMessage(t, roomID, "Hello World!") + alice.MustSendMessage(t, roomID, "Hello World!") bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody("Hello World!")).Waitf(t, 2*time.Second, "bob did not see event with body 'Hello World!'") // stop bob's client, but grab the access token first so we can re-use it bobOpts := bob.Opts() @@ -113,7 +113,7 @@ func TestUnprocessedToDeviceMessagesArentLostOnRestart(t *testing.T) { t.Logf("to-device msgs sent") // send a message as alice to make a new room key - eventID := alice.SendMessage(t, roomID, "Kick to make a new room key!") + eventID := alice.MustSendMessage(t, roomID, "Kick to make a new room key!") // client specific impls to handle restarts. switch clientType.Lang { @@ -315,7 +315,7 @@ func TestToDeviceMessagesAreBatched(t *testing.T) { return nil }, }, func() { - alice.SendMessage(t, roomID, "this should cause to-device msgs to be sent") + alice.MustSendMessage(t, roomID, "this should cause to-device msgs to be sent") time.Sleep(time.Second) waiter.Waitf(t, 5*time.Second, "did not see /sendToDevice") }) @@ -348,7 +348,7 @@ func TestToDeviceMessagesArentLostWhenKeysQueryFails(t *testing.T) { tc.WithAliceAndBobSyncing(t, func(alice, bob api.TestClient) { msg := "hello world" msg2 := "new device message from alice" - alice.SendMessage(t, roomID, msg) + alice.MustSendMessage(t, roomID, msg) bob.WaitUntilEventInRoom(t, roomID, api.CheckEventHasBody(msg)).Waitf(t, 5*time.Second, "bob failed to see message from alice") // Block /keys/query requests waiter := helpers.NewWaiter() @@ -375,7 +375,7 @@ func TestToDeviceMessagesArentLostWhenKeysQueryFails(t *testing.T) { time.Sleep(time.Second) // Alice sends a message on the new device. - eventID = alice2.SendMessage(t, roomID, msg2) + eventID = alice2.MustSendMessage(t, roomID, msg2) waiter.Waitf(t, 3*time.Second, "did not see /keys/query") time.Sleep(3 * time.Second) // let Bob retry /keys/query @@ -473,7 +473,7 @@ func TestToDeviceMessagesAreProcessedInOrder(t *testing.T) { for i := 0; i < numMsgsPerClient; i++ { for _, c := range clients { body := fmt.Sprintf("Message %d", i+1) - eventID := c.SendMessage(t, roomID, body) + eventID := c.MustSendMessage(t, roomID, body) timelineEvents = append(timelineEvents, struct { ID string Body string From bad61788ff18e84f2f7d2da93ada89c2b4e2834e Mon Sep 17 00:00:00 2001 From: Kegan Dougal <7190048+kegsay@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:15:12 +0100 Subject: [PATCH 4/4] ensure single line logging to stop syntax errors on multilined error args --- internal/api/js/js.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/api/js/js.go b/internal/api/js/js.go index 0140843..bb2afed 100644 --- a/internal/api/js/js.go +++ b/internal/api/js/js.go @@ -679,8 +679,9 @@ func (c *JSClient) WaitUntilEventInRoom(t ct.TestLike, roomID string, checker fu func (c *JSClient) Logf(t ct.TestLike, format string, args ...interface{}) { t.Helper() formatted := fmt.Sprintf(t.Name()+": "+format, args...) + firstLine := strings.Split(formatted, "\n")[0] if c.browser.Ctx.Err() == nil { // don't log on dead browsers - chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(`console.log("%s");`, strings.Replace(formatted, `"`, `\"`, -1))) + chrome.MustRunAsyncFn[chrome.Void](t, c.browser.Ctx, fmt.Sprintf(`console.log("%s");`, strings.Replace(firstLine, `"`, `\"`, -1))) t.Logf(format, args...) } }