Skip to content

Commit 5c27e30

Browse files
authoredMay 30, 2024··
Fix the queueToDevice tests for the new fakeindexeddb (#4225)
dumbmatter/fakeIndexedDB#93 causes a bunch of tests to start failing because the fake timers need running in order for fake indexeddb to work. It also seems to cause failures to bleed between tests somehow if fake timers are enabled/disabled. This keeps all the fake timer tests in one suite and all the others in another, which appears to work. This should allow #4224 to be merged.
1 parent 8dfb6de commit 5c27e30

File tree

1 file changed

+235
-193
lines changed

1 file changed

+235
-193
lines changed
 

‎spec/unit/queueToDevice.spec.ts

+235-193
Original file line numberDiff line numberDiff line change
@@ -61,251 +61,293 @@ describe.each([[StoreType.Memory], [StoreType.IndexedDB]])("queueToDevice (%s st
6161
let httpBackend: MockHttpBackend;
6262
let client: MatrixClient;
6363

64-
beforeEach(async function () {
65-
jest.runOnlyPendingTimers();
66-
jest.useRealTimers();
67-
httpBackend = new MockHttpBackend();
68-
69-
let store: IStore;
70-
if (storeType === StoreType.IndexedDB) {
71-
const idbStore = new IndexedDBStore({ indexedDB: fakeIndexedDB });
72-
await idbStore.startup();
73-
store = idbStore;
74-
} else {
75-
store = new MemoryStore();
76-
}
77-
78-
client = new MatrixClient({
79-
baseUrl: "https://my.home.server",
80-
accessToken: "my.access.token",
81-
fetchFn: httpBackend.fetchFn as typeof global.fetch,
82-
store,
64+
/**
65+
* We need to split the tests into regular ones (these) and ones that use fake timers,
66+
* because the fake indexeddb uses timers too and appears make tests cause other tests
67+
* to fail if we keep enabling/disabling fake timers within the same test suite.
68+
*/
69+
describe("non-timed tests", () => {
70+
beforeEach(async function () {
71+
httpBackend = new MockHttpBackend();
72+
73+
let store: IStore;
74+
if (storeType === StoreType.IndexedDB) {
75+
const idbStore = new IndexedDBStore({ indexedDB: fakeIndexedDB });
76+
await idbStore.startup();
77+
store = idbStore;
78+
} else {
79+
store = new MemoryStore();
80+
}
81+
82+
client = new MatrixClient({
83+
baseUrl: "https://my.home.server",
84+
accessToken: "my.access.token",
85+
fetchFn: httpBackend.fetchFn as typeof global.fetch,
86+
store,
87+
});
8388
});
84-
});
85-
86-
afterEach(function () {
87-
jest.useRealTimers();
88-
client.stopClient();
89-
});
9089

91-
it("sends a to-device message", async function () {
92-
httpBackend
93-
.when("PUT", "/sendToDevice/org.example.foo/")
94-
.check((request) => {
95-
expect(request.data).toEqual(EXPECTED_BODY);
96-
})
97-
.respond(200, {});
98-
99-
await client.queueToDevice({
100-
eventType: "org.example.foo",
101-
batch: [FAKE_MSG],
90+
afterEach(function () {
91+
client.stopClient();
10292
});
10393

104-
await httpBackend.flushAllExpected();
105-
// let the code handle the response to the request so we don't get
106-
// log output after the test has finished (apparently stopping the
107-
// client in aftereach is not sufficient.)
108-
await flushPromises();
109-
});
110-
111-
it("retries on error", async function () {
112-
jest.useFakeTimers();
113-
114-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
115-
116-
httpBackend
117-
.when("PUT", "/sendToDevice/org.example.foo/")
118-
.check((request) => {
119-
expect(request.data).toEqual(EXPECTED_BODY);
120-
})
121-
.respond(200, {});
94+
it("sends a to-device message", async function () {
95+
httpBackend
96+
.when("PUT", "/sendToDevice/org.example.foo/")
97+
.check((request) => {
98+
expect(request.data).toEqual(EXPECTED_BODY);
99+
})
100+
.respond(200, {});
101+
102+
await client.queueToDevice({
103+
eventType: "org.example.foo",
104+
batch: [FAKE_MSG],
105+
});
122106

123-
await client.queueToDevice({
124-
eventType: "org.example.foo",
125-
batch: [FAKE_MSG],
107+
await httpBackend.flushAllExpected();
108+
// let the code handle the response to the request so we don't get
109+
// log output after the test has finished (apparently stopping the
110+
// client in aftereach is not sufficient.)
111+
await flushPromises();
126112
});
127-
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
128-
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
129113

130-
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
114+
it("retries on retryImmediately()", async function () {
115+
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
116+
versions: ["v1.1"],
117+
});
131118

132-
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
119+
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
133120

134-
// flush, as per comment in first test
135-
await flushPromises();
136-
});
121+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
122+
123+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
137124

138-
it("stops retrying on 4xx errors", async function () {
139-
jest.useFakeTimers();
125+
await client.queueToDevice({
126+
eventType: "org.example.foo",
127+
batch: [FAKE_MSG],
128+
});
129+
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
130+
await flushPromises();
140131

141-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(400);
132+
client.retryImmediately();
142133

143-
await client.queueToDevice({
144-
eventType: "org.example.foo",
145-
batch: [FAKE_MSG],
134+
// longer timeout here to try & avoid flakiness
135+
expect(await httpBackend.flush(undefined, 1, 3000)).toEqual(1);
146136
});
147-
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
148-
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
149137

150-
// Asserting that another request is never made is obviously
151-
// a bit tricky - we just flush the queue what should hopefully
152-
// be plenty of times and assert that nothing comes through.
153-
let tries = 0;
154-
await flushAndRunTimersUntil(() => ++tries === 10);
138+
it("retries on when client is started", async function () {
139+
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
140+
versions: ["v1.1"],
141+
});
155142

156-
expect(httpBackend.requests.length).toEqual(0);
157-
});
143+
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
158144

159-
it("honours ratelimiting", async function () {
160-
jest.useFakeTimers();
145+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
161146

162-
// pick something obscure enough it's unlikley to clash with a
163-
// retry delay the algorithm uses anyway
164-
const retryDelay = 279 * 1000;
147+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
165148

166-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(429, {
167-
errcode: "M_LIMIT_EXCEEDED",
168-
retry_after_ms: retryDelay,
169-
});
149+
await client.queueToDevice({
150+
eventType: "org.example.foo",
151+
batch: [FAKE_MSG],
152+
});
153+
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
154+
await flushPromises();
170155

171-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
156+
client.stopClient();
157+
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
172158

173-
await client.queueToDevice({
174-
eventType: "org.example.foo",
175-
batch: [FAKE_MSG],
159+
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
176160
});
177-
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
178-
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
179-
await flushPromises();
180161

181-
logger.info("Advancing clock to just before expected retry time...");
162+
it("retries when a message is retried", async function () {
163+
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
164+
versions: ["v1.1"],
165+
});
182166

183-
jest.advanceTimersByTime(retryDelay - 1000);
184-
await flushPromises();
167+
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
185168

186-
expect(httpBackend.requests.length).toEqual(0);
169+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
187170

188-
logger.info("Advancing clock past expected retry time...");
171+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
189172

190-
jest.advanceTimersByTime(2000);
191-
await flushPromises();
173+
await client.queueToDevice({
174+
eventType: "org.example.foo",
175+
batch: [FAKE_MSG],
176+
});
192177

193-
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
194-
});
178+
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
179+
await flushPromises();
180+
181+
const dummyEvent = new MatrixEvent({
182+
event_id: "!fake:example.org",
183+
});
184+
const mockRoom = {
185+
updatePendingEvent: jest.fn(),
186+
hasEncryptionStateEvent: jest.fn().mockReturnValue(false),
187+
} as unknown as Room;
188+
client.resendEvent(dummyEvent, mockRoom);
195189

196-
it("retries on retryImmediately()", async function () {
197-
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
198-
versions: ["v1.1"],
190+
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
199191
});
200192

201-
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
193+
it("splits many messages into multiple HTTP requests", async function () {
194+
const batch: ToDeviceBatch = {
195+
eventType: "org.example.foo",
196+
batch: [],
197+
};
198+
199+
for (let i = 0; i <= 20; ++i) {
200+
batch.batch.push({
201+
userId: `@user${i}:example.org`,
202+
deviceId: FAKE_DEVICE_ID,
203+
payload: FAKE_PAYLOAD,
204+
});
205+
}
206+
207+
const expectedCounts = [20, 1];
208+
httpBackend
209+
.when("PUT", "/sendToDevice/org.example.foo/")
210+
.check((request) => {
211+
expect(
212+
removeElement(expectedCounts, (c) => c === Object.keys(request.data.messages).length),
213+
).toBeTruthy();
214+
})
215+
.respond(200, {});
216+
httpBackend
217+
.when("PUT", "/sendToDevice/org.example.foo/")
218+
.check((request) => {
219+
expect(Object.keys(request.data.messages).length).toEqual(1);
220+
})
221+
.respond(200, {});
222+
223+
await client.queueToDevice(batch);
224+
await httpBackend.flushAllExpected();
225+
226+
// flush, as per comment in first test
227+
await flushPromises();
228+
});
229+
});
202230

203-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
231+
describe("async tests", () => {
232+
beforeAll(() => {
233+
jest.useFakeTimers();
234+
});
204235

205-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
236+
afterAll(() => {
237+
jest.useRealTimers();
238+
});
206239

207-
await client.queueToDevice({
208-
eventType: "org.example.foo",
209-
batch: [FAKE_MSG],
240+
beforeEach(async function () {
241+
httpBackend = new MockHttpBackend();
242+
243+
let store: IStore;
244+
if (storeType === StoreType.IndexedDB) {
245+
const idbStore = new IndexedDBStore({ indexedDB: fakeIndexedDB });
246+
let storeStarted = false;
247+
idbStore.startup().then(() => {
248+
storeStarted = true;
249+
});
250+
await flushAndRunTimersUntil(() => storeStarted);
251+
store = idbStore;
252+
} else {
253+
store = new MemoryStore();
254+
}
255+
256+
client = new MatrixClient({
257+
baseUrl: "https://my.home.server",
258+
accessToken: "my.access.token",
259+
fetchFn: httpBackend.fetchFn as typeof global.fetch,
260+
store,
261+
});
210262
});
211-
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
212-
await flushPromises();
213263

214-
client.retryImmediately();
264+
afterEach(function () {
265+
client.stopClient();
266+
});
215267

216-
// longer timeout here to try & avoid flakiness
217-
expect(await httpBackend.flush(undefined, 1, 3000)).toEqual(1);
218-
});
268+
it("retries on error", async function () {
269+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
219270

220-
it("retries on when client is started", async function () {
221-
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
222-
versions: ["v1.1"],
223-
});
271+
httpBackend
272+
.when("PUT", "/sendToDevice/org.example.foo/")
273+
.check((request) => {
274+
expect(request.data).toEqual(EXPECTED_BODY);
275+
})
276+
.respond(200, {});
224277

225-
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
278+
client
279+
.queueToDevice({
280+
eventType: "org.example.foo",
281+
batch: [FAKE_MSG],
282+
})
283+
.then();
284+
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
285+
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
226286

227-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
287+
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
228288

229-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
289+
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
230290

231-
await client.queueToDevice({
232-
eventType: "org.example.foo",
233-
batch: [FAKE_MSG],
291+
// flush, as per comment in first test
292+
await flushPromises();
234293
});
235-
expect(await httpBackend.flush(undefined, 1, 1)).toEqual(1);
236-
await flushPromises();
237294

238-
client.stopClient();
239-
await Promise.all([client.startClient(), httpBackend.flush("/_matrix/client/versions", 1, 20)]);
295+
it("stops retrying on 4xx errors", async function () {
296+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(400);
297+
298+
client
299+
.queueToDevice({
300+
eventType: "org.example.foo",
301+
batch: [FAKE_MSG],
302+
})
303+
.then();
304+
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
305+
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
306+
307+
// Asserting that another request is never made is obviously
308+
// a bit tricky - we just flush the queue what should hopefully
309+
// be plenty of times and assert that nothing comes through.
310+
let tries = 0;
311+
await flushAndRunTimersUntil(() => ++tries === 10);
312+
313+
expect(httpBackend.requests.length).toEqual(0);
314+
});
240315

241-
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
242-
});
316+
it("honours ratelimiting", async function () {
317+
// pick something obscure enough it's unlikley to clash with a
318+
// retry delay the algorithm uses anyway
319+
const retryDelay = 279 * 1000;
243320

244-
it("retries when a message is retried", async function () {
245-
httpBackend.when("GET", "/_matrix/client/versions").respond(200, {
246-
versions: ["v1.1"],
247-
});
321+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(429, {
322+
errcode: "M_LIMIT_EXCEEDED",
323+
retry_after_ms: retryDelay,
324+
});
248325

249-
await Promise.all([client.startClient(), httpBackend.flush(undefined, 1, 20)]);
326+
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
250327

251-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(500);
328+
client
329+
.queueToDevice({
330+
eventType: "org.example.foo",
331+
batch: [FAKE_MSG],
332+
})
333+
.then();
334+
await flushAndRunTimersUntil(() => httpBackend.requests.length > 0);
335+
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
336+
await flushPromises();
252337

253-
httpBackend.when("PUT", "/sendToDevice/org.example.foo/").respond(200, {});
338+
logger.info("Advancing clock to just before expected retry time...");
254339

255-
await client.queueToDevice({
256-
eventType: "org.example.foo",
257-
batch: [FAKE_MSG],
258-
});
340+
jest.advanceTimersByTime(retryDelay - 1000);
341+
await flushPromises();
259342

260-
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
261-
await flushPromises();
343+
expect(httpBackend.requests.length).toEqual(0);
262344

263-
const dummyEvent = new MatrixEvent({
264-
event_id: "!fake:example.org",
265-
});
266-
const mockRoom = {
267-
updatePendingEvent: jest.fn(),
268-
hasEncryptionStateEvent: jest.fn().mockReturnValue(false),
269-
} as unknown as Room;
270-
client.resendEvent(dummyEvent, mockRoom);
345+
logger.info("Advancing clock past expected retry time...");
271346

272-
expect(await httpBackend.flush(undefined, 1, 20)).toEqual(1);
273-
});
347+
jest.advanceTimersByTime(2000);
348+
await flushPromises();
274349

275-
it("splits many messages into multiple HTTP requests", async function () {
276-
const batch: ToDeviceBatch = {
277-
eventType: "org.example.foo",
278-
batch: [],
279-
};
280-
281-
for (let i = 0; i <= 20; ++i) {
282-
batch.batch.push({
283-
userId: `@user${i}:example.org`,
284-
deviceId: FAKE_DEVICE_ID,
285-
payload: FAKE_PAYLOAD,
286-
});
287-
}
288-
289-
const expectedCounts = [20, 1];
290-
httpBackend
291-
.when("PUT", "/sendToDevice/org.example.foo/")
292-
.check((request) => {
293-
expect(
294-
removeElement(expectedCounts, (c) => c === Object.keys(request.data.messages).length),
295-
).toBeTruthy();
296-
})
297-
.respond(200, {});
298-
httpBackend
299-
.when("PUT", "/sendToDevice/org.example.foo/")
300-
.check((request) => {
301-
expect(Object.keys(request.data.messages).length).toEqual(1);
302-
})
303-
.respond(200, {});
304-
305-
await client.queueToDevice(batch);
306-
await httpBackend.flushAllExpected();
307-
308-
// flush, as per comment in first test
309-
await flushPromises();
350+
expect(httpBackend.flushSync(undefined, 1)).toEqual(1);
351+
});
310352
});
311353
});

0 commit comments

Comments
 (0)
Please sign in to comment.