import { afterAll, describe, expect, it } from "vitest"; import processPlugin from "@composio/ao-plugin-runtime-process"; import type { RuntimeHandle } from "@composio/ao-core"; import { sleep } from "./helpers/polling.js"; describe("runtime-process (integration)", () => { const runtime = processPlugin.create(); const sessionId = `proc-inttest-${Date.now()}`; let handle: RuntimeHandle; afterAll(async () => { try { await runtime.destroy(handle); } catch { /* best-effort cleanup */ } }, 30_000); it("creates a child process", async () => { handle = await runtime.create({ sessionId, workspacePath: "/tmp", launchCommand: "cat", // cat echoes stdin to stdout environment: { AO_TEST: "1" }, }); expect(handle.id).toBe(sessionId); expect(handle.runtimeName).toBe("process"); expect(handle.data.pid).toBeTypeOf("number"); }); it("isAlive returns true for running process", async () => { expect(await runtime.isAlive(handle)).toBe(true); }); it("sendMessage writes to stdin and output is captured", async () => { await runtime.sendMessage(handle, "hello from test"); await sleep(200); // give time for stdout to be captured const output = await runtime.getOutput(handle); expect(output).toContain("hello from test"); }); it("getMetrics returns uptime", async () => { const metrics = await runtime.getMetrics!(handle); expect(metrics.uptimeMs).toBeGreaterThan(0); }); it("getAttachInfo returns PID", async () => { const info = await runtime.getAttachInfo!(handle); expect(info.type).toBe("process"); expect(info.target).toMatch(/^\d+$/); }); it("rejects duplicate session IDs", async () => { await expect( runtime.create({ sessionId, workspacePath: "/tmp", launchCommand: "cat", environment: {}, }), ).rejects.toThrow("already exists"); }); it("sendMessage throws for unknown session", async () => { await expect( runtime.sendMessage({ id: "nonexistent", runtimeName: "process", data: {} }, "hi"), ).rejects.toThrow("No process found"); }); it("destroy kills the process", async () => { await runtime.destroy(handle); await sleep(200); // give time for exit handler expect(await runtime.isAlive(handle)).toBe(false); }); it("getOutput returns empty for destroyed session", async () => { const output = await runtime.getOutput(handle); expect(output).toBe(""); }); it("isAlive returns false for unknown session", async () => { expect(await runtime.isAlive({ id: "nonexistent", runtimeName: "process", data: {} })).toBe( false, ); }); it("destroy is idempotent", async () => { await runtime.destroy(handle); // should not throw }); });