A tenant's administrator can turn legacy mail apps off for the organization
INBUXA's tenant switch (legacy-protocols LP-9 to LP-18) in the
administration. Each tenant's sheet gains "Legacy mail apps": whether IMAP,
POP3, ManageSieve and sending from mail apps are on or off on the tenant's
domains, and the switch.
Nobody turns it off by accident. "Turn off legacy protocols…" first shows
who would notice -- every account in the tenant that signed in with a
legacy mail app in the last 30 days, with the protocols and when (LP-15) --
and the statement of what it means, "for everyone in {tenant}" (LP-16),
then asks for the phrase "turn off legacy mail", matched exactly (LP-17).
Turning it back on is one click; the server refuses while it has legacy
protocols off for everyone, and its words are shown. A tenant's switch
closes no port, so the statement names none.
It needs the domain permissions the server checks for the switch; with
read-only access the state shows and the buttons don't. On a server that
isn't INBUXA, or is older, the section isn't there.
The dashboard says so while legacy mail is off for the signed-in
administrator's organization (LP-18), from the same session flag as
Settings › Security's line.
Every new string in all nine languages, the register each catalog uses, and
the count in each language's plural forms.
This commit is contained in:
1 parent
4cac9088dd
commit
8abf3a96aa
16 files changed
+597
-1
No files matched your search
@@ -0,0 +1,101 @@
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
import type { TenantLegacy } from "@/lib/admin/adminLegacyProtocols";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const api = vi.hoisted(() => ({
|
||||
state: null as TenantLegacy | null,
|
||||
set: vi.fn(async () => {}),
|
||||
}));
|
||||
vi.mock("@/lib/admin/adminLegacyProtocols", async (original) => ({
|
||||
...(await original<typeof import("@/lib/admin/adminLegacyProtocols")>()),
|
||||
fetchTenantLegacy: vi.fn(async () => api.state),
|
||||
setTenantLegacy: api.set,
|
||||
}));
|
||||
|
||||
const { TenantLegacyProtocols } = await import("../TenantLegacyProtocols");
|
||||
|
||||
const signIn = (permissions: string[]) =>
|
||||
useSession.setState({ session: { capabilities: {}, accounts: {}, primaryAccounts: {}, username: "[email protected]", ihasmail: { permissions } } as unknown as JmapSession });
|
||||
const button = (host: HTMLElement, label: string) => [...host.querySelectorAll("button")].find((b) => b.textContent?.trim() === label);
|
||||
const type = async (el: HTMLInputElement, value: string) => {
|
||||
await act(async () => {
|
||||
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(el, value);
|
||||
el.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* INBUXA legacy-protocols at tenant scope: nobody turns it off by accident
|
||||
* (the statement and a typed phrase first), and turning it back on is one
|
||||
* click.
|
||||
*/
|
||||
describe("a tenant's legacy mail protocols switch", () => {
|
||||
let host: HTMLDivElement;
|
||||
let root: Root;
|
||||
const render = async () => {
|
||||
await act(async () => {
|
||||
root.render(<TenantLegacyProtocols tenantId="t1" tenantName="Acme Corp" />);
|
||||
});
|
||||
await act(async () => {});
|
||||
};
|
||||
beforeEach(() => {
|
||||
host = document.createElement("div");
|
||||
document.body.appendChild(host);
|
||||
root = createRoot(host);
|
||||
api.set.mockClear();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await act(async () => root.unmount());
|
||||
host.remove();
|
||||
});
|
||||
|
||||
it("isn't there on a server without the switch", async () => {
|
||||
api.state = null;
|
||||
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||
await render();
|
||||
expect(host.textContent).toBe("");
|
||||
});
|
||||
|
||||
it("shows who would notice and what it means, then asks for the exact phrase", async () => {
|
||||
api.state = { off: false, recent: [{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
|
||||
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||
await render();
|
||||
await act(async () => button(host, "Turn off legacy protocols…")!.click());
|
||||
|
||||
expect(host.textContent).toContain("1 account used a legacy mail app in the last 30 days.");
|
||||
expect(host.textContent).toContain("[email protected]: IMAP");
|
||||
expect(host.textContent).toContain("turned off for everyone in Acme Corp");
|
||||
// A tenant's switch closes no port, so the statement names none.
|
||||
expect(host.textContent).not.toContain("firewall");
|
||||
|
||||
const confirm = button(host, "Turn off legacy protocols")!;
|
||||
const box = host.querySelector<HTMLInputElement>("#admin-legacy-confirm")!;
|
||||
await type(box, "Turn off legacy mail");
|
||||
expect(confirm.disabled).toBe(true);
|
||||
await type(box, "turn off legacy mail");
|
||||
expect(confirm.disabled).toBe(false);
|
||||
await act(async () => confirm.click());
|
||||
expect(api.set).toHaveBeenCalledWith("t1", true);
|
||||
});
|
||||
|
||||
it("turns it back on with one click", async () => {
|
||||
api.state = { off: true, recent: [] };
|
||||
signIn(["sysDomainGet", "sysDomainUpdate"]);
|
||||
await render();
|
||||
await act(async () => button(host, "Turn legacy protocols back on")!.click());
|
||||
expect(api.set).toHaveBeenCalledWith("t1", false);
|
||||
});
|
||||
|
||||
it("shows the state but no switch to someone who can't change domains", async () => {
|
||||
api.state = { off: true, recent: null };
|
||||
signIn(["sysDomainGet"]);
|
||||
await render();
|
||||
expect(host.textContent).toContain("Off for Acme Corp");
|
||||
expect(host.querySelector("button")).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user