mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 23:43:16 +01:00
Failed lifecycle verification could allocate another test environment every minute indefinitely. Count failed host verification tests separately from claims, persist a repair wait for confirmed configuration failures or three generic failed tests, and clear it through authorized retry or execution configuration changes. Preserve typed temporary execution-target failures through adapter checks so timeouts, transport failures and interrupted probes keep automatic recovery. Retain existing readiness, authorization, termination and cleanup gates. Document the policy and add real PostgreSQL persistence and actual OpenCode adapter regressions. Verified 398 focused tests (one Windows-only test skipped on macOS), repository-wide typecheck and build, structural checks, fully green current-head CI and Greptile 5/5 with no unresolved comments. Co-Authored-By: Paperclip <noreply@paperclip.ing>
56 lines
3.3 KiB
TypeScript
56 lines
3.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import type { AgentLifecycleState } from "@paperclipai/shared";
|
|
import { expect, it, vi } from "vitest";
|
|
import { AgentLifecycleStatus, agentLifecycleRefetchInterval } from "./AgentLifecycleStatus";
|
|
|
|
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
|
|
|
it.each([
|
|
["preparing", "Preparing agent"], ["verifying", "Verifying agent"],
|
|
["pausing", "Pausing agent"], ["resuming", "Resuming agent"],
|
|
["terminating", "Terminating agent"], ["cleaning_up", "Cleaning up agent"],
|
|
] as const)("shows and refreshes %s, including incomplete termination", (lifecycleState, label) => {
|
|
const html = renderToStaticMarkup(<AgentLifecycleStatus agent={{ lifecycleState, lifecycleError: null }} onRetry={() => {}} retryPending={false} />);
|
|
expect(html).toContain(label);
|
|
expect(html).toContain('role="status"');
|
|
expect(html).toContain('aria-hidden="true"');
|
|
expect(html).not.toContain("Retry");
|
|
expect(agentLifecycleRefetchInterval(lifecycleState)).toBe(2_000);
|
|
});
|
|
|
|
it.each([undefined, "ready", "paused", "pending_approval", "terminated", "rejected"] as const)("removes progress for %s while still checking for external changes", lifecycleState => {
|
|
expect(renderToStaticMarkup(<AgentLifecycleStatus agent={{ lifecycleState, lifecycleError: null }} onRetry={() => {}} retryPending={false} />)).toBe("");
|
|
expect(agentLifecycleRefetchInterval(lifecycleState)).toBe(30_000);
|
|
});
|
|
|
|
it("does not promise automatic retry of a failed configuration check", () => {
|
|
const html = renderToStaticMarkup(<AgentLifecycleStatus agent={{ lifecycleState: "verifying", lifecycleError: "Update Configuration or choose Retry now." }} onRetry={() => {}} retryPending={false} />);
|
|
expect(html).toContain("Retry now");
|
|
expect(html).not.toContain("Paperclip retries automatically");
|
|
});
|
|
|
|
it("retries a failed step once while pending and removes the failure after recovery", async () => {
|
|
const container = document.createElement("div");
|
|
const root = createRoot(container);
|
|
const retry = vi.fn();
|
|
const agent = { lifecycleState: "cleaning_up" as AgentLifecycleState, lifecycleError: "The lifecycle step failed. Retry the operation." };
|
|
try {
|
|
await act(async () => root.render(<AgentLifecycleStatus agent={agent} onRetry={retry} retryPending={false} />));
|
|
expect(container.textContent).toContain(agent.lifecycleError);
|
|
expect(container.textContent).toContain("Agent cleanup failed");
|
|
expect(container.textContent).toContain("Paperclip retries automatically");
|
|
expect(container.textContent).not.toContain("Required resources are being removed");
|
|
await act(async () => container.querySelector("button")!.click());
|
|
expect(retry).toHaveBeenCalledOnce();
|
|
await act(async () => root.render(<AgentLifecycleStatus agent={agent} onRetry={retry} retryPending />));
|
|
expect(container.querySelector("button")!.disabled).toBe(true);
|
|
await act(async () => container.querySelector("button")!.click());
|
|
expect(retry).toHaveBeenCalledOnce();
|
|
await act(async () => root.render(<AgentLifecycleStatus agent={{ lifecycleState: "terminated", lifecycleError: null }} onRetry={retry} retryPending={false} />));
|
|
expect(container.textContent).toBe("");
|
|
} finally { await act(async () => root.unmount()); }
|
|
});
|