inbuxa can now switch IMAP, POP3 and ManageSieve off one at a time, server-wide and per organization, and the session lists what is still allowed for the account (legacyAllowed). Where the webmail said "legacy protocols are off", it now also covers the case where only some are: - Security & sessions, above app passwords: "Your organization has turned off POP3 for mail apps. Mail apps that use it can't connect to this account; others still can." - The Administration dashboard: "Some legacy mail protocols are off for your organization: POP3." - An organization's sheet in Administration: its switch stays the all-or-nothing one; when only some are off it names them and points to the console, where they're switched one at a time, and "Turn legacy protocols back on" turns them all back on. With every protocol off, the existing wording shows, as before. From a server that doesn't send legacyAllowed nothing new appears. 3 new strings in all nine catalogs, unreviewed. Tested: unit tests for reading the session and a tenant's switches; the existing tests updated for the new field; typecheck; the whole suite (1475 tests); and in headless Chrome against a local server with POP3 off, where Security & sessions showed the new note.
102 lines
4.1 KiB
TypeScript
102 lines
4.1 KiB
TypeScript
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, partlyOff: [], 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, partlyOff: [], 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, partlyOff: [], recent: null };
|
|
signIn(["sysDomainGet"]);
|
|
await render();
|
|
expect(host.textContent).toContain("Off for Acme Corp");
|
|
expect(host.querySelector("button")).toBeNull();
|
|
});
|
|
});
|