feat: align SDK with updated OpenAPI spec and add missing unit tests

- 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
This commit is contained in:
Tasnim Kabir Sadik
2026-05-16 19:14:55 +06:00
parent a69118aa2d
commit 349b230913
18 changed files with 1249 additions and 52 deletions

View File

@ -3,6 +3,7 @@ import type { AddressInfo } from "node:net";
import { afterEach, describe, expect, it, vi } from "vitest";
import { WebSocketServer } from "ws";
import { AsyncQueue } from "../src/_shared/async-queue.js";
import { HttpClient } from "../src/_shared/http.js";
import { WsConnection } from "../src/_shared/websocket.js";
import { resolveConfig } from "../src/config.js";
@ -233,6 +234,57 @@ describe("HttpClient", () => {
await assertion;
});
it("downloads binary response bodies as ReadableStream", async () => {
const body = new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.close();
},
});
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response(body, { status: 200 })),
);
const client = new HttpClient({ baseUrl: "https://api.example.com" });
const stream = await client.download("/v1/file", { path: "/a.txt" });
expect(stream).toBeInstanceOf(ReadableStream);
const reader = stream.getReader();
const { value } = await reader.read();
expect(value).toEqual(new Uint8Array([1, 2, 3]));
});
it("handles content-length zero as empty response", async () => {
vi.stubGlobal(
"fetch",
vi.fn(
async () =>
new Response(null, {
status: 200,
headers: { "content-length": "0" },
}),
),
);
const client = new HttpClient({ baseUrl: "https://api.example.com" });
const result = await client.get("/v1/empty");
expect(result).toBeUndefined();
});
it("handles empty text body as undefined", async () => {
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response("", { status: 200 })),
);
const client = new HttpClient({ baseUrl: "https://api.example.com" });
const result = await client.get("/v1/blank");
expect(result).toBeUndefined();
});
});
describe("WsConnection", () => {
@ -263,7 +315,8 @@ describe("WsConnection", () => {
await expect(receivedByServer).resolves.toEqual({ type: "start" });
expect(messages).toEqual([{ type: "ready" }]);
connection.close();
await connection[Symbol.asyncDispose]();
expect(connection.isClosed).toBe(true);
server.close();
});
@ -285,3 +338,37 @@ describe("WsConnection", () => {
vi.useRealTimers();
});
});
describe("AsyncQueue", () => {
it("rejects waiting consumers when failed", async () => {
const queue = new AsyncQueue<string>();
const pending = queue.next();
queue.fail(new Error("socket died"));
await expect(pending).rejects.toThrow("socket died");
await expect(queue.next()).rejects.toThrow("socket died");
});
it("ends the queue from return and resolves as done", async () => {
const queue = new AsyncQueue<string>();
const result = await queue.return();
expect(result).toEqual({ done: true, value: undefined });
await expect(queue.next()).resolves.toEqual({
done: true,
value: undefined,
});
});
it("propagates error through throw and rejects future reads", async () => {
const queue = new AsyncQueue<string>();
const error = new Error("consumer threw");
const result = queue.throw(error);
await expect(result).rejects.toThrow("consumer threw");
await expect(queue.next()).rejects.toThrow("consumer threw");
});
});