Harden custom image setup terminal fallback

This commit is contained in:
Cody committed 2026-07-01 17:15:16 +00:00
1 parent b97afa57aa
commit f9188e1976
14 files changed
+1611 -48

No files matched your search

+99
View File
@@ -172,6 +172,27 @@ function createTemplate(overrides: Record<string, unknown> = {}) {
};
}
function supportedDaytonaCapabilities() {
return {
adapters: [],
drivers: { local: "supported", ssh: "supported", sandbox: "supported", plugin: "unsupported" },
sandboxProviders: {
daytona: {
status: "supported",
supportsSavedProbe: true,
supportsUnsavedProbe: true,
supportsRunExecution: true,
supportsReusableLeases: true,
supportsInteractiveSetup: true,
interactiveSetupConnectionTypes: ["ssh"],
supportsTemplateCapture: true,
supportsTemplateDelete: true,
displayName: "Daytona",
},
},
};
}
describe("CompanyEnvironments — test provider button", () => {
let container: HTMLDivElement;
let root: ReturnType<typeof createRoot> | null;
@@ -601,6 +622,84 @@ describe("CompanyEnvironments — test provider button", () => {
expect(getOpenDialog()?.textContent).not.toContain(command);
});
it("shows a setup connection refresh fallback without breaking finish or cancel", async () => {
root = createRoot(container);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
mockEnvironmentsApi.list.mockResolvedValue([
{ id: "env-1", name: "Daytona", driver: "sandbox", description: null, config: { provider: "daytona" } },
]);
mockEnvironmentsApi.capabilities.mockResolvedValue(supportedDaytonaCapabilities());
mockEnvironmentsApi.customImageTemplate.mockResolvedValue({
activeTemplate: null,
activeSession: createSession(),
latestSession: createSession(),
});
mockEnvironmentsApi.customImageSetupSession.mockRejectedValue(new Error("proxy unavailable"));
await act(async () => {
root!.render(
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<CompanyEnvironments />
</TooltipProvider>
</QueryClientProvider>,
);
});
await flushReact();
await act(async () => click(editButtons(container)[0]));
await waitForAssertion(() => {
expect(getOpenDialog()?.textContent).toContain("Setup connection details could not be refreshed.");
});
const dialog = getOpenDialog()!;
expect(findButton(dialog, "Finished")?.disabled).toBe(false);
expect(findButton(dialog, "Cancel")?.disabled).toBe(false);
await act(async () => click(findButton(dialog, "Finished")));
await flushReact();
expect(mockEnvironmentsApi.finishCustomImageSetupSession).toHaveBeenCalledExactlyOnceWith("session-1", {});
});
it("shows provider fallback messaging for unsupported setup connection payloads", async () => {
root = createRoot(container);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
mockEnvironmentsApi.list.mockResolvedValue([
{ id: "env-1", name: "Daytona", driver: "sandbox", description: null, config: { provider: "daytona" } },
]);
mockEnvironmentsApi.capabilities.mockResolvedValue(supportedDaytonaCapabilities());
mockEnvironmentsApi.customImageTemplate.mockResolvedValue({
activeTemplate: null,
activeSession: createSession(),
latestSession: createSession(),
});
mockEnvironmentsApi.customImageSetupSession.mockResolvedValue({
session: createSession(),
connectionPayload: { type: "browser_terminal", command: null },
});
await act(async () => {
root!.render(
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<CompanyEnvironments />
</TooltipProvider>
</QueryClientProvider>,
);
});
await flushReact();
await act(async () => click(editButtons(container)[0]));
await waitForAssertion(() => {
expect(getOpenDialog()?.textContent).toContain("Browser terminal is not available for this provider connection.");
});
const dialog = getOpenDialog()!;
expect(findButton(dialog, "Finished")?.disabled).toBe(false);
expect(findButton(dialog, "Cancel")?.disabled).toBe(false);
});
it("shows active template controls for refresh, rollback, and disable", async () => {
root = createRoot(container);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
+30 -2
View File
@@ -199,6 +199,27 @@ function readConnectionCommand(payload: EnvironmentCustomImageConnectionPayload
: null;
}
function setupConnectionFallbackMessage(input: {
payload: EnvironmentCustomImageConnectionPayload | null;
refreshError: unknown;
isLoading: boolean;
}): string | null {
if (input.refreshError) {
return "Setup connection details could not be refreshed. You can still finish or cancel this setup.";
}
if (input.isLoading) return null;
if (!input.payload) {
return "Connection details are not available yet. You can still finish or cancel this setup.";
}
if (input.payload.type !== "ssh") {
return "Browser terminal is not available for this provider connection. Use the provider setup instructions, then finish or cancel here.";
}
if (!readConnectionCommand(input.payload)) {
return "Connection details are not available yet. You can still finish or cancel this setup.";
}
return null;
}
function capabilityState(capability: EnvironmentProviderCapability | null | undefined) {
if (!capability || capability.status !== "supported" || !capability.supportsInteractiveSetup) {
return {
@@ -451,6 +472,13 @@ function EnvironmentImageTemplatePanel({
? sessionQuery.data?.connectionPayload ?? null
: null;
const connectionCommand = readConnectionCommand(connectionPayload);
const connectionFallbackMessage = session?.status === "waiting_for_user"
? setupConnectionFallbackMessage({
payload: connectionPayload,
refreshError: sessionQuery.isError ? sessionQuery.error : null,
isLoading: sessionQuery.isLoading,
})
: null;
const sessionExpiresAt = formatDateTime(connectionPayload?.expiresAt ?? session?.expiresAt ?? null);
const capturedAt = formatDateTime(activeTemplate?.capturedAt ?? activeTemplate?.createdAt ?? null);
const lastUsedAt = formatDateTime(activeTemplate?.lastUsedAt ?? null);
@@ -500,9 +528,9 @@ function EnvironmentImageTemplatePanel({
</code>
</div>
) : null}
{session.status === "waiting_for_user" && !connectionCommand ? (
{session.status === "waiting_for_user" && connectionFallbackMessage ? (
<div className="mt-2 text-xs text-muted-foreground">
Connection details are not available yet.
{connectionFallbackMessage}
</div>
) : null}
{session.failureReason ? (