Delegate to new ResetIdentityDialog from SetupEncryptionBody (#29701)

This commit is contained in:
Andy Balaam
2025-04-30 11:08:38 +01:00
committed by GitHub
parent 4f4f391959
commit 23597e959b
16 changed files with 453 additions and 218 deletions

View File

@@ -0,0 +1,63 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2018-2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { act } from "react";
import { render } from "jest-matrix-react";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { type Mocked } from "jest-mock";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { getMockClientWithEventEmitter } from "../../../../test-utils";
import { ResetIdentityDialog } from "../../../../../src/components/views/dialogs/ResetIdentityDialog";
describe("ResetIdentityDialog", () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should call onReset and onFinished when we click Continue", async () => {
const client = mockClient();
const onFinished = jest.fn();
const onReset = jest.fn();
const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
await act(async () => dialog.getByRole("button", { name: "Continue" }).click());
expect(onReset).toHaveBeenCalled();
expect(onFinished).toHaveBeenCalled();
expect(client.getCrypto()?.resetEncryption).toHaveBeenCalled();
});
it("should call onFinished when we click Cancel", async () => {
const client = mockClient();
const onFinished = jest.fn();
const onReset = jest.fn();
const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
await act(async () => dialog.getByRole("button", { name: "Cancel" }).click());
expect(onFinished).toHaveBeenCalled();
expect(onReset).not.toHaveBeenCalled();
expect(client.getCrypto()?.resetEncryption).not.toHaveBeenCalled();
});
});
function mockClient(): Mocked<MatrixClient> {
const mockCrypto = {
resetEncryption: jest.fn().mockResolvedValue(null),
} as unknown as Mocked<CryptoApi>;
return getMockClientWithEventEmitter({
getCrypto: jest.fn().mockReturnValue(mockCrypto),
});
}

View File

@@ -0,0 +1,88 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2018-2022 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { act } from "react";
import { render, screen } from "jest-matrix-react";
import { type Mocked } from "jest-mock";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import SetupEncryptionDialog from "../../../../../src/components/views/dialogs/security/SetupEncryptionDialog";
import { getMockClientWithEventEmitter } from "../../../../test-utils";
import { Phase, SetupEncryptionStore } from "../../../../../src/stores/SetupEncryptionStore";
import Modal from "../../../../../src/Modal";
describe("SetupEncryptionDialog", () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should launch a dialog when I say Proceed, then be finished when I reset", async () => {
mockClient();
const store = new SetupEncryptionStore();
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
// Given when you open the reset dialog we immediately reset
jest.spyOn(Modal, "createDialog").mockImplementation((_, props) => {
// Simulate doing the reset in the dialog
props?.onReset();
return {
close: jest.fn(),
finished: Promise.resolve([]),
};
});
// When we launch the dialog and set it ready to start
const onFinished = jest.fn();
render(<SetupEncryptionDialog onFinished={onFinished} />);
await act(async () => await store.fetchKeyInfo());
expect(store.phase).toBe(Phase.Intro);
// And we hit the Proceed with reset button.
// (The createDialog mock above simulates the user doing the reset)
await act(async () => screen.getByRole("button", { name: "Proceed with reset" }).click());
// Then the phase has been set to Finished
expect(store.phase).toBe(Phase.Finished);
});
});
function mockClient() {
const mockCrypto = {
getDeviceVerificationStatus: jest.fn().mockResolvedValue({
crossSigningVerified: false,
}),
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
isCrossSigningReady: jest.fn().mockResolvedValue(true),
isSecretStorageReady: jest.fn().mockResolvedValue(true),
userHasCrossSigningKeys: jest.fn(),
getActiveSessionBackupVersion: jest.fn(),
getCrossSigningStatus: jest.fn().mockReturnValue({
publicKeysOnDevice: true,
privateKeysInSecretStorage: true,
privateKeysCachedLocally: {
masterKey: true,
selfSigningKey: true,
userSigningKey: true,
},
}),
getSessionBackupPrivateKey: jest.fn(),
isEncryptionEnabledInRoom: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
} as unknown as Mocked<CryptoApi>;
const userId = "@user:server";
getMockClientWithEventEmitter({
getCrypto: jest.fn().mockReturnValue(mockCrypto),
getUserId: jest.fn().mockReturnValue(userId),
secretStorage: { isStored: jest.fn().mockReturnValue({}) },
});
}

