Skip to content

Commit

Permalink
Add tests to RestoreKeyBackupDialog
Browse files Browse the repository at this point in the history
  • Loading branch information
florianduros committed Nov 8, 2024
1 parent ff35a1b commit 0a0ba2a
Show file tree
Hide file tree
Showing 4 changed files with 343 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,

<form className="mx_RestoreKeyBackupDialog_primaryContainer">
<input
data-testid="passphraseInput"
type="password"
className="mx_RestoreKeyBackupDialog_passPhraseInput"
onChange={this.onPassPhraseChange}
Expand Down
5 changes: 5 additions & 0 deletions test/test-utils/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export function createTestClient(): MatrixClient {
createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
bootstrapSecretStorage: jest.fn(),
isDehydrationSupported: jest.fn().mockResolvedValue(false),
restoreKeyBackup: jest.fn(),
restoreKeyBackupWithPassphrase: jest.fn(),
loadSessionBackupPrivateKeyFromSecretStorage: jest.fn(),
storeSessionBackupPrivateKey: jest.fn(),
}),

getPushActionsForEvent: jest.fn(),
Expand Down Expand Up @@ -275,6 +279,7 @@ export function createTestClient(): MatrixClient {
sendStickerMessage: jest.fn(),
getLocalAliases: jest.fn().mockReturnValue([]),
uploadDeviceSigningKeys: jest.fn(),
isKeyBackupKeyStored: jest.fn().mockResolvedValue(null),
} as unknown as MatrixClient;

client.reEmitter = new ReEmitter(client);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import React from "react";
import { screen, render, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
// Needed to be able to mock decodeRecoveryKey
// eslint-disable-next-line no-restricted-imports
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
Expand All @@ -17,9 +19,16 @@ import RestoreKeyBackupDialog from "../../../../../../src/components/views/dialo
import { stubClient } from "../../../../../test-utils";

describe("<RestoreKeyBackupDialog />", () => {
const keyBackupRestoreResult = {
total: 2,
imported: 1,
};

let matrixClient: MatrixClient;
beforeEach(() => {
stubClient();
matrixClient = stubClient();
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({ version: "1" } as KeyBackupInfo);
});

it("should render", async () => {
Expand Down Expand Up @@ -48,4 +57,71 @@ describe("<RestoreKeyBackupDialog />", () => {
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

it("should restore key backup when the key is cached", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup").mockResolvedValue(keyBackupRestoreResult);

const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

it("should restore key backup when the key is in secret storage", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValueOnce(new Error("key backup not found"))
// Resolve when trying to restore from secret storage
.mockResolvedValue(keyBackupRestoreResult);
jest.spyOn(matrixClient.secretStorage, "hasKey").mockResolvedValue(true);
jest.spyOn(matrixClient, "isKeyBackupKeyStored").mockResolvedValue({});

const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

it("should restore key backup when security key is filled by user", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValueOnce(new Error("key backup not found"))
// Resolve when trying to restore from recovery key
.mockResolvedValue(keyBackupRestoreResult);

const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Key")).toBeInTheDocument());

await userEvent.type(screen.getByRole("textbox"), "my security key");
await userEvent.click(screen.getByRole("button", { name: "Next" }));

await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});

test("should restore key backup when passphrase is filled", async () => {
// Determine that the passphrase is required
jest.spyOn(matrixClient, "getKeyBackupVersion").mockResolvedValue({
version: "1",
auth_data: {
private_key_salt: "salt",
private_key_iterations: 1,
},
} as KeyBackupInfo);

jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValue(new Error("key backup not found"));

jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackupWithPassphrase").mockResolvedValue(
keyBackupRestoreResult,
);

const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Phrase")).toBeInTheDocument());
// Not role for password https://github.com/w3c/aria/issues/935
await userEvent.type(screen.getByTestId("passphraseInput"), "my passphrase");
await userEvent.click(screen.getByRole("button", { name: "Next" }));

await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();

Check failure on line 125 in test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx

View workflow job for this annotation

GitHub Actions / Jest (2)

<RestoreKeyBackupDialog /> › should restore key backup when passphrase is filled

expect(received).toMatchSnapshot() Snapshot name: `<RestoreKeyBackupDialog /> should restore key backup when passphrase is filled 1` New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. This is likely because this test is run in a continuous integration (CI) environment in which snapshots are not written by default. Received: <DocumentFragment> <div data-focus-guard="true" style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" tabindex="0" /> <div aria-labelledby="mx_BaseDialog_title" class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth" data-focus-lock-disabled="false" role="dialog" > <div class="mx_Dialog_header" > <h1 class="mx_Heading_h3 mx_Dialog_title" id="mx_BaseDialog_title" > Keys restored </h1> </div> <div class="mx_RestoreKeyBackupDialog_content" > <div> <p> Successfully restored 1 keys </p> <p> Failed to decrypt 1 sessions! </p> <div class="mx_Dialog_buttons" > <span class="mx_Dialog_buttons_row" > <button class="mx_Dialog_primary" data-testid="dialog-primary-button" type="button" > OK </button> </span> </div> </div> </div> <div aria-label="Close dialog" class="mx_AccessibleButton mx_Dialog_cancelButton" role="button" tabindex="0" /> </div> <div data-focus-guard="true" style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;" tabindex="0" /> </DocumentFragment> at Object.toMatchSnapshot (test/unit-tests/components/views/dialogs/security/RestoreKeyBackupDialog-test.tsx:125:30)
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,263 @@ exports[`<RestoreKeyBackupDialog /> should render 1`] = `
/>
</DocumentFragment>
`;

exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is required 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

exports[`<RestoreKeyBackupDialog /> should restore key backup when security key is filled by user 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is cached 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is in secret storage 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

0 comments on commit 0a0ba2a

Please sign in to comment.