Allow Boat disconnect while cleanup remains pending

This commit is contained in:
Dotta committed 2026-10-11 00:43:19 -05:00
1 parent a48dafe553
commit 83ec986f59
2 files changed
+47 -3

No files matched your search

+42
View File
@@ -1871,6 +1871,48 @@ describe("CompanyEnvironments — test provider button", () => {
expect(mockEnvironmentsApi.remove).not.toHaveBeenCalled();
});
it.each([
{ driver: "computer", instanceDefault: false, allowed: true },
{ driver: "computer", instanceDefault: true, allowed: false },
{ driver: "sandbox", instanceDefault: false, allowed: false },
])("matches pending-cleanup removal policy for $driver (instance default=$instanceDefault)", async ({ driver, instanceDefault, allowed }) => {
mockEnvironmentsApi.list.mockResolvedValue([{
id: "env-1", name: "Cleanup environment", driver, status: "active",
config: driver === "computer" ? { provider: "boat", sandboxId: "bx_existing" } : { provider: "daytona" },
}]);
mockEnvironmentsApi.deleteBlastRadius.mockResolvedValue(createDeleteBlastRadius({
canDelete: false,
deleteBlockedReasons: instanceDefault ? ["instance_default", "pending_cleanup"] : ["pending_cleanup"],
staticReferences: { isInstanceDefault: instanceDefault },
pendingCleanupLeaseCount: 1,
}));
root = createRoot(container);
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
await act(async () => {
root!.render(renderCompanyEnvironments(queryClient, `${ENVIRONMENTS_PATH}/env-1/edit`));
});
await waitForAssertion(() => {
expect(document.body.querySelector("[data-testid='environment-delete-button']")).not.toBeNull();
});
await act(async () => click(document.body.querySelector("[data-testid='environment-delete-button']")));
await waitForAssertion(() => {
expect(mockEnvironmentsApi.deleteBlastRadius).toHaveBeenCalledWith("env-1");
const confirm = document.body.querySelector<HTMLButtonElement>("[data-testid='environment-delete-confirm']");
expect(confirm).not.toBeNull();
expect(confirm!.disabled).toBe(!allowed);
});
if (allowed) {
expect(document.body.textContent).toContain("Disconnect computer");
await act(async () => click(document.body.querySelector("[data-testid='environment-delete-confirm']")));
await waitForAssertion(() => expect(mockEnvironmentsApi.remove).toHaveBeenCalledExactlyOnceWith("env-1"));
} else {
expect(mockEnvironmentsApi.remove).not.toHaveBeenCalled();
expect(document.body.textContent).toContain(instanceDefault
? "Choose another default environment before disconnecting this computer."
: "Cannot delete this environment while a sandbox cleanup is pending.");
}
});
it("links the workspaces holding blocking sandbox leases in the delete dialog", async () => {
mockEnvironmentsApi.deleteBlastRadius.mockResolvedValue(
createDeleteBlastRadius({
+5 -3
View File
@@ -103,19 +103,21 @@ function environmentEditPath(environmentId: string) {
// the server enforces these gates with a 409; this copy lets the modal explain
// the block before the user hits it.
function environmentDeleteBlockMessage(impact: EnvironmentDeleteBlastRadius, isComputer = false): string | null {
if (isComputer) {
return impact.staticReferences.isInstanceDefault
? "Choose another default environment before disconnecting this computer."
: null;
}
if (impact.staticReferences.isManagedLocal) {
return "Cannot delete the managed local environment.";
}
if (impact.staticReferences.isInstanceDefault) {
if (isComputer) return "Choose another default environment before disconnecting this computer.";
return "Cannot delete the current instance default environment. Set a new default environment before deleting this one.";
}
if (impact.pendingCleanupLeaseCount > 0) {
if (isComputer) return "Computer cleanup is pending. Wait for it to finish, then retry disconnecting. Its files will remain on Boat.";
return "Cannot delete this environment while a sandbox cleanup is pending. Wait for the cleanup sweep to destroy the orphan sandbox, then retry.";
}
if (impact.reusableSandboxLeaseCount > 0) {
if (isComputer) return "This computer still has saved runner connections. Disconnecting stops its runners and retains its files on Boat.";
return "Cannot delete this environment while it has a reusable sandbox lease. Remove the associated execution workspace or issue so Paperclip can destroy the sandbox, then retry.";
}
return null;