- Update generated types from new openapi.yaml (capsule stats, usage, metrics, pause/resume lifecycle, host/channel management, auth flow) - Add Capsule pause/resume/ping/getMetrics lifecycle methods - Add Capsule.waitForReady abort signal support - Add PtyManager.connect and PtySession disposal - Fix HttpClient empty-body response handling (content-length: 0) - Add streamProcess() to CommandManager for background process streams - Add integration tests for capsule lifecycle, git, and PTY features - Add unit tests for AsyncQueue error paths, PtySession.close, Git.checkout without create, Git.add single string, Notebook.execCell error case, and PtyStartOptions fields
132 lines
3.1 KiB
TypeScript
132 lines
3.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
|
|
import { Capsule } from "../src/capsule.js";
|
|
|
|
describe("PtyManager", () => {
|
|
it("starts a PTY, sends controls, and yields events", async () => {
|
|
const capsule = new Capsule("cap_1", {
|
|
baseUrl: "https://api.example.com",
|
|
});
|
|
const sent: unknown[] = [];
|
|
let onMessage: ((message: unknown) => void) | undefined;
|
|
const close = vi.fn();
|
|
vi.spyOn(capsule.client.capsules, "ptySession").mockImplementation(
|
|
async (_id, opts) => {
|
|
onMessage = opts.onMessage;
|
|
return {
|
|
close,
|
|
get isClosed() {
|
|
return false;
|
|
},
|
|
send: (message: unknown) => sent.push(message),
|
|
} as never;
|
|
},
|
|
);
|
|
|
|
const session = await capsule.pty.start({
|
|
cmd: "/bin/sh",
|
|
cols: 100,
|
|
rows: 30,
|
|
});
|
|
expect(sent).toEqual([
|
|
{ cmd: "/bin/sh", cols: 100, rows: 30, type: "start" },
|
|
]);
|
|
|
|
session.input("ls\n");
|
|
session.resize(120, 40);
|
|
session.kill();
|
|
expect(sent.slice(1)).toEqual([
|
|
{ data: Buffer.from("ls\n").toString("base64"), type: "input" },
|
|
{ cols: 120, rows: 40, type: "resize" },
|
|
{ type: "kill" },
|
|
]);
|
|
|
|
const event = session.events.next();
|
|
onMessage?.({ data: Buffer.from("ok").toString("base64"), type: "output" });
|
|
await expect(event).resolves.toEqual({
|
|
done: false,
|
|
value: {
|
|
data: Buffer.from("ok").toString("base64"),
|
|
type: "output",
|
|
},
|
|
});
|
|
|
|
await session[Symbol.asyncDispose]();
|
|
expect(close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("connects to an existing PTY tag", async () => {
|
|
const capsule = new Capsule("cap_1", {
|
|
baseUrl: "https://api.example.com",
|
|
});
|
|
const sent: unknown[] = [];
|
|
vi.spyOn(capsule.client.capsules, "ptySession").mockResolvedValue({
|
|
close: vi.fn(),
|
|
get isClosed() {
|
|
return false;
|
|
},
|
|
send: (message: unknown) => sent.push(message),
|
|
} as never);
|
|
|
|
await capsule.pty.connect("pty-tag");
|
|
|
|
expect(sent).toEqual([{ tag: "pty-tag", type: "connect" }]);
|
|
});
|
|
|
|
it("closes the connection when close() is called directly", async () => {
|
|
const capsule = new Capsule("cap_1", {
|
|
baseUrl: "https://api.example.com",
|
|
});
|
|
const close = vi.fn();
|
|
vi.spyOn(capsule.client.capsules, "ptySession").mockResolvedValue({
|
|
close,
|
|
get isClosed() {
|
|
return false;
|
|
},
|
|
send: vi.fn(),
|
|
} as never);
|
|
|
|
const session = await capsule.pty.start();
|
|
session.close();
|
|
|
|
expect(close).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it("sends all PtyStartOptions fields in the start message", async () => {
|
|
const capsule = new Capsule("cap_1", {
|
|
baseUrl: "https://api.example.com",
|
|
});
|
|
const sent: unknown[] = [];
|
|
vi.spyOn(capsule.client.capsules, "ptySession").mockResolvedValue({
|
|
close: vi.fn(),
|
|
get isClosed() {
|
|
return false;
|
|
},
|
|
send: (message: unknown) => sent.push(message),
|
|
} as never);
|
|
|
|
await capsule.pty.start({
|
|
cmd: "/bin/bash",
|
|
args: ["--login"],
|
|
cols: 120,
|
|
rows: 40,
|
|
envs: { TERM: "xterm-256color" },
|
|
cwd: "/home/user",
|
|
user: "user",
|
|
});
|
|
|
|
expect(sent).toEqual([
|
|
{
|
|
type: "start",
|
|
cmd: "/bin/bash",
|
|
args: ["--login"],
|
|
cols: 120,
|
|
rows: 40,
|
|
envs: { TERM: "xterm-256color" },
|
|
cwd: "/home/user",
|
|
user: "user",
|
|
},
|
|
]);
|
|
});
|
|
});
|