Use test helpers from js-sdk for encrypted events (#12364)

* Use test helpers from js-sdk for encrypted events

Rather than gut-wrenching into the matrix-js-sdk, let's use the newly-exported
test helpers to generate encrypted events.

* Fix up
This commit is contained in:
Richard van der Hoff 2024-03-22 15:48:29 +00:00 committed by GitHub
parent 8f22550f19
commit 64806d0490
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 110 deletions

View file

@ -78,6 +78,7 @@ module.exports = {
"!matrix-js-sdk/src/matrix", "!matrix-js-sdk/src/matrix",
"!matrix-js-sdk/src/crypto-api", "!matrix-js-sdk/src/crypto-api",
"!matrix-js-sdk/src/types", "!matrix-js-sdk/src/types",
"!matrix-js-sdk/src/testing",
"matrix-js-sdk/lib", "matrix-js-sdk/lib",
"matrix-js-sdk/lib/", "matrix-js-sdk/lib/",
"matrix-js-sdk/lib/**", "matrix-js-sdk/lib/**",

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { mkDecryptionFailureMatrixEvent } from "matrix-js-sdk/src/testing";
import { DecryptionFailureTracker } from "../src/DecryptionFailureTracker"; import { DecryptionFailureTracker } from "../src/DecryptionFailureTracker";
@ -26,20 +26,18 @@ class MockDecryptionError extends Error {
} }
} }
function createFailedDecryptionEvent() { async function createFailedDecryptionEvent() {
const event = new MatrixEvent({ return await mkDecryptionFailureMatrixEvent({
event_id: "event-id-" + Math.random().toString(16).slice(2), roomId: "!room:id",
content: { sender: "@alice:example.com",
algorithm: "m.megolm.v1.aes-sha2", code: "UNKNOWN_ERROR",
}, msg: ":(",
}); });
event.setClearData(event.badEncryptedMessage(":("));
return event;
} }
describe("DecryptionFailureTracker", function () { describe("DecryptionFailureTracker", function () {
it("tracks a failed decryption for a visible event", function () { it("tracks a failed decryption for a visible event", async function () {
const failedDecryptionEvent = createFailedDecryptionEvent(); const failedDecryptionEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -61,8 +59,8 @@ describe("DecryptionFailureTracker", function () {
expect(count).not.toBe(0, "should track a failure for an event that failed decryption"); expect(count).not.toBe(0, "should track a failure for an event that failed decryption");
}); });
it("tracks a failed decryption with expected raw error for a visible event", function () { it("tracks a failed decryption with expected raw error for a visible event", async function () {
const failedDecryptionEvent = createFailedDecryptionEvent(); const failedDecryptionEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
let reportedRawCode = ""; let reportedRawCode = "";
@ -89,8 +87,8 @@ describe("DecryptionFailureTracker", function () {
expect(reportedRawCode).toBe("INBOUND_SESSION_MISMATCH_ROOM_ID", "Should add the rawCode to the event context"); expect(reportedRawCode).toBe("INBOUND_SESSION_MISMATCH_ROOM_ID", "Should add the rawCode to the event context");
}); });
it("tracks a failed decryption for an event that becomes visible later", function () { it("tracks a failed decryption for an event that becomes visible later", async function () {
const failedDecryptionEvent = createFailedDecryptionEvent(); const failedDecryptionEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -112,8 +110,8 @@ describe("DecryptionFailureTracker", function () {
expect(count).not.toBe(0, "should track a failure for an event that failed decryption"); expect(count).not.toBe(0, "should track a failure for an event that failed decryption");
}); });
it("does not track a failed decryption for an event that never becomes visible", function () { it("does not track a failed decryption for an event that never becomes visible", async function () {
const failedDecryptionEvent = createFailedDecryptionEvent(); const failedDecryptionEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -133,8 +131,8 @@ describe("DecryptionFailureTracker", function () {
expect(count).toBe(0, "should not track a failure for an event that never became visible"); expect(count).toBe(0, "should not track a failure for an event that never became visible");
}); });
it("does not track a failed decryption where the event is subsequently successfully decrypted", () => { it("does not track a failed decryption where the event is subsequently successfully decrypted", async () => {
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
(total) => { (total) => {
expect(true).toBe(false, "should not track an event that has since been decrypted correctly"); expect(true).toBe(false, "should not track an event that has since been decrypted correctly");
@ -161,8 +159,8 @@ describe("DecryptionFailureTracker", function () {
it( it(
"does not track a failed decryption where the event is subsequently successfully decrypted " + "does not track a failed decryption where the event is subsequently successfully decrypted " +
"and later becomes visible", "and later becomes visible",
() => { async () => {
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
(total) => { (total) => {
expect(true).toBe(false, "should not track an event that has since been decrypted correctly"); expect(true).toBe(false, "should not track an event that has since been decrypted correctly");
@ -187,9 +185,9 @@ describe("DecryptionFailureTracker", function () {
}, },
); );
it("only tracks a single failure per event, despite multiple failed decryptions for multiple events", () => { it("only tracks a single failure per event, despite multiple failed decryptions for multiple events", async () => {
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent(); const decryptedEvent2 = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -223,8 +221,8 @@ describe("DecryptionFailureTracker", function () {
expect(count).toBe(2, count + " failures tracked, should only track a single failure per event"); expect(count).toBe(2, count + " failures tracked, should only track a single failure per event");
}); });
it("should not track a failure for an event that was tracked previously", () => { it("should not track a failure for an event that was tracked previously", async () => {
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -251,11 +249,11 @@ describe("DecryptionFailureTracker", function () {
expect(count).toBe(1, "should only track a single failure per event"); expect(count).toBe(1, "should only track a single failure per event");
}); });
it.skip("should not track a failure for an event that was tracked in a previous session", () => { it.skip("should not track a failure for an event that was tracked in a previous session", async () => {
// This test uses localStorage, clear it beforehand // This test uses localStorage, clear it beforehand
localStorage.clear(); localStorage.clear();
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
let count = 0; let count = 0;
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
@ -292,16 +290,16 @@ describe("DecryptionFailureTracker", function () {
expect(count).toBe(1, count + " failures tracked, should only track a single failure per event"); expect(count).toBe(1, count + " failures tracked, should only track a single failure per event");
}); });
it("should count different error codes separately for multiple failures with different error codes", () => { it("should count different error codes separately for multiple failures with different error codes", async () => {
const counts = {}; const counts = {};
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total), (total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(error) => (error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError"), (error) => (error === "UnknownError" ? "UnknownError" : "OlmKeysNotSentError"),
); );
const decryptedEvent1 = createFailedDecryptionEvent(); const decryptedEvent1 = await createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent(); const decryptedEvent2 = await createFailedDecryptionEvent();
const decryptedEvent3 = createFailedDecryptionEvent(); const decryptedEvent3 = await createFailedDecryptionEvent();
const error1 = new MockDecryptionError("UnknownError"); const error1 = new MockDecryptionError("UnknownError");
const error2 = new MockDecryptionError("OlmKeysNotSentError"); const error2 = new MockDecryptionError("OlmKeysNotSentError");
@ -325,16 +323,16 @@ describe("DecryptionFailureTracker", function () {
expect(counts["OlmKeysNotSentError"]).toBe(2, "should track two OlmKeysNotSentError"); expect(counts["OlmKeysNotSentError"]).toBe(2, "should track two OlmKeysNotSentError");
}); });
it("should aggregate error codes correctly", () => { it("should aggregate error codes correctly", async () => {
const counts = {}; const counts = {};
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total), (total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(errorCode) => "OlmUnspecifiedError", (errorCode) => "OlmUnspecifiedError",
); );
const decryptedEvent1 = createFailedDecryptionEvent(); const decryptedEvent1 = await createFailedDecryptionEvent();
const decryptedEvent2 = createFailedDecryptionEvent(); const decryptedEvent2 = await createFailedDecryptionEvent();
const decryptedEvent3 = createFailedDecryptionEvent(); const decryptedEvent3 = await createFailedDecryptionEvent();
const error1 = new MockDecryptionError("ERROR_CODE_1"); const error1 = new MockDecryptionError("ERROR_CODE_1");
const error2 = new MockDecryptionError("ERROR_CODE_2"); const error2 = new MockDecryptionError("ERROR_CODE_2");
@ -359,14 +357,14 @@ describe("DecryptionFailureTracker", function () {
); );
}); });
it("should remap error codes correctly", () => { it("should remap error codes correctly", async () => {
const counts = {}; const counts = {};
const tracker = new DecryptionFailureTracker( const tracker = new DecryptionFailureTracker(
(total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total), (total, errorCode) => (counts[errorCode] = (counts[errorCode] || 0) + total),
(errorCode) => Array.from(errorCode).reverse().join(""), (errorCode) => Array.from(errorCode).reverse().join(""),
); );
const decryptedEvent = createFailedDecryptionEvent(); const decryptedEvent = await createFailedDecryptionEvent();
const error = new MockDecryptionError("ERROR_CODE_1"); const error = new MockDecryptionError("ERROR_CODE_1");

View file

@ -30,20 +30,13 @@ import {
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api"; import { EventEncryptionInfo, EventShieldColour, EventShieldReason } from "matrix-js-sdk/src/crypto-api";
import { TooltipProvider } from "@vector-im/compound-web"; import { TooltipProvider } from "@vector-im/compound-web";
import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile"; import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../test-utils";
filterConsole,
flushPromises,
getRoomContext,
mkEncryptedEvent,
mkEvent,
mkMessage,
stubClient,
} from "../../../test-utils";
import { mkThread } from "../../../test-utils/threads"; import { mkThread } from "../../../test-utils/threads";
import DMRoomMap from "../../../../src/utils/DMRoomMap"; import DMRoomMap from "../../../../src/utils/DMRoomMap";
import dis from "../../../../src/dispatcher/dispatcher"; import dis from "../../../../src/dispatcher/dispatcher";
@ -225,11 +218,11 @@ describe("EventTile", () => {
}); });
it("shows a warning for an event from an unverified device", async () => { it("shows a warning for an event from an unverified device", async () => {
mxEvent = await mkEncryptedEvent({ mxEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(mxEvent.getId()!, { eventToEncryptionInfoMap.set(mxEvent.getId()!, {
shieldColour: EventShieldColour.RED, shieldColour: EventShieldColour.RED,
@ -250,11 +243,11 @@ describe("EventTile", () => {
}); });
it("shows no shield for a verified event", async () => { it("shows no shield for a verified event", async () => {
mxEvent = await mkEncryptedEvent({ mxEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(mxEvent.getId()!, { eventToEncryptionInfoMap.set(mxEvent.getId()!, {
shieldColour: EventShieldColour.NONE, shieldColour: EventShieldColour.NONE,
@ -279,11 +272,11 @@ describe("EventTile", () => {
[EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"], [EventShieldReason.AUTHENTICITY_NOT_GUARANTEED, "can't be guaranteed"],
[EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"], [EventShieldReason.MISMATCHED_SENDER_KEY, "Encrypted by an unverified session"],
])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => { ])("shows the correct reason code for %i (%s)", async (reasonCode: EventShieldReason, expectedText: string) => {
mxEvent = await mkEncryptedEvent({ mxEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(mxEvent.getId()!, { eventToEncryptionInfoMap.set(mxEvent.getId()!, {
shieldColour: EventShieldColour.GREY, shieldColour: EventShieldColour.GREY,
@ -337,11 +330,11 @@ describe("EventTile", () => {
it("should update the warning when the event is edited", async () => { it("should update the warning when the event is edited", async () => {
// we start out with an event from the trusted device // we start out with an event from the trusted device
mxEvent = await mkEncryptedEvent({ mxEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(mxEvent.getId()!, { eventToEncryptionInfoMap.set(mxEvent.getId()!, {
shieldColour: EventShieldColour.NONE, shieldColour: EventShieldColour.NONE,
@ -360,11 +353,11 @@ describe("EventTile", () => {
expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0); expect(container.getElementsByClassName("mx_EventTile_e2eIcon")).toHaveLength(0);
// then we replace the event with one from the unverified device // then we replace the event with one from the unverified device
const replacementEvent = await mkEncryptedEvent({ const replacementEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(replacementEvent.getId()!, { eventToEncryptionInfoMap.set(replacementEvent.getId()!, {
shieldColour: EventShieldColour.RED, shieldColour: EventShieldColour.RED,
@ -388,11 +381,11 @@ describe("EventTile", () => {
jest.spyOn(client, "isRoomEncrypted").mockReturnValue(true); jest.spyOn(client, "isRoomEncrypted").mockReturnValue(true);
// we start out with an event from the trusted device // we start out with an event from the trusted device
mxEvent = await mkEncryptedEvent({ mxEvent = await mkEncryptedMatrixEvent({
plainContent: { msgtype: "m.text", body: "msg1" }, plainContent: { msgtype: "m.text", body: "msg1" },
plainType: "m.room.message", plainType: "m.room.message",
user: "@alice:example.org", sender: "@alice:example.org",
room: room.roomId, roomId: room.roomId,
}); });
eventToEncryptionInfoMap.set(mxEvent.getId()!, { eventToEncryptionInfoMap.set(mxEvent.getId()!, {

View file

@ -36,7 +36,6 @@ import {
IPushRules, IPushRules,
RelationType, RelationType,
JoinRule, JoinRule,
IEventDecryptionResult,
OidcClientConfig, OidcClientConfig,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
@ -404,50 +403,6 @@ export function mkEvent(opts: MakeEventProps): MatrixEvent {
return mxEvent; return mxEvent;
} }
/**
* Create an m.room.encrypted event
*
* @param opts - Values for the event
* @param opts.room - The ID of the room for the event
* @param opts.user - The sender of the event
* @param opts.plainType - The type the event will have, once it has been decrypted
* @param opts.plainContent - The content the event will have, once it has been decrypted
*/
export async function mkEncryptedEvent(opts: {
room: Room["roomId"];
user: User["userId"];
plainType: string;
plainContent: IContent;
}): Promise<MatrixEvent> {
// we construct an event which has been decrypted by stubbing out CryptoBackend.decryptEvent and then
// calling MatrixEvent.attemptDecryption.
const mxEvent = mkEvent({
type: "m.room.encrypted",
room: opts.room,
user: opts.user,
event: true,
content: {},
});
const decryptionResult: IEventDecryptionResult = {
claimedEd25519Key: "",
clearEvent: {
type: opts.plainType,
content: opts.plainContent,
},
forwardingCurve25519KeyChain: [],
senderCurve25519Key: "",
untrusted: false,
};
const mockCrypto = {
decryptEvent: async (_ev): Promise<IEventDecryptionResult> => decryptionResult,
} as Parameters<MatrixEvent["attemptDecryption"]>[0];
await mxEvent.attemptDecryption(mockCrypto);
return mxEvent;
}
/** /**
* Create an m.room.member event. * Create an m.room.member event.
* @param {Object} opts Values for the membership. * @param {Object} opts Values for the membership.