mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
5b9824c379
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
285 lines
8.0 KiB
TypeScript
285 lines
8.0 KiB
TypeScript
import { render, screen, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { FILTER_FIELD } from "@/types/filters";
|
|
|
|
import { AccountsSelector } from "./accounts-selector";
|
|
|
|
const multiSelectContentSpy = vi.fn();
|
|
const multiSelectSpy = vi.fn();
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
useSearchParams: () => new URLSearchParams(),
|
|
}));
|
|
|
|
vi.mock("@/hooks/use-url-filters", () => ({
|
|
useUrlFilters: () => ({
|
|
navigateWithParams: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("@/components/icons/providers-badge", () => ({
|
|
AWSProviderBadge: () => <span>AWS</span>,
|
|
AzureProviderBadge: () => <span>Azure</span>,
|
|
GCPProviderBadge: () => <span>GCP</span>,
|
|
CloudflareProviderBadge: () => <span>Cloudflare</span>,
|
|
GitHubProviderBadge: () => <span>GitHub</span>,
|
|
GoogleWorkspaceProviderBadge: () => <span>Google Workspace</span>,
|
|
IacProviderBadge: () => <span>IaC</span>,
|
|
ImageProviderBadge: () => <span>Image</span>,
|
|
KS8ProviderBadge: () => <span>Kubernetes</span>,
|
|
M365ProviderBadge: () => <span>M365</span>,
|
|
MongoDBAtlasProviderBadge: () => <span>MongoDB Atlas</span>,
|
|
OpenStackProviderBadge: () => <span>OpenStack</span>,
|
|
OracleCloudProviderBadge: () => <span>Oracle Cloud</span>,
|
|
AlibabaCloudProviderBadge: () => <span>Alibaba Cloud</span>,
|
|
VercelProviderBadge: () => <span>Vercel</span>,
|
|
OktaProviderBadge: () => <span>Okta</span>,
|
|
}));
|
|
|
|
vi.mock("@/components/shadcn/select/multiselect", () => ({
|
|
MultiSelect: ({
|
|
children,
|
|
open,
|
|
onOpenChange,
|
|
}: {
|
|
children: React.ReactNode;
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
}) => {
|
|
multiSelectSpy({ open });
|
|
return (
|
|
<div data-open={String(open)}>
|
|
<button type="button" onClick={() => onOpenChange?.(true)}>
|
|
Open selector
|
|
</button>
|
|
{children}
|
|
</div>
|
|
);
|
|
},
|
|
MultiSelectTrigger: ({ children }: { children: React.ReactNode }) => (
|
|
<div data-testid="trigger">{children}</div>
|
|
),
|
|
MultiSelectValue: ({ placeholder }: { placeholder: string }) => (
|
|
<span>{placeholder}</span>
|
|
),
|
|
MultiSelectContent: ({
|
|
children,
|
|
search,
|
|
}: {
|
|
children: React.ReactNode;
|
|
search?: unknown;
|
|
}) => {
|
|
multiSelectContentSpy(search);
|
|
return <div>{children}</div>;
|
|
},
|
|
MultiSelectItem: ({
|
|
children,
|
|
disabled,
|
|
value,
|
|
keywords,
|
|
onSelect,
|
|
}: {
|
|
children: React.ReactNode;
|
|
disabled?: boolean;
|
|
value: string;
|
|
keywords?: string[];
|
|
onSelect?: (value: string) => void;
|
|
}) => (
|
|
<button
|
|
type="button"
|
|
data-value={value}
|
|
data-keywords={keywords?.join("|")}
|
|
data-disabled={disabled ? "true" : "false"}
|
|
onClick={() => onSelect?.(value)}
|
|
>
|
|
{children}
|
|
</button>
|
|
),
|
|
}));
|
|
|
|
const providers = [
|
|
{
|
|
id: "provider-1",
|
|
type: "providers" as const,
|
|
attributes: {
|
|
provider: "aws" as const,
|
|
uid: "123456789012",
|
|
alias: "Production AWS",
|
|
status: "completed" as const,
|
|
resources: 0,
|
|
connection: {
|
|
connected: true,
|
|
last_checked_at: "2026-04-13T00:00:00Z",
|
|
},
|
|
scanner_args: {
|
|
only_logs: false,
|
|
excluded_checks: [],
|
|
aws_retries_max_attempts: 3,
|
|
},
|
|
inserted_at: "2026-04-13T00:00:00Z",
|
|
updated_at: "2026-04-13T00:00:00Z",
|
|
created_by: {
|
|
object: "user",
|
|
id: "user-1",
|
|
},
|
|
},
|
|
relationships: {
|
|
secret: {
|
|
data: null,
|
|
},
|
|
provider_groups: {
|
|
meta: {
|
|
count: 0,
|
|
},
|
|
data: [],
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
describe("AccountsSelector", () => {
|
|
it("passes searchable dropdown defaults to MultiSelectContent", () => {
|
|
render(<AccountsSelector providers={providers} />);
|
|
|
|
expect(multiSelectContentSpy).toHaveBeenCalledWith({
|
|
placeholder: "Search Providers...",
|
|
emptyMessage: "No Providers found.",
|
|
});
|
|
expect(screen.getByText("All Providers")).toBeInTheDocument();
|
|
expect(screen.getByText("Production AWS")).toBeInTheDocument();
|
|
});
|
|
|
|
it("supports contextual placeholder and empty-selection copy", () => {
|
|
render(
|
|
<AccountsSelector
|
|
providers={providers}
|
|
placeholder="Select a Provider"
|
|
emptySelectionLabel="No provider selected"
|
|
clearSelectionLabel="Clear provider selection"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText("Select a Provider")).toBeInTheDocument();
|
|
expect(screen.getByText("No provider selected")).toBeInTheDocument();
|
|
});
|
|
|
|
it("allows disabling search explicitly", () => {
|
|
render(<AccountsSelector providers={providers} search={false} />);
|
|
|
|
expect(multiSelectContentSpy).toHaveBeenLastCalledWith(false);
|
|
});
|
|
|
|
it("passes visible account labels as search keywords instead of only the internal id", () => {
|
|
render(<AccountsSelector providers={providers} />);
|
|
|
|
expect(
|
|
screen.getByText("Production AWS").closest("[data-value]"),
|
|
).toHaveAttribute(
|
|
"data-keywords",
|
|
expect.stringContaining("Production AWS"),
|
|
);
|
|
expect(
|
|
screen.getByText("Production AWS").closest("[data-value]"),
|
|
).toHaveAttribute("data-keywords", expect.stringContaining("123456789012"));
|
|
});
|
|
|
|
it("can use provider UID values for pages whose API filters by provider_uid__in", () => {
|
|
render(
|
|
<AccountsSelector
|
|
providers={providers}
|
|
filterKey={FILTER_FIELD.PROVIDER_UID}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByText("Production AWS").closest("[data-value]"),
|
|
).toHaveAttribute("data-value", "123456789012");
|
|
});
|
|
|
|
it("disables select all when every account is already shown", () => {
|
|
render(<AccountsSelector providers={providers} />);
|
|
|
|
expect(
|
|
screen.getByRole("option", { name: /select all Providers/i }),
|
|
).toHaveAttribute("aria-disabled", "true");
|
|
expect(screen.getByText("All selected")).toBeInTheDocument();
|
|
});
|
|
|
|
it("marks configured account values as disabled", () => {
|
|
render(
|
|
<AccountsSelector
|
|
providers={providers}
|
|
disabledValues={["provider-1"]}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByText("Production AWS").closest("[data-value]"),
|
|
).toHaveAttribute("data-disabled", "true");
|
|
expect(screen.getByText("Disconnected")).toBeInTheDocument();
|
|
});
|
|
|
|
it("can close the dropdown after selecting a launch-scan provider", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<AccountsSelector
|
|
providers={providers}
|
|
closeOnSelect
|
|
onBatchChange={vi.fn()}
|
|
selectedValues={[]}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByRole("button", { name: /open selector/i }));
|
|
expect(multiSelectSpy).toHaveBeenLastCalledWith({ open: true });
|
|
|
|
await user.click(screen.getByRole("button", { name: /production aws/i }));
|
|
|
|
expect(multiSelectSpy).toHaveBeenLastCalledWith({ open: false });
|
|
});
|
|
|
|
it("shows the provider icon next to the name in the trigger for a single selection", async () => {
|
|
render(
|
|
<AccountsSelector
|
|
providers={providers}
|
|
onBatchChange={vi.fn()}
|
|
selectedValues={["provider-1"]}
|
|
/>,
|
|
);
|
|
|
|
const trigger = screen.getByTestId("trigger");
|
|
expect(await within(trigger).findByText("AWS")).toBeInTheDocument();
|
|
expect(within(trigger).getByText("Production AWS")).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders one icon per selected account without deduping by provider type", async () => {
|
|
const secondAws = {
|
|
...providers[0],
|
|
id: "provider-2",
|
|
attributes: {
|
|
...providers[0].attributes,
|
|
uid: "999999999999",
|
|
alias: "Staging AWS",
|
|
},
|
|
};
|
|
|
|
render(
|
|
<AccountsSelector
|
|
providers={[providers[0], secondAws]}
|
|
onBatchChange={vi.fn()}
|
|
selectedValues={["provider-1", "provider-2"]}
|
|
/>,
|
|
);
|
|
|
|
const trigger = screen.getByTestId("trigger");
|
|
// Two AWS accounts -> two AWS icons in the trigger (no dedupe).
|
|
expect(await within(trigger).findAllByText("AWS")).toHaveLength(2);
|
|
expect(
|
|
within(trigger).getByText("2 Providers selected"),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|