Skip to content

Commit 5ba0c3d

Browse files
committed
Factor out a seprate OutgoingRequestProcessor
1 parent 64197bf commit 5ba0c3d

File tree

4 files changed

+266
-160
lines changed

4 files changed

+266
-160
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2023 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import MockHttpBackend from "matrix-mock-request";
18+
import { Mocked } from "jest-mock";
19+
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
20+
import {
21+
KeysBackupRequest,
22+
KeysClaimRequest,
23+
KeysQueryRequest,
24+
KeysUploadRequest,
25+
SignatureUploadRequest,
26+
} from "@matrix-org/matrix-sdk-crypto-js";
27+
28+
import { TypedEventEmitter } from "../../../src/models/typed-event-emitter";
29+
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixHttpApi } from "../../../src";
30+
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
31+
32+
describe("OutgoingRequestProcessor", () => {
33+
/** the OutgoingRequestProcessor implementation under test */
34+
let processor: OutgoingRequestProcessor;
35+
36+
/** A mock http backend which processor is connected to */
37+
let httpBackend: MockHttpBackend;
38+
39+
/** a mocked-up OlmMachine which processor is connected to */
40+
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
41+
42+
/** wait for a call to olmMachine.markRequestAsSent */
43+
function awaitCallToMarkAsSent(): Promise<void> {
44+
return new Promise((resolve, _reject) => {
45+
olmMachine.markRequestAsSent.mockImplementationOnce(async () => {
46+
console.log("received call to markAsSent");
47+
resolve(undefined);
48+
});
49+
});
50+
}
51+
52+
beforeEach(async () => {
53+
httpBackend = new MockHttpBackend();
54+
55+
const dummyEventEmitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
56+
const httpApi = new MatrixHttpApi(dummyEventEmitter, {
57+
baseUrl: "https://example.com",
58+
prefix: "/_matrix",
59+
fetchFn: httpBackend.fetchFn as typeof global.fetch,
60+
onlyData: true,
61+
});
62+
63+
olmMachine = {
64+
markRequestAsSent: jest.fn(),
65+
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
66+
67+
processor = new OutgoingRequestProcessor(olmMachine, httpApi);
68+
});
69+
70+
/* simple requests that map directly to the request body */
71+
const tests: Array<[any, "POST" | "PUT", string]> = [
72+
[KeysUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/upload"],
73+
[KeysQueryRequest, "POST", "https://example.com/_matrix/client/v3/keys/query"],
74+
[KeysClaimRequest, "POST", "https://example.com/_matrix/client/v3/keys/claim"],
75+
[SignatureUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/signatures/upload"],
76+
[KeysBackupRequest, "PUT", "https://example.com/_matrix/client/v3/room_keys/keys"],
77+
];
78+
79+
for (const [RequestClass, expectedMethod, expectedPath] of tests) {
80+
it(`should handle ${RequestClass.name}s`, async () => {
81+
const testBody = '{ "foo": "bar" }';
82+
const outgoingRequest = new RequestClass("1234", testBody);
83+
84+
const reqProm = processor.makeOutgoingRequest(outgoingRequest);
85+
86+
const testResponse = '{ "result": 1 }';
87+
httpBackend
88+
.when(expectedMethod, "/_matrix")
89+
.check((req) => {
90+
expect(req.path).toEqual(expectedPath);
91+
expect(req.rawData).toEqual(testBody);
92+
expect(req.headers["Accept"]).toEqual("application/json");
93+
expect(req.headers["Content-Type"]).toEqual("application/json");
94+
})
95+
.respond(200, testResponse, true);
96+
97+
const markSentCallPromise = awaitCallToMarkAsSent();
98+
await httpBackend.flushAllExpected();
99+
100+
await Promise.all([reqProm, markSentCallPromise]);
101+
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
102+
httpBackend.verifyNoOutstandingRequests();
103+
});
104+
}
105+
106+
it("does not explode with unknown requests", async () => {
107+
const outgoingRequest = { id: "5678", type: 987 };
108+
const markSentCallPromise = awaitCallToMarkAsSent();
109+
await Promise.all([processor.makeOutgoingRequest(outgoingRequest), markSentCallPromise]);
110+
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("5678", 987, "");
111+
});
112+
});

spec/unit/rust-crypto.spec.ts renamed to spec/unit/rust-crypto/rust-crypto.spec.ts

Lines changed: 46 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,16 @@ limitations under the License.
1717
import "fake-indexeddb/auto";
1818
import { IDBFactory } from "fake-indexeddb";
1919
import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js";
20-
import {
21-
KeysBackupRequest,
22-
KeysClaimRequest,
23-
KeysQueryRequest,
24-
KeysUploadRequest,
25-
OlmMachine,
26-
SignatureUploadRequest,
27-
} from "@matrix-org/matrix-sdk-crypto-js";
20+
import { KeysQueryRequest, OlmMachine } from "@matrix-org/matrix-sdk-crypto-js";
2821
import { Mocked } from "jest-mock";
29-
import MockHttpBackend from "matrix-mock-request";
3022

31-
import { RustCrypto } from "../../src/rust-crypto/rust-crypto";
32-
import { initRustCrypto } from "../../src/rust-crypto";
33-
import { HttpApiEvent, HttpApiEventHandlerMap, IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../src";
34-
import { TypedEventEmitter } from "../../src/models/typed-event-emitter";
35-
import { mkEvent } from "../test-utils/test-utils";
36-
import { CryptoBackend } from "../../src/common-crypto/CryptoBackend";
37-
import { IEventDecryptionResult } from "../../src/@types/crypto";
23+
import { RustCrypto } from "../../../src/rust-crypto/rust-crypto";
24+
import { initRustCrypto } from "../../../src/rust-crypto";
25+
import { IToDeviceEvent, MatrixClient, MatrixHttpApi } from "../../../src";
26+
import { mkEvent } from "../../test-utils/test-utils";
27+
import { CryptoBackend } from "../../../src/common-crypto/CryptoBackend";
28+
import { IEventDecryptionResult } from "../../../src/@types/crypto";
29+
import { OutgoingRequestProcessor } from "../../../src/rust-crypto/OutgoingRequestProcessor";
3830

3931
afterEach(() => {
4032
// reset fake-indexeddb after each test, to make sure we don't leak connections
@@ -106,8 +98,8 @@ describe("RustCrypto", () => {
10698
/** the RustCrypto implementation under test */
10799
let rustCrypto: RustCrypto;
108100

109-
/** A mock http backend which rustCrypto is connected to */
110-
let httpBackend: MockHttpBackend;
101+
/** A mock OutgoingRequestProcessor which rustCrypto is connected to */
102+
let outgoingRequestProcessor: Mocked<OutgoingRequestProcessor>;
111103

112104
/** a mocked-up OlmMachine which rustCrypto is connected to */
113105
let olmMachine: Mocked<RustSdkCryptoJs.OlmMachine>;
@@ -116,120 +108,81 @@ describe("RustCrypto", () => {
116108
* the front of the queue, until it is empty. */
117109
let outgoingRequestQueue: Array<Array<any>>;
118110

119-
/** wait for a call to olmMachine.markRequestAsSent */
120-
function awaitCallToMarkAsSent(): Promise<void> {
121-
return new Promise((resolve, _reject) => {
122-
olmMachine.markRequestAsSent.mockImplementationOnce(async () => {
123-
resolve(undefined);
111+
/** wait for a call to outgoingRequestProcessor.makeOutgoingRequest.
112+
*
113+
* The promise resolves to a callback: the makeOutgoingRequest call will not complete until the returned
114+
* callback is called.
115+
*/
116+
function awaitCallToMakeOutgoingRequest(): Promise<() => void> {
117+
return new Promise<() => void>((resolveCalledPromise, _reject) => {
118+
outgoingRequestProcessor.makeOutgoingRequest.mockImplementationOnce(async () => {
119+
const completePromise = new Promise<void>((resolveCompletePromise, _reject) => {
120+
resolveCalledPromise(resolveCompletePromise);
121+
});
122+
return completePromise;
124123
});
125124
});
126125
}
127126

128127
beforeEach(async () => {
129-
httpBackend = new MockHttpBackend();
130-
131128
await RustSdkCryptoJs.initAsync();
132129

133-
const dummyEventEmitter = new TypedEventEmitter<HttpApiEvent, HttpApiEventHandlerMap>();
134-
const httpApi = new MatrixHttpApi(dummyEventEmitter, {
135-
baseUrl: "https://example.com",
136-
prefix: "/_matrix",
137-
fetchFn: httpBackend.fetchFn as typeof global.fetch,
138-
onlyData: true,
139-
});
140-
141130
// for these tests we use a mock OlmMachine, with an implementation of outgoingRequests that
142131
// returns objects from outgoingRequestQueue
143132
outgoingRequestQueue = [];
144133
olmMachine = {
145134
outgoingRequests: jest.fn().mockImplementation(() => {
146135
return Promise.resolve(outgoingRequestQueue.shift() ?? []);
147136
}),
148-
markRequestAsSent: jest.fn(),
149137
close: jest.fn(),
150138
} as unknown as Mocked<RustSdkCryptoJs.OlmMachine>;
151139

152-
rustCrypto = new RustCrypto(olmMachine, httpApi, TEST_USER, TEST_DEVICE_ID);
153-
});
140+
outgoingRequestProcessor = {
141+
makeOutgoingRequest: jest.fn(),
142+
} as unknown as Mocked<OutgoingRequestProcessor>;
154143

155-
it("should poll for outgoing messages", () => {
156-
rustCrypto.onSyncCompleted({});
157-
expect(olmMachine.outgoingRequests).toHaveBeenCalled();
144+
rustCrypto = new RustCrypto(olmMachine, {} as MatrixHttpApi<any>, TEST_USER, TEST_DEVICE_ID);
145+
rustCrypto["outgoingRequestProcessor"] = outgoingRequestProcessor;
158146
});
159147

160-
/* simple requests that map directly to the request body */
161-
const tests: Array<[any, "POST" | "PUT", string]> = [
162-
[KeysUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/upload"],
163-
[KeysQueryRequest, "POST", "https://example.com/_matrix/client/v3/keys/query"],
164-
[KeysClaimRequest, "POST", "https://example.com/_matrix/client/v3/keys/claim"],
165-
[SignatureUploadRequest, "POST", "https://example.com/_matrix/client/v3/keys/signatures/upload"],
166-
[KeysBackupRequest, "PUT", "https://example.com/_matrix/client/v3/room_keys/keys"],
167-
];
168-
169-
for (const [RequestClass, expectedMethod, expectedPath] of tests) {
170-
it(`should handle ${RequestClass.name}s`, async () => {
171-
const testBody = '{ "foo": "bar" }';
172-
const outgoingRequest = new RequestClass("1234", testBody);
173-
outgoingRequestQueue.push([outgoingRequest]);
174-
175-
const testResponse = '{ "result": 1 }';
176-
httpBackend
177-
.when(expectedMethod, "/_matrix")
178-
.check((req) => {
179-
expect(req.path).toEqual(expectedPath);
180-
expect(req.rawData).toEqual(testBody);
181-
expect(req.headers["Accept"]).toEqual("application/json");
182-
expect(req.headers["Content-Type"]).toEqual("application/json");
183-
})
184-
.respond(200, testResponse, true);
185-
186-
rustCrypto.onSyncCompleted({});
187-
188-
expect(olmMachine.outgoingRequests).toHaveBeenCalledTimes(1);
189-
190-
const markSentCallPromise = awaitCallToMarkAsSent();
191-
await httpBackend.flushAllExpected();
192-
193-
await markSentCallPromise;
194-
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("1234", outgoingRequest.type, testResponse);
195-
httpBackend.verifyNoOutstandingRequests();
196-
});
197-
}
198-
199-
it("does not explode with unknown requests", async () => {
200-
const outgoingRequest = { id: "5678", type: 987 };
201-
outgoingRequestQueue.push([outgoingRequest]);
148+
it("should poll for outgoing messages and send them", async () => {
149+
const testReq = new KeysQueryRequest("1234", "{}");
150+
outgoingRequestQueue.push([testReq]);
202151

152+
const makeRequestPromise = awaitCallToMakeOutgoingRequest();
203153
rustCrypto.onSyncCompleted({});
204154

205-
await awaitCallToMarkAsSent();
206-
expect(olmMachine.markRequestAsSent).toHaveBeenCalledWith("5678", 987, "");
155+
await makeRequestPromise;
156+
expect(olmMachine.outgoingRequests).toHaveBeenCalled();
157+
expect(outgoingRequestProcessor.makeOutgoingRequest).toHaveBeenCalledWith(testReq);
207158
});
208159

209160
it("stops looping when stop() is called", async () => {
210-
const testResponse = '{ "result": 1 }';
211-
212161
for (let i = 0; i < 5; i++) {
213162
outgoingRequestQueue.push([new KeysQueryRequest("1234", "{}")]);
214-
httpBackend.when("POST", "/_matrix").respond(200, testResponse, true);
215163
}
216164

165+
let makeRequestPromise = awaitCallToMakeOutgoingRequest();
166+
217167
rustCrypto.onSyncCompleted({});
218168

219169
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeTruthy();
220170

221171
// go a couple of times round the loop
222-
await httpBackend.flush("/_matrix", 1);
223-
await awaitCallToMarkAsSent();
172+
let resolveMakeRequest = await makeRequestPromise;
173+
makeRequestPromise = awaitCallToMakeOutgoingRequest();
174+
resolveMakeRequest();
224175

225-
await httpBackend.flush("/_matrix", 1);
226-
await awaitCallToMarkAsSent();
176+
resolveMakeRequest = await makeRequestPromise;
177+
makeRequestPromise = awaitCallToMakeOutgoingRequest();
178+
resolveMakeRequest();
227179

228180
// a second sync while this is going on shouldn't make any difference
229181
rustCrypto.onSyncCompleted({});
230182

231-
await httpBackend.flush("/_matrix", 1);
232-
await awaitCallToMarkAsSent();
183+
resolveMakeRequest = await makeRequestPromise;
184+
outgoingRequestProcessor.makeOutgoingRequest.mockReset();
185+
resolveMakeRequest();
233186

234187
// now stop...
235188
rustCrypto.stop();
@@ -241,7 +194,7 @@ describe("RustCrypto", () => {
241194
setTimeout(resolve, 100);
242195
});
243196
expect(rustCrypto["outgoingRequestLoopRunning"]).toBeFalsy();
244-
httpBackend.verifyNoOutstandingRequests();
197+
expect(outgoingRequestProcessor.makeOutgoingRequest).not.toHaveBeenCalled();
245198
expect(olmMachine.outgoingRequests).not.toHaveBeenCalled();
246199

247200
// we sent three, so there should be 2 left

0 commit comments

Comments
 (0)