Show only the Enterprise notice on Tenants when the server is not Enterprise
On a server that does not report Enterprise -- or reports no edition -- tenants hold nobody to anything beyond an ordinary user's permissions, so the page is the notice alone: no New tenant, no search, no list, and no tenant query is made. The mock's edition is MOCK_EDITION now (default oss, as before), so MOCK_EDITION=enterprise brings the section back to work on.
This commit is contained in:
@@ -18,14 +18,33 @@ const PAGE_SIZE = 50;
|
||||
* Tenants: separate organisations on one server, each with its own people,
|
||||
* domains and limits.
|
||||
*
|
||||
* Shown to whoever may read them, whatever the edition says -- the edition is a
|
||||
* licence claim, not an authority -- but on a server that does not report
|
||||
* Enterprise the page says what that means for the people inside one.
|
||||
* The section is offered to whoever may read tenants, but on a server that does
|
||||
* not report Enterprise the page is only the notice: tenants there hold nobody
|
||||
* to anything beyond an ordinary user's permissions, so there is nothing worth
|
||||
* creating or listing. A server that reports no edition at all counts as not
|
||||
* Enterprise.
|
||||
*/
|
||||
export function TenantsAdmin({ selectedId }: { selectedId?: string }) {
|
||||
const edition = useSession((s) => s.session?.ihasmail?.server?.edition ?? null);
|
||||
if (edition !== "enterprise") {
|
||||
return (
|
||||
<div>
|
||||
<div className="admin-head">
|
||||
<div className="grow">
|
||||
<h1>{t("Tenants")}</h1>
|
||||
<p className="lead">{t("Separate organisations on one server, each with its own people, domains and limits.")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="admin-notice warn">{t("Tenants are a Stalwart Enterprise feature. This server does not report Enterprise, so anyone inside a tenant has only an ordinary user's permissions.")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <EnterpriseTenants selectedId={selectedId} />;
|
||||
}
|
||||
|
||||
function EnterpriseTenants({ selectedId }: { selectedId?: string }) {
|
||||
const [, navigate] = useLocation();
|
||||
const perms = usePermissions();
|
||||
const edition = useSession((s) => s.session?.ihasmail?.server?.edition ?? null);
|
||||
const [text, setText] = useState("");
|
||||
const [query, setQuery] = useState("");
|
||||
const [position, setPosition] = useState(0);
|
||||
@@ -100,10 +119,6 @@ export function TenantsAdmin({ selectedId }: { selectedId?: string }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{edition !== "enterprise" && (
|
||||
<p className="admin-notice warn">{t("Tenants are a Stalwart Enterprise feature. This server does not report Enterprise, so anyone inside a tenant has only an ordinary user's permissions.")}</p>
|
||||
)}
|
||||
|
||||
<div className="admin-toolbar">
|
||||
<label className="admin-search">
|
||||
<Search size={16} aria-hidden="true" />
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { act } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Router } from "wouter";
|
||||
import { memoryLocation } from "wouter/memory-location";
|
||||
import { useSession } from "@/store/session";
|
||||
import type { JmapSession } from "@/jmap/types";
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
const api = vi.hoisted(() => ({ queryTenants: vi.fn(async () => ({ ids: ["t1"], total: 1 })) }));
|
||||
vi.mock("@/lib/adminTenants", async (original) => ({
|
||||
...(await original<typeof import("@/lib/adminTenants")>()),
|
||||
queryTenants: api.queryTenants,
|
||||
getTenants: vi.fn(async () => [{ id: "t1", name: "Acme Corp", quotas: {}, usedDiskQuota: 0 }]),
|
||||
}));
|
||||
|
||||
const { TenantsAdmin } = await import("../TenantsAdmin");
|
||||
|
||||
const PERMS = ["sysTenantGet", "sysTenantQuery", "sysTenantCreate"];
|
||||
const signIn = (edition: string | null) =>
|
||||
useSession.setState({ session: { capabilities: {}, accounts: {}, primaryAccounts: {}, username: "[email protected]", ihasmail: { permissions: PERMS, server: { edition } } } as unknown as JmapSession });
|
||||
|
||||
/** Tenants are managed on Enterprise only; anywhere else the page is the notice and nothing more. */
|
||||
describe("the Tenants page", () => {
|
||||
let host: HTMLDivElement;
|
||||
let root: Root;
|
||||
const render = async () => {
|
||||
const { hook } = memoryLocation({ path: "/admin/tenants" });
|
||||
await act(async () => {
|
||||
root.render(<Router hook={hook}><TenantsAdmin /></Router>);
|
||||
});
|
||||
await act(async () => {});
|
||||
};
|
||||
beforeEach(() => {
|
||||
host = document.createElement("div");
|
||||
document.body.appendChild(host);
|
||||
root = createRoot(host);
|
||||
api.queryTenants.mockClear();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await act(async () => root.unmount());
|
||||
host.remove();
|
||||
});
|
||||
|
||||
for (const edition of ["community", "oss", null]) {
|
||||
it(`shows only the notice on ${edition ?? "a server that reports no edition"}`, async () => {
|
||||
signIn(edition);
|
||||
await render();
|
||||
expect(host.querySelector(".admin-notice.warn")?.textContent).toContain("Tenants are a Stalwart Enterprise feature");
|
||||
expect(host.textContent).not.toContain("New tenant");
|
||||
expect(host.querySelector('input[type="search"]')).toBeNull();
|
||||
expect(host.querySelector(".admin-table")).toBeNull();
|
||||
expect(api.queryTenants).not.toHaveBeenCalled();
|
||||
});
|
||||
}
|
||||
|
||||
it("lists and offers tenants on Enterprise, without the notice", async () => {
|
||||
signIn("enterprise");
|
||||
await render();
|
||||
expect(host.querySelector(".admin-notice.warn")).toBeNull();
|
||||
expect(host.textContent).toContain("New tenant");
|
||||
expect(host.querySelector(".admin-table")?.textContent).toContain("Acme Corp");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user