You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-13 19:42:25 +03:00
The earlier commit, d3ce0cb82f
, has most of the juicy details on this. In addition to d3ce's changes, we also:
* Use `TestClient` in many integration tests due to subtle behaviour changes in imports when switching to ES6. Namely the behaviour where setting the request function is less reliable in the way we did it, but `TestClient` is very reliable.
* We now use the Olm loader more often to avoid having to maintain so much duplicate code. This makes the imports slightly easier to read.
400 lines
14 KiB
JavaScript
400 lines
14 KiB
JavaScript
/*
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2018 New Vector Ltd
|
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
import {TestClient} from '../TestClient';
|
|
import * as testUtils from '../test-utils';
|
|
import {logger} from '../../src/logger';
|
|
|
|
const ROOM_ID = "!room:id";
|
|
|
|
/**
|
|
* get a /sync response which contains a single e2e room (ROOM_ID), with the
|
|
* members given
|
|
*
|
|
* @param {string[]} roomMembers
|
|
*
|
|
* @return {object} sync response
|
|
*/
|
|
function getSyncResponse(roomMembers) {
|
|
const stateEvents = [
|
|
testUtils.mkEvent({
|
|
type: 'm.room.encryption',
|
|
skey: '',
|
|
content: {
|
|
algorithm: 'm.megolm.v1.aes-sha2',
|
|
},
|
|
}),
|
|
];
|
|
|
|
Array.prototype.push.apply(
|
|
stateEvents,
|
|
roomMembers.map(
|
|
(m) => testUtils.mkMembership({
|
|
mship: 'join',
|
|
sender: m,
|
|
}),
|
|
),
|
|
);
|
|
|
|
const syncResponse = {
|
|
next_batch: 1,
|
|
rooms: {
|
|
join: {
|
|
[ROOM_ID]: {
|
|
state: {
|
|
events: stateEvents,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
return syncResponse;
|
|
}
|
|
|
|
|
|
describe("DeviceList management:", function() {
|
|
if (!global.Olm) {
|
|
logger.warn('not running deviceList tests: Olm not present');
|
|
return;
|
|
}
|
|
|
|
let sessionStoreBackend;
|
|
let aliceTestClient;
|
|
|
|
async function createTestClient() {
|
|
const testClient = new TestClient(
|
|
"@alice:localhost", "xzcvb", "akjgkrgjs", sessionStoreBackend,
|
|
);
|
|
await testClient.client.initCrypto();
|
|
return testClient;
|
|
}
|
|
|
|
beforeEach(async function() {
|
|
// we create our own sessionStoreBackend so that we can use it for
|
|
// another TestClient.
|
|
sessionStoreBackend = new testUtils.MockStorageApi();
|
|
|
|
aliceTestClient = await createTestClient();
|
|
});
|
|
|
|
afterEach(function() {
|
|
return aliceTestClient.stop();
|
|
});
|
|
|
|
it("Alice shouldn't do a second /query for non-e2e-capable devices", function() {
|
|
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
|
|
return aliceTestClient.start().then(function() {
|
|
const syncResponse = getSyncResponse(['@bob:xyz']);
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, syncResponse);
|
|
|
|
return aliceTestClient.flushSync();
|
|
}).then(function() {
|
|
logger.log("Forcing alice to download our device keys");
|
|
|
|
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(200, {
|
|
device_keys: {
|
|
'@bob:xyz': {},
|
|
},
|
|
});
|
|
|
|
return Promise.all([
|
|
aliceTestClient.client.downloadKeys(['@bob:xyz']),
|
|
aliceTestClient.httpBackend.flush('/keys/query', 1),
|
|
]);
|
|
}).then(function() {
|
|
logger.log("Telling alice to send a megolm message");
|
|
|
|
aliceTestClient.httpBackend.when(
|
|
'PUT', '/send/',
|
|
).respond(200, {
|
|
event_id: '$event_id',
|
|
});
|
|
|
|
return Promise.all([
|
|
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
|
|
|
|
// the crypto stuff can take a while, so give the requests a whole second.
|
|
aliceTestClient.httpBackend.flushAllExpected({
|
|
timeout: 1000,
|
|
}),
|
|
]);
|
|
});
|
|
});
|
|
|
|
|
|
it("We should not get confused by out-of-order device query responses",
|
|
() => {
|
|
// https://github.com/vector-im/riot-web/issues/3126
|
|
aliceTestClient.expectKeyQuery({device_keys: {'@alice:localhost': {}}});
|
|
return aliceTestClient.start().then(() => {
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
|
200, getSyncResponse(['@bob:xyz', '@chris:abc']));
|
|
return aliceTestClient.flushSync();
|
|
}).then(() => {
|
|
// to make sure the initial device queries are flushed out, we
|
|
// attempt to send a message.
|
|
|
|
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
|
|
200, {
|
|
device_keys: {
|
|
'@bob:xyz': {},
|
|
'@chris:abc': {},
|
|
},
|
|
},
|
|
);
|
|
|
|
aliceTestClient.httpBackend.when('PUT', '/send/').respond(
|
|
200, {event_id: '$event1'});
|
|
|
|
return Promise.all([
|
|
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
|
|
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
|
|
() => aliceTestClient.httpBackend.flush('/send/', 1),
|
|
),
|
|
aliceTestClient.client._crypto._deviceList.saveIfDirty(),
|
|
]);
|
|
}).then(() => {
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
expect(data.syncToken).toEqual(1);
|
|
});
|
|
|
|
// invalidate bob's and chris's device lists in separate syncs
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, {
|
|
next_batch: '2',
|
|
device_lists: {
|
|
changed: ['@bob:xyz'],
|
|
},
|
|
});
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(200, {
|
|
next_batch: '3',
|
|
device_lists: {
|
|
changed: ['@chris:abc'],
|
|
},
|
|
});
|
|
// flush both syncs
|
|
return aliceTestClient.flushSync().then(() => {
|
|
return aliceTestClient.flushSync();
|
|
});
|
|
}).then(() => {
|
|
// check that we don't yet have a request for chris's devices.
|
|
aliceTestClient.httpBackend.when('POST', '/keys/query', {
|
|
device_keys: {
|
|
'@chris:abc': {},
|
|
},
|
|
token: '3',
|
|
}).respond(200, {
|
|
device_keys: {'@chris:abc': {}},
|
|
});
|
|
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
|
}).then((flushed) => {
|
|
expect(flushed).toEqual(0);
|
|
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
}).then(() => {
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
if (bobStat != 1 && bobStat != 2) {
|
|
throw new Error('Unexpected status for bob: wanted 1 or 2, got ' +
|
|
bobStat);
|
|
}
|
|
const chrisStat = data.trackingStatus['@chris:abc'];
|
|
if (chrisStat != 1 && chrisStat != 2) {
|
|
throw new Error(
|
|
'Unexpected status for chris: wanted 1 or 2, got ' + chrisStat,
|
|
);
|
|
}
|
|
});
|
|
|
|
// now add an expectation for a query for bob's devices, and let
|
|
// it complete.
|
|
aliceTestClient.httpBackend.when('POST', '/keys/query', {
|
|
device_keys: {
|
|
'@bob:xyz': {},
|
|
},
|
|
token: '2',
|
|
}).respond(200, {
|
|
device_keys: {'@bob:xyz': {}},
|
|
});
|
|
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
|
}).then((flushed) => {
|
|
expect(flushed).toEqual(1);
|
|
|
|
// wait for the client to stop processing the response
|
|
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
|
|
}).then(() => {
|
|
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
}).then(() => {
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
expect(bobStat).toEqual(3);
|
|
const chrisStat = data.trackingStatus['@chris:abc'];
|
|
if (chrisStat != 1 && chrisStat != 2) {
|
|
throw new Error(
|
|
'Unexpected status for chris: wanted 1 or 2, got ' + bobStat,
|
|
);
|
|
}
|
|
});
|
|
|
|
// now let the query for chris's devices complete.
|
|
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
|
}).then((flushed) => {
|
|
expect(flushed).toEqual(1);
|
|
|
|
// wait for the client to stop processing the response
|
|
return aliceTestClient.client.downloadKeys(['@chris:abc']);
|
|
}).then(() => {
|
|
return aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
}).then(() => {
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
const chrisStat = data.trackingStatus['@bob:xyz'];
|
|
|
|
expect(bobStat).toEqual(3);
|
|
expect(chrisStat).toEqual(3);
|
|
expect(data.syncToken).toEqual(3);
|
|
});
|
|
});
|
|
}).timeout(3000);
|
|
|
|
// https://github.com/vector-im/riot-web/issues/4983
|
|
describe("Alice should know she has stale device lists", () => {
|
|
beforeEach(async function() {
|
|
await aliceTestClient.start();
|
|
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
|
200, getSyncResponse(['@bob:xyz']));
|
|
await aliceTestClient.flushSync();
|
|
|
|
aliceTestClient.httpBackend.when('POST', '/keys/query').respond(
|
|
200, {
|
|
device_keys: {
|
|
'@bob:xyz': {},
|
|
},
|
|
},
|
|
);
|
|
await aliceTestClient.httpBackend.flush('/keys/query', 1);
|
|
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
|
|
expect(bobStat).toBeGreaterThan(
|
|
0, "Alice should be tracking bob's device list",
|
|
);
|
|
});
|
|
});
|
|
|
|
it("when Bob leaves", async function() {
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
|
200, {
|
|
next_batch: 2,
|
|
device_lists: {
|
|
left: ['@bob:xyz'],
|
|
},
|
|
rooms: {
|
|
join: {
|
|
[ROOM_ID]: {
|
|
timeline: {
|
|
events: [
|
|
testUtils.mkMembership({
|
|
mship: 'leave',
|
|
sender: '@bob:xyz',
|
|
}),
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
|
|
await aliceTestClient.flushSync();
|
|
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
|
|
expect(bobStat).toEqual(
|
|
0, "Alice should have marked bob's device list as untracked",
|
|
);
|
|
});
|
|
});
|
|
|
|
it("when Alice leaves", async function() {
|
|
aliceTestClient.httpBackend.when('GET', '/sync').respond(
|
|
200, {
|
|
next_batch: 2,
|
|
device_lists: {
|
|
left: ['@bob:xyz'],
|
|
},
|
|
rooms: {
|
|
leave: {
|
|
[ROOM_ID]: {
|
|
timeline: {
|
|
events: [
|
|
testUtils.mkMembership({
|
|
mship: 'leave',
|
|
sender: '@bob:xyz',
|
|
}),
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
await aliceTestClient.flushSync();
|
|
await aliceTestClient.client._crypto._deviceList.saveIfDirty();
|
|
|
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
|
|
expect(bobStat).toEqual(
|
|
0, "Alice should have marked bob's device list as untracked",
|
|
);
|
|
});
|
|
});
|
|
|
|
it("when Bob leaves whilst Alice is offline", async function() {
|
|
aliceTestClient.stop();
|
|
|
|
const anotherTestClient = await createTestClient();
|
|
|
|
try {
|
|
await anotherTestClient.start();
|
|
anotherTestClient.httpBackend.when('GET', '/sync').respond(
|
|
200, getSyncResponse([]));
|
|
await anotherTestClient.flushSync();
|
|
await anotherTestClient.client._crypto._deviceList.saveIfDirty();
|
|
|
|
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
|
|
|
expect(bobStat).toEqual(
|
|
0, "Alice should have marked bob's device list as untracked",
|
|
);
|
|
});
|
|
} finally {
|
|
anotherTestClient.stop();
|
|
}
|
|
});
|
|
});
|
|
});
|