You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-25 05:23:13 +03:00
397 lines
14 KiB
JavaScript
397 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/element-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/element-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();
|
|
}
|
|
});
|
|
});
|
|
});
|