feat: add high-level capsule feature modules
This commit is contained in:
116
tests/commands.test.ts
Normal file
116
tests/commands.test.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { Capsule } from "../src/capsule.js";
|
||||
|
||||
describe("CommandManager", () => {
|
||||
it("executes foreground commands through the capsule client", async () => {
|
||||
const capsule = new Capsule("cap_1", {
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
const exec = vi.spyOn(capsule.client.capsules, "exec").mockResolvedValue({
|
||||
exit_code: 0,
|
||||
stdout: "ok\n",
|
||||
});
|
||||
|
||||
await expect(
|
||||
capsule.commands.exec("node", {
|
||||
args: ["--version"],
|
||||
timeoutSec: 5,
|
||||
}),
|
||||
).resolves.toMatchObject({ stdout: "ok\n" });
|
||||
|
||||
expect(exec).toHaveBeenCalledWith("cap_1", {
|
||||
args: ["--version"],
|
||||
background: false,
|
||||
cmd: "node",
|
||||
timeout_sec: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it("starts, lists, and kills background processes", async () => {
|
||||
const capsule = new Capsule("cap_1", {
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
const exec = vi.spyOn(capsule.client.capsules, "exec").mockResolvedValue({
|
||||
pid: 123,
|
||||
tag: "worker",
|
||||
});
|
||||
const listProcesses = vi
|
||||
.spyOn(capsule.client.capsules, "listProcesses")
|
||||
.mockResolvedValue({ processes: [{ pid: 123, tag: "worker" }] });
|
||||
const killProcess = vi
|
||||
.spyOn(capsule.client.capsules, "killProcess")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
await expect(
|
||||
capsule.commands.start("sleep", {
|
||||
args: ["60"],
|
||||
cwd: "/tmp",
|
||||
envs: { A: "1" },
|
||||
tag: "worker",
|
||||
}),
|
||||
).resolves.toMatchObject({ tag: "worker" });
|
||||
await expect(capsule.commands.list()).resolves.toMatchObject({
|
||||
processes: [{ tag: "worker" }],
|
||||
});
|
||||
await expect(
|
||||
capsule.commands.kill("worker", "SIGTERM"),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
expect(exec).toHaveBeenCalledWith("cap_1", {
|
||||
args: ["60"],
|
||||
background: true,
|
||||
cmd: "sleep",
|
||||
cwd: "/tmp",
|
||||
envs: { A: "1" },
|
||||
tag: "worker",
|
||||
timeout_sec: 30,
|
||||
});
|
||||
expect(listProcesses).toHaveBeenCalledWith("cap_1");
|
||||
expect(killProcess).toHaveBeenCalledWith("cap_1", "worker", {
|
||||
signal: "SIGTERM",
|
||||
});
|
||||
});
|
||||
|
||||
it("streams command events over the exec WebSocket", async () => {
|
||||
const capsule = new Capsule("cap_1", {
|
||||
baseUrl: "https://api.example.com",
|
||||
});
|
||||
const sent: unknown[] = [];
|
||||
let onMessage: ((message: unknown) => void) | undefined;
|
||||
vi.spyOn(capsule.client.capsules, "execStream").mockImplementation(
|
||||
async (_id, opts) => {
|
||||
onMessage = opts.onMessage;
|
||||
return {
|
||||
close: vi.fn(),
|
||||
get isClosed() {
|
||||
return false;
|
||||
},
|
||||
send: (message: unknown) => sent.push(message),
|
||||
} as never;
|
||||
},
|
||||
);
|
||||
|
||||
const events = capsule.commands.stream("printf", { args: ["hello"] });
|
||||
const first = events.next();
|
||||
await vi.waitFor(() => expect(sent).toHaveLength(1));
|
||||
expect(sent).toEqual([{ args: ["hello"], cmd: "printf", type: "start" }]);
|
||||
|
||||
onMessage?.({ data: "hello", type: "stdout" });
|
||||
await expect(first).resolves.toEqual({
|
||||
done: false,
|
||||
value: { data: "hello", type: "stdout" },
|
||||
});
|
||||
|
||||
const done = events.next();
|
||||
onMessage?.({ exit_code: 0, type: "exit" });
|
||||
await expect(done).resolves.toEqual({
|
||||
done: false,
|
||||
value: { exit_code: 0, type: "exit" },
|
||||
});
|
||||
await expect(events.next()).resolves.toEqual({
|
||||
done: true,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user