diff --git a/src/components/views/settings/encryption/RecoveryPanel.tsx b/src/components/views/settings/encryption/RecoveryPanel.tsx index b047b528e3..df555a583a 100644 --- a/src/components/views/settings/encryption/RecoveryPanel.tsx +++ b/src/components/views/settings/encryption/RecoveryPanel.tsx @@ -68,7 +68,7 @@ export function RecoveryPanel({ onSetUpRecoveryClick, onChangingRecoveryKeyClick let content: JSX.Element; switch (state) { case "loading": - content = ; + content = ; break; case "missing_backup": content = ( diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index f9aee512a3..902dc32e6e 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -127,7 +127,10 @@ export function createTestClient(): MatrixClient { bootstrapCrossSigning: jest.fn(), getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null), isKeyBackupTrusted: jest.fn().mockResolvedValue({}), - createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}), + createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({ + privateKey: new Uint8Array(32), + encodedPrivateKey: "encoded private key", + }), bootstrapSecretStorage: jest.fn(), isDehydrationSupported: jest.fn().mockResolvedValue(false), restoreKeyBackup: jest.fn(), @@ -136,6 +139,17 @@ export function createTestClient(): MatrixClient { storeSessionBackupPrivateKey: jest.fn(), getKeyBackupInfo: jest.fn().mockResolvedValue(null), getEncryptionInfoForEvent: jest.fn().mockResolvedValue(null), + getCrossSigningStatus: jest.fn().mockResolvedValue({ + publicKeysOnDevice: false, + privateKeysInSecretStorage: false, + privateKeysCachedLocally: { + masterKey: false, + selfSigningKey: false, + userSigningKey: false, + }, + }), + isCrossSigningReady: jest.fn().mockResolvedValue(false), + checkKeyBackupAndEnable: jest.fn().mockResolvedValue(null), }), getPushActionsForEvent: jest.fn(), diff --git a/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx new file mode 100644 index 0000000000..ce81b51b6f --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; +import { render, screen } from "jest-matrix-react"; +import { waitFor } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; +import { KeyBackupCheck } from "matrix-js-sdk/src/crypto-api"; + +import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils"; +import { RecoveryPanel } from "../../../../../../src/components/views/settings/encryption/RecoveryPanel"; +import { accessSecretStorage } from "../../../../../../src/SecurityManager"; + +jest.mock("../../../../../../src/SecurityManager", () => ({ + accessSecretStorage: jest.fn(), +})); + +describe("", () => { + let matrixClient: MatrixClient; + + beforeEach(() => { + matrixClient = createTestClient(); + mocked(accessSecretStorage).mockClear().mockResolvedValue(); + }); + + function renderRecoverPanel( + props = { + onSetUpRecoveryClick: jest.fn(), + onChangingRecoveryKeyClick: jest.fn(), + }, + ) { + return render(, withClientContextRenderOptions(matrixClient)); + } + + it("should be in loading state when checking backup and the cached keys", () => { + jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockImplementation( + () => new Promise(() => {}), + ); + + const { asFragment } = renderRecoverPanel(); + expect(screen.getByLabelText("Loading…")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should ask to set up a recovery key when there is no key backup", async () => { + const user = userEvent.setup(); + + const onSetUpRecoveryClick = jest.fn(); + const { asFragment } = renderRecoverPanel({ onSetUpRecoveryClick, onChangingRecoveryKeyClick: jest.fn() }); + + await waitFor(() => screen.getByRole("button", { name: "Set up recovery" })); + expect(asFragment()).toMatchSnapshot(); + + await user.click(screen.getByRole("button", { name: "Set up recovery" })); + expect(onSetUpRecoveryClick).toHaveBeenCalled(); + }); + + it("should ask to enter the recovery key when secrets are not cached", async () => { + jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockResolvedValue({} as KeyBackupCheck); + const user = userEvent.setup(); + const { asFragment } = renderRecoverPanel(); + + await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" })); + expect(asFragment()).toMatchSnapshot(); + + await user.click(screen.getByRole("button", { name: "Enter recovery key" })); + expect(accessSecretStorage).toHaveBeenCalled(); + }); + + it("should allow to change the recovery key when everything is good", async () => { + jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockResolvedValue({} as KeyBackupCheck); + jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({ + privateKeysInSecretStorage: true, + publicKeysOnDevice: true, + privateKeysCachedLocally: { + masterKey: true, + selfSigningKey: true, + userSigningKey: true, + }, + }); + const user = userEvent.setup(); + + const onChangingRecoveryKeyClick = jest.fn(); + const { asFragment } = renderRecoverPanel({ onSetUpRecoveryClick: jest.fn(), onChangingRecoveryKeyClick }); + await waitFor(() => screen.getByRole("button", { name: "Change recovery key" })); + expect(asFragment()).toMatchSnapshot(); + + await user.click(screen.getByRole("button", { name: "Change recovery key" })); + expect(onChangingRecoveryKeyClick).toHaveBeenCalled(); + }); +}); diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap new file mode 100644 index 0000000000..864116a9c2 --- /dev/null +++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should allow to change the recovery key when everything is good 1`] = ` + +
+
+

+ Recovery +

+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. +
+ +
+
+`; + +exports[` should ask to enter the recovery key when secrets are not cached 1`] = ` + +
+
+

+ Recovery +

+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. + + + + + Your key storage is out of sync. Click the button below to fix the problem. + +
+
+ +
+
+`; + +exports[` should ask to set up a recovery key when there is no key backup 1`] = ` + +
+
+

+ Recovery + + Recommended + +

+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. +
+ +
+
+`; + +exports[` should be in loading state when checking backup and the cached keys 1`] = ` + +
+
+

+ Recovery +

+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices. +
+ + + +
+
+`;