Remove abandoned MSC3886, MSC3903, MSC3906 implementations (#28274)

* Remove abandoned MSC3886, MSC3903, MSC3906 implementations

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove stale snapshots

* Improve coverage

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-10-24 13:58:39 +01:00 committed by GitHub
parent 6d0d237c79
commit 5b5348ec1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 60 additions and 1373 deletions

View file

@ -7,20 +7,18 @@ Please see LICENSE files in the repository root for full details.
*/
import { cleanup, render, waitFor } from "jest-matrix-react";
import { MockedObject, mocked } from "jest-mock";
import { mocked, MockedObject } from "jest-mock";
import React from "react";
import {
MSC3906Rendezvous,
LegacyRendezvousFailureReason,
ClientRendezvousFailureReason,
MSC4108SignInWithQR,
MSC4108FailureReason,
MSC4108SignInWithQR,
RendezvousError,
} from "matrix-js-sdk/src/rendezvous";
import { HTTPError, LoginTokenPostResponse } from "matrix-js-sdk/src/matrix";
import { HTTPError, MatrixClient } from "matrix-js-sdk/src/matrix";
import LoginWithQR from "../../../../../../src/components/views/auth/LoginWithQR";
import { Click, Mode, Phase } from "../../../../../../src/components/views/auth/LoginWithQR-types";
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
jest.mock("matrix-js-sdk/src/rendezvous");
jest.mock("matrix-js-sdk/src/rendezvous/transports");
@ -65,9 +63,6 @@ describe("<LoginWithQR />", () => {
mode: Mode.Show,
onFinished: jest.fn(),
};
const mockConfirmationDigits = "mock-confirmation-digits";
const mockRendezvousCode = "mock-rendezvous-code";
const newDeviceId = "new-device-id";
beforeEach(() => {
mockedFlow.mockReset();
@ -82,264 +77,10 @@ describe("<LoginWithQR />", () => {
cleanup();
});
describe("MSC3906", () => {
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
<React.StrictMode>
<LoginWithQR {...defaultProps} {...props} />
</React.StrictMode>
);
beforeEach(() => {
jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockResolvedValue();
// @ts-ignore
// workaround for https://github.com/facebook/jest/issues/9675
MSC3906Rendezvous.prototype.code = mockRendezvousCode;
jest.spyOn(MSC3906Rendezvous.prototype, "cancel").mockResolvedValue();
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockResolvedValue(mockConfirmationDigits);
jest.spyOn(MSC3906Rendezvous.prototype, "declineLoginOnExistingDevice").mockResolvedValue();
jest.spyOn(MSC3906Rendezvous.prototype, "approveLoginOnExistingDevice").mockResolvedValue(newDeviceId);
jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockResolvedValue(undefined);
client.requestLoginToken.mockResolvedValue({
login_token: "token",
expires_in_ms: 1000 * 1000,
} as LoginTokenPostResponse); // we force the type here so that it works with versions of js-sdk that don't have r1 support yet
});
test("no homeserver support", async () => {
// simulate no support
jest.spyOn(MSC3906Rendezvous.prototype, "generateCode").mockRejectedValue("");
render(getComponent({ client }));
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.Error,
failureReason: LegacyRendezvousFailureReason.HomeserverLacksSupport,
onClick: expect.any(Function),
}),
);
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
expect(rendezvous.generateCode).toHaveBeenCalled();
});
test("failed to connect", async () => {
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockRejectedValue("");
render(getComponent({ client }));
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.Error,
failureReason: ClientRendezvousFailureReason.Unknown,
onClick: expect.any(Function),
}),
);
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
});
test("render QR then back", async () => {
const onFinished = jest.fn();
jest.spyOn(MSC3906Rendezvous.prototype, "startAfterShowingCode").mockReturnValue(unresolvedPromise());
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.ShowingQR,
}),
),
);
// display QR code
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.ShowingQR,
code: mockRendezvousCode,
onClick: expect.any(Function),
});
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
// back
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Back);
expect(onFinished).toHaveBeenCalledWith(false);
expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled);
});
test("render QR then decline", async () => {
const onFinished = jest.fn();
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.LegacyConnected,
}),
),
);
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.LegacyConnected,
confirmationDigits: mockConfirmationDigits,
onClick: expect.any(Function),
});
// decline
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Decline);
expect(onFinished).toHaveBeenCalledWith(false);
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
expect(rendezvous.declineLoginOnExistingDevice).toHaveBeenCalled();
});
test("approve - no crypto", async () => {
(client as any).getCrypto = () => undefined;
const onFinished = jest.fn();
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.LegacyConnected,
}),
),
);
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.LegacyConnected,
confirmationDigits: mockConfirmationDigits,
onClick: expect.any(Function),
});
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
// approve
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Approve);
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.WaitingForDevice,
}),
),
);
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
expect(onFinished).toHaveBeenCalledWith(true);
});
test("approve + verifying", async () => {
const onFinished = jest.fn();
jest.spyOn(MSC3906Rendezvous.prototype, "verifyNewDeviceOnExistingDevice").mockImplementation(() =>
unresolvedPromise(),
);
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.LegacyConnected,
}),
),
);
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.LegacyConnected,
confirmationDigits: mockConfirmationDigits,
onClick: expect.any(Function),
});
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
// approve
const onClick = mockedFlow.mock.calls[0][0].onClick;
onClick(Click.Approve);
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.Verifying,
}),
),
);
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled();
// expect(onFinished).toHaveBeenCalledWith(true);
});
test("approve + verify", async () => {
const onFinished = jest.fn();
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.LegacyConnected,
}),
),
);
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.LegacyConnected,
confirmationDigits: mockConfirmationDigits,
onClick: expect.any(Function),
});
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
// approve
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Approve);
expect(rendezvous.approveLoginOnExistingDevice).toHaveBeenCalledWith("token");
expect(rendezvous.verifyNewDeviceOnExistingDevice).toHaveBeenCalled();
expect(rendezvous.close).toHaveBeenCalled();
expect(onFinished).toHaveBeenCalledWith(true);
});
test("approve - rate limited", async () => {
mocked(client.requestLoginToken).mockRejectedValue(new HTTPError("rate limit reached", 429));
const onFinished = jest.fn();
render(getComponent({ client, onFinished }));
const rendezvous = mocked(MSC3906Rendezvous).mock.instances[0];
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.LegacyConnected,
}),
),
);
expect(mockedFlow).toHaveBeenLastCalledWith({
phase: Phase.LegacyConnected,
confirmationDigits: mockConfirmationDigits,
onClick: expect.any(Function),
});
expect(rendezvous.generateCode).toHaveBeenCalled();
expect(rendezvous.startAfterShowingCode).toHaveBeenCalled();
// approve
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Approve);
// the 429 error should be handled and mapped
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.Error,
failureReason: "rate_limited",
}),
),
);
});
});
describe("MSC4108", () => {
const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => (
<React.StrictMode>
<LoginWithQR {...defaultProps} {...props} legacy={false} />
<LoginWithQR {...defaultProps} {...props} />
</React.StrictMode>
);
@ -363,7 +104,7 @@ describe("<LoginWithQR />", () => {
const onClick = mockedFlow.mock.calls[0][0].onClick;
await onClick(Click.Back);
expect(onFinished).toHaveBeenCalledWith(false);
expect(rendezvous.cancel).toHaveBeenCalledWith(LegacyRendezvousFailureReason.UserCancelled);
expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UserCancelled);
});
test("failed to connect", async () => {
@ -404,6 +145,27 @@ describe("<LoginWithQR />", () => {
expect(global.window.open).toHaveBeenCalledWith("mock-verification-uri", "_blank");
});
test("handles errors during protocol negotiation", async () => {
render(getComponent({ client }));
jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue();
const err = new RendezvousError("Unknown Failure", MSC4108FailureReason.UnsupportedProtocol);
// @ts-ignore work-around for lazy mocks
err.code = MSC4108FailureReason.UnsupportedProtocol;
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockRejectedValue(err);
await waitFor(() =>
expect(mockedFlow).toHaveBeenLastCalledWith(
expect.objectContaining({
phase: Phase.ShowingQR,
}),
),
);
await waitFor(() => {
const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0];
expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UnsupportedProtocol);
});
});
test("handles errors during reciprocation", async () => {
render(getComponent({ client }));
jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({});