diff --git a/ui/src/pages/CloudUpstream.test.tsx b/ui/src/pages/CloudUpstream.test.tsx index 21f4a29464c..4eaee06876e 100644 --- a/ui/src/pages/CloudUpstream.test.tsx +++ b/ui/src/pages/CloudUpstream.test.tsx @@ -38,7 +38,7 @@ vi.mock("@/context/BreadcrumbContext", () => ({ vi.mock("@/context/CompanyContext", () => ({ useCompany: () => ({ - selectedCompany: { id: "company-1", name: "Paperclip" }, + selectedCompany: { id: "company-1", name: "Paperclip", issuePrefix: "PAP" }, selectedCompanyId: "company-1", }), })); @@ -136,6 +136,54 @@ describe("CloudUpstream", () => { }); }); + it("sends a company-prefixed redirectUri when starting Connect", async () => { + mockCloudUpstreamsApi.list.mockResolvedValue({ connections: [], runs: [] }); + mockCloudUpstreamsApi.startConnect.mockResolvedValue({ + pendingConnectionId: "pending-1", + authorizationUrl: "https://cloud.example/upstream-consent?state=abc", + }); + const root = createRoot(container); + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + + await act(async () => { + root.render( + + + , + ); + }); + await flushReact(); + await flushReact(); + + const input = container.querySelector("input[aria-label='Paperclip Cloud stack URL']"); + expect(input).toBeTruthy(); + await act(async () => { + const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!; + setter.call(input!, "https://cloud.example/PAP/dashboard"); + input!.dispatchEvent(new Event("input", { bubbles: true })); + }); + await flushReact(); + + const connectButton = Array.from(container.querySelectorAll("button")) + .find((button) => button.textContent?.trim() === "Connect") as HTMLButtonElement | undefined; + expect(connectButton).toBeTruthy(); + + await act(async () => { + connectButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); + }); + await flushReact(); + + expect(mockCloudUpstreamsApi.startConnect).toHaveBeenCalledWith({ + companyId: "company-1", + remoteUrl: "https://cloud.example/PAP/dashboard", + redirectUri: `${window.location.origin}/PAP/company/settings/cloud-upstream`, + }); + + await act(async () => { + root.unmount(); + }); + }); + it("keeps retry only for failed or cancelled runs", async () => { mockCloudUpstreamsApi.list.mockResolvedValue(stateWithRun(buildRun({ status: "failed" }))); const root = createRoot(container); diff --git a/ui/src/pages/CloudUpstream.tsx b/ui/src/pages/CloudUpstream.tsx index b99f4e80840..fe3735cf478 100644 --- a/ui/src/pages/CloudUpstream.tsx +++ b/ui/src/pages/CloudUpstream.tsx @@ -24,6 +24,7 @@ import { cloudUpstreamsApi } from "@/api/cloudUpstreams"; import { instanceSettingsApi } from "@/api/instanceSettings"; import { useBreadcrumbs } from "@/context/BreadcrumbContext"; import { useCompany } from "@/context/CompanyContext"; +import { applyCompanyPrefix } from "@/lib/company-routes"; import { Link, useLocation } from "@/lib/router"; import { queryKeys } from "@/lib/queryKeys"; @@ -101,6 +102,8 @@ export function CloudUpstream() { const state = callbackParams.get("state"); const callbackError = callbackParams.get("error"); + const settingsPath = applyCompanyPrefix("/company/settings/cloud-upstream", selectedCompany?.issuePrefix ?? null); + const finishMutation = useMutation({ mutationFn: (input: { pendingConnectionId: string; code: string; state: string }) => cloudUpstreamsApi.finishConnect(input), @@ -109,7 +112,7 @@ export function CloudUpstream() { setNotice("Cloud upstream connection approved."); setActionError(null); await invalidateUpstreams(); - window.history.replaceState(null, "", "/company/settings/cloud-upstream"); + window.history.replaceState(null, "", settingsPath); }, onError: (error) => setActionError(error instanceof Error ? error.message : "Failed to finish connection."), }); @@ -135,7 +138,7 @@ export function CloudUpstream() { cloudUpstreamsApi.startConnect({ companyId: selectedCompanyId!, remoteUrl, - redirectUri: `${window.location.origin}/company/settings/cloud-upstream`, + redirectUri: `${window.location.origin}${settingsPath}`, }), onSuccess: (result) => { localStorage.setItem(PENDING_CONNECTION_KEY, result.pendingConnectionId);