Skip to content

Commit 77487ce

Browse files
committed
tests: Implement E2EOTKClaimResponder class
A new test helper, which intercepts `/keys/claim`, allowing clients under test to claim OTKs uploaded by other devices.
1 parent bb18565 commit 77487ce

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

spec/test-utils/E2EKeyReceiver.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,18 @@ export class E2EKeyReceiver implements IE2EKeyReceiver {
234234
await this.oneTimeKeysPromise;
235235
return this.oneTimeKeys;
236236
}
237+
238+
/**
239+
* If no one-time keys have yet been uploaded, return `null`.
240+
* Otherwise, pop a key from the uploaded list.
241+
*/
242+
public getOneTimeKey(): [string, IOneTimeKey] | null {
243+
const keys = Object.entries(this.oneTimeKeys);
244+
if (keys.length == 0) {
245+
return null;
246+
}
247+
const [otkId, otk] = keys[0];
248+
delete this.oneTimeKeys[otkId];
249+
return [otkId, otk];
250+
}
237251
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2025 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 fetchMock from "fetch-mock-jest";
18+
19+
import { MapWithDefault } from "../../src/utils";
20+
import { type E2EKeyReceiver } from "./E2EKeyReceiver";
21+
import { type IClaimKeysRequest } from "../../src";
22+
23+
/**
24+
* An object which intercepts `/keys/claim` fetches via fetch-mock.
25+
*/
26+
export class E2EOTKClaimResponder {
27+
private e2eKeyReceiversByUserByDevice = new MapWithDefault<string, Map<string, E2EKeyReceiver>>(() => new Map());
28+
29+
/**
30+
* Construct a new E2EOTKClaimResponder.
31+
*
32+
* It will immediately register an intercept of `/keys/claim` requests for the given homeserverUrl.
33+
* Only /claim requests made to this server will be intercepted: this allows a single test to use more than one
34+
* client and have the keys collected separately.
35+
*
36+
* @param homeserverUrl - the Homeserver Url of the client under test.
37+
*/
38+
public constructor(homeserverUrl: string) {
39+
const listener = (url: string, options: RequestInit) => this.onKeyClaimRequest(options);
40+
fetchMock.post(new URL("/_matrix/client/v3/keys/claim", homeserverUrl).toString(), listener);
41+
}
42+
43+
private onKeyClaimRequest(options: RequestInit) {
44+
const content = JSON.parse(options.body as string) as IClaimKeysRequest;
45+
const response = {
46+
one_time_keys: {} as { [userId: string]: any },
47+
};
48+
for (const [userId, devices] of Object.entries(content["one_time_keys"])) {
49+
for (const deviceId of Object.keys(devices)) {
50+
const e2eKeyReceiver = this.e2eKeyReceiversByUserByDevice.get(userId)?.get(deviceId);
51+
const otk = e2eKeyReceiver?.getOneTimeKey();
52+
if (otk) {
53+
const [keyId, key] = otk;
54+
response.one_time_keys[userId] ??= {};
55+
response.one_time_keys[userId][deviceId] = {
56+
[keyId]: key,
57+
};
58+
}
59+
}
60+
}
61+
return response;
62+
}
63+
64+
/**
65+
* Add an E2EKeyReceiver to poll for uploaded keys
66+
*
67+
* When the `/keys/claim` request is received, a OTK will be removed from the `E2EKeyReceiver` and
68+
* added to the response.
69+
*/
70+
public addKeyReceiver(userId: string, deviceId: string, e2eKeyReceiver: E2EKeyReceiver) {
71+
this.e2eKeyReceiversByUserByDevice.getOrCreate(userId).set(deviceId, e2eKeyReceiver);
72+
}
73+
}

0 commit comments

Comments
 (0)