// @vitest-environment jsdom // // "Sign in as someone else" with a Google account the server will not sign in (not approved, or // suspended) comes back to the account still signed in here, with the flags the sign-in page reads; // a tab the server refused because another account signed in under it reloads with a flag of its // own. Each says so in the signed-in app's words, not the sign-in page's. import { afterEach, describe, expect, it, vi } from "vitest"; import { ACCOUNT_CHANGED_FLAG, accountChangedUrl, accountSwitchNotice, clearEntryUrl, reloadAfterRefusal, } from "./entryUrl"; import { HttpError } from "./api/core"; const notice = (search: string, hash = "") => accountSwitchNotice(new URLSearchParams(search), new URLSearchParams(hash)); afterEach(() => { window.history.replaceState(null, "", "/"); vi.unstubAllGlobals(); }); describe("a switch of account, told to the account signed in", () => { it("names the address a request was recorded for", () => { expect(notice("", "access=requested&email=b%2Btag%40example.com")).toEqual({ level: "info", key: "header.account.switchRequested", values: { email: "b+tag@example.com" }, }); }); it("names the address the request lives under when that is another spelling", () => { expect(notice("", "access=requested&email=bob%40x.com&linked=b.ob%40x.com")?.values).toEqual({ email: "b.ob@x.com", }); }); it("says the account is not approved, and nothing changed here", () => { expect(notice("", "access=denied")).toEqual({ level: "warning", key: "header.account.switchNotApproved", }); }); it("says that account is suspended, not this one", () => { expect(notice("?login=suspended")).toEqual({ level: "warning", key: "header.account.switchSuspended", }); }); it("says the tab reloaded as the account signed in now", () => { expect(notice(`?page=stats&${ACCOUNT_CHANGED_FLAG}`)).toEqual({ level: "info", key: "header.account.tabSwitched", }); }); it("says nothing on an ordinary load", () => { expect(notice("?page=stats", "")).toBeNull(); }); }); describe("the reload of a tab another account signed in under", () => { it("stays on its page and carries the flag", () => { window.history.replaceState(null, "", "/?page=stats&chan=UC1#x=1"); const url = new URL(accountChangedUrl(), "http://h"); expect(url.pathname).toBe("/"); expect(Object.fromEntries(url.searchParams)).toEqual({ page: "stats", chan: "UC1", account: "changed", }); expect(url.hash).toBe("#x=1"); }); it("drops the flag once read, so a later reload does not say it again", () => { window.history.replaceState(null, "", `/?page=stats&${ACCOUNT_CHANGED_FLAG}`); clearEntryUrl(); expect(window.location.search).toBe("?page=stats"); }); it("stays on this origin when the path starts with //", () => { // Glued as `pathname + query`, a path of `//evil.com/x` names another host: history refuses // it, and the boot that strips the flag threw into the crash screen. const origin = window.location.origin; window.history.replaceState(null, "", `${origin}//evil.com/x?page=stats`); window.history.replaceState(null, "", accountChangedUrl()); expect(window.location.origin).toBe(origin); expect(window.location.pathname).toBe("//evil.com/x"); clearEntryUrl(); expect(window.location.origin).toBe(origin); expect(window.location.pathname).toBe("//evil.com/x"); expect(window.location.search).toBe("?page=stats"); }); }); describe("a tab whose session the server refused", () => { const stubLocation = () => { const loc = { href: "http://h/?page=stats", pathname: "/", search: "?page=stats", hash: "", replace: vi.fn(), reload: vi.fn(), }; vi.stubGlobal("location", loc); return loc; }; it("reloads as the account signed in now, saying why, when it showed another", () => { const loc = stubLocation(); reloadAfterRefusal(new HttpError(401, "x", { code: "refused", reason: "account_changed" })); expect(loc.replace).toHaveBeenCalledWith(`/?page=stats&${ACCOUNT_CHANGED_FLAG}`); expect(loc.reload).not.toHaveBeenCalled(); }); it("reloads plainly when the session ended", () => { const loc = stubLocation(); reloadAfterRefusal(new HttpError(401, "x", { code: "refused", reason: "not_authenticated" })); expect(loc.reload).toHaveBeenCalled(); expect(loc.replace).not.toHaveBeenCalled(); }); });