@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.
Files changed (100) hide show
  1. package/README.md +75 -3
  2. package/dist/index.d.ts +323 -175
  3. package/dist/index.js +441 -386
  4. package/dist/index.js.map +1 -1
  5. package/package.json +1 -2
  6. package/src/client.ts +0 -293
  7. package/src/errors.ts +0 -136
  8. package/src/http.test.ts +0 -374
  9. package/src/http.ts +0 -390
  10. package/src/index.ts +0 -564
  11. package/src/resources/admin.ts +0 -359
  12. package/src/resources/analytics.ts +0 -60
  13. package/src/resources/api-keys.ts +0 -135
  14. package/src/resources/audit-log.ts +0 -64
  15. package/src/resources/benchmarks.ts +0 -104
  16. package/src/resources/builder-sessions.ts +0 -75
  17. package/src/resources/channels.ts +0 -143
  18. package/src/resources/checkpoints.ts +0 -225
  19. package/src/resources/command-center.ts +0 -73
  20. package/src/resources/community.ts +0 -103
  21. package/src/resources/completions.ts +0 -104
  22. package/src/resources/computer-auto-stop.ts +0 -43
  23. package/src/resources/computer-env.ts +0 -76
  24. package/src/resources/computer-logs.ts +0 -50
  25. package/src/resources/computer-osa.ts +0 -76
  26. package/src/resources/computer-ports.ts +0 -91
  27. package/src/resources/computer-terminal.ts +0 -61
  28. package/src/resources/computer-volumes.ts +0 -64
  29. package/src/resources/computer.ts +0 -551
  30. package/src/resources/computers.ts +0 -75
  31. package/src/resources/credits.ts +0 -43
  32. package/src/resources/cron-jobs.ts +0 -191
  33. package/src/resources/custom_domains.ts +0 -123
  34. package/src/resources/dashboard.ts +0 -49
  35. package/src/resources/databases.ts +0 -218
  36. package/src/resources/deployments.test.ts +0 -194
  37. package/src/resources/deployments.ts +0 -1060
  38. package/src/resources/desktop.ts +0 -134
  39. package/src/resources/docker-deploy.test.ts +0 -102
  40. package/src/resources/docker-deploy.ts +0 -183
  41. package/src/resources/egress.test.ts +0 -318
  42. package/src/resources/egressAudit.ts +0 -245
  43. package/src/resources/egressNetwork.ts +0 -450
  44. package/src/resources/egressSecrets.ts +0 -577
  45. package/src/resources/email.ts +0 -212
  46. package/src/resources/embeddings.ts +0 -36
  47. package/src/resources/events.ts +0 -296
  48. package/src/resources/exec.ts +0 -319
  49. package/src/resources/external-keys.ts +0 -79
  50. package/src/resources/files.test.ts +0 -339
  51. package/src/resources/files.ts +0 -220
  52. package/src/resources/flat-custom-domains.ts +0 -127
  53. package/src/resources/functions.ts +0 -178
  54. package/src/resources/governance.test.ts +0 -355
  55. package/src/resources/governance.ts +0 -528
  56. package/src/resources/health-checks.ts +0 -165
  57. package/src/resources/integrations.ts +0 -183
  58. package/src/resources/mcp.ts +0 -70
  59. package/src/resources/models.ts +0 -45
  60. package/src/resources/network_policy.ts +0 -73
  61. package/src/resources/open-computers/agents.ts +0 -91
  62. package/src/resources/open-computers/apps.ts +0 -102
  63. package/src/resources/open-computers/clusters.ts +0 -88
  64. package/src/resources/open-computers/desktop.ts +0 -34
  65. package/src/resources/open-computers/files.ts +0 -97
  66. package/src/resources/open-computers/hosts.ts +0 -85
  67. package/src/resources/open-computers/index.ts +0 -98
  68. package/src/resources/open-computers/jobs.ts +0 -75
  69. package/src/resources/open-computers/open_computers.test.ts +0 -288
  70. package/src/resources/open-computers/secrets.ts +0 -115
  71. package/src/resources/open-computers/terminal.ts +0 -33
  72. package/src/resources/open-computers/tunnels.ts +0 -87
  73. package/src/resources/open-computers/types.ts +0 -343
  74. package/src/resources/open-computers/workspaces.ts +0 -135
  75. package/src/resources/org-invites.ts +0 -189
  76. package/src/resources/phase1.test.ts +0 -187
  77. package/src/resources/project-auth.ts +0 -142
  78. package/src/resources/project-integrations.ts +0 -133
  79. package/src/resources/provider-defaults.ts +0 -89
  80. package/src/resources/quotas.ts +0 -77
  81. package/src/resources/regions.ts +0 -94
  82. package/src/resources/sandbox-processes.ts +0 -112
  83. package/src/resources/sandbox-shares.ts +0 -83
  84. package/src/resources/sandbox-templates.ts +0 -195
  85. package/src/resources/sandboxes.live.test.ts +0 -92
  86. package/src/resources/sandboxes.test.ts +0 -653
  87. package/src/resources/sandboxes.ts +0 -1433
  88. package/src/resources/settings.ts +0 -143
  89. package/src/resources/snapshots-standalone.ts +0 -51
  90. package/src/resources/storage.ts +0 -221
  91. package/src/resources/tenant-events.ts +0 -32
  92. package/src/resources/tenant.ts +0 -148
  93. package/src/resources/usage.ts +0 -85
  94. package/src/resources/volumes.ts +0 -117
  95. package/src/resources/webhooks.ts +0 -224
  96. package/src/resources/workspace-invites.ts +0 -188
  97. package/src/resources/workspace-members.test.ts +0 -121
  98. package/src/resources/workspace-members.ts +0 -143
  99. package/src/resources/workspaces.ts +0 -285
  100. 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
- });