diff --git a/lib/crypto/index.js b/lib/crypto/index.js index 9bfeb3b2337..035c81d223c 100644 --- a/lib/crypto/index.js +++ b/lib/crypto/index.js @@ -55,8 +55,8 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId) { this._deviceId = deviceId; this._initialSyncCompleted = false; - // userId -> deviceId -> true - this._pendingNewDevices = {}; + // userId -> true + this._pendingUsersWithNewDevices = {}; this._olmDevice = new OlmDevice(sessionStore); @@ -77,20 +77,31 @@ function Crypto(baseApis, eventEmitter, sessionStore, userId, deviceId) { this._deviceKeys["curve25519:" + this._deviceId] = this._olmDevice.deviceCurve25519Key; - // add our own deviceinfo to the sessionstore - var deviceInfo = { - keys: this._deviceKeys, - algorithms: this._supportedAlgorithms, - verified: DeviceVerification.VERIFIED, - }; var myDevices = this._sessionStore.getEndToEndDevicesForUser( this._userId - ) || {}; - myDevices[this._deviceId] = deviceInfo; - this._sessionStore.storeEndToEndDevicesForUser( - this._userId, myDevices ); + if (!myDevices) { + // we don't yet have a list of our own devices; make sure we + // get one when we flush the pendingUsersWithNewDevices. + this._pendingUsersWithNewDevices[this._userId] = true; + myDevices = {}; + } + + if (!myDevices[this._deviceId]) { + // add our own deviceinfo to the sessionstore + var deviceInfo = { + keys: this._deviceKeys, + algorithms: this._supportedAlgorithms, + verified: DeviceVerification.VERIFIED, + }; + + myDevices[this._deviceId] = deviceInfo; + this._sessionStore.storeEndToEndDevicesForUser( + this._userId, myDevices + ); + } + _registerEventHandlers(this, eventEmitter); // map from userId -> deviceId -> roomId -> timestamp @@ -1134,8 +1145,7 @@ Crypto.prototype._onNewDeviceEvent = function(event) { return; } - this._pendingNewDevices[userId] = this._pendingNewDevices[userId] || {}; - this._pendingNewDevices[userId][deviceId] = true; + this._pendingUsersWithNewDevices[userId] = true; // we delay handling these until the intialsync has completed, so that we // can do all of them together. @@ -1150,10 +1160,7 @@ Crypto.prototype._onNewDeviceEvent = function(event) { Crypto.prototype._flushNewDeviceRequests = function() { var self = this; - var pending = this._pendingNewDevices; - var users = utils.keys(pending).filter(function(u) { - return utils.keys(pending[u]).length > 0; - }); + var users = utils.keys(this._pendingUsersWithNewDevices); if (users.length === 0) { return; @@ -1163,7 +1170,7 @@ Crypto.prototype._flushNewDeviceRequests = function() { // we've kicked off requests to these users: remove their // pending flag for now. - this._pendingNewDevices = {}; + this._pendingUsersWithNewDevices = {}; users.map(function(u) { r[u] = r[u].catch(function(e) { @@ -1175,8 +1182,7 @@ Crypto.prototype._flushNewDeviceRequests = function() { // mean that we will do another download in the future, but won't // tight-loop. // - self._pendingNewDevices[u] = self._pendingNewDevices[u] || {}; - utils.update(self._pendingNewDevices[u], pending[u]); + self._pendingUsersWithNewDevices[u] = true; }); }); diff --git a/lib/crypto/olmlib.js b/lib/crypto/olmlib.js index 341e0ecd7bd..fc2a66fe8c9 100644 --- a/lib/crypto/olmlib.js +++ b/lib/crypto/olmlib.js @@ -159,9 +159,10 @@ module.exports.ensureOlmSessionsForDevices = function( return baseApis.claimOneTimeKeys( devicesWithoutSession, oneTimeKeyAlgorithm ).then(function(res) { + var otk_res = res.one_time_keys || {}; for (var userId in devicesByUser) { if (!devicesByUser.hasOwnProperty(userId)) { continue; } - var userRes = res.one_time_keys[userId] || {}; + var userRes = otk_res[userId] || {}; var devices = devicesByUser[userId]; for (var j = 0; j < devices.length; j++) { var deviceInfo = devices[j]; diff --git a/spec/integ/matrix-client-crypto.spec.js b/spec/integ/matrix-client-crypto.spec.js index b563a08c834..9d7cbbd5e75 100644 --- a/spec/integ/matrix-client-crypto.spec.js +++ b/spec/integ/matrix-client-crypto.spec.js @@ -383,6 +383,15 @@ function recvMessage(httpBackend, client, sender, message) { function aliStartClient() { expectAliKeyUpload().catch(test_utils.failTest); + + // ali will try to query her own keys on start + aliHttpBackend.when("POST", "/keys/query").respond(200, function(path, content) { + expect(content.device_keys[aliUserId]).toEqual({}); + var result = {}; + result[aliUserId] = {}; + return {device_keys: result}; + }); + startClient(aliHttpBackend, aliClient); return aliHttpBackend.flush().then(function() { console.log("Ali client started"); @@ -391,6 +400,15 @@ function aliStartClient() { function bobStartClient() { expectBobKeyUpload().catch(test_utils.failTest); + + // bob will try to query his own keys on start + bobHttpBackend.when("POST", "/keys/query").respond(200, function(path, content) { + expect(content.device_keys[bobUserId]).toEqual({}); + var result = {}; + result[bobUserId] = {}; + return {device_keys: result}; + }); + startClient(bobHttpBackend, bobClient); return bobHttpBackend.flush().then(function() { console.log("Bob client started"); diff --git a/spec/integ/megolm.spec.js b/spec/integ/megolm.spec.js index 086180fe78c..3c0da50c4c4 100644 --- a/spec/integ/megolm.spec.js +++ b/spec/integ/megolm.spec.js @@ -59,12 +59,25 @@ function TestClient(userId, deviceId, accessToken) { /** * start the client, and wait for it to initialise. * + * @param {object?} deviceQueryResponse the list of our existing devices to return from + * the /query request. Defaults to empty device list * @return {Promise} */ -TestClient.prototype.start = function() { +TestClient.prototype.start = function(existingDevices) { var self = this; + this.httpBackend.when("GET", "/pushrules").respond(200, {}); this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" }); + + this.httpBackend.when('POST', '/keys/query').respond(200, function(path, content) { + expect(content.device_keys[self.userId]).toEqual({}); + var res = existingDevices; + if (!res) { + res = { device_keys: {} }; + res.device_keys[self.userId] = {}; + } + return res; + }); this.httpBackend.when("POST", "/keys/upload").respond(200, function(path, content) { expect(content.one_time_keys).not.toBeDefined(); expect(content.device_keys).toBeDefined(); @@ -253,38 +266,81 @@ describe("megolm", function() { var testOlmAccount; var testSenderKey; var aliceTestClient; - var testDeviceKeys; - - beforeEach(function(done) { - test_utils.beforeEach(this); - - aliceTestClient = new TestClient( - "@alice:localhost", "xzcvb", "akjgkrgjs" - ); - testOlmAccount = new Olm.Account(); - testOlmAccount.create(); + /** + * Get the device keys for testOlmAccount in a format suitable for a + * response to /keys/query + */ + function getTestKeysQueryResponse(userId) { var testE2eKeys = JSON.parse(testOlmAccount.identity_keys()); - testSenderKey = testE2eKeys.curve25519; - - testDeviceKeys = { + var testDeviceKeys = { algorithms: ['m.olm.v1.curve25519-aes-sha2', 'm.megolm.v1.aes-sha2'], device_id: 'DEVICE_ID', keys: { 'curve25519:DEVICE_ID': testE2eKeys.curve25519, 'ed25519:DEVICE_ID': testE2eKeys.ed25519, }, - user_id: '@bob:xyz', + user_id: userId, }; var j = anotherjson.stringify(testDeviceKeys); var sig = testOlmAccount.sign(j); - testDeviceKeys.signatures = { - '@bob:xyz': { - 'ed25519:DEVICE_ID': sig, - }, + testDeviceKeys.signatures = {}; + testDeviceKeys.signatures[userId] = { + 'ed25519:DEVICE_ID': sig, + }; + + var queryResponse = { + device_keys: {}, }; - return aliceTestClient.start().nodeify(done); + queryResponse.device_keys[userId] = { + 'DEVICE_ID': testDeviceKeys, + }; + + return queryResponse; + } + + /** + * Get a one-time key for testOlmAccount in a format suitable for a + * response to /keys/claim + */ + function getTestKeysClaimResponse(userId) { + testOlmAccount.generate_one_time_keys(1); + var testOneTimeKeys = JSON.parse(testOlmAccount.one_time_keys()); + testOlmAccount.mark_keys_as_published(); + + var keyId = utils.keys(testOneTimeKeys.curve25519)[0]; + var oneTimeKey = testOneTimeKeys.curve25519[keyId]; + var keyResult = { + 'key': oneTimeKey, + }; + var j = anotherjson.stringify(keyResult); + var sig = testOlmAccount.sign(j); + keyResult.signatures = {}; + keyResult.signatures[userId] = { + 'ed25519:DEVICE_ID': sig, + }; + + var claimResponse = {one_time_keys: {}}; + claimResponse.one_time_keys[userId] = { + 'DEVICE_ID': {}, + }; + claimResponse.one_time_keys[userId].DEVICE_ID['signed_curve25519:' + keyId] = + keyResult; + return claimResponse; + } + + beforeEach(function() { + test_utils.beforeEach(this); + + aliceTestClient = new TestClient( + "@alice:localhost", "xzcvb", "akjgkrgjs" + ); + + testOlmAccount = new Olm.Account(); + testOlmAccount.create(); + var testE2eKeys = JSON.parse(testOlmAccount.identity_keys()); + testSenderKey = testE2eKeys.curve25519; }); afterEach(function() { @@ -292,44 +348,47 @@ describe("megolm", function() { }); it("Alice receives a megolm message", function(done) { - var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); - - var groupSession = new Olm.OutboundGroupSession(); - groupSession.create(); - - // make the room_key event - var roomKeyEncrypted = encryptGroupSessionKey({ - senderKey: testSenderKey, - recipient: aliceTestClient, - p2pSession: p2pSession, - groupSession: groupSession, - room_id: ROOM_ID, - }); - - // encrypt a message with the group session - var messageEncrypted = encryptMegolmEvent({ - senderKey: testSenderKey, - groupSession: groupSession, - room_id: ROOM_ID, - }); - - // Alice gets both the events in a single sync - var syncResponse = { - next_batch: 1, - to_device: { - events: [roomKeyEncrypted], - }, - rooms: { - join: {}, - }, - }; - syncResponse.rooms.join[ROOM_ID] = { - timeline: { - events: [messageEncrypted], - }, - }; - aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); - return aliceTestClient.httpBackend.flush("/sync", 1).then(function() { + return aliceTestClient.start().then(function() { + var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); + + var groupSession = new Olm.OutboundGroupSession(); + groupSession.create(); + + // make the room_key event + var roomKeyEncrypted = encryptGroupSessionKey({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, + groupSession: groupSession, + room_id: ROOM_ID, + }); + + // encrypt a message with the group session + var messageEncrypted = encryptMegolmEvent({ + senderKey: testSenderKey, + groupSession: groupSession, + room_id: ROOM_ID, + }); + + // Alice gets both the events in a single sync + var syncResponse = { + next_batch: 1, + to_device: { + events: [roomKeyEncrypted], + }, + rooms: { + join: {}, + }, + }; + syncResponse.rooms.join[ROOM_ID] = { + timeline: { + events: [messageEncrypted], + }, + }; + + aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse); + return aliceTestClient.httpBackend.flush("/sync", 1); + }).then(function() { var room = aliceTestClient.client.getRoom(ROOM_ID); var event = room.getLiveTimeline().getEvents()[0]; expect(event.getContent().body).toEqual('42'); @@ -337,65 +396,67 @@ describe("megolm", function() { }); it("Alice gets a second room_key message", function(done) { - var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); - - var groupSession = new Olm.OutboundGroupSession(); - groupSession.create(); - - // make the room_key event - var roomKeyEncrypted1 = encryptGroupSessionKey({ - senderKey: testSenderKey, - recipient: aliceTestClient, - p2pSession: p2pSession, - groupSession: groupSession, - room_id: ROOM_ID, - }); - - // encrypt a message with the group session - var messageEncrypted = encryptMegolmEvent({ - senderKey: testSenderKey, - groupSession: groupSession, - room_id: ROOM_ID, - }); - - // make a second room_key event now that we have advanced the group - // session. - var roomKeyEncrypted2 = encryptGroupSessionKey({ - senderKey: testSenderKey, - recipient: aliceTestClient, - p2pSession: p2pSession, - groupSession: groupSession, - room_id: ROOM_ID, - }); - - // on the first sync, send the best room key - aliceTestClient.httpBackend.when("GET", "/sync").respond(200, { - next_batch: 1, - to_device: { - events: [roomKeyEncrypted1], - }, - }); - - // on the second sync, send the advanced room key, along with the - // message. This simulates the situation where Alice has been sent a - // later copy of the room key and is reloading the client. - var syncResponse2 = { - next_batch: 2, - to_device: { - events: [roomKeyEncrypted2], - }, - rooms: { - join: {}, - }, - }; - syncResponse2.rooms.join[ROOM_ID] = { - timeline: { - events: [messageEncrypted], - }, - }; - aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2); + return aliceTestClient.start().then(function() { + var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); + + var groupSession = new Olm.OutboundGroupSession(); + groupSession.create(); + + // make the room_key event + var roomKeyEncrypted1 = encryptGroupSessionKey({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, + groupSession: groupSession, + room_id: ROOM_ID, + }); + + // encrypt a message with the group session + var messageEncrypted = encryptMegolmEvent({ + senderKey: testSenderKey, + groupSession: groupSession, + room_id: ROOM_ID, + }); - return aliceTestClient.httpBackend.flush("/sync", 2).then(function() { + // make a second room_key event now that we have advanced the group + // session. + var roomKeyEncrypted2 = encryptGroupSessionKey({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, + groupSession: groupSession, + room_id: ROOM_ID, + }); + + // on the first sync, send the best room key + aliceTestClient.httpBackend.when("GET", "/sync").respond(200, { + next_batch: 1, + to_device: { + events: [roomKeyEncrypted1], + }, + }); + + // on the second sync, send the advanced room key, along with the + // message. This simulates the situation where Alice has been sent a + // later copy of the room key and is reloading the client. + var syncResponse2 = { + next_batch: 2, + to_device: { + events: [roomKeyEncrypted2], + }, + rooms: { + join: {}, + }, + }; + syncResponse2.rooms.join[ROOM_ID] = { + timeline: { + events: [messageEncrypted], + }, + }; + aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2); + + return aliceTestClient.httpBackend.flush("/sync", 2); + }).then(function() { var room = aliceTestClient.client.getRoom(ROOM_ID); var event = room.getLiveTimeline().getEvents()[0]; expect(event.getContent().body).toEqual('42'); @@ -403,54 +464,52 @@ describe("megolm", function() { }); it('Alice sends a megolm message', function(done) { - // establish an olm session with alice - var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); - - var olmEvent = encryptOlmEvent({ - senderKey: testSenderKey, - recipient: aliceTestClient, - p2pSession: p2pSession, - }); - - var syncResponse = { - next_batch: 1, - to_device: { - events: [olmEvent], - }, - rooms: { - join: {}, - }, - }; - syncResponse.rooms.join[ROOM_ID] = { - state: { - events: [ - test_utils.mkEvent({ - type: 'm.room.encryption', - skey: '', - content: { - algorithm: 'm.megolm.v1.aes-sha2', - }, - }), - test_utils.mkMembership({ - mship: 'join', - sender: '@bob:xyz', - }), - ], - }, - }; - aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + var p2pSession; - var inboundGroupSession; + return aliceTestClient.start().then(function() { + // establish an olm session with alice + p2pSession = createOlmSession(testOlmAccount, aliceTestClient); - return aliceTestClient.httpBackend.flush('/sync', 1).then(function() { - aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, { - device_keys: { - '@bob:xyz': { - 'DEVICE_ID': testDeviceKeys, - }, - } + var olmEvent = encryptOlmEvent({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, }); + var syncResponse = { + next_batch: 1, + to_device: { + events: [olmEvent], + }, + rooms: { + join: {}, + }, + }; + syncResponse.rooms.join[ROOM_ID] = { + state: { + events: [ + test_utils.mkEvent({ + type: 'm.room.encryption', + skey: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, + }), + test_utils.mkMembership({ + mship: 'join', + sender: '@bob:xyz', + }), + ], + }, + }; + aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + return aliceTestClient.httpBackend.flush('/sync', 1); + }).then(function() { + var inboundGroupSession; + aliceTestClient.httpBackend.when('POST', '/keys/query').respond( + 200, getTestKeysQueryResponse('@bob:xyz') + ); + aliceTestClient.httpBackend.when( 'PUT', '/sendToDevice/m.room.encrypted/' ).respond(200, function(path, content) { @@ -487,32 +546,34 @@ describe("megolm", function() { }); it("Alice shouldn't do a second /query for non-e2e-capable devices", function(done) { - var syncResponse = { - next_batch: 1, - rooms: { - join: {}, - }, - }; - syncResponse.rooms.join[ROOM_ID] = { - state: { - events: [ - test_utils.mkEvent({ - type: 'm.room.encryption', - skey: '', - content: { - algorithm: 'm.megolm.v1.aes-sha2', - }, - }), - test_utils.mkMembership({ - mship: 'join', - sender: '@bob:xyz', - }), - ], - }, - }; - aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); - - return aliceTestClient.httpBackend.flush('/sync', 1).then(function() { + return aliceTestClient.start().then(function() { + var syncResponse = { + next_batch: 1, + rooms: { + join: {}, + }, + }; + syncResponse.rooms.join[ROOM_ID] = { + state: { + events: [ + test_utils.mkEvent({ + type: 'm.room.encryption', + skey: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, + }), + test_utils.mkMembership({ + mship: 'join', + sender: '@bob:xyz', + }), + ], + }, + }; + aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + + return aliceTestClient.httpBackend.flush('/sync', 1); + }).then(function() { console.log("Forcing alice to download our device keys"); aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, { @@ -543,54 +604,52 @@ describe("megolm", function() { it("We shouldn't attempt to send to blocked devices", function(done) { - // establish an olm session with alice - var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); - - var olmEvent = encryptOlmEvent({ - senderKey: testSenderKey, - recipient: aliceTestClient, - p2pSession: p2pSession, - }); - - var syncResponse = { - next_batch: 1, - to_device: { - events: [olmEvent], - }, - rooms: { - join: {}, - }, - }; - - syncResponse.rooms.join[ROOM_ID] = { - state: { - events: [ - test_utils.mkEvent({ - type: 'm.room.encryption', - skey: '', - content: { - algorithm: 'm.megolm.v1.aes-sha2', - }, - }), - test_utils.mkMembership({ - mship: 'join', - sender: '@bob:xyz', - }), - ], - }, - }; - aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + return aliceTestClient.start().then(function() { + // establish an olm session with alice + var p2pSession = createOlmSession(testOlmAccount, aliceTestClient); + + var olmEvent = encryptOlmEvent({ + senderKey: testSenderKey, + recipient: aliceTestClient, + p2pSession: p2pSession, + }); - return aliceTestClient.httpBackend.flush('/sync', 1).then(function() { + var syncResponse = { + next_batch: 1, + to_device: { + events: [olmEvent], + }, + rooms: { + join: {}, + }, + }; + + syncResponse.rooms.join[ROOM_ID] = { + state: { + events: [ + test_utils.mkEvent({ + type: 'm.room.encryption', + skey: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, + }), + test_utils.mkMembership({ + mship: 'join', + sender: '@bob:xyz', + }), + ], + }, + }; + aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + + return aliceTestClient.httpBackend.flush('/sync', 1); + }).then(function() { console.log('Forcing alice to download our device keys'); - aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, { - device_keys: { - '@bob:xyz': { - 'DEVICE_ID': testDeviceKeys, - }, - } - }); + aliceTestClient.httpBackend.when('POST', '/keys/query').respond( + 200, getTestKeysQueryResponse('@bob:xyz') + ); return q.all([ aliceTestClient.client.downloadKeys(['@bob:xyz']), @@ -614,4 +673,91 @@ describe("megolm", function() { }).nodeify(done); }); + + // https://github.com/vector-im/riot-web/issues/2676 + it("Alice should send to her other devices", function(done) { + // for this test, we make the testOlmAccount be another of Alice's devices. + // it ought to get include in messages Alice sends. + + var p2pSession; + var inboundGroupSession; + var decrypted; + + return aliceTestClient.start( + getTestKeysQueryResponse(aliceTestClient.userId) + ).then(function() { + // an encrypted room with just alice + var syncResponse = { + next_batch: 1, + rooms: { + join: {}, + }, + }; + syncResponse.rooms.join[ROOM_ID] = { + state: { + events: [ + test_utils.mkEvent({ + type: 'm.room.encryption', + skey: '', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + }, + }), + test_utils.mkMembership({ + mship: 'join', + sender: aliceTestClient.userId, + }), + ], + }, + }; + aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse); + + return aliceTestClient.httpBackend.flush(); + }).then(function() { + aliceTestClient.httpBackend.when('POST', '/keys/claim').respond( + 200, function(path, content) + { + expect(content.one_time_keys[aliceTestClient.userId].DEVICE_ID) + .toEqual("signed_curve25519"); + return getTestKeysClaimResponse(aliceTestClient.userId); + }); + + aliceTestClient.httpBackend.when( + 'PUT', '/sendToDevice/m.room.encrypted/' + ).respond(200, function(path, content) { + console.log("sendToDevice: ", content); + var m = content.messages[aliceTestClient.userId].DEVICE_ID; + var ct = m.ciphertext[testSenderKey]; + expect(ct.type).toEqual(0); // pre-key message + + p2pSession = new Olm.Session(); + p2pSession.create_inbound(testOlmAccount, ct.body); + var decrypted = JSON.parse(p2pSession.decrypt(ct.type, ct.body)); + + expect(decrypted.type).toEqual('m.room_key'); + inboundGroupSession = new Olm.InboundGroupSession(); + inboundGroupSession.create(decrypted.content.session_key); + return {}; + }); + + aliceTestClient.httpBackend.when( + 'PUT', '/send/' + ).respond(200, function(path, content) { + var ct = content.ciphertext; + decrypted = JSON.parse(inboundGroupSession.decrypt(ct)); + console.log('Decrypted received megolm message', decrypted); + return { + event_id: '$event_id', + }; + }); + + return q.all([ + aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'), + aliceTestClient.httpBackend.flush(), + ]); + }).then(function() { + expect(decrypted.type).toEqual('m.room.message'); + expect(decrypted.content.body).toEqual('test'); + }).nodeify(done); + }); });