import { act, renderHook, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { useOrgSetupStore } from "@/store/organizations/store"; import { useProviderWizardStore } from "@/store/provider-wizard/store"; import { ORG_WIZARD_STEP } from "@/types/organizations"; import { PROVIDER_WIZARD_MODE, PROVIDER_WIZARD_STEP, } from "@/types/provider-wizard"; import type { ProviderWizardInitialData } from "../types"; import { useProviderWizardController } from "./use-provider-wizard-controller"; const { refreshMock, requestOpenOnWizardCloseMock } = vi.hoisted(() => ({ refreshMock: vi.fn(), requestOpenOnWizardCloseMock: vi.fn(), })); vi.mock("next/navigation", () => ({ useRouter: () => ({ refresh: refreshMock, }), })); vi.mock("@/store/onboarding-checkpoint", () => ({ useOnboardingCheckpointStore: { getState: () => ({ requestOpenOnWizardClose: requestOpenOnWizardCloseMock, }), }, })); vi.mock("next-auth/react", () => ({ useSession: () => ({ data: null, status: "unauthenticated", }), })); describe("useProviderWizardController", () => { beforeEach(() => { vi.useRealTimers(); vi.clearAllMocks(); requestOpenOnWizardCloseMock.mockClear(); sessionStorage.clear(); localStorage.clear(); // Checkpoint is Cloud-only. vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true"); useProviderWizardStore.getState().reset(); useOrgSetupStore.getState().reset(); }); it("refreshes providers data when the wizard closes", () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); // When act(() => { result.current.handleClose(); }); // Then expect(onOpenChange).toHaveBeenCalledWith(false); expect(refreshMock).toHaveBeenCalledTimes(1); }); it("requests the onboarding checkpoint with providerConnected:true when a provider was created", () => { const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); act(() => { useProviderWizardStore.getState().setProvider({ id: "provider-1", type: "aws", uid: "111111111111", alias: "production", }); }); act(() => { result.current.handleClose(); }); // providerId is read before the store reset clears it. expect(requestOpenOnWizardCloseMock).toHaveBeenCalledWith({ providerConnected: true, }); }); it("requests the onboarding checkpoint with providerConnected:false when no provider was created", () => { const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); act(() => { result.current.handleClose(); }); expect(requestOpenOnWizardCloseMock).toHaveBeenCalledWith({ providerConnected: false, }); }); it("does not request the onboarding checkpoint in self-hosted (OSS) deployments", () => { vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false"); const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); act(() => { useProviderWizardStore.getState().setProvider({ id: "provider-1", type: "aws", uid: "111111111111", alias: "production", }); }); act(() => { result.current.handleClose(); }); // Checkpoint stays untouched, but the close still refreshes. expect(requestOpenOnWizardCloseMock).not.toHaveBeenCalled(); expect(refreshMock).toHaveBeenCalledTimes(1); }); it("hydrates update mode when initial data is provided", async () => { // Given const onOpenChange = vi.fn(); // When const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, initialData: { providerId: "provider-1", providerType: "aws", providerUid: "111111111111", providerAlias: "production", secretId: "secret-1", mode: PROVIDER_WIZARD_MODE.UPDATE, }, }), ); // Then await waitFor(() => { expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.CREDENTIALS); }); expect(result.current.modalTitle).toBe("Update Provider Credentials"); expect(result.current.isProviderFlow).toBe(true); expect(result.current.docsLink).toBe( "https://goto.prowler.com/provider-aws", ); const state = useProviderWizardStore.getState(); expect(state.providerId).toBe("provider-1"); expect(state.providerType).toBe("aws"); expect(state.providerUid).toBe("111111111111"); expect(state.providerAlias).toBe("production"); expect(state.secretId).toBe("secret-1"); expect(state.mode).toBe(PROVIDER_WIZARD_MODE.UPDATE); }); it("switches into and out of organizations flow", () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); // When act(() => { result.current.openOrganizationsFlow(); }); // Then expect(result.current.wizardVariant).toBe("organizations"); expect(result.current.isProviderFlow).toBe(false); expect(result.current.orgCurrentStep).toBe(ORG_WIZARD_STEP.SETUP); expect(result.current.docsLink).toBe( "https://docs.prowler.com/user-guide/tutorials/prowler-cloud-aws-organizations", ); // When act(() => { result.current.backToProviderFlow(); }); // Then expect(result.current.wizardVariant).toBe("provider"); expect(result.current.isProviderFlow).toBe(true); expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.CONNECT); }); it("moves to launch step after a successful connection test in add mode", () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); // When act(() => { result.current.setCurrentStep(PROVIDER_WIZARD_STEP.TEST); result.current.handleTestSuccess(); }); // Then expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.LAUNCH); expect(onOpenChange).not.toHaveBeenCalled(); }); it("closes the wizard after a successful connection test in update mode", async () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, initialData: { providerId: "provider-1", providerType: "aws", providerUid: "111111111111", providerAlias: "production", secretId: "secret-1", mode: PROVIDER_WIZARD_MODE.UPDATE, }, }), ); await waitFor(() => { expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.CREDENTIALS); }); // When act(() => { result.current.setCurrentStep(PROVIDER_WIZARD_STEP.TEST); result.current.handleTestSuccess(); }); // Credential rotation skips the launch/schedule step. expect(onOpenChange).toHaveBeenCalledWith(false); expect(refreshMock).toHaveBeenCalledTimes(1); expect(result.current.currentStep).not.toBe(PROVIDER_WIZARD_STEP.LAUNCH); }); it("does not override launch footer config in the controller", () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); // When act(() => { result.current.setCurrentStep(PROVIDER_WIZARD_STEP.LAUNCH); }); // Then expect(result.current.resolvedFooterConfig.showAction).toBe(false); expect(result.current.resolvedFooterConfig.showBack).toBe(false); expect(onOpenChange).not.toHaveBeenCalled(); }); it("does not reset organizations step when org store updates while modal is open", () => { // Given const onOpenChange = vi.fn(); const { result } = renderHook(() => useProviderWizardController({ open: true, onOpenChange, }), ); act(() => { result.current.openOrganizationsFlow(); result.current.setOrgCurrentStep(ORG_WIZARD_STEP.VALIDATE); }); // When act(() => { useOrgSetupStore .getState() .setOrganization("org-1", "My Org", "o-abc123def4"); useOrgSetupStore.getState().setDiscovery("disc-1", { roots: [], organizational_units: [], accounts: [], }); }); // Then expect(result.current.wizardVariant).toBe("organizations"); expect(result.current.orgCurrentStep).toBe(ORG_WIZARD_STEP.VALIDATE); }); it("does not rehydrate wizard state when initial data changes while modal remains open", async () => { // Given const onOpenChange = vi.fn(); const { result, rerender } = renderHook( ({ open, initialData, }: { open: boolean; initialData?: ProviderWizardInitialData; }) => useProviderWizardController({ open, onOpenChange, initialData, }), { initialProps: { open: true, initialData: { providerId: "provider-1", providerType: "gcp", providerUid: "project-123", providerAlias: "gcp-main", secretId: null, mode: PROVIDER_WIZARD_MODE.ADD, }, }, }, ); await waitFor(() => { expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.CREDENTIALS); }); act(() => { useProviderWizardStore.getState().setVia("service-account"); result.current.setCurrentStep(PROVIDER_WIZARD_STEP.TEST); }); // Provider data refreshes while modal is still open — user progress must be kept. rerender({ open: true, initialData: { providerId: "provider-1", providerType: "gcp", providerUid: "project-123", providerAlias: "gcp-main", secretId: "secret-1", mode: PROVIDER_WIZARD_MODE.UPDATE, }, }); expect(result.current.currentStep).toBe(PROVIDER_WIZARD_STEP.TEST); expect(useProviderWizardStore.getState().via).toBe("service-account"); }); });