mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 12:24:45 +00:00
387 lines
12 KiB
TypeScript
387 lines
12 KiB
TypeScript
import { act, render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const { addProvider, updateProvider, getInstalledRegistryProviderOptions } =
|
|
vi.hoisted(() => ({
|
|
addProvider: vi.fn(),
|
|
updateProvider: vi.fn(),
|
|
getInstalledRegistryProviderOptions: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/hooks/use-auth", () => ({
|
|
useAuth: () => ({ permissions: { manage_registry: true } }),
|
|
}));
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
useRouter: () => ({ push: vi.fn() }),
|
|
}));
|
|
vi.mock("@/actions/providers/providers", () => ({
|
|
addProvider,
|
|
updateProvider,
|
|
}));
|
|
vi.mock("@/actions/providers/registry-provider", () => ({
|
|
addRegistryProvider: vi.fn(),
|
|
}));
|
|
vi.mock("@/actions/registry/registry", () => ({
|
|
getInstalledRegistryProviderOptions,
|
|
}));
|
|
|
|
import { useCloudUpgradeStore } from "@/store/cloud-upgrade/store";
|
|
|
|
import { ConnectAccountForm } from "./connect-account-form";
|
|
|
|
describe("provider account aliases", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
getInstalledRegistryProviderOptions.mockResolvedValue({
|
|
status: "access_denied",
|
|
});
|
|
});
|
|
|
|
it("saves an alias changed after an account was already created", async () => {
|
|
// Given
|
|
const onSuccess = vi.fn();
|
|
const user = userEvent.setup();
|
|
const account = {
|
|
id: "existing",
|
|
attributes: { provider: "github", uid: "octocat", alias: "Original" },
|
|
};
|
|
addProvider.mockResolvedValue({ data: account });
|
|
updateProvider.mockResolvedValue({
|
|
data: {
|
|
...account,
|
|
attributes: { ...account.attributes, alias: "Edited" },
|
|
},
|
|
});
|
|
render(<ConnectAccountForm onSuccess={onSuccess} />);
|
|
await user.click(screen.getByRole("option", { name: "GitHub" }));
|
|
await user.type(
|
|
screen.getByRole("textbox", { name: "Username/Organization" }),
|
|
"octocat",
|
|
);
|
|
const alias = screen.getByRole("textbox", {
|
|
name: "Provider alias (optional)",
|
|
});
|
|
await user.type(alias, "Original");
|
|
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
await waitFor(() => expect(onSuccess).toHaveBeenCalledOnce());
|
|
|
|
// When
|
|
await user.clear(alias);
|
|
await user.type(alias, "Edited");
|
|
await user.click(screen.getByRole("button", { name: "Next" }));
|
|
|
|
// Then
|
|
await waitFor(() =>
|
|
expect(onSuccess).toHaveBeenLastCalledWith({
|
|
id: "existing",
|
|
providerType: "github",
|
|
uid: "octocat",
|
|
alias: "Edited",
|
|
}),
|
|
);
|
|
expect(addProvider).toHaveBeenCalledOnce();
|
|
expect(Object.fromEntries(updateProvider.mock.calls[0][0])).toMatchObject({
|
|
providerId: "existing",
|
|
providerAlias: "Edited",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Registry provider source tabs", () => {
|
|
it("explains Registry setup in the Registry tab without claiming a connection failure", async () => {
|
|
// Given
|
|
const user = userEvent.setup();
|
|
getInstalledRegistryProviderOptions.mockResolvedValue({
|
|
status: "onboarding",
|
|
});
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// When
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
|
|
// Then
|
|
expect(
|
|
await screen.findByText("Registry is not connected yet"),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByRole("button", { name: "Unlock Registry" }),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("Registry providers could not be loaded"),
|
|
).not.toBeInTheDocument();
|
|
|
|
// When / Then: the built-in list never gets a banner pushed above it.
|
|
await user.click(screen.getByRole("tab", { name: "All providers" }));
|
|
expect(
|
|
screen.queryByText("Registry is not connected yet"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("offers the Registry paid plan upgrade in the Registry tab while keeping built-in providers usable", async () => {
|
|
// Given
|
|
getInstalledRegistryProviderOptions.mockResolvedValue({
|
|
status: "subscription_required",
|
|
});
|
|
const user = userEvent.setup();
|
|
useCloudUpgradeStore.getState().closeCloudUpgrade();
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// When
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
await user.click(
|
|
await screen.findByRole("button", { name: "Unlock Registry" }),
|
|
);
|
|
|
|
// Then
|
|
expect(useCloudUpgradeStore.getState().activeFeature).toBe("registry");
|
|
|
|
// When
|
|
await user.click(screen.getByRole("tab", { name: "All providers" }));
|
|
|
|
// Then
|
|
expect(
|
|
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByRole("button", { name: "Unlock Registry" }),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("Registry providers could not be loaded"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("button", { name: "Retry Registry providers" }),
|
|
).not.toBeInTheDocument();
|
|
useCloudUpgradeStore.getState().closeCloudUpgrade();
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "true");
|
|
vi.stubEnv("UI_REGISTRY_ENABLED", "true");
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it("shows both source tabs before discovery settles and loads Registry providers in place", async () => {
|
|
// Given: Registry discovery is still in flight.
|
|
const user = userEvent.setup();
|
|
let settleDiscovery: (result: {
|
|
status: "ready";
|
|
options: { type: string; label: string }[];
|
|
}) => void = () => {};
|
|
getInstalledRegistryProviderOptions.mockReturnValue(
|
|
new Promise((resolve) => {
|
|
settleDiscovery = resolve;
|
|
}),
|
|
);
|
|
|
|
// When
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// Then: the tabs are there from the first render.
|
|
expect(screen.getByRole("tab", { name: "All providers" })).toHaveAttribute(
|
|
"aria-selected",
|
|
"true",
|
|
);
|
|
expect(screen.getByRole("tab", { name: "Registry" })).toBeVisible();
|
|
|
|
// When
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
|
|
// Then
|
|
expect(
|
|
screen.getByRole("status", { name: "Loading Registry providers" }),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByText("No Registry providers available."),
|
|
).not.toBeInTheDocument();
|
|
|
|
// When
|
|
await act(async () => {
|
|
settleDiscovery({
|
|
status: "ready",
|
|
options: [{ type: "acme", label: "Acme Cloud" }],
|
|
});
|
|
});
|
|
|
|
// Then
|
|
expect(
|
|
screen.getByRole("option", { name: /Acme Cloud Registry/ }),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByRole("status", { name: "Loading Registry providers" }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("never mentions Registry outside Cloud, even when discovery would fail", async () => {
|
|
// Given
|
|
vi.stubEnv("UI_CLOUD_ENABLED", "false");
|
|
getInstalledRegistryProviderOptions.mockRejectedValue(new Error("network"));
|
|
|
|
// When
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// Then
|
|
expect(
|
|
await screen.findByRole("option", { name: /Amazon Web Services/ }),
|
|
).toBeVisible();
|
|
expect(getInstalledRegistryProviderOptions).not.toHaveBeenCalled();
|
|
expect(
|
|
screen.queryByText("Registry providers could not be loaded"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("tab", { name: "Registry" }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("hides the source tabs without asking discovery when the deployment has Registry off", () => {
|
|
// Given
|
|
vi.stubEnv("UI_REGISTRY_ENABLED", "false");
|
|
|
|
// When
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// Then
|
|
expect(getInstalledRegistryProviderOptions).not.toHaveBeenCalled();
|
|
expect(screen.queryByRole("tablist")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
it("hides the source tabs when discovery denies the user access", async () => {
|
|
// Given
|
|
getInstalledRegistryProviderOptions.mockResolvedValue({
|
|
status: "access_denied",
|
|
});
|
|
|
|
// When
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
await waitFor(() =>
|
|
expect(getInstalledRegistryProviderOptions).toHaveBeenCalled(),
|
|
);
|
|
|
|
// Then
|
|
expect(
|
|
screen.getByRole("option", { name: /Amazon Web Services/ }),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByRole("tab", { name: "Registry" }),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("tab", { name: "All providers" }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("offers a retry in the Registry tab when access is unknown", async () => {
|
|
// Given
|
|
const user = userEvent.setup();
|
|
getInstalledRegistryProviderOptions
|
|
.mockResolvedValueOnce({ status: "unknown" })
|
|
.mockResolvedValueOnce({ status: "ready", options: [] });
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
|
|
// When
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
|
|
// Then
|
|
expect(
|
|
await screen.findByText("Registry providers could not be loaded"),
|
|
).toBeVisible();
|
|
|
|
// When
|
|
await user.click(
|
|
screen.getByRole("button", { name: "Retry Registry providers" }),
|
|
);
|
|
|
|
// Then
|
|
expect(
|
|
await screen.findByText("No Registry providers available."),
|
|
).toBeVisible();
|
|
expect(
|
|
screen.queryByText("Registry providers could not be loaded"),
|
|
).not.toBeInTheDocument();
|
|
expect(getInstalledRegistryProviderOptions).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("shows a retry in flight, ignores repeat clicks and keeps focus on the button", async () => {
|
|
// Given
|
|
const user = userEvent.setup();
|
|
let settleRetry: (result: { status: "error" }) => void = () => {};
|
|
getInstalledRegistryProviderOptions
|
|
.mockResolvedValueOnce({ status: "error" })
|
|
.mockReturnValueOnce(
|
|
new Promise((resolve) => {
|
|
settleRetry = resolve;
|
|
}),
|
|
);
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
const retry = await screen.findByRole("button", {
|
|
name: "Retry Registry providers",
|
|
});
|
|
|
|
// When
|
|
await user.click(retry);
|
|
await user.click(retry);
|
|
|
|
// Then: the warning stays mounted, so the pressed button is never lost.
|
|
expect(retry).toHaveTextContent("Retrying…");
|
|
expect(retry).toHaveAttribute("aria-disabled", "true");
|
|
expect(retry).toHaveFocus();
|
|
expect(getInstalledRegistryProviderOptions).toHaveBeenCalledTimes(2);
|
|
|
|
// When: the retry fails again
|
|
settleRetry({ status: "error" });
|
|
|
|
// Then
|
|
await waitFor(() =>
|
|
expect(retry).toHaveTextContent("Retry Registry providers"),
|
|
);
|
|
expect(retry).not.toHaveAttribute("aria-disabled", "true");
|
|
});
|
|
|
|
it("keeps a retry in flight when an artifact change reloads discovery meanwhile", async () => {
|
|
// Given
|
|
const user = userEvent.setup();
|
|
let settleRetry: (result: { status: "error" }) => void = () => {};
|
|
getInstalledRegistryProviderOptions
|
|
.mockResolvedValueOnce({ status: "error" })
|
|
.mockReturnValueOnce(
|
|
new Promise((resolve) => {
|
|
settleRetry = resolve;
|
|
}),
|
|
)
|
|
.mockResolvedValueOnce({ status: "error" });
|
|
render(<ConnectAccountForm onSuccess={vi.fn()} />);
|
|
await user.click(screen.getByRole("tab", { name: "Registry" }));
|
|
const retry = await screen.findByRole("button", {
|
|
name: "Retry Registry providers",
|
|
});
|
|
await user.click(retry);
|
|
|
|
// When: an unrelated reload settles before the retry does
|
|
window.dispatchEvent(new CustomEvent("registry-artifacts-changed"));
|
|
await waitFor(() =>
|
|
expect(getInstalledRegistryProviderOptions).toHaveBeenCalledTimes(3),
|
|
);
|
|
await user.click(retry);
|
|
|
|
// Then: only the retry itself may end the retry
|
|
expect(retry).toHaveTextContent("Retrying…");
|
|
expect(getInstalledRegistryProviderOptions).toHaveBeenCalledTimes(3);
|
|
|
|
// When
|
|
settleRetry({ status: "error" });
|
|
|
|
// Then
|
|
await waitFor(() =>
|
|
expect(retry).toHaveTextContent("Retry Registry providers"),
|
|
);
|
|
});
|
|
});
|