Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace deprecated TestClient with fetchMock #3550

Merged
merged 12 commits into from
Jul 25, 2023
17 changes: 0 additions & 17 deletions spec/TestClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import { syncPromise } from "./test-utils/test-utils";
import { createClient, IStartClientOpts } from "../src/matrix";
import { ICreateClientOpts, IDownloadKeyResult, MatrixClient, PendingEventOrdering } from "../src/client";
import { MockStorageApi } from "./MockStorageApi";
import { encodeUri } from "../src/utils";
import { IKeyBackupSession } from "../src/crypto/keybackup";
import { IKeysUploadResponse, IUploadKeysRequest } from "../src/client";
import { ISyncResponder } from "./test-utils/SyncResponder";

Expand Down Expand Up @@ -214,21 +212,6 @@ export class TestClient implements IE2EKeyReceiver, ISyncResponder {
});
}

/**
* Set up expectations that the client will query key backups for a particular session
*/
public expectKeyBackupQuery(roomId: string, sessionId: string, status: number, response: IKeyBackupSession) {
this.httpBackend
.when(
"GET",
encodeUri("/room_keys/keys/$roomId/$sessionId", {
$roomId: roomId,
$sessionId: sessionId,
}),
)
.respond(status, response);
}

/**
* get the uploaded curve25519 device key
*
Expand Down
160 changes: 87 additions & 73 deletions spec/integ/crypto/megolm-backup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { Account } from "@matrix-org/olm";
import fetchMock from "fetch-mock-jest";

import { logger } from "../../../src/logger";
import { decodeRecoveryKey } from "../../../src/crypto/recoverykey";
import { IKeyBackupInfo, IKeyBackupSession } from "../../../src/crypto/keybackup";
import { TestClient } from "../../TestClient";
import { IEvent } from "../../../src";
import { MatrixEvent, MatrixEventEvent } from "../../../src/models/event";
import { createClient, ICreateClientOpts, IEvent, MatrixClient } from "../../../src";
import { MatrixEventEvent } from "../../../src/models/event";
import { SyncResponder } from "../../test-utils/SyncResponder";
import { E2EKeyReceiver } from "../../test-utils/E2EKeyReceiver";
import { E2EKeyResponder } from "../../test-utils/E2EKeyResponder";
import { mockInitialApiRequests } from "../../test-utils/mockEndpoints";
import { syncPromise } from "../../test-utils/test-utils";

const ROOM_ID = "!ROOM:ID";

/** The homeserver url that we give to the test client, and where we intercept /sync, /keys, etc requests. */
const TEST_HOMESERVER_URL = "https://alice-server.com";

const SESSION_ID = "o+21hSjP+mgEmcfdslPsQdvzWnkdt0Wyo00Kp++R8Kc";

