Files
prowler/ui/components/providers/table/data-table-row-actions.test.tsx
T

389 lines
11 KiB
TypeScript

import { Row } from "@tanstack/react-table";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { ORG_SETUP_PHASE, ORG_WIZARD_STEP } from "@/types/organizations";
import {
PROVIDERS_GROUP_KIND,
PROVIDERS_ROW_TYPE,
ProvidersTableRow,
} from "@/types/providers-table";
const checkConnectionProviderMock = vi.hoisted(() => vi.fn());
vi.mock("@/actions/organizations/organizations", () => ({
updateOrganizationName: vi.fn(),
}));
vi.mock("@/actions/providers/providers", () => ({
checkConnectionProvider: checkConnectionProviderMock,
}));
vi.mock("../forms/delete-form", () => ({
DeleteForm: () => null,
}));
vi.mock("../forms/delete-organization-form", () => ({
DeleteOrganizationForm: () => null,
}));
vi.mock("../forms/edit-name-form", () => ({
EditNameForm: () => null,
}));
vi.mock("@/components/ui", () => ({
useToast: () => ({ toast: vi.fn() }),
}));
vi.mock("@/lib/provider-helpers", () => ({
testProviderConnection: vi.fn(),
}));
import { DataTableRowActions } from "./data-table-row-actions";
const createRow = (hasSecret = false) =>
({
original: {
id: "provider-1",
rowType: PROVIDERS_ROW_TYPE.PROVIDER,
type: "providers",
attributes: {
provider: "aws",
uid: "111111111111",
alias: "AWS App Account",
status: "completed",
resources: 0,
connection: {
connected: true,
last_checked_at: "2025-02-13T11:17:00Z",
},
scanner_args: {
only_logs: false,
excluded_checks: [],
aws_retries_max_attempts: 3,
},
inserted_at: "2025-02-13T11:17:00Z",
updated_at: "2025-02-13T11:17:00Z",
created_by: {
object: "user",
id: "user-1",
},
},
relationships: {
secret: {
data: hasSecret ? { id: "secret-1", type: "secrets" } : null,
},
provider_groups: {
meta: {
count: 0,
},
data: [],
},
},
groupNames: [],
},
}) as unknown as Row<ProvidersTableRow>;
const createOrgRow = () =>
({
original: {
id: "org-1",
rowType: PROVIDERS_ROW_TYPE.ORGANIZATION,
groupKind: PROVIDERS_GROUP_KIND.ORGANIZATION,
name: "My AWS Organization",
externalId: "o-abc123def4",
parentExternalId: null,
organizationId: "org-1",
providerCount: 3,
subRows: [
{
id: "provider-child-1",
rowType: PROVIDERS_ROW_TYPE.PROVIDER,
type: "providers",
attributes: { provider: "aws", uid: "111", alias: null },
relationships: {
secret: { data: { id: "secret-1", type: "secrets" } },
},
},
{
id: "provider-child-2",
rowType: PROVIDERS_ROW_TYPE.PROVIDER,
type: "providers",
attributes: { provider: "aws", uid: "222", alias: null },
relationships: { secret: { data: null } },
},
],
},
}) as unknown as Row<ProvidersTableRow>;
const createOuRow = () =>
({
original: {
id: "ou-1",
rowType: PROVIDERS_ROW_TYPE.ORGANIZATION,
groupKind: PROVIDERS_GROUP_KIND.ORGANIZATION_UNIT,
name: "Production OU",
externalId: "ou-abc123",
parentExternalId: "o-abc123def4",
organizationId: "org-1",
providerCount: 2,
subRows: [
{
id: "provider-ou-child-1",
rowType: PROVIDERS_ROW_TYPE.PROVIDER,
type: "providers",
attributes: { provider: "aws", uid: "333", alias: null },
relationships: {
secret: { data: { id: "secret-2", type: "secrets" } },
},
},
],
},
}) as unknown as Row<ProvidersTableRow>;
describe("DataTableRowActions", () => {
it("renders Add Credentials for provider rows without credentials", async () => {
// Given
const user = userEvent.setup();
render(
<DataTableRowActions
row={createRow(false)}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
// When
await user.click(screen.getByRole("button"));
// Then
expect(screen.getByText("Edit Provider Alias")).toBeInTheDocument();
expect(screen.getByText("Add Credentials")).toBeInTheDocument();
expect(screen.getByText("Test Connection")).toBeInTheDocument();
expect(screen.getByText("Delete Provider")).toBeInTheDocument();
expect(screen.queryByText("Update Credentials")).not.toBeInTheDocument();
});
it("renders Update Credentials for provider rows with credentials", async () => {
// Given
const user = userEvent.setup();
render(
<DataTableRowActions
row={createRow(true)}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
// When
await user.click(screen.getByRole("button"));
// Then
expect(screen.getByText("Update Credentials")).toBeInTheDocument();
expect(screen.queryByText("Add Credentials")).not.toBeInTheDocument();
});
it("renders all 4 organization actions for org rows", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOrgRow()}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
expect(screen.getByText("Edit Organization Name")).toBeInTheDocument();
expect(screen.getByText("Update Credentials")).toBeInTheDocument();
// 1 of 2 child providers has a secret
expect(screen.getByText("Test Connections (1)")).toBeInTheDocument();
expect(screen.getByText("Delete Organization")).toBeInTheDocument();
});
it("renders Delete Organization with destructive styling for org rows", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOrgRow()}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
const deleteItem = screen.getByText("Delete Organization");
// Destructive items are rendered with error text color
const menuItem = deleteItem.closest("[role='menuitem']");
expect(menuItem).toBeInTheDocument();
expect(menuItem).toHaveClass("text-text-error-primary");
});
it("renders only Test Connections and Delete for OU rows", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOuRow()}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
expect(screen.getByText("Test Connections (1)")).toBeInTheDocument();
expect(screen.getByText("Delete Organization Unit")).toBeInTheDocument();
});
it("shows selected provider count in Test Connections when org row has active selection", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOrgRow()}
hasSelection={true}
isRowSelected={false}
testableProviderIds={["provider-child-1", "provider-standalone"]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
// Should show count of selected testable providers (2), not all org children (1)
expect(screen.getByText("Test Connections (2)")).toBeInTheDocument();
expect(screen.queryByText("Test Connections (1)")).not.toBeInTheDocument();
});
it("shows selected provider count in Test Connections when OU row has active selection", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOuRow()}
hasSelection={true}
isRowSelected={false}
testableProviderIds={["provider-ou-child-1", "provider-standalone"]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
// Should show count of selected testable providers (2), not all OU children (1)
expect(screen.getByText("Test Connections (2)")).toBeInTheDocument();
expect(screen.queryByText("Test Connections (1)")).not.toBeInTheDocument();
});
it("does NOT render Edit Organization Name or Update Credentials for OU rows", async () => {
const user = userEvent.setup();
render(
<DataTableRowActions
row={createOuRow()}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={vi.fn()}
/>,
);
await user.click(screen.getByRole("button"));
expect(
screen.queryByText("Edit Organization Name"),
).not.toBeInTheDocument();
expect(screen.queryByText("Update Credentials")).not.toBeInTheDocument();
});
it("opens the shared provider wizard when provider credentials action is selected", async () => {
// Given
const user = userEvent.setup();
const onOpenProviderWizard = vi.fn();
render(
<DataTableRowActions
row={createRow(true)}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={onOpenProviderWizard}
onOpenOrganizationWizard={vi.fn()}
/>,
);
// When
await user.click(screen.getByRole("button"));
await user.click(screen.getByText("Update Credentials"));
// Then
expect(onOpenProviderWizard).toHaveBeenCalledWith({
providerId: "provider-1",
providerType: "aws",
providerUid: "111111111111",
providerAlias: "AWS App Account",
secretId: "secret-1",
mode: "update",
});
});
it("opens the shared organization wizard when org credentials action is selected", async () => {
// Given
const user = userEvent.setup();
const onOpenOrganizationWizard = vi.fn();
render(
<DataTableRowActions
row={createOrgRow()}
hasSelection={false}
isRowSelected={false}
testableProviderIds={[]}
onClearSelection={vi.fn()}
onOpenProviderWizard={vi.fn()}
onOpenOrganizationWizard={onOpenOrganizationWizard}
/>,
);
// When
await user.click(screen.getByRole("button"));
await user.click(screen.getByText("Update Credentials"));
// Then
expect(onOpenOrganizationWizard).toHaveBeenCalledWith({
organizationId: "org-1",
organizationName: "My AWS Organization",
externalId: "o-abc123def4",
targetStep: ORG_WIZARD_STEP.SETUP,
targetPhase: ORG_SETUP_PHASE.ACCESS,
intent: "edit-credentials",
});
});
});