feat: add low-level Wrenn client resources

Implement WrennClient with typed resource mappings for auth, account,
API keys, users, teams, capsules, files, snapshots, hosts, and channels.
Add endpoint mapping tests plus live integration tess that authenticate
with WRENN_TEST_EMAIL/WRENN_TEST_PASS and use WRENN_API_KEY for API-key
scoped endpoints
This commit is contained in:
Tasnim Kabir Sadik
2026-05-09 18:05:53 +06:00
parent 5b3f2741a3
commit f1522eaa0b
7 changed files with 2120 additions and 183 deletions

View File

@ -29,6 +29,8 @@ export interface RequestOptions {
signal?: AbortSignal;
/** Return response text instead of parsing JSON. */
asText?: boolean;
/** Fetch redirect behavior. Defaults to the runtime fetch default. */
redirect?: RequestRedirect;
}
/** Thin `fetch` wrapper with Wrenn authentication and error mapping. */
@ -137,6 +139,29 @@ export class HttpClient {
return res.body;
}
/**
* Sends a request and returns the raw `Response` object.
*
* This is intended for endpoints that intentionally return non-JSON responses,
* such as OAuth redirects. Unlike {@link request}, this method does not map
* non-OK statuses to SDK errors.
*
* @param method - HTTP method to use.
* @param path - API path relative to the configured base URL.
* @param body - Optional JSON request body.
* @param opts - Optional query, header, auth, timeout, and redirect settings.
* @returns Raw fetch response.
* @throws TimeoutError When the configured timeout aborts the request.
*/
response(
method: string,
path: string,
body?: unknown,
opts?: RequestOptions,
): Promise<Response> {
return this.rawRequest(method, path, body, opts);
}
/**
* Sends a request and parses the response as JSON unless `asText` is set.
*
@ -188,6 +213,7 @@ export class HttpClient {
method,
headers: { ...headers, ...opts?.headers },
};
if (opts?.redirect) init.redirect = opts.redirect;
if (body !== undefined) {
init.body = JSON.stringify(body);

1412
src/client.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,26 @@ export type { HttpClientConfig, RequestOptions } from "./_shared/http.js";
export { HttpClient } from "./_shared/http.js";
export type { WsConnectionOpts } from "./_shared/websocket.js";
export { WsConnection } from "./_shared/websocket.js";
export type {
FileUploadInput,
OperationJsonBody,
OperationJsonResponse,
OperationQueryParams,
OperationRequestOptions,
} from "./client.js";
export {
AccountResource,
APIKeysResource,
AuthResource,
CapsulesResource,
ChannelsResource,
FilesResource,
HostsResource,
SnapshotsResource,
TeamsResource,
UsersResource,
WrennClient,
} from "./client.js";
export type { ClientConfig, ResolvedClientConfig } from "./config.js";
export {
DEFAULT_BASE_URL,

492
tests/client.test.ts Normal file
View File

@ -0,0 +1,492 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { WrennClient } from "../src/client.js";
interface CapturedRequest {
url: string;
init: RequestInit;
}
function setupFetch(status = 200, body: unknown = { ok: true }) {
const calls: CapturedRequest[] = [];
const fetchMock = vi.fn(
async (url: string | URL | Request, init?: RequestInit) => {
calls.push({ url: String(url), init: init ?? {} });
if (status === 204) return new Response(null, { status });
return Response.json(body, { status });
},
);
vi.stubGlobal("fetch", fetchMock);
return { calls, fetchMock };
}
function expectLastCall(
calls: CapturedRequest[],
expected: { method: string; url: string; body?: unknown },
) {
const call = calls.at(-1);
expect(call?.url).toBe(expected.url);
expect(call?.init.method).toBe(expected.method);
if (expected.body !== undefined) {
expect(call?.init.body).toBe(JSON.stringify(expected.body));
}
}
describe("WrennClient", () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});
it("initializes every resource with resolved auth headers", async () => {
const { calls } = setupFetch();
const client = new WrennClient({
apiKey: "api-key",
baseUrl: "https://api.example.com/",
hostToken: "host-token",
token: "jwt-token",
});
await client.auth.login({ email: "a@example.com", password: "password" });
expect(client.account).toBeDefined();
expect(client.apiKeys).toBeDefined();
expect(client.users).toBeDefined();
expect(client.teams).toBeDefined();
expect(client.capsules).toBeDefined();
expect(client.files).toBeDefined();
expect(client.snapshots).toBeDefined();
expect(client.hosts).toBeDefined();
expect(client.channels).toBeDefined();
expect(calls.at(-1)?.init.headers).toMatchObject({
Accept: "application/json",
Authorization: "Bearer jwt-token",
"Content-Type": "application/json",
"X-API-Key": "api-key",
"X-Host-Token": "host-token",
});
});
it("maps auth endpoints", async () => {
const { calls } = setupFetch();
const client = new WrennClient({ baseUrl: "https://api.example.com" });
await client.auth.signup({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/auth/signup",
});
await client.auth.activate({ token: "activation-token" });
expectLastCall(calls, {
body: { token: "activation-token" },
method: "POST",
url: "https://api.example.com/v1/auth/activate",
});
await client.auth.login({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/auth/login",
});
await client.auth.oauthRedirect("github", { redirect: "manual" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/auth/oauth/github",
});
expect(calls.at(-1)?.init.redirect).toBe("manual");
await client.auth.oauthCallback("github", { code: "code", state: "state" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/auth/oauth/github/callback?code=code&state=state",
});
await client.auth.switchTeam({ team_id: "team_1" });
expectLastCall(calls, {
body: { team_id: "team_1" },
method: "POST",
url: "https://api.example.com/v1/auth/switch-team",
});
});
it("maps account, API key, user, and team endpoints", async () => {
const { calls } = setupFetch();
const client = new WrennClient({ baseUrl: "https://api.example.com" });
await client.account.getMe();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/me",
});
await client.account.updateName({ name: "New Name" });
expectLastCall(calls, {
body: { name: "New Name" },
method: "PATCH",
url: "https://api.example.com/v1/me",
});
await client.account.deleteAccount({ confirmation: "a@example.com" });
expectLastCall(calls, {
body: { confirmation: "a@example.com" },
method: "DELETE",
url: "https://api.example.com/v1/me",
});
await client.account.changePassword({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/me/password",
});
await client.account.requestPasswordReset({ email: "a@example.com" });
expectLastCall(calls, {
body: { email: "a@example.com" },
method: "POST",
url: "https://api.example.com/v1/me/password/reset",
});
await client.account.confirmPasswordReset({
new_password: "password",
token: "token",
});
expectLastCall(calls, {
body: { new_password: "password", token: "token" },
method: "POST",
url: "https://api.example.com/v1/me/password/reset/confirm",
});
await client.account.connectProvider("github");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/me/providers/github/connect",
});
await client.account.disconnectProvider("github");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/me/providers/github",
});
await client.apiKeys.list();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/api-keys",
});
await client.apiKeys.create({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/api-keys",
});
await client.apiKeys.delete("key/1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/api-keys/key%2F1",
});
await client.users.search({ email: "alice@" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/users/search?email=alice%40",
});
await client.teams.list();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/teams",
});
await client.teams.create({ name: "Team" });
expectLastCall(calls, {
body: { name: "Team" },
method: "POST",
url: "https://api.example.com/v1/teams",
});
await client.teams.get("team/1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/teams/team%2F1",
});
await client.teams.rename("team_1", { name: "New" });
expectLastCall(calls, {
body: { name: "New" },
method: "PATCH",
url: "https://api.example.com/v1/teams/team_1",
});
await client.teams.delete("team_1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/teams/team_1",
});
await client.teams.listMembers("team_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/teams/team_1/members",
});
await client.teams.addMember("team_1", { email: "a@example.com" });
expectLastCall(calls, {
body: { email: "a@example.com" },
method: "POST",
url: "https://api.example.com/v1/teams/team_1/members",
});
await client.teams.updateMemberRole("team_1", "user_1", { role: "admin" });
expectLastCall(calls, {
body: { role: "admin" },
method: "PATCH",
url: "https://api.example.com/v1/teams/team_1/members/user_1",
});
await client.teams.removeMember("team_1", "user_1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/teams/team_1/members/user_1",
});
await client.teams.leave("team_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/teams/team_1/leave",
});
});
it("maps capsule, file, and snapshot endpoints", async () => {
const { calls } = setupFetch();
const client = new WrennClient({ baseUrl: "https://api.example.com" });
await client.capsules.create({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules",
});
await client.capsules.list();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules",
});
await client.capsules.get("cap_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules/cap_1",
});
await client.capsules.destroy("cap_1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/capsules/cap_1",
});
await client.capsules.exec("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/exec",
});
await client.capsules.listProcesses("cap_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules/cap_1/processes",
});
await client.capsules.killProcess("cap_1", "pid/1", { signal: "SIGTERM" });
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/capsules/cap_1/processes/pid%2F1?signal=SIGTERM",
});
await client.capsules.ping("cap_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/ping",
});
await client.capsules.metrics("cap_1", { range: "10m" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules/cap_1/metrics?range=10m",
});
await client.capsules.pause("cap_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/pause",
});
await client.capsules.resume("cap_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/resume",
});
await client.capsules.stats({ range: "1h" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules/stats?range=1h",
});
await client.capsules.usage({ from: "2026-01-01", to: "2026-01-02" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/capsules/usage?from=2026-01-01&to=2026-01-02",
});
await client.files.upload("cap_1", { file: "hello", path: "/tmp/a.txt" });
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/write",
});
expect(calls.at(-1)?.init.body).toBeInstanceOf(FormData);
await client.files.download("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/read",
});
await client.files.list("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/list",
});
await client.files.mkdir("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/mkdir",
});
await client.files.remove("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/remove",
});
await client.files.streamUpload("cap_1", {
file: "hello",
path: "/tmp/a.txt",
});
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/stream/write",
});
await client.files.streamDownload("cap_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/capsules/cap_1/files/stream/read",
});
await client.snapshots.create({} as never, { overwrite: "true" });
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/snapshots?overwrite=true",
});
await client.snapshots.list({ type: "base" });
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/snapshots?type=base",
});
await client.snapshots.delete("snap/1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/snapshots/snap%2F1",
});
});
it("maps host and channel endpoints", async () => {
const { calls } = setupFetch();
const client = new WrennClient({ baseUrl: "https://api.example.com" });
await client.hosts.create({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/hosts",
});
await client.hosts.list();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/hosts",
});
await client.hosts.get("host_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/hosts/host_1",
});
await client.hosts.delete("host_1", { force: true });
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/hosts/host_1?force=true",
});
await client.hosts.regenerateToken("host_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/hosts/host_1/token",
});
await client.hosts.register({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/hosts/register",
});
await client.hosts.heartbeat("host_1");
expectLastCall(calls, {
method: "POST",
url: "https://api.example.com/v1/hosts/host_1/heartbeat",
});
await client.hosts.refreshToken({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/hosts/auth/refresh",
});
await client.hosts.deletePreview("host_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/hosts/host_1/delete-preview",
});
await client.hosts.listTags("host_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/hosts/host_1/tags",
});
await client.hosts.addTag("host_1", {} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/hosts/host_1/tags",
});
await client.hosts.removeTag("host_1", "gpu/a");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/hosts/host_1/tags/gpu%2Fa",
});
await client.channels.create({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/channels",
});
await client.channels.list();
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/channels",
});
await client.channels.test({} as never);
expectLastCall(calls, {
body: {},
method: "POST",
url: "https://api.example.com/v1/channels/test",
});
await client.channels.get("channel_1");
expectLastCall(calls, {
method: "GET",
url: "https://api.example.com/v1/channels/channel_1",
});
await client.channels.update("channel_1", {} as never);
expectLastCall(calls, {
body: {},
method: "PATCH",
url: "https://api.example.com/v1/channels/channel_1",
});
await client.channels.delete("channel_1");
expectLastCall(calls, {
method: "DELETE",
url: "https://api.example.com/v1/channels/channel_1",
});
await client.channels.rotateConfig("channel_1", {} as never);
expectLastCall(calls, {
body: {},
method: "PUT",
url: "https://api.example.com/v1/channels/channel_1/config",
});
});
});

View File

@ -0,0 +1,85 @@
import { beforeAll, describe, expect, it } from "vitest";
import { WrennClient } from "../../src/client.js";
import { DEFAULT_BASE_URL } from "../../src/config.js";
const baseUrl = process.env.WRENN_BASE_URL ?? DEFAULT_BASE_URL;
const apiKey = process.env.WRENN_API_KEY;
const testEmail = process.env.WRENN_TEST_EMAIL;
const testPassword = process.env.WRENN_TEST_PASS;
const describeWithApiKey = apiKey ? describe : describe.skip;
const describeWithLogin = testEmail && testPassword ? describe : describe.skip;
describeWithApiKey("WrennClient live API key integration", () => {
const client = new WrennClient({ apiKey, baseUrl });
it("lists capsules from the real Wrenn API", async () => {
const capsules = await client.capsules.list();
expect(Array.isArray(capsules)).toBe(true);
});
it("lists snapshot templates from the real Wrenn API", async () => {
const snapshots = await client.snapshots.list();
expect(Array.isArray(snapshots)).toBe(true);
});
it("gets capsule stats from the real Wrenn API", async () => {
const stats = await client.capsules.stats({ range: "1h" });
expect(stats).toBeTypeOf("object");
});
it("gets capsule usage from the real Wrenn API", async () => {
const usage = await client.capsules.usage();
expect(usage).toBeTypeOf("object");
});
});
describeWithLogin("WrennClient live login integration", () => {
let client: WrennClient;
beforeAll(async () => {
const authClient = new WrennClient({ baseUrl });
const auth = await authClient.auth.login({
email: testEmail as string,
password: testPassword as string,
});
expect(auth.token).toBeTypeOf("string");
client = new WrennClient({ baseUrl, token: auth.token });
});
it("gets the current account profile from the real Wrenn API", async () => {
const me = await client.account.getMe();
expect(me).toBeTypeOf("object");
});
it("lists teams from the real Wrenn API", async () => {
const teams = await client.teams.list();
expect(Array.isArray(teams)).toBe(true);
});
it("lists API keys from the real Wrenn API", async () => {
const keys = await client.apiKeys.list();
expect(Array.isArray(keys)).toBe(true);
});
it("lists notification channels from the real Wrenn API", async () => {
const channels = await client.channels.list();
expect(Array.isArray(channels)).toBe(true);
});
it("lists hosts from the real Wrenn API", async () => {
const hosts = await client.hosts.list();
expect(Array.isArray(hosts)).toBe(true);
});
});

View File

@ -1,183 +0,0 @@
import {
createServer,
type IncomingMessage,
type ServerResponse,
} from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, describe, expect, it } from "vitest";
import { WebSocketServer } from "ws";
import { HttpClient } from "../../src/_shared/http.js";
import { WsConnection } from "../../src/_shared/websocket.js";
import { ConflictError, TimeoutError } from "../../src/exceptions.js";
interface CapturedRequest {
method?: string;
url?: string;
headers: IncomingMessage["headers"];
body: string;
}
function readRequestBody(request: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = "";
request.setEncoding("utf8");
request.on("data", (chunk) => {
body += chunk;
});
request.on("end", () => resolve(body));
request.on("error", reject);
});
}
function listen(server: ReturnType<typeof createServer>): Promise<number> {
return new Promise((resolve) => {
server.listen(0, "127.0.0.1", () => {
const address = server.address() as AddressInfo;
resolve(address.port);
});
});
}
function closeHttpServer(
server: ReturnType<typeof createServer>,
): Promise<void> {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error) reject(error);
else resolve();
});
});
}
function closeWebSocketServer(server: WebSocketServer): Promise<void> {
return new Promise((resolve, reject) => {
server.close((error) => {
if (error) reject(error);
else resolve();
});
});
}
describe("foundation integration", () => {
const cleanup: Array<() => Promise<void>> = [];
afterEach(async () => {
await Promise.all(cleanup.splice(0).map((close) => close()));
});
it("sends JSON requests through a real HTTP server", async () => {
let captured: CapturedRequest | undefined;
const server = createServer(async (request, response: ServerResponse) => {
captured = {
method: request.method,
url: request.url,
headers: request.headers,
body: await readRequestBody(request),
};
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ ok: true }));
});
const port = await listen(server);
cleanup.push(() => closeHttpServer(server));
const client = new HttpClient({
baseUrl: `http://127.0.0.1:${port}`,
apiKey: "api-key",
token: "jwt-token",
hostToken: "host-token",
});
await expect(
client.post(
"/v1/foundation",
{ hello: "world" },
{ params: { page: 1 } },
),
).resolves.toEqual({ ok: true });
expect(captured).toMatchObject({
method: "POST",
url: "/v1/foundation?page=1",
body: JSON.stringify({ hello: "world" }),
});
expect(captured?.headers["content-type"]).toBe("application/json");
expect(captured?.headers.accept).toBe("application/json");
expect(captured?.headers.authorization).toBe("Bearer jwt-token");
expect(captured?.headers["x-api-key"]).toBe("api-key");
expect(captured?.headers["x-host-token"]).toBe("host-token");
});
it("maps real HTTP error responses to SDK errors", async () => {
const server = createServer((_request, response) => {
response.writeHead(409, { "Content-Type": "application/json" });
response.end(
JSON.stringify({
error: { code: "capsule_busy", message: "Capsule is busy" },
}),
);
});
const port = await listen(server);
cleanup.push(() => closeHttpServer(server));
const client = new HttpClient({ baseUrl: `http://127.0.0.1:${port}` });
await expect(client.get("/v1/error")).rejects.toMatchObject({
code: "capsule_busy",
message: "Capsule is busy",
statusCode: 409,
});
await expect(client.get("/v1/error")).rejects.toBeInstanceOf(ConflictError);
});
it("aborts real HTTP requests using timeoutMs", async () => {
const server = createServer((_request, response) => {
setTimeout(() => {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ ok: true }));
}, 200);
});
const port = await listen(server);
cleanup.push(() => closeHttpServer(server));
const client = new HttpClient({ baseUrl: `http://127.0.0.1:${port}` });
await expect(
client.get("/v1/slow", { timeoutMs: 10 }),
).rejects.toBeInstanceOf(TimeoutError);
});
it("exchanges JSON messages through a real WebSocket server", async () => {
const server = new WebSocketServer({ port: 0, host: "127.0.0.1" });
cleanup.push(() => closeWebSocketServer(server));
const receivedByServer = new Promise<unknown>((resolve) => {
server.on("connection", (socket, request) => {
expect(request.headers["x-api-key"]).toBe("api-key");
expect(request.headers["x-host-token"]).toBe("host-token");
socket.on("message", (raw) => resolve(JSON.parse(raw.toString())));
socket.send(JSON.stringify({ type: "ready" }));
});
});
await new Promise<void>((resolve) => server.once("listening", resolve));
const address = server.address() as AddressInfo;
const messages: unknown[] = [];
const connection = await WsConnection.connect({
baseUrl: `http://127.0.0.1:${address.port}`,
path: "/v1/ws",
apiKey: "api-key",
hostToken: "host-token",
onMessage: (message) => messages.push(message),
});
connection.send({ type: "start" });
await expect(receivedByServer).resolves.toEqual({ type: "start" });
expect(messages).toEqual([{ type: "ready" }]);
expect(connection.isClosed).toBe(false);
connection.close();
});
});

View File

@ -0,0 +1,85 @@
import { beforeAll, describe, expect, it } from "vitest";
import { WrennClient } from "../../src/client.js";
import { DEFAULT_BASE_URL } from "../../src/config.js";
const baseUrl = process.env.WRENN_BASE_URL ?? DEFAULT_BASE_URL;
const apiKey = process.env.WRENN_API_KEY;
const testEmail = process.env.WRENN_TEST_EMAIL;
const testPassword = process.env.WRENN_TEST_PASS;
const describeWithApiKey = apiKey ? describe : describe.skip;
const describeWithLogin = testEmail && testPassword ? describe : describe.skip;
describeWithApiKey("WrennClient live API key integration", () => {
const client = new WrennClient({ apiKey, baseUrl });
it("lists capsules from the real Wrenn API", async () => {
const capsules = await client.capsules.list();
expect(Array.isArray(capsules)).toBe(true);
});
it("lists snapshot templates from the real Wrenn API", async () => {
const snapshots = await client.snapshots.list();
expect(Array.isArray(snapshots)).toBe(true);
});
it("gets capsule stats from the real Wrenn API", async () => {
const stats = await client.capsules.stats({ range: "1h" });
expect(stats).toBeTypeOf("object");
});
it("gets capsule usage from the real Wrenn API", async () => {
const usage = await client.capsules.usage();
expect(usage).toBeTypeOf("object");
});
});
describeWithLogin("WrennClient live login integration", () => {
let client: WrennClient;
beforeAll(async () => {
const authClient = new WrennClient({ baseUrl });
const auth = await authClient.auth.login({
email: testEmail as string,
password: testPassword as string,
});
expect(auth.token).toBeTypeOf("string");
client = new WrennClient({ baseUrl, token: auth.token });
});
it("gets the current account profile from the real Wrenn API", async () => {
const me = await client.account.getMe();
expect(me).toBeTypeOf("object");
});
it("lists teams from the real Wrenn API", async () => {
const teams = await client.teams.list();
expect(Array.isArray(teams)).toBe(true);
});
it("lists API keys from the real Wrenn API", async () => {
const keys = await client.apiKeys.list();
expect(Array.isArray(keys)).toBe(true);
});
it("lists notification channels from the real Wrenn API", async () => {
const channels = await client.channels.list();
expect(Array.isArray(channels)).toBe(true);
});
it("lists hosts from the real Wrenn API", async () => {
const hosts = await client.hosts.list();
expect(Array.isArray(hosts)).toBe(true);
});
});