const ENCRYPTED_EVENT: Partial<IEvent> = {
Expand Down Expand Up @@ -70,57 +77,65 @@ const CURVE25519_BACKUP_INFO: IKeyBackupInfo = {
version: "1",
auth_data: {
public_key: "hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo",
// Will be updated with correct value on the fly
signatures: {},
},
};

const RECOVERY_KEY = "EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d";

/**
* start an Olm session with a given recipient
*/
function createOlmSession(olmAccount: Olm.Account, recipientTestClient: TestClient): Promise<Olm.Session> {
return recipientTestClient.awaitOneTimeKeyUpload().then((keys) => {
const otkId = Object.keys(keys)[0];
const otk = keys[otkId];

const session = new global.Olm.Session();
session.create_outbound(olmAccount, recipientTestClient.getDeviceKey(), otk.key);
return session;
});
}
const TEST_USER_ID = "@alice:localhost";
const TEST_DEVICE_ID = "xzcvb";

describe("megolm key backups", function () {
if (!global.Olm) {
logger.warn("not running megolm tests: Olm not present");
return;
}
const Olm = global.Olm;
let testOlmAccount: Olm.Account;
let aliceTestClient: TestClient;

const setupTestClient = (): [Account, TestClient] => {
const aliceTestClient = new TestClient("@alice:localhost", "xzcvb", "akjgkrgjs");
const testOlmAccount = new Olm.Account();
testOlmAccount!.create();
let aliceClient: MatrixClient;
/** an object which intercepts `/sync` requests on the test homeserver */
let syncResponder: SyncResponder;

/** an object which intercepts `/keys/upload` requests on the test homeserver */
let e2eKeyReceiver: E2EKeyReceiver;
/** an object which intercepts `/keys/query` requests on the test homeserver */
let e2eKeyResponder: E2EKeyResponder;

jest.useFakeTimers();

beforeEach(async () => {
// anything that we don't have a specific matcher for silently returns a 404
fetchMock.catch(404);
fetchMock.config.warnOnFallback = false;

mockInitialApiRequests(TEST_HOMESERVER_URL);
syncResponder = new SyncResponder(TEST_HOMESERVER_URL);
e2eKeyReceiver = new E2EKeyReceiver(TEST_HOMESERVER_URL);
e2eKeyResponder = new E2EKeyResponder(TEST_HOMESERVER_URL);
e2eKeyResponder.addKeyReceiver(TEST_USER_ID, e2eKeyReceiver);
});

return [testOlmAccount, aliceTestClient];
};
afterEach(async () => {
if (aliceClient !== undefined) {
await aliceClient.stopClient();
}

beforeAll(function () {
return Olm.init();
});
// Allow in-flight things to complete before we tear down the test
await jest.runAllTimersAsync();

beforeEach(async function () {
[testOlmAccount, aliceTestClient] = setupTestClient();
await aliceTestClient!.client.initCrypto();
aliceTestClient!.client.crypto!.backupManager.backupInfo = CURVE25519_BACKUP_INFO;
fetchMock.mockReset();
});

afterEach(function () {
return aliceTestClient!.stop();
});
async function initTestClient(opts: Partial<ICreateClientOpts> = {}): Promise<MatrixClient> {
const client = createClient({
baseUrl: TEST_HOMESERVER_URL,
userId: TEST_USER_ID,
accessToken: "akjgkrgjs",
deviceId: TEST_DEVICE_ID,
...opts,
});
await client.initCrypto();

return client;
}

it("Alice checks key backups when receiving a message she can't decrypt", function () {
it("Alice checks key backups when receiving a message she can't decrypt", async function () {
const syncResponse = {
next_batch: 1,
rooms: {
Expand All @@ -134,37 +149,36 @@ describe("megolm key backups", function () {
},
};

return aliceTestClient!
.start()
.then(() => {
return createOlmSession(testOlmAccount, aliceTestClient);
})
.then(() => {
const privkey = decodeRecoveryKey(RECOVERY_KEY);
return aliceTestClient!.client!.crypto!.storeSessionBackupPrivateKey(privkey);
})
.then(() => {
aliceTestClient!.httpBackend.when("GET", "/sync").respond(200, syncResponse);
aliceTestClient!.expectKeyBackupQuery(ROOM_ID, SESSION_ID, 200, CURVE25519_KEY_BACKUP_DATA);
return aliceTestClient!.httpBackend.flushAllExpected();
})
.then(function (): Promise<MatrixEvent> {
const room = aliceTestClient!.client.getRoom(ROOM_ID)!;
const event = room.getLiveTimeline().getEvents()[0];

if (event.getContent()) {
return Promise.resolve(event);
}

return new Promise((resolve, reject) => {
event.once(MatrixEventEvent.Decrypted, (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});
})
.then((event) => {
expect(event.getContent()).toEqual("testytest");
fetchMock.get("express:/_matrix/client/v3/room_keys/keys/:room_id/:session_id", CURVE25519_KEY_BACKUP_DATA);

// mock for the outgoing key requests that will be sent
fetchMock.put("express:/_matrix/client/r0/sendToDevice/m.room_key_request/:txid", {});

// We'll need to add a signature to the backup data, so take a copy to avoid mutating global state.
const backupData = JSON.parse(JSON.stringify(CURVE25519_BACKUP_INFO));
fetchMock.get("path:/_matrix/client/v3/room_keys/version", backupData);

aliceClient = await initTestClient();
await aliceClient.crypto!.signObject(backupData.auth_data);
await aliceClient.crypto!.storeSessionBackupPrivateKey(decodeRecoveryKey(RECOVERY_KEY));
await aliceClient.crypto!.backupManager!.checkAndStart();

// start after saving the private key
await aliceClient.startClient();

syncResponder.sendOrQueueSyncResponse(syncResponse);
await syncPromise(aliceClient);

const room = aliceClient.getRoom(ROOM_ID)!;

const event = room.getLiveTimeline().getEvents()[0];
await new Promise((resolve, reject) => {
event.once(MatrixEventEvent.Decrypted, (ev) => {
logger.log(`${Date.now()} event ${event.getId()} now decrypted`);
resolve(ev);
});
});

expect(event.getContent()).toEqual("testytest");
});
});