Unit test token login flow in MatrixChat
(#11143)
* test tokenlogin * whitespace * tidy * strict
This commit is contained in:
parent
9704c9fc13
commit
a87362a048
2 changed files with 178 additions and 7 deletions
|
@ -664,7 +664,7 @@ async function persistCredentials(credentials: IMatrixClientCreds): Promise<void
|
||||||
if (credentials.accessToken) {
|
if (credentials.accessToken) {
|
||||||
localStorage.setItem("mx_has_access_token", "true");
|
localStorage.setItem("mx_has_access_token", "true");
|
||||||
} else {
|
} else {
|
||||||
localStorage.deleteItem("mx_has_access_token");
|
localStorage.removeItem("mx_has_access_token");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (credentials.pickleKey) {
|
if (credentials.pickleKey) {
|
||||||
|
|
|
@ -116,7 +116,13 @@ describe("<MatrixChat />", () => {
|
||||||
};
|
};
|
||||||
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) =>
|
const getComponent = (props: Partial<ComponentProps<typeof MatrixChat>> = {}) =>
|
||||||
render(<MatrixChat {...defaultProps} {...props} />);
|
render(<MatrixChat {...defaultProps} {...props} />);
|
||||||
const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
|
const localStorageSetSpy = jest.spyOn(localStorage.__proto__, "setItem");
|
||||||
|
const localStorageGetSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
|
||||||
|
const localStorageClearSpy = jest.spyOn(localStorage.__proto__, "clear");
|
||||||
|
const sessionStorageSetSpy = jest.spyOn(sessionStorage.__proto__, "setItem");
|
||||||
|
|
||||||
|
// make test results readable
|
||||||
|
filterConsole("Failed to parse localStorage object");
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
mockClient = getMockClientWithEventEmitter(getMockClientMethods());
|
mockClient = getMockClientWithEventEmitter(getMockClientMethods());
|
||||||
|
@ -124,10 +130,14 @@ describe("<MatrixChat />", () => {
|
||||||
unstable_features: {},
|
unstable_features: {},
|
||||||
versions: [],
|
versions: [],
|
||||||
});
|
});
|
||||||
localStorageSpy.mockReset();
|
localStorageGetSpy.mockReset();
|
||||||
|
localStorageSetSpy.mockReset();
|
||||||
|
sessionStorageSetSpy.mockReset();
|
||||||
jest.spyOn(StorageManager, "idbLoad").mockRestore();
|
jest.spyOn(StorageManager, "idbLoad").mockRestore();
|
||||||
jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined);
|
jest.spyOn(StorageManager, "idbSave").mockResolvedValue(undefined);
|
||||||
jest.spyOn(defaultDispatcher, "dispatch").mockClear();
|
jest.spyOn(defaultDispatcher, "dispatch").mockClear();
|
||||||
|
|
||||||
|
await clearAllModals();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should render spinner while app is loading", () => {
|
it("should render spinner while app is loading", () => {
|
||||||
|
@ -151,7 +161,7 @@ describe("<MatrixChat />", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorageSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || "");
|
localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || "");
|
||||||
|
|
||||||
jest.spyOn(StorageManager, "idbLoad").mockImplementation(async (table, key) => {
|
jest.spyOn(StorageManager, "idbLoad").mockImplementation(async (table, key) => {
|
||||||
const safeKey = Array.isArray(key) ? key[0] : key;
|
const safeKey = Array.isArray(key) ? key[0] : key;
|
||||||
|
@ -350,9 +360,6 @@ describe("<MatrixChat />", () => {
|
||||||
const userName = "ernie";
|
const userName = "ernie";
|
||||||
const password = "ilovebert";
|
const password = "ilovebert";
|
||||||
|
|
||||||
// make test results readable
|
|
||||||
filterConsole("Failed to parse localStorage object");
|
|
||||||
|
|
||||||
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
const getComponentAndWaitForReady = async (): Promise<RenderResult> => {
|
||||||
const renderResult = getComponent();
|
const renderResult = getComponent();
|
||||||
// wait for welcome page chrome render
|
// wait for welcome page chrome render
|
||||||
|
@ -535,4 +542,168 @@ describe("<MatrixChat />", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("when query params have a loginToken", () => {
|
||||||
|
const loginToken = "test-login-token";
|
||||||
|
const realQueryParams = {
|
||||||
|
loginToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockLocalStorage: Record<string, string> = {
|
||||||
|
mx_sso_hs_url: serverConfig.hsUrl,
|
||||||
|
mx_sso_is_url: serverConfig.isUrl,
|
||||||
|
// these are only going to be set during login
|
||||||
|
mx_hs_url: serverConfig.hsUrl,
|
||||||
|
mx_is_url: serverConfig.isUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
let loginClient!: ReturnType<typeof getMockClientWithEventEmitter>;
|
||||||
|
const userId = "@alice:server.org";
|
||||||
|
const deviceId = "test-device-id";
|
||||||
|
const accessToken = "test-access-token";
|
||||||
|
const clientLoginResponse = {
|
||||||
|
user_id: userId,
|
||||||
|
device_id: deviceId,
|
||||||
|
access_token: accessToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
loginClient = getMockClientWithEventEmitter(getMockClientMethods());
|
||||||
|
// this is used to create a temporary client during login
|
||||||
|
jest.spyOn(MatrixJs, "createClient").mockReturnValue(loginClient);
|
||||||
|
|
||||||
|
loginClient.login.mockClear().mockResolvedValue(clientLoginResponse);
|
||||||
|
|
||||||
|
localStorageGetSpy.mockImplementation((key: unknown) => mockLocalStorage[key as string] || "");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show an error dialog when no homeserver is found in local storage", async () => {
|
||||||
|
localStorageGetSpy.mockReturnValue(undefined);
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
expect(localStorageGetSpy).toHaveBeenCalledWith("mx_sso_hs_url");
|
||||||
|
expect(localStorageGetSpy).toHaveBeenCalledWith("mx_sso_is_url");
|
||||||
|
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
// warning dialog
|
||||||
|
expect(
|
||||||
|
within(dialog).getByText(
|
||||||
|
"We asked the browser to remember which homeserver you use to let you sign in, " +
|
||||||
|
"but unfortunately your browser has forgotten it. Go to the sign in page and try again.",
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should attempt token login", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
expect(loginClient.login).toHaveBeenCalledWith("m.login.token", {
|
||||||
|
initial_device_display_name: undefined,
|
||||||
|
token: loginToken,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call onTokenLoginCompleted", async () => {
|
||||||
|
const onTokenLoginCompleted = jest.fn();
|
||||||
|
getComponent({ realQueryParams, onTokenLoginCompleted });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(onTokenLoginCompleted).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when login fails", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
loginClient.login.mockRejectedValue(new Error("oups"));
|
||||||
|
});
|
||||||
|
it("should show a dialog", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
const dialog = await screen.findByRole("dialog");
|
||||||
|
|
||||||
|
// warning dialog
|
||||||
|
expect(
|
||||||
|
within(dialog).getByText(
|
||||||
|
"There was a problem communicating with the homeserver, please try again later.",
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not clear storage", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(loginClient.clearStores).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when login succeeds", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(StorageManager, "idbLoad").mockImplementation(
|
||||||
|
async (_table: string, key: string | string[]) => {
|
||||||
|
if (key === "mx_access_token") {
|
||||||
|
return accessToken as any;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("should clear storage", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
// just check we called the clearStorage function
|
||||||
|
expect(loginClient.clearStores).toHaveBeenCalled();
|
||||||
|
expect(localStorageClearSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should persist login credentials", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_hs_url", serverConfig.hsUrl);
|
||||||
|
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_user_id", userId);
|
||||||
|
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_has_access_token", "true");
|
||||||
|
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_device_id", deviceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set fresh login flag in session storage", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(sessionStorageSetSpy).toHaveBeenCalledWith("mx_fresh_login", "true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should override hsUrl in creds when login response wellKnown differs from config", async () => {
|
||||||
|
const hsUrlFromWk = "https://hsfromwk.org";
|
||||||
|
const loginResponseWithWellKnown = {
|
||||||
|
...clientLoginResponse,
|
||||||
|
well_known: {
|
||||||
|
"m.homeserver": {
|
||||||
|
base_url: hsUrlFromWk,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
loginClient.login.mockResolvedValue(loginResponseWithWellKnown);
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
|
expect(localStorageSetSpy).toHaveBeenCalledWith("mx_hs_url", hsUrlFromWk);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should continue to post login setup when no session is found in local storage", async () => {
|
||||||
|
getComponent({ realQueryParams });
|
||||||
|
|
||||||
|
// logged in but waiting for sync screen
|
||||||
|
await screen.findByText("Logout");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue