Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { SettingsSection } from "../shared/SettingsSection";
import { _t } from "../../../../languageHandler";
import { SettingsSubheader } from "../SettingsSubheader";
import { accessSecretStorage } from "../../../../SecurityManager";
import DeviceListener from "../../../../DeviceListener";
import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext";
import { resetKeyBackupAndWait } from "../../../../utils/crypto/resetKeyBackup";

interface RecoveryPanelOutOfSyncProps {
/**
Expand All @@ -33,6 +36,8 @@ interface RecoveryPanelOutOfSyncProps {
* the client.
*/
export function RecoveryPanelOutOfSync({ onForgotRecoveryKey, onFinish }: RecoveryPanelOutOfSyncProps): JSX.Element {
const matrixClient = useMatrixClientContext();

return (
<SettingsSection
legacy={false}
Expand All @@ -55,7 +60,29 @@ export function RecoveryPanelOutOfSync({ onForgotRecoveryKey, onFinish }: Recove
kind="primary"
Icon={KeyIcon}
onClick={async () => {
await accessSecretStorage();
const crypto = matrixClient.getCrypto()!;

const deviceListener = DeviceListener.sharedInstance();

// we need to call keyStorageOutOfSyncNeedsBackupReset here because
// deviceListener.whilePaused() sets its client to undefined, so
// keyStorageOutOfSyncNeedsBackupReset won't be able to check
// the backup state.
const needsBackupReset = await deviceListener.keyStorageOutOfSyncNeedsBackupReset(false);

// pause the device listener because we could be making lots
// of changes, and don't want toasts to pop up and disappear
// while we're doing it
await deviceListener.whilePaused(async () => {
await accessSecretStorage(async () => {
// Reset backup if needed.
if (needsBackupReset) {
await resetKeyBackupAndWait(crypto);
} else if (await matrixClient.isKeyBackupKeyStored()) {
await crypto.loadSessionBackupPrivateKeyFromSecretStorage();
}
});
});
Comment on lines +76 to +85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is happening if this promises are rejected?

onFinish();
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,29 @@ import { mocked } from "jest-mock";

import { RecoveryPanelOutOfSync } from "../../../../../../src/components/views/settings/encryption/RecoveryPanelOutOfSync";
import { accessSecretStorage } from "../../../../../../src/SecurityManager";
import DeviceListener from "../../../../../../src/DeviceListener";
import { stubClient } from "../../../../../test-utils";
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";

jest.mock("../../../../../../src/SecurityManager", () => ({
accessSecretStorage: jest.fn(),
}));

describe("<RecoveyPanelOutOfSync />", () => {
function renderComponent(onFinish = jest.fn(), onForgotRecoveryKey = jest.fn()) {
return render(<RecoveryPanelOutOfSync onFinish={onFinish} onForgotRecoveryKey={onForgotRecoveryKey} />);
stubClient();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using MatrixClientPeg.safeGet(), prefer to create the matrix client and put it an a context

Suggested change
stubClient();
createTestClient();

return render(
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
<RecoveryPanelOutOfSync onFinish={onFinish} onForgotRecoveryKey={onForgotRecoveryKey} />
</MatrixClientContext.Provider>,
);
Comment on lines +27 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return render(
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>
<RecoveryPanelOutOfSync onFinish={onFinish} onForgotRecoveryKey={onForgotRecoveryKey} />
</MatrixClientContext.Provider>,
);
return render(<RecoveryPanelOutOfSync onFinish={onFinish} onForgotRecoveryKey={onForgotRecoveryKey} />, withClientContextRenderOptions(matrixClient));

}

afterEach(() => {
jest.restoreAllMocks();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jest.restoreAllMocks();
jest.clearAllMocks()

});

it("should render", () => {
const { asFragment } = renderComponent();
expect(asFragment()).toMatchSnapshot();
Expand All @@ -38,14 +51,42 @@ describe("<RecoveyPanelOutOfSync />", () => {
});

it("should access to 4S and call onFinish when 'Enter recovery key' is clicked", async () => {
jest.spyOn(DeviceListener.sharedInstance(), "keyStorageOutOfSyncNeedsBackupReset").mockResolvedValue(false);

const user = userEvent.setup();
mocked(accessSecretStorage)
.mockClear()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With jest.clearAllMocks() in afteEach, this not necessary

.mockImplementation(async (func = async (): Promise<void> => {}) => {
return await func();
});
Comment on lines +59 to +61
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to call the function in mockImplementation? Or returning a promise for accessSecretStorage is enough?

Suggested change
.mockImplementation(async (func = async (): Promise<void> => {}) => {
return await func();
});
.mockResolvedValue(undefined)


const onFinish = jest.fn();
renderComponent(onFinish);

await user.click(screen.getByRole("button", { name: "Enter recovery key" }));
expect(accessSecretStorage).toHaveBeenCalled();
expect(onFinish).toHaveBeenCalled();

expect(MatrixClientPeg.safeGet().getCrypto()!.resetKeyBackup).not.toHaveBeenCalled();
});

it("should reset key backup if needed", async () => {
jest.spyOn(DeviceListener.sharedInstance(), "keyStorageOutOfSyncNeedsBackupReset").mockResolvedValue(true);

const user = userEvent.setup();
mocked(accessSecretStorage).mockClear().mockResolvedValue();
mocked(accessSecretStorage)
.mockClear()
.mockImplementation(async (func = async (): Promise<void> => {}) => {
return await func();
});

const onFinish = jest.fn();
renderComponent(onFinish);

await user.click(screen.getByRole("button", { name: "Enter recovery key" }));
expect(accessSecretStorage).toHaveBeenCalled();
expect(onFinish).toHaveBeenCalled();

expect(MatrixClientPeg.safeGet().getCrypto()!.resetKeyBackup).toHaveBeenCalled();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto matrix client

});
});