496 lines
14 KiB
TypeScript
496 lines
14 KiB
TypeScript
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);
|
|
expect((calls.at(-1)?.init.body as FormData).get("file")).toBeInstanceOf(
|
|
Blob,
|
|
);
|
|
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",
|
|
});
|
|
});
|
|
});
|