Skip to content

Commit 819fc75

Browse files
authored
Fetch capabilities in the background (#4246)
* Fetch capabilities in the background & keep them up to date * Add missed await * Replace some more runAllTimers and round down the wait time for sanity * Remove double comment * Typo * Add a method back that will fetch capabilities if they're not already there * Add tests * Catch exception here too * Add test for room version code
1 parent c70aa33 commit 819fc75

File tree

11 files changed

+340
-99
lines changed

11 files changed

+340
-99
lines changed

spec/integ/crypto/megolm-backup.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
796796

797797
const result = await aliceCrypto.checkKeyBackupAndEnable();
798798
expect(result).toBeTruthy();
799-
jest.runAllTimers();
799+
jest.advanceTimersByTime(10 * 60 * 1000);
800800
await failurePromise;
801801

802802
// Fix the endpoint to do successful uploads
@@ -829,7 +829,7 @@ describe.each(Object.entries(CRYPTO_BACKENDS))("megolm-keys backup (%s)", (backe
829829
});
830830

831831
// run the timers, which will make the backup loop redo the request
832-
await jest.runAllTimersAsync();
832+
await jest.advanceTimersByTimeAsync(10 * 60 * 1000);
833833
await successPromise;
834834
await allKeysUploadedPromise;
835835
});

spec/integ/matrix-client-methods.spec.ts

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,18 +1293,109 @@ describe("MatrixClient", function () {
12931293
});
12941294

