@miosa/sdk 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -3
- package/dist/index.d.ts +323 -175
- package/dist/index.js +441 -386
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/client.ts +0 -293
- package/src/errors.ts +0 -136
- package/src/http.test.ts +0 -374
- package/src/http.ts +0 -390
- package/src/index.ts +0 -564
- package/src/resources/admin.ts +0 -359
- package/src/resources/analytics.ts +0 -60
- package/src/resources/api-keys.ts +0 -135
- package/src/resources/audit-log.ts +0 -64
- package/src/resources/benchmarks.ts +0 -104
- package/src/resources/builder-sessions.ts +0 -75
- package/src/resources/channels.ts +0 -143
- package/src/resources/checkpoints.ts +0 -225
- package/src/resources/command-center.ts +0 -73
- package/src/resources/community.ts +0 -103
- package/src/resources/completions.ts +0 -104
- package/src/resources/computer-auto-stop.ts +0 -43
- package/src/resources/computer-env.ts +0 -76
- package/src/resources/computer-logs.ts +0 -50
- package/src/resources/computer-osa.ts +0 -76
- package/src/resources/computer-ports.ts +0 -91
- package/src/resources/computer-terminal.ts +0 -61
- package/src/resources/computer-volumes.ts +0 -64
- package/src/resources/computer.ts +0 -551
- package/src/resources/computers.ts +0 -75
- package/src/resources/credits.ts +0 -43
- package/src/resources/cron-jobs.ts +0 -191
- package/src/resources/custom_domains.ts +0 -123
- package/src/resources/dashboard.ts +0 -49
- package/src/resources/databases.ts +0 -218
- package/src/resources/deployments.test.ts +0 -194
- package/src/resources/deployments.ts +0 -1060
- package/src/resources/desktop.ts +0 -134
- package/src/resources/docker-deploy.test.ts +0 -102
- package/src/resources/docker-deploy.ts +0 -183
- package/src/resources/egress.test.ts +0 -318
- package/src/resources/egressAudit.ts +0 -245
- package/src/resources/egressNetwork.ts +0 -450
- package/src/resources/egressSecrets.ts +0 -577
- package/src/resources/email.ts +0 -212
- package/src/resources/embeddings.ts +0 -36
- package/src/resources/events.ts +0 -296
- package/src/resources/exec.ts +0 -319
- package/src/resources/external-keys.ts +0 -79
- package/src/resources/files.test.ts +0 -339
- package/src/resources/files.ts +0 -220
- package/src/resources/flat-custom-domains.ts +0 -127
- package/src/resources/functions.ts +0 -178
- package/src/resources/governance.test.ts +0 -355
- package/src/resources/governance.ts +0 -528
- package/src/resources/health-checks.ts +0 -165
- package/src/resources/integrations.ts +0 -183
- package/src/resources/mcp.ts +0 -70
- package/src/resources/models.ts +0 -45
- package/src/resources/network_policy.ts +0 -73
- package/src/resources/open-computers/agents.ts +0 -91
- package/src/resources/open-computers/apps.ts +0 -102
- package/src/resources/open-computers/clusters.ts +0 -88
- package/src/resources/open-computers/desktop.ts +0 -34
- package/src/resources/open-computers/files.ts +0 -97
- package/src/resources/open-computers/hosts.ts +0 -85
- package/src/resources/open-computers/index.ts +0 -98
- package/src/resources/open-computers/jobs.ts +0 -75
- package/src/resources/open-computers/open_computers.test.ts +0 -288
- package/src/resources/open-computers/secrets.ts +0 -115
- package/src/resources/open-computers/terminal.ts +0 -33
- package/src/resources/open-computers/tunnels.ts +0 -87
- package/src/resources/open-computers/types.ts +0 -343
- package/src/resources/open-computers/workspaces.ts +0 -135
- package/src/resources/org-invites.ts +0 -189
- package/src/resources/phase1.test.ts +0 -187
- package/src/resources/project-auth.ts +0 -142
- package/src/resources/project-integrations.ts +0 -133
- package/src/resources/provider-defaults.ts +0 -89
- package/src/resources/quotas.ts +0 -77
- package/src/resources/regions.ts +0 -94
- package/src/resources/sandbox-processes.ts +0 -112
- package/src/resources/sandbox-shares.ts +0 -83
- package/src/resources/sandbox-templates.ts +0 -195
- package/src/resources/sandboxes.live.test.ts +0 -92
- package/src/resources/sandboxes.test.ts +0 -653
- package/src/resources/sandboxes.ts +0 -1433
- package/src/resources/settings.ts +0 -143
- package/src/resources/snapshots-standalone.ts +0 -51
- package/src/resources/storage.ts +0 -221
- package/src/resources/tenant-events.ts +0 -32
- package/src/resources/tenant.ts +0 -148
- package/src/resources/usage.ts +0 -85
- package/src/resources/volumes.ts +0 -117
- package/src/resources/webhooks.ts +0 -224
- package/src/resources/workspace-invites.ts +0 -188
- package/src/resources/workspace-members.test.ts +0 -121
- package/src/resources/workspace-members.ts +0 -143
- package/src/resources/workspaces.ts +0 -285
- package/src/types.ts +0 -463
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Functions resource — serverless edge functions.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { randomUUID } from "node:crypto";
|
|
6
|
-
|
|
7
|
-
import type { HttpClient } from "../http.js";
|
|
8
|
-
|
|
9
|
-
// ── Branded IDs ──────────────────────────────────────────────────────────────
|
|
10
|
-
|
|
11
|
-
export type FunctionId = string & { readonly __brand: "FunctionId" };
|
|
12
|
-
|
|
13
|
-
// ── Resource shapes ──────────────────────────────────────────────────────────
|
|
14
|
-
|
|
15
|
-
export interface FunctionData {
|
|
16
|
-
id: FunctionId;
|
|
17
|
-
tenant_id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
state?: string;
|
|
20
|
-
runtime?: string | null;
|
|
21
|
-
handler?: string | null;
|
|
22
|
-
memory_mb?: number | null;
|
|
23
|
-
timeout_sec?: number | null;
|
|
24
|
-
env?: Record<string, string>;
|
|
25
|
-
created_at?: string;
|
|
26
|
-
updated_at?: string;
|
|
27
|
-
[key: string]: unknown;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ── Request payloads ─────────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
export interface FunctionListParams {
|
|
33
|
-
limit?: number;
|
|
34
|
-
cursor?: string;
|
|
35
|
-
state?: string;
|
|
36
|
-
[key: string]: string | number | boolean | undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface FunctionCreateParams {
|
|
40
|
-
name: string;
|
|
41
|
-
runtime?: string;
|
|
42
|
-
handler?: string;
|
|
43
|
-
memory_mb?: number;
|
|
44
|
-
memoryMb?: number;
|
|
45
|
-
timeout_sec?: number;
|
|
46
|
-
timeoutSec?: number;
|
|
47
|
-
env?: Record<string, string>;
|
|
48
|
-
idempotencyKey?: string;
|
|
49
|
-
[key: string]: unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface FunctionUpdateParams {
|
|
53
|
-
name?: string;
|
|
54
|
-
runtime?: string;
|
|
55
|
-
handler?: string;
|
|
56
|
-
memory_mb?: number;
|
|
57
|
-
memoryMb?: number;
|
|
58
|
-
timeout_sec?: number;
|
|
59
|
-
timeoutSec?: number;
|
|
60
|
-
env?: Record<string, string>;
|
|
61
|
-
[key: string]: unknown;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface FunctionInvokeParams {
|
|
65
|
-
payload?: Record<string, unknown>;
|
|
66
|
-
headers?: Record<string, string>;
|
|
67
|
-
idempotencyKey?: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
function unwrap<T>(payload: unknown): T {
|
|
73
|
-
if (payload && typeof payload === "object" && "data" in (payload as object)) {
|
|
74
|
-
return (payload as { data: T }).data;
|
|
75
|
-
}
|
|
76
|
-
return payload as T;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function listItems<T>(
|
|
80
|
-
payload: unknown,
|
|
81
|
-
candidateKeys: string[] = ["data", "functions", "items"],
|
|
82
|
-
): T[] {
|
|
83
|
-
if (Array.isArray(payload)) return payload;
|
|
84
|
-
if (!payload || typeof payload !== "object") return [];
|
|
85
|
-
const p = payload as Record<string, unknown>;
|
|
86
|
-
if (Array.isArray(p.data)) return p.data as T[];
|
|
87
|
-
for (const key of candidateKeys) {
|
|
88
|
-
if (Array.isArray(p[key])) return p[key] as T[];
|
|
89
|
-
}
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function stripUndefined(
|
|
94
|
-
input: Record<string, unknown>,
|
|
95
|
-
): Record<string, unknown> {
|
|
96
|
-
return Object.fromEntries(
|
|
97
|
-
Object.entries(input).filter(([, v]) => v !== undefined),
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function idempotencyKey(key?: string): string {
|
|
102
|
-
return key ?? randomUUID();
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Main resource ─────────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
export class Functions {
|
|
108
|
-
constructor(private readonly http: HttpClient) {}
|
|
109
|
-
|
|
110
|
-
async list(params: FunctionListParams = {}): Promise<FunctionData[]> {
|
|
111
|
-
const query = stripUndefined({ ...params }) as Record<
|
|
112
|
-
string,
|
|
113
|
-
string | number | boolean | undefined
|
|
114
|
-
>;
|
|
115
|
-
const data = await this.http.get<unknown>("/functions", query);
|
|
116
|
-
return listItems<FunctionData>(data);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async get(functionId: string): Promise<FunctionData> {
|
|
120
|
-
const data = await this.http.get<unknown>(`/functions/${functionId}`);
|
|
121
|
-
return unwrap<FunctionData>(data);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async create(params: FunctionCreateParams): Promise<FunctionData> {
|
|
125
|
-
const { idempotencyKey: ikey, memoryMb, timeoutSec, ...rest } = params;
|
|
126
|
-
const body = stripUndefined({
|
|
127
|
-
...rest,
|
|
128
|
-
memory_mb: memoryMb ?? rest.memory_mb,
|
|
129
|
-
timeout_sec: timeoutSec ?? rest.timeout_sec,
|
|
130
|
-
});
|
|
131
|
-
const data = await this.http.request<unknown>("/functions", {
|
|
132
|
-
method: "POST",
|
|
133
|
-
body,
|
|
134
|
-
headers: { "Idempotency-Key": idempotencyKey(ikey) },
|
|
135
|
-
});
|
|
136
|
-
return unwrap<FunctionData>(data);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async update(
|
|
140
|
-
functionId: string,
|
|
141
|
-
params: FunctionUpdateParams,
|
|
142
|
-
): Promise<FunctionData> {
|
|
143
|
-
const { memoryMb, timeoutSec, ...rest } = params;
|
|
144
|
-
const body = stripUndefined({
|
|
145
|
-
...rest,
|
|
146
|
-
memory_mb: memoryMb ?? rest.memory_mb,
|
|
147
|
-
timeout_sec: timeoutSec ?? rest.timeout_sec,
|
|
148
|
-
});
|
|
149
|
-
const data = await this.http.patch<unknown>(
|
|
150
|
-
`/functions/${functionId}`,
|
|
151
|
-
body,
|
|
152
|
-
);
|
|
153
|
-
return unwrap<FunctionData>(data);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async delete(functionId: string): Promise<void> {
|
|
157
|
-
await this.http.delete<unknown>(`/functions/${functionId}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async invoke(
|
|
161
|
-
functionId: string,
|
|
162
|
-
params: FunctionInvokeParams = {},
|
|
163
|
-
): Promise<Record<string, unknown>> {
|
|
164
|
-
const { payload = {}, headers, idempotencyKey: ikey } = params;
|
|
165
|
-
const data = await this.http.request<unknown>(
|
|
166
|
-
`/functions/${functionId}/invoke`,
|
|
167
|
-
{
|
|
168
|
-
method: "POST",
|
|
169
|
-
body: payload,
|
|
170
|
-
headers: {
|
|
171
|
-
"Idempotency-Key": idempotencyKey(ikey),
|
|
172
|
-
...headers,
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
return (data ?? {}) as Record<string, unknown>;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import type { HttpClient } from "../http.js";
|
|
3
|
-
import {
|
|
4
|
-
BillingResource,
|
|
5
|
-
BulkResource,
|
|
6
|
-
ExternalUsers,
|
|
7
|
-
GovernanceTenant,
|
|
8
|
-
GovernanceWorkspaces,
|
|
9
|
-
} from "./governance.js";
|
|
10
|
-
import { ApiKeys } from "./api-keys.js";
|
|
11
|
-
import { Admin } from "./admin.js";
|
|
12
|
-
|
|
13
|
-
const mockGet = vi.fn();
|
|
14
|
-
const mockPost = vi.fn();
|
|
15
|
-
const mockPut = vi.fn();
|
|
16
|
-
const mockPatch = vi.fn();
|
|
17
|
-
const mockDelete = vi.fn();
|
|
18
|
-
const mockStream = vi.fn();
|
|
19
|
-
const mockRequest = vi.fn();
|
|
20
|
-
|
|
21
|
-
function makeHttp(): HttpClient {
|
|
22
|
-
return {
|
|
23
|
-
get: mockGet,
|
|
24
|
-
post: mockPost,
|
|
25
|
-
put: mockPut,
|
|
26
|
-
patch: mockPatch,
|
|
27
|
-
delete: mockDelete,
|
|
28
|
-
request: mockRequest,
|
|
29
|
-
stream: mockStream,
|
|
30
|
-
} as unknown as HttpClient;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
vi.resetAllMocks();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// ── GovernanceTenant.policy ───────────────────────────────────────────────────
|
|
38
|
-
|
|
39
|
-
describe("GovernanceTenant.policy", () => {
|
|
40
|
-
it("get calls GET /tenant/policy and unwraps data", async () => {
|
|
41
|
-
mockGet.mockResolvedValue({ data: { quotas: { max_sandboxes: 10 } } });
|
|
42
|
-
const t = new GovernanceTenant(makeHttp());
|
|
43
|
-
const result = await t.policy.get();
|
|
44
|
-
expect(mockGet).toHaveBeenCalledWith("/tenant/policy");
|
|
45
|
-
expect((result as Record<string, unknown>).quotas).toBeDefined();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("set calls PUT /tenant/policy with body", async () => {
|
|
49
|
-
mockPut.mockResolvedValue({ data: { quotas: { max_sandboxes: 5 } } });
|
|
50
|
-
const t = new GovernanceTenant(makeHttp());
|
|
51
|
-
await t.policy.set({ quotas: { max_sandboxes: 5 } });
|
|
52
|
-
expect(mockPut).toHaveBeenCalledWith("/tenant/policy", {
|
|
53
|
-
quotas: { max_sandboxes: 5 },
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("delete calls DELETE /tenant/policy", async () => {
|
|
58
|
-
mockDelete.mockResolvedValue(null);
|
|
59
|
-
const t = new GovernanceTenant(makeHttp());
|
|
60
|
-
await t.policy.delete();
|
|
61
|
-
expect(mockDelete).toHaveBeenCalledWith("/tenant/policy");
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ── GovernanceTenant.members ──────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
describe("GovernanceTenant.members", () => {
|
|
68
|
-
it("list returns members array", async () => {
|
|
69
|
-
mockGet.mockResolvedValue({
|
|
70
|
-
data: [{ id: "m1", email: "a@b.com", role: "admin" }],
|
|
71
|
-
});
|
|
72
|
-
const t = new GovernanceTenant(makeHttp());
|
|
73
|
-
const members = await t.members.list();
|
|
74
|
-
expect(members).toHaveLength(1);
|
|
75
|
-
expect(members[0].role).toBe("admin");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("invite posts email and role", async () => {
|
|
79
|
-
mockPost.mockResolvedValue({ data: { id: "m2", role: "developer" } });
|
|
80
|
-
const t = new GovernanceTenant(makeHttp());
|
|
81
|
-
const result = await t.members.invite("x@y.com", "developer");
|
|
82
|
-
expect(mockPost).toHaveBeenCalledWith("/tenant/members", {
|
|
83
|
-
email: "x@y.com",
|
|
84
|
-
role: "developer",
|
|
85
|
-
});
|
|
86
|
-
expect(result.role).toBe("developer");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("updateRole patches member role", async () => {
|
|
90
|
-
mockPatch.mockResolvedValue({ data: { id: "m1", role: "admin" } });
|
|
91
|
-
const t = new GovernanceTenant(makeHttp());
|
|
92
|
-
await t.members.updateRole("m1", "admin");
|
|
93
|
-
expect(mockPatch).toHaveBeenCalledWith("/tenant/members/m1/role", {
|
|
94
|
-
role: "admin",
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("remove deletes member", async () => {
|
|
99
|
-
mockDelete.mockResolvedValue(null);
|
|
100
|
-
const t = new GovernanceTenant(makeHttp());
|
|
101
|
-
await t.members.remove("m1");
|
|
102
|
-
expect(mockDelete).toHaveBeenCalledWith("/tenant/members/m1");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("transferOwnership posts new_owner_user_id", async () => {
|
|
106
|
-
mockPost.mockResolvedValue({ data: { transferred: true } });
|
|
107
|
-
const t = new GovernanceTenant(makeHttp());
|
|
108
|
-
await t.members.transferOwnership("user_new");
|
|
109
|
-
expect(mockPost).toHaveBeenCalledWith("/tenant/transfer-ownership", {
|
|
110
|
-
new_owner_user_id: "user_new",
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ── GovernanceWorkspaces ──────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
describe("GovernanceWorkspaces", () => {
|
|
118
|
-
it("list returns workspace array", async () => {
|
|
119
|
-
mockGet.mockResolvedValue({ data: [{ id: "ws_1", name: "alpha" }] });
|
|
120
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
121
|
-
const items = await gw.list();
|
|
122
|
-
expect(items).toHaveLength(1);
|
|
123
|
-
expect(items[0].id).toBe("ws_1");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("create posts name", async () => {
|
|
127
|
-
mockPost.mockResolvedValue({ data: { id: "ws_2", name: "beta" } });
|
|
128
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
129
|
-
await gw.create("beta");
|
|
130
|
-
expect(mockPost).toHaveBeenCalledWith("/workspaces", { name: "beta" });
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("workspace(id).policy.get calls correct path", async () => {
|
|
134
|
-
mockGet.mockResolvedValue({ data: { quotas: {} } });
|
|
135
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
136
|
-
await gw.workspace("ws_1").policy.get();
|
|
137
|
-
expect(mockGet).toHaveBeenCalledWith("/workspaces/ws_1/policy");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("workspace(id).policy.set calls PUT", async () => {
|
|
141
|
-
mockPut.mockResolvedValue({ data: {} });
|
|
142
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
143
|
-
await gw.workspace("ws_1").policy.set({ quotas: { max_sandboxes: 3 } });
|
|
144
|
-
expect(mockPut).toHaveBeenCalledWith("/workspaces/ws_1/policy", {
|
|
145
|
-
quotas: { max_sandboxes: 3 },
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("workspace(id).members.list calls correct path", async () => {
|
|
150
|
-
mockGet.mockResolvedValue({ data: [{ id: "m1", role: "developer" }] });
|
|
151
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
152
|
-
const members = await gw.workspace("ws_1").members.list();
|
|
153
|
-
expect(mockGet).toHaveBeenCalledWith("/workspaces/ws_1/members");
|
|
154
|
-
expect(members[0].role).toBe("developer");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("workspace(id).members.invite posts correctly", async () => {
|
|
158
|
-
mockPost.mockResolvedValue({ data: { id: "m2" } });
|
|
159
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
160
|
-
await gw.workspace("ws_1").members.invite("bob@acme.com", "viewer");
|
|
161
|
-
expect(mockPost).toHaveBeenCalledWith("/workspaces/ws_1/members", {
|
|
162
|
-
email: "bob@acme.com",
|
|
163
|
-
role: "viewer",
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("workspace(id).transfer posts resource_ids and target", async () => {
|
|
168
|
-
mockPost.mockResolvedValue({ data: { transferred: 2 } });
|
|
169
|
-
const gw = new GovernanceWorkspaces(makeHttp());
|
|
170
|
-
await gw.workspace("ws_1").transfer(["sbx_1", "sbx_2"], "ws_2");
|
|
171
|
-
expect(mockPost).toHaveBeenCalledWith("/workspaces/ws_1/transfer", {
|
|
172
|
-
resource_ids: ["sbx_1", "sbx_2"],
|
|
173
|
-
target_workspace_id: "ws_2",
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// ── ExternalUsers.policy ──────────────────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
describe("ExternalUsers.policy", () => {
|
|
181
|
-
it("get calls correct path", async () => {
|
|
182
|
-
mockGet.mockResolvedValue({ data: { lifecycle: {} } });
|
|
183
|
-
const eu = new ExternalUsers(makeHttp());
|
|
184
|
-
await eu.call("alice").policy.get();
|
|
185
|
-
expect(mockGet).toHaveBeenCalledWith("/external-users/alice/policy");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("effective returns typed EffectivePolicyDoc with source fields", async () => {
|
|
189
|
-
mockGet.mockResolvedValue({
|
|
190
|
-
lifecycle: {
|
|
191
|
-
default_idle_timeout_sec: { value: 600, source: "user" },
|
|
192
|
-
default_timeout_sec: { value: 86400, source: "tenant" },
|
|
193
|
-
},
|
|
194
|
-
quotas: {
|
|
195
|
-
max_sandboxes: { value: 5, source: "workspace" },
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
const eu = new ExternalUsers(makeHttp());
|
|
199
|
-
const eff = await eu.call("alice").policy.effective();
|
|
200
|
-
expect(eff.lifecycle["default_idle_timeout_sec"]?.value).toBe(600);
|
|
201
|
-
expect(eff.lifecycle["default_idle_timeout_sec"]?.source).toBe("user");
|
|
202
|
-
expect(eff.quotas["max_sandboxes"]?.value).toBe(5);
|
|
203
|
-
expect(eff.quotas["max_sandboxes"]?.source).toBe("workspace");
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// ── BulkResource ──────────────────────────────────────────────────────────────
|
|
208
|
-
|
|
209
|
-
describe("BulkResource", () => {
|
|
210
|
-
it("sandboxes.pause posts ids", async () => {
|
|
211
|
-
mockPost.mockResolvedValue({ queued: 3, job_id: "job_1" });
|
|
212
|
-
const bulk = new BulkResource(makeHttp());
|
|
213
|
-
const result = await bulk.sandboxes.pause({
|
|
214
|
-
ids: ["sbx_1", "sbx_2", "sbx_3"],
|
|
215
|
-
});
|
|
216
|
-
expect(mockPost).toHaveBeenCalledWith("/bulk/sandboxes/pause", {
|
|
217
|
-
ids: ["sbx_1", "sbx_2", "sbx_3"],
|
|
218
|
-
});
|
|
219
|
-
expect(result.job_id).toBe("job_1");
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it("sandboxes.destroy posts filter", async () => {
|
|
223
|
-
mockPost.mockResolvedValue({ queued: 5, job_id: "job_2" });
|
|
224
|
-
const bulk = new BulkResource(makeHttp());
|
|
225
|
-
const result = await bulk.sandboxes.destroy({ filter: { state: "idle" } });
|
|
226
|
-
expect(mockPost).toHaveBeenCalledWith("/bulk/sandboxes/destroy", {
|
|
227
|
-
filter: { state: "idle" },
|
|
228
|
-
});
|
|
229
|
-
expect(result.queued).toBe(5);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("policy.apply posts tier + ids + policy", async () => {
|
|
233
|
-
mockPost.mockResolvedValue({ queued: 10, job_id: "job_3" });
|
|
234
|
-
const bulk = new BulkResource(makeHttp());
|
|
235
|
-
await bulk.policy.apply({
|
|
236
|
-
tier: "external_user",
|
|
237
|
-
idsOrFilter: ["u1", "u2"],
|
|
238
|
-
policy: { quotas: { max_sandboxes: 3 } },
|
|
239
|
-
});
|
|
240
|
-
expect(mockPost).toHaveBeenCalledWith("/bulk/policy/apply", {
|
|
241
|
-
tier: "external_user",
|
|
242
|
-
ids: ["u1", "u2"],
|
|
243
|
-
policy: { quotas: { max_sandboxes: 3 } },
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("jobs.get returns job status", async () => {
|
|
248
|
-
mockGet.mockResolvedValue({
|
|
249
|
-
data: { id: "job_1", status: "completed", processed: 3 },
|
|
250
|
-
});
|
|
251
|
-
const bulk = new BulkResource(makeHttp());
|
|
252
|
-
const job = await bulk.jobs.get("job_1");
|
|
253
|
-
expect(job.status).toBe("completed");
|
|
254
|
-
expect(job.processed).toBe(3);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// ── BillingResource ───────────────────────────────────────────────────────────
|
|
259
|
-
|
|
260
|
-
describe("BillingResource", () => {
|
|
261
|
-
it("invoices.list returns array", async () => {
|
|
262
|
-
mockGet.mockResolvedValue({
|
|
263
|
-
data: [{ id: "inv_1", amount: 5000 }],
|
|
264
|
-
});
|
|
265
|
-
const billing = new BillingResource(makeHttp());
|
|
266
|
-
const invoices = await billing.invoices.list();
|
|
267
|
-
expect(invoices[0].id).toBe("inv_1");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it("invoices.get returns single invoice", async () => {
|
|
271
|
-
mockGet.mockResolvedValue({
|
|
272
|
-
data: { id: "inv_1", amount: 5000, line_items: [] },
|
|
273
|
-
});
|
|
274
|
-
const billing = new BillingResource(makeHttp());
|
|
275
|
-
const inv = await billing.invoices.get("inv_1");
|
|
276
|
-
expect(inv.id).toBe("inv_1");
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it("paymentMethods returns list", async () => {
|
|
280
|
-
mockGet.mockResolvedValue({
|
|
281
|
-
data: [{ id: "pm_1", brand: "visa", last4: "4242" }],
|
|
282
|
-
});
|
|
283
|
-
const billing = new BillingResource(makeHttp());
|
|
284
|
-
const methods = await billing.paymentMethods();
|
|
285
|
-
expect(methods[0].brand).toBe("visa");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it("upcoming returns preview", async () => {
|
|
289
|
-
mockGet.mockResolvedValue({
|
|
290
|
-
data: { amount: 1200, period_end: "2026-06-01" },
|
|
291
|
-
});
|
|
292
|
-
const billing = new BillingResource(makeHttp());
|
|
293
|
-
const upcoming = await billing.upcoming();
|
|
294
|
-
expect((upcoming as Record<string, unknown>).amount).toBe(1200);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// ── ApiKeys.createScoped ──────────────────────────────────────────────────────
|
|
299
|
-
|
|
300
|
-
describe("ApiKeys.createScoped", () => {
|
|
301
|
-
it("posts to /api-keys/scoped with correct body", async () => {
|
|
302
|
-
mockPost.mockResolvedValue({
|
|
303
|
-
data: { id: "key_1", token: "msk_l2_abc" },
|
|
304
|
-
});
|
|
305
|
-
const keys = new ApiKeys(makeHttp());
|
|
306
|
-
const result = await keys.createScoped({
|
|
307
|
-
externalUserId: "alice-42",
|
|
308
|
-
scopes: ["sandboxes:read", "sandboxes:exec"],
|
|
309
|
-
expiresAt: "2026-12-31T00:00:00Z",
|
|
310
|
-
});
|
|
311
|
-
expect(mockPost).toHaveBeenCalledWith("/api-keys/scoped", {
|
|
312
|
-
external_user_id: "alice-42",
|
|
313
|
-
scopes: ["sandboxes:read", "sandboxes:exec"],
|
|
314
|
-
expires_at: "2026-12-31T00:00:00Z",
|
|
315
|
-
});
|
|
316
|
-
expect(result.token).toBe("msk_l2_abc");
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it("omits expires_at when not provided", async () => {
|
|
320
|
-
mockPost.mockResolvedValue({ data: { id: "key_2" } });
|
|
321
|
-
const keys = new ApiKeys(makeHttp());
|
|
322
|
-
await keys.createScoped({
|
|
323
|
-
externalUserId: "bob",
|
|
324
|
-
scopes: ["sandboxes:read"],
|
|
325
|
-
});
|
|
326
|
-
const body = mockPost.mock.calls[0][1] as Record<string, unknown>;
|
|
327
|
-
expect(body["expires_at"]).toBeUndefined();
|
|
328
|
-
});
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
// ── Admin.impersonate ─────────────────────────────────────────────────────────
|
|
332
|
-
|
|
333
|
-
describe("Admin.impersonate", () => {
|
|
334
|
-
it("posts to /admin/impersonate with external_user_id and ttl_sec", async () => {
|
|
335
|
-
mockPost.mockResolvedValue({
|
|
336
|
-
token: "msi_abc",
|
|
337
|
-
expires_at: "2026-06-01T00:00:00Z",
|
|
338
|
-
});
|
|
339
|
-
const admin = new Admin(makeHttp());
|
|
340
|
-
const result = await admin.impersonate("alice-42", { ttlSec: 1800 });
|
|
341
|
-
expect(mockPost).toHaveBeenCalledWith("/admin/impersonate", {
|
|
342
|
-
external_user_id: "alice-42",
|
|
343
|
-
ttl_sec: 1800,
|
|
344
|
-
});
|
|
345
|
-
expect(result.token).toBe("msi_abc");
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("defaults ttl_sec to 3600", async () => {
|
|
349
|
-
mockPost.mockResolvedValue({ token: "msi_xyz" });
|
|
350
|
-
const admin = new Admin(makeHttp());
|
|
351
|
-
await admin.impersonate("bob");
|
|
352
|
-
const body = mockPost.mock.calls[0][1] as Record<string, unknown>;
|
|
353
|
-
expect(body["ttl_sec"]).toBe(3600);
|
|
354
|
-
});
|
|
355
|
-
});
|