fix(ui): reuse provider secret on credentials retry

- Switch credentials step to update flow whenever a provider secret exists

- Persist loaded provider secret id during connection test step

- Add regression tests for add->retry flow after failed role authentication
This commit is contained in:
alejandrobailo committed 2026-02-25 10:17:57 +01:00
1 parent c05ab7a388
commit 7ca08a8367
4 files changed
+155 -6

No files matched your search

@@ -0,0 +1,79 @@
import { render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useProviderWizardStore } from "@/store/provider-wizard/store";
import { PROVIDER_WIZARD_MODE } from "@/types/provider-wizard";
import { CredentialsStep } from "./credentials-step";
vi.mock("../../workflow/forms", () => ({
AddViaCredentialsForm: () => <div>add-via-credentials-form</div>,
AddViaRoleForm: () => <div>add-via-role-form</div>,
UpdateViaCredentialsForm: () => <div>update-via-credentials-form</div>,
UpdateViaRoleForm: () => <div>update-via-role-form</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/aws", () => ({
SelectViaAWS: () => <div>select-via-aws</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/alibabacloud", () => ({
SelectViaAlibabaCloud: () => <div>select-via-alibabacloud</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/cloudflare", () => ({
SelectViaCloudflare: () => <div>select-via-cloudflare</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/gcp", () => ({
AddViaServiceAccountForm: () => <div>add-via-service-account-form</div>,
SelectViaGCP: () => <div>select-via-gcp</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/github", () => ({
SelectViaGitHub: () => <div>select-via-github</div>,
}));
vi.mock("../../workflow/forms/select-credentials-type/m365", () => ({
SelectViaM365: () => <div>select-via-m365</div>,
}));
vi.mock("../../workflow/forms/update-via-service-account-key-form", () => ({
UpdateViaServiceAccountForm: () => (
<div>update-via-service-account-form</div>
),
}));
describe("CredentialsStep", () => {
beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
useProviderWizardStore.getState().reset();
});
it("renders update role form when secret already exists in add mode", () => {
// Given
useProviderWizardStore.setState({
providerId: "provider-1",
providerType: "aws",
providerUid: "111111111111",
providerAlias: "Production",
via: "role",
secretId: "secret-1",
mode: PROVIDER_WIZARD_MODE.ADD,
});
// When
render(
<CredentialsStep
onNext={vi.fn()}
onBack={vi.fn()}
onFooterChange={vi.fn()}
/>,
);
// Then
expect(screen.getByText("update-via-role-form")).toBeInTheDocument();
expect(screen.queryByText("add-via-role-form")).not.toBeInTheDocument();
});
});
@@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
import { getProviderFormType } from "@/lib/provider-helpers";
import { useProviderWizardStore } from "@/store/provider-wizard/store";
import { PROVIDER_WIZARD_MODE } from "@/types/provider-wizard";
import { ProviderType } from "@/types/providers";
import {
@@ -39,7 +38,7 @@ export function CredentialsStep({
onBack,
onFooterChange,
}: CredentialsStepProps) {
const { providerId, providerType, providerUid, via, secretId, mode, setVia } =
const { providerId, providerType, providerUid, via, secretId, setVia } =
useProviderWizardStore();
const [isFormLoading, setIsFormLoading] = useState(false);
const [isFormValid, setIsFormValid] = useState(false);
@@ -50,8 +49,7 @@ export function CredentialsStep({
providerType && providerId
? getProviderFormType(providerType, via || undefined)
: null;
const shouldUseUpdateForms =
mode === PROVIDER_WIZARD_MODE.UPDATE && Boolean(secretId);
const shouldUseUpdateForms = Boolean(secretId);
const handleBack = () => {
if (via) {
@@ -0,0 +1,67 @@
import { render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useProviderWizardStore } from "@/store/provider-wizard/store";
import { PROVIDER_WIZARD_MODE } from "@/types/provider-wizard";
import { TestConnectionStep } from "./test-connection-step";
const { getProviderMock } = vi.hoisted(() => ({
getProviderMock: vi.fn(),
}));
vi.mock("@/actions/providers", () => ({
getProvider: getProviderMock,
}));
vi.mock("../../workflow/forms/test-connection-form", () => ({
TestConnectionForm: () => <div data-testid="test-connection-form" />,
}));
describe("TestConnectionStep", () => {
beforeEach(() => {
sessionStorage.clear();
localStorage.clear();
getProviderMock.mockReset();
useProviderWizardStore.getState().reset();
});
it("stores provider secret id after loading provider data", async () => {
// Given
useProviderWizardStore.setState({
providerId: "provider-1",
providerType: "aws",
mode: PROVIDER_WIZARD_MODE.ADD,
});
getProviderMock.mockResolvedValue({
data: {
id: "provider-1",
attributes: {
uid: "111111111111",
provider: "aws",
alias: "Production",
connection: { connected: false, last_checked_at: null },
scanner_args: {},
},
relationships: {
secret: { data: { type: "provider-secrets", id: "secret-1" } },
},
},
});
// When
render(
<TestConnectionStep
onSuccess={vi.fn()}
onResetCredentials={vi.fn()}
onFooterChange={vi.fn()}
/>,
);
// Then
await waitFor(() => {
expect(screen.getByTestId("test-connection-form")).toBeInTheDocument();
});
expect(useProviderWizardStore.getState().secretId).toBe("secret-1");
});
});
@@ -27,7 +27,8 @@ export function TestConnectionStep({
onResetCredentials,
onFooterChange,
}: TestConnectionStepProps) {
const { providerId, providerType, mode } = useProviderWizardStore();
const { providerId, providerType, mode, setSecretId } =
useProviderWizardStore();
const [providerData, setProviderData] =
useState<TestConnectionProviderData | null>(null);
const [isLoadingProvider, setIsLoadingProvider] = useState(true);
@@ -62,11 +63,15 @@ export function TestConnectionStep({
setErrorMessage(
response.errors[0]?.detail || "Failed to load provider.",
);
setSecretId(null);
setProviderData(null);
setIsLoadingProvider(false);
return;
}
const resolvedSecretId =
response?.data?.relationships?.secret?.data?.id ?? null;
setSecretId(resolvedSecretId);
setProviderData(response as TestConnectionProviderData);
setIsLoadingProvider(false);
}
@@ -76,7 +81,7 @@ export function TestConnectionStep({
return () => {
isMounted = false;
};
}, [providerId, providerType]);
}, [providerId, providerType, setSecretId]);
useEffect(() => {
const canSubmit = !isLoadingProvider && !errorMessage && !!providerData;