View File

@@ -6,7 +6,7 @@ Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen } from "jest-matrix-react";
import { act, render, screen } from "jest-matrix-react";
import { mocked } from "jest-mock";
import EventEmitter from "events";
@@ -76,4 +76,20 @@ describe("CompleteSecurity", () => {
expect(screen.queryByRole("button", { name: "Skip verification for now" })).not.toBeInTheDocument();
});
it("Renders a warning if user hits Reset", async () => {
// Given a store and a dialog based on it
const store = new SetupEncryptionStore();
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
const panel = await act(() => render(<CompleteSecurity onFinished={() => {}} />));
// When we hit reset
await act(async () => panel.getByRole("button", { name: "Proceed with reset" }).click());
// Then the reset identity dialog appears
expect(
screen.getByRole("heading", { name: "Are you sure you want to reset your identity?" }),
).toBeInTheDocument();
expect(panel.getByRole("button", { name: "Continue" })).toBeInTheDocument();
});
});

View File

@@ -24,9 +24,9 @@ describe("<ResetIdentityPanel />", () => {
it("should reset the encryption when the continue button is clicked", async () => {
const user = userEvent.setup();
const onFinish = jest.fn();
const onReset = jest.fn();
const { asFragment } = render(
<ResetIdentityPanel variant="compromised" onFinish={onFinish} onCancelClick={jest.fn()} />,
<ResetIdentityPanel variant="compromised" onReset={onReset} onCancelClick={jest.fn()} />,
withClientContextRenderOptions(matrixClient),
);
expect(asFragment()).toMatchSnapshot();
@@ -43,22 +43,22 @@ describe("<ResetIdentityPanel />", () => {
await sleep(0);
expect(matrixClient.getCrypto()!.resetEncryption).toHaveBeenCalled();
expect(onFinish).toHaveBeenCalled();
expect(onReset).toHaveBeenCalled();
});
it("should display the 'forgot recovery key' variant correctly", async () => {
const onFinish = jest.fn();
const onReset = jest.fn();
const { asFragment } = render(
<ResetIdentityPanel variant="forgot" onFinish={onFinish} onCancelClick={jest.fn()} />,
<ResetIdentityPanel variant="forgot" onReset={onReset} onCancelClick={jest.fn()} />,
withClientContextRenderOptions(matrixClient),
);
expect(asFragment()).toMatchSnapshot();
});
it("should display the 'sync failed' variant correctly", async () => {
const onFinish = jest.fn();
const onReset = jest.fn();
const { asFragment } = render(
<ResetIdentityPanel variant="sync_failed" onFinish={onFinish} onCancelClick={jest.fn()} />,
<ResetIdentityPanel variant="sync_failed" onReset={onReset} onCancelClick={jest.fn()} />,
withClientContextRenderOptions(matrixClient),
);
expect(asFragment()).toMatchSnapshot();

View File

@@ -9,7 +9,7 @@ Please see LICENSE files in the repository root for full details.
import { mocked, type Mocked } from "jest-mock";
import { type MatrixClient, Device } from "matrix-js-sdk/src/matrix";
import { type SecretStorageKeyDescriptionAesV1, type ServerSideSecretStorage } from "matrix-js-sdk/src/secret-storage";
import { type BootstrapCrossSigningOpts, type CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { type CryptoApi, DeviceVerificationStatus } from "matrix-js-sdk/src/crypto-api";
import { accessSecretStorage } from "../../../src/SecurityManager";
import { SetupEncryptionStore } from "../../../src/stores/SetupEncryptionStore";
@@ -152,21 +152,4 @@ describe("SetupEncryptionStore", () => {
await dehydrationPromise;
});
});
it("resetConfirm should work with a cached account password", async () => {
const makeRequest = jest.fn();
mockCrypto.bootstrapCrossSigning.mockImplementation(async (opts: BootstrapCrossSigningOpts) => {
await opts?.authUploadDeviceSigningKeys?.(makeRequest);
});
mocked(accessSecretStorage).mockImplementation(async (func?: () => Promise<void>) => {
await func!();
});
await setupEncryptionStore.resetConfirm();
expect(mocked(accessSecretStorage)).toHaveBeenCalledWith(expect.any(Function), {
forceReset: true,
resetCrossSigning: true,
});
});
});