12951295
describe("getCapabilities", () => {
1296-
it("should cache by default", async () => {
1296+
it("should return cached capabilities if present", async () => {
1297+
const capsObject = {
1298+
"m.change_password": false,
1299+
};
1300+
1301+
httpBackend!.when("GET", "/versions").respond(200, {});
1302+
httpBackend!.when("GET", "/pushrules").respond(200, {});
1303+
httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
12971304
httpBackend.when("GET", "/capabilities").respond(200, {
1298-
capabilities: {
1299-
"m.change_password": false,
1300-
},
1305+
capabilities: capsObject,
1306+
});
1307+
1308+
client.startClient();
1309+
await httpBackend!.flushAllExpected();
1310+
1311+
expect(await client.getCapabilities()).toEqual(capsObject);
1312+
});
1313+
1314+
it("should fetch capabilities if cache not present", async () => {
1315+
const capsObject = {
1316+
"m.change_password": false,
1317+
};
1318+
1319+
httpBackend.when("GET", "/capabilities").respond(200, {
1320+
capabilities: capsObject,
1321+
});
1322+
1323+
const capsPromise = client.getCapabilities();
1324+
await httpBackend!.flushAllExpected();
1325+
1326+
expect(await capsPromise).toEqual(capsObject);
1327+
});
1328+
});
1329+
1330+
describe("getCachedCapabilities", () => {
1331+
it("should return cached capabilities or undefined", async () => {
1332+
const capsObject = {
1333+
"m.change_password": false,
1334+
};
1335+
1336+
httpBackend!.when("GET", "/versions").respond(200, {});
1337+
httpBackend!.when("GET", "/pushrules").respond(200, {});
1338+
httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
1339+
httpBackend.when("GET", "/capabilities").respond(200, {
1340+
capabilities: capsObject,
1341+
});
1342+
1343+
expect(client.getCachedCapabilities()).toBeUndefined();
1344+
1345+
client.startClient();
1346+
1347+
await httpBackend!.flushAllExpected();
1348+
1349+
expect(client.getCachedCapabilities()).toEqual(capsObject);
1350+
});
1351+
});
1352+
1353+
describe("fetchCapabilities", () => {
1354+
const capsObject = {
1355+
"m.change_password": false,
1356+
};
1357+
1358+
beforeEach(() => {
1359+
httpBackend.when("GET", "/capabilities").respond(200, {
1360+
capabilities: capsObject,
13011361
});
1302-
const prom = httpBackend.flushAllExpected();
1303-
const capabilities1 = await client.getCapabilities();
1304-
const capabilities2 = await client.getCapabilities();
1362+
});
1363+
1364+
afterEach(() => {
1365+
jest.useRealTimers();
1366+
});
1367+
1368+
it("should always fetch capabilities and then cache", async () => {
1369+
const prom = client.fetchCapabilities();
1370+
await httpBackend.flushAllExpected();
1371+
const caps = await prom;
1372+
1373+
expect(caps).toEqual(capsObject);
1374+
});
1375+
1376+
it("should write-through the cache", async () => {
1377+
httpBackend!.when("GET", "/versions").respond(200, {});
1378+
httpBackend!.when("GET", "/pushrules").respond(200, {});
1379+
httpBackend!.when("POST", "/filter").respond(200, { filter_id: "a filter id" });
1380+
1381+
client.startClient();
1382+
await httpBackend!.flushAllExpected();
1383+
1384+
expect(client.getCachedCapabilities()).toEqual(capsObject);
1385+
1386+
const newCapsObject = {
1387+
"m.change_password": true,
1388+
};
1389+
1390+
httpBackend.when("GET", "/capabilities").respond(200, {
1391+
capabilities: newCapsObject,
1392+
});
1393+
1394+
const prom = client.fetchCapabilities();
1395+
await httpBackend.flushAllExpected();
13051396
await prom;
13061397

1307-
expect(capabilities1).toStrictEqual(capabilities2);
1398+
expect(client.getCachedCapabilities()).toEqual(newCapsObject);
13081399
});
13091400
});
13101401

spec/integ/matrix-client-syncing-errors.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,21 @@ describe("MatrixClient syncing errors", () => {
105105

106106
await client!.startClient();
107107
expect(await syncEvents[0].promise).toBe(SyncState.Error);
108-
jest.runAllTimers(); // this will skip forward to trigger the keepAlive/sync
108+
jest.advanceTimersByTime(60 * 1000); // this will skip forward to trigger the keepAlive/sync
109109
expect(await syncEvents[1].promise).toBe(SyncState.Error);
110-
jest.runAllTimers(); // this will skip forward to trigger the keepAlive/sync
110+
jest.advanceTimersByTime(60 * 1000); // this will skip forward to trigger the keepAlive/sync
111111
expect(await syncEvents[2].promise).toBe(SyncState.Prepared);
112-
jest.runAllTimers(); // this will skip forward to trigger the keepAlive/sync
112+
jest.advanceTimersByTime(60 * 1000); // this will skip forward to trigger the keepAlive/sync
113113
expect(await syncEvents[3].promise).toBe(SyncState.Syncing);
114-
jest.runAllTimers(); // this will skip forward to trigger the keepAlive/sync
114+
jest.advanceTimersByTime(60 * 1000); // this will skip forward to trigger the keepAlive/sync
115115
expect(await syncEvents[4].promise).toBe(SyncState.Syncing);
116116
});
117117

118118
it("should stop sync keep alive when client is stopped.", async () => {
119119
jest.useFakeTimers();
120120
fetchMock.config.overwriteRoutes = false;
121121
fetchMock
122+
.get("end:capabilities", {})
122123
.getOnce("end:versions", {}) // first version check without credentials needs to succeed
123124
.get("end:versions", unknownTokenErrorData) // further version checks fails with 401
124125
.get("end:pushrules/", 401) // fails with 401 without an error. This does happen in practice e.g. with Synapse

spec/test-utils/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,6 @@ export const mockClientMethodsEvents = () => ({
8888
export const mockClientMethodsServer = (): Partial<Record<MethodLikeKeys<MatrixClient>, unknown>> => ({
8989
getIdentityServerUrl: jest.fn(),
9090
getHomeserverUrl: jest.fn(),
91-
getCapabilities: jest.fn().mockReturnValue({}),
91+
getCachedCapabilities: jest.fn().mockReturnValue({}),
9292
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
9393
});

spec/unit/rendezvous/rendezvous.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function makeMockClient(opts: {
116116
},
117117
};
118118
},
119-
getCapabilities() {
119+
getCachedCapabilities() {
120120
return opts.msc3882r0Only
121121
? {}
122122
: {

spec/unit/room.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,4 +4034,47 @@ describe("Room", function () {
40344034
expect(room.getLastThread()).toBe(thread2);
40354035
});
40364036
});
4037+
4038+
describe("getRecommendedVersion", () => {
4039+
it("returns the server's recommended version from capabilities", async () => {
4040+
const client = new TestClient(userA).client;
4041+
client.getCapabilities = jest.fn().mockReturnValue({
4042+
["m.room_versions"]: {
4043+
default: "1",
4044+
available: ["1", "2"],
4045+
},
4046+
});
4047+
const room = new Room(roomId, client, userA);
4048+
expect(await room.getRecommendedVersion()).toEqual({
4049+
version: "1",
4050+
needsUpgrade: false,
4051+
urgent: false,
4052+
});
4053+
});
4054+
4055+
it("force-refreshes versions to make sure an upgrade is necessary", async () => {
4056+
const client = new TestClient(userA).client;
4057+
client.getCapabilities = jest.fn().mockReturnValue({
4058+
["m.room_versions"]: {
4059+
default: "5",
4060+
available: ["5"],
4061+
},
4062+
});
4063+
4064+
client.fetchCapabilities = jest.fn().mockResolvedValue({
4065+
["m.room_versions"]: {
4066+
default: "1",
4067+
available: ["1"],
4068+
},
4069+
});
4070+
4071+
const room = new Room(roomId, client, userA);
4072+
expect(await room.getRecommendedVersion()).toEqual({
4073+
version: "1",
4074+
needsUpgrade: false,
4075+
urgent: false,
4076+
});
4077+
expect(client.fetchCapabilities).toHaveBeenCalled();
4078+
});
4079+
});
40374080
});

0 commit comments

Comments
 (0)