diff --git a/Hippo.Web/ClientApp/src/Shared/RequireAupAgreement.tsx b/Hippo.Web/ClientApp/src/Shared/RequireAupAgreement.tsx index 230f9838..44b63a36 100644 --- a/Hippo.Web/ClientApp/src/Shared/RequireAupAgreement.tsx +++ b/Hippo.Web/ClientApp/src/Shared/RequireAupAgreement.tsx @@ -22,8 +22,11 @@ export const RequireAupAgreement = (props: Props) => { const hasSystemPermission = context.user.permissions.some( (p) => p.role === "System", ); + const hasClusterAdminPermission = context.user.permissions.some( + (p) => p.role === "ClusterAdmin" && p.cluster === clusterName, + ); const hasFinancialAdminPermission = context.user.permissions.some( - (p) => p.role === "FinancialAdmin", + (p) => p.role === "FinancialAdmin" && p.cluster === clusterName, ); const cluster = context.clusters.find((c) => c.name === clusterName); const account = context.accounts.find((a) => a.cluster === clusterName); @@ -77,6 +80,7 @@ export const RequireAupAgreement = (props: Props) => { if ( hasSystemPermission || + hasClusterAdminPermission || !cluster.acceptableUsePolicyUrl || !cluster.acceptableUsePolicyUpdatedOn ) { diff --git a/Hippo.Web/ClientApp/src/Shared/usePermissions.ts b/Hippo.Web/ClientApp/src/Shared/usePermissions.ts index ba35810d..4177fc69 100644 --- a/Hippo.Web/ClientApp/src/Shared/usePermissions.ts +++ b/Hippo.Web/ClientApp/src/Shared/usePermissions.ts @@ -12,8 +12,8 @@ export const usePermissions = () => { ); const canViewGroup = (groupName: string) => { - if (isSystemAdmin || isClusterAdmin) return true; if (!clusterName) return false; + if (isSystemAdmin || isClusterAdmin) return true; const account = accounts.find((a) => a.cluster === clusterName); if (!account) return false; if (account.adminOfGroups.some((g) => g.name === groupName)) return true; @@ -22,8 +22,8 @@ export const usePermissions = () => { }; const canManageGroup = (groupName: string) => { - if (isSystemAdmin) return true; if (!clusterName) return false; + if (isSystemAdmin || isClusterAdmin) return true; const account = accounts.find((a) => a.cluster === clusterName); if (!account) return false; if (account.adminOfGroups.some((g) => g.name === groupName)) return true; diff --git a/Hippo.Web/ClientApp/src/components/Account/Requests.test.tsx b/Hippo.Web/ClientApp/src/components/Account/Requests.test.tsx index e905ce2b..bf145e34 100644 --- a/Hippo.Web/ClientApp/src/components/Account/Requests.test.tsx +++ b/Hippo.Web/ClientApp/src/components/Account/Requests.test.tsx @@ -2,6 +2,7 @@ import { MemoryRouter, Route, Routes } from "react-router-dom"; import { fakeAccounts, + fakeClusterAdminAppContextNoAccount, fakeGroupAdminAppContext, fakeGroups, fakeRequests, @@ -12,17 +13,17 @@ import { responseMap } from "../../test/testHelpers"; import App from "../../App"; import { Requests } from "./Requests"; import { ModalProvider } from "react-modal-hook"; -import { render, screen } from "@testing-library/react"; +import { render, screen, within } from "@testing-library/react"; import "@testing-library/jest-dom"; import userEvent from "@testing-library/user-event"; import { act } from "react"; -import { RequireAupAgreement } from "../../Shared/RequireAupAgreement"; import AppContext from "../../Shared/AppContext"; globalThis.IS_REACT_ACT_ENVIRONMENT = true; const testCluster = fakeAccounts[0].cluster; const approveUrl = `/${testCluster}/approve`; +const pendingRequestCountText = `There are ${fakeRequests.length} request(s) awaiting approval`; beforeEach(() => { const requestResponse = Promise.resolve({ @@ -74,11 +75,11 @@ it("shows pending approvals count", async () => { ); }); expect( - await screen.findByText("There are 2 request(s) awaiting approval"), + await screen.findByText(pendingRequestCountText), ).toBeVisible(); }); -it("shows approval button for each pending account", async () => { +it("shows approval buttons for each request the group admin can manage", async () => { await act(async () => { render( @@ -92,7 +93,7 @@ it("shows approval button for each pending account", async () => { ).toHaveLength(2); }); -it("shows reject button for each pending account", async () => { +it("shows reject buttons for each request the group admin can manage", async () => { await act(async () => { render( @@ -106,6 +107,44 @@ it("shows reject button for each pending account", async () => { ); }); +it("shows action buttons for CreateGroup requests for cluster admins without an account", async () => { + (global as any).Hippo = fakeClusterAdminAppContextNoAccount; + + await act(async () => { + render( + + + , + ); + }); + + expect( + await screen.findByText(pendingRequestCountText), + ).toBeVisible(); + const createGroupCell = (await screen.findAllByText("Create Group")).find( + (element) => element.tagName === "TD", + ); + expect(createGroupCell).toBeDefined(); + const createGroupRow = createGroupCell?.closest("tr"); + expect(createGroupRow).not.toBeNull(); + if (!createGroupRow) { + throw new Error("Expected Create Group request row to be present"); + } + expect( + await screen.findAllByRole("button", { name: "Approve" }), + ).toHaveLength(fakeRequests.length); + expect( + await screen.findAllByRole("button", { name: "Reject" }), + ).toHaveLength(fakeRequests.length); + expect( + within(createGroupRow).getByRole("button", { name: "Approve" }), + ).toBeVisible(); + expect( + within(createGroupRow).getByRole("button", { name: "Reject" }), + ).toBeVisible(); + expect(screen.queryByText("Acceptable Use Policy")).not.toBeInTheDocument(); +}); + it("displays dialog when reject is clicked", async () => { const user = userEvent.setup(); await act(async () => { @@ -123,7 +162,7 @@ it("displays dialog when reject is clicked", async () => { ); }); expect( - await screen.findByText("There are 2 request(s) awaiting approval"), + await screen.findByText(pendingRequestCountText), ).toBeVisible(); await act(async () => { @@ -149,7 +188,7 @@ it("calls approve and filters list when approve is clicked", async () => { ); }); expect( - await screen.findByText("There are 2 request(s) awaiting approval"), + await screen.findByText(pendingRequestCountText), ).toBeVisible(); await act(async () => { diff --git a/Hippo.Web/ClientApp/src/test/mockData.ts b/Hippo.Web/ClientApp/src/test/mockData.ts index d4b81cba..eaff4c5a 100644 --- a/Hippo.Web/ClientApp/src/test/mockData.ts +++ b/Hippo.Web/ClientApp/src/test/mockData.ts @@ -8,6 +8,7 @@ import { PuppetUserRecord, RequestModel, AccountRequestDataModel, + GroupRequestDataModel, RequestStatus, GroupAccountModel } from "../types"; @@ -153,6 +154,25 @@ export const fakeRequests: RequestModel[] = [ accessTypes: ["SshKey"], } as AccountRequestDataModel, }, + { + id: 3, + requesterEmail: fakeUser.email, + requesterName: fakeUser.name, + action: "CreateGroup", + groupModel: { + id: 3, + name: "group3", + displayName: "Group 3", + admins: [fakeAdminGroupAccount], + data: {} as PuppetGroupRecord, + }, + status: RequestStatus.PendingApproval, + cluster: "caesfarm", + data: { + name: "group3", + displayName: "Group 3", + } as GroupRequestDataModel, + }, ]; const fakeCluster: ClusterModel = { @@ -216,6 +236,25 @@ export const fakeGroupAdminAppContext: AppContextShape = { featureFlags: fakeFeatureFlags, }; +export const fakeClusterAdminAppContextNoAccount: AppContextShape = { + antiForgeryToken: "fakeAntiForgeryToken", + user: { + detail: { + ...fakeUser, + }, + permissions: [ + { + role: "ClusterAdmin", + cluster: "caesfarm", + }, + ], + }, + accounts: [], + clusters: [fakeCluster], + openRequests: fakeRequests, + featureFlags: fakeFeatureFlags, +}; + export const fakeSetContext = vi.fn();