@autifyhq/aximo-cli 0.12.0 → 0.13.0
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 +1 -1
- package/dist/commands/mcp/server.test.js +3 -1
- package/dist/commands/mcp/tools.js +105 -2
- package/dist/commands/mcp/tools.test.js +139 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -660,7 +660,7 @@ claude mcp add aximo -- aximo mcp
|
|
|
660
660
|
|
|
661
661
|
VS Code (Copilot agent mode) uses the same entry under a `servers` key in `.vscode/mcp.json`.
|
|
662
662
|
|
|
663
|
-
The server exposes
|
|
663
|
+
The server exposes 24 tools: 14 read-only ones for inspecting sessions, cases, plans, and plan runs (`aximo_get_session_timeline` is the load-bearing one for Playwright export; `aximo_list_plans` and `aximo_get_plan` cover plan discovery; `aximo_list_plan_runs` and `aximo_get_plan_run` cover run-status polling), 8 that author content (create, update, and delete cases, create and update plans, save a session as a case), and 2 that trigger runs (`aximo_run_case` starts a case run and returns the `sessionId`; `aximo_run_plan` starts a plan run and returns the `planRunId` — both spend organization credits and require the case or plan to be `apiEnabled`). See the [MCP reference](./skills/aximo-cli/references/mcp.md) for per-client config paths, the full tool table, the Playwright export prompt, and a no-client smoke test.
|
|
664
664
|
|
|
665
665
|
## Agent Skills
|
|
666
666
|
|
|
@@ -24,7 +24,7 @@ async function connect() {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
describe("createMcpServer wiring", () => {
|
|
27
|
-
it("exposes all
|
|
27
|
+
it("exposes all 24 tools through tools/list with valid JSON Schema", async () => {
|
|
28
28
|
const { client, cleanup } = await connect();
|
|
29
29
|
try {
|
|
30
30
|
const { tools } = await client.listTools();
|
|
@@ -42,12 +42,14 @@ describe("createMcpServer wiring", () => {
|
|
|
42
42
|
"aximo_create_case",
|
|
43
43
|
"aximo_update_case",
|
|
44
44
|
"aximo_delete_case",
|
|
45
|
+
"aximo_run_case",
|
|
45
46
|
"aximo_create_plan",
|
|
46
47
|
"aximo_add_case_to_plan",
|
|
47
48
|
"aximo_remove_case_from_plan",
|
|
48
49
|
"aximo_list_plans",
|
|
49
50
|
"aximo_get_plan",
|
|
50
51
|
"aximo_update_plan",
|
|
52
|
+
"aximo_run_plan",
|
|
51
53
|
"aximo_list_plan_runs",
|
|
52
54
|
"aximo_get_plan_run",
|
|
53
55
|
"aximo_save_session_as_case",
|
|
@@ -31,8 +31,12 @@ async function call(fn) {
|
|
|
31
31
|
}
|
|
32
32
|
if (status === 404)
|
|
33
33
|
return err(e.message || "Not found");
|
|
34
|
-
if (status === 429)
|
|
35
|
-
|
|
34
|
+
if (status === 429) {
|
|
35
|
+
const retryAfter = e.retryAfter;
|
|
36
|
+
return err("Rate limited", retryAfter
|
|
37
|
+
? `Back off and retry after ${retryAfter} seconds.`
|
|
38
|
+
: "Back off before retrying; Aximo rate-limits requests per organization.");
|
|
39
|
+
}
|
|
36
40
|
if (typeof status === "number")
|
|
37
41
|
return err(`HTTP ${status}: ${e.message}`);
|
|
38
42
|
if (e.message.startsWith("No API key configured")) {
|
|
@@ -54,6 +58,18 @@ function buildQuery(params) {
|
|
|
54
58
|
}
|
|
55
59
|
return out;
|
|
56
60
|
}
|
|
61
|
+
// Mobile app selection accepted by the run triggers (multi-app, max 3).
|
|
62
|
+
// ARN fields are the encrypted tokens Aximo returns, never raw AWS ARNs.
|
|
63
|
+
const runAppsSchema = z
|
|
64
|
+
.array(z.object({
|
|
65
|
+
uploadArn: z
|
|
66
|
+
.string()
|
|
67
|
+
.describe("Encrypted upload token (from aximo_get_case config or `aximo upload`)."),
|
|
68
|
+
uploadType: z.enum(["own_app", "existing", "sample"]),
|
|
69
|
+
appName: z.string().nullable(),
|
|
70
|
+
bundleId: z.string().optional(),
|
|
71
|
+
}))
|
|
72
|
+
.max(3);
|
|
57
73
|
export function defineTools(deps = {}) {
|
|
58
74
|
return [
|
|
59
75
|
{
|
|
@@ -243,6 +259,45 @@ export function defineTools(deps = {}) {
|
|
|
243
259
|
return await request(`/api/cases/${encodeURIComponent(caseId)}`, { method: "DELETE" }, deps);
|
|
244
260
|
}),
|
|
245
261
|
},
|
|
262
|
+
{
|
|
263
|
+
name: "aximo_run_case",
|
|
264
|
+
title: "Run test case",
|
|
265
|
+
description: "Start a run of a saved test case and return the new sessionId. The case must be apiEnabled (aximo_get_case shows this). All fields except caseId are per-run overrides of the case's stored per-target configuration: omit them to run with the saved defaults. Poll aximo_get_session with the returned sessionId to follow the run; wait at least 5 seconds between polls.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
caseId: z.string().uuid(),
|
|
268
|
+
model: z
|
|
269
|
+
.string()
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("Agent model override (e.g. 'sonnet', 'haiku', 'auto'). Server-validated."),
|
|
272
|
+
hostnameOverrideIds: z
|
|
273
|
+
.array(z.string().uuid())
|
|
274
|
+
.optional()
|
|
275
|
+
.describe("Web cases: hostname override ids to apply for this run."),
|
|
276
|
+
userDeviceId: z
|
|
277
|
+
.string()
|
|
278
|
+
.uuid()
|
|
279
|
+
.optional()
|
|
280
|
+
.describe("A user-owned (BYOD) device id; overrides the device bound on the case."),
|
|
281
|
+
deviceArn: z
|
|
282
|
+
.string()
|
|
283
|
+
.optional()
|
|
284
|
+
.describe("Mobile cases: encrypted device token; overrides the saved device."),
|
|
285
|
+
deviceName: z.string().optional(),
|
|
286
|
+
platform: z.enum(["ANDROID", "IOS"]).optional(),
|
|
287
|
+
uploadArn: z
|
|
288
|
+
.string()
|
|
289
|
+
.optional()
|
|
290
|
+
.describe("Mobile cases: encrypted app upload token; overrides the saved app."),
|
|
291
|
+
uploadType: z.enum(["own_app", "existing", "sample"]).optional(),
|
|
292
|
+
appName: z.string().optional(),
|
|
293
|
+
apps: runAppsSchema
|
|
294
|
+
.optional()
|
|
295
|
+
.describe("Mobile multi-app runs: up to 3 apps to install; overrides the saved apps and takes precedence over the single-app uploadArn/uploadType/appName fields."),
|
|
296
|
+
},
|
|
297
|
+
handler: async ({ caseId, ...body }) => call(async () => {
|
|
298
|
+
return await request(`/api/triggers/cases/${encodeURIComponent(caseId)}/run`, { method: "POST", body }, deps);
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
246
301
|
{
|
|
247
302
|
name: "aximo_create_plan",
|
|
248
303
|
title: "Create test plan",
|
|
@@ -323,6 +378,54 @@ export function defineTools(deps = {}) {
|
|
|
323
378
|
return await request(`/api/plans/${encodeURIComponent(planId)}`, { method: "PATCH", body }, deps);
|
|
324
379
|
}),
|
|
325
380
|
},
|
|
381
|
+
{
|
|
382
|
+
name: "aximo_run_plan",
|
|
383
|
+
title: "Run test plan",
|
|
384
|
+
description: "Start a run of a test plan (all its cases) and return the new planRunId, totalCases, and the effective executionMode. The plan must be apiEnabled (aximo_get_plan shows this). All fields except planId are per-run overrides of the plan's stored settings: omit them to run with the saved defaults. Poll aximo_get_plan_run with the returned planRunId to follow the run; wait at least 5 seconds between polls.",
|
|
385
|
+
inputSchema: {
|
|
386
|
+
planId: z.string().uuid(),
|
|
387
|
+
model: z
|
|
388
|
+
.string()
|
|
389
|
+
.optional()
|
|
390
|
+
.describe("Agent model override (e.g. 'sonnet', 'haiku', 'auto'). Server-validated."),
|
|
391
|
+
executionMode: z
|
|
392
|
+
.enum(["parallel", "sequential"])
|
|
393
|
+
.optional()
|
|
394
|
+
.describe("Override the plan's stored Execution setting for this run only."),
|
|
395
|
+
mobileConfig: z
|
|
396
|
+
.object({
|
|
397
|
+
deviceArn: z.string().describe("Encrypted device token."),
|
|
398
|
+
deviceName: z.string(),
|
|
399
|
+
platform: z.enum(["ANDROID", "IOS"]).optional(),
|
|
400
|
+
uploadArn: z.string().optional().describe("Encrypted app upload token."),
|
|
401
|
+
uploadType: z.enum(["own_app", "existing", "sample"]).optional(),
|
|
402
|
+
appName: z.string().optional(),
|
|
403
|
+
apps: runAppsSchema
|
|
404
|
+
.optional()
|
|
405
|
+
.describe("Takes precedence over the single-app uploadArn/uploadType/appName fields."),
|
|
406
|
+
})
|
|
407
|
+
.optional()
|
|
408
|
+
.describe("Device + app for the plan's mobile cases; overrides the plan's stored config."),
|
|
409
|
+
desktopConfig: z
|
|
410
|
+
.object({
|
|
411
|
+
devices: z.array(z.object({ userDeviceId: z.string().uuid() })).min(1),
|
|
412
|
+
})
|
|
413
|
+
.optional()
|
|
414
|
+
.describe("User-owned (BYOD) desktop devices to run desktop cases on; overrides the plan's stored desktop config."),
|
|
415
|
+
variables: z
|
|
416
|
+
.array(z.object({
|
|
417
|
+
name: z
|
|
418
|
+
.string()
|
|
419
|
+
.describe("Uppercase letters, digits, underscores; must start with a letter."),
|
|
420
|
+
value: z.string(),
|
|
421
|
+
}))
|
|
422
|
+
.optional()
|
|
423
|
+
.describe("Per-run variable values merged over the plan's stored variables by name; never persisted on the plan."),
|
|
424
|
+
},
|
|
425
|
+
handler: async ({ planId, ...body }) => call(async () => {
|
|
426
|
+
return await request(`/api/triggers/plans/${encodeURIComponent(planId)}/run`, { method: "POST", body }, deps);
|
|
427
|
+
}),
|
|
428
|
+
},
|
|
326
429
|
{
|
|
327
430
|
name: "aximo_list_plan_runs",
|
|
328
431
|
title: "List plan runs",
|
|
@@ -34,12 +34,14 @@ describe("defineTools", () => {
|
|
|
34
34
|
"aximo_create_case",
|
|
35
35
|
"aximo_update_case",
|
|
36
36
|
"aximo_delete_case",
|
|
37
|
+
"aximo_run_case",
|
|
37
38
|
"aximo_create_plan",
|
|
38
39
|
"aximo_add_case_to_plan",
|
|
39
40
|
"aximo_remove_case_from_plan",
|
|
40
41
|
"aximo_list_plans",
|
|
41
42
|
"aximo_get_plan",
|
|
42
43
|
"aximo_update_plan",
|
|
44
|
+
"aximo_run_plan",
|
|
43
45
|
"aximo_list_plan_runs",
|
|
44
46
|
"aximo_get_plan_run",
|
|
45
47
|
"aximo_save_session_as_case",
|
|
@@ -203,6 +205,75 @@ describe("aximo_update_case", () => {
|
|
|
203
205
|
expect(body).toEqual({ startUrl: null });
|
|
204
206
|
});
|
|
205
207
|
});
|
|
208
|
+
describe("aximo_run_case", () => {
|
|
209
|
+
const caseId = "77777777-7777-4777-9777-777777777777";
|
|
210
|
+
it("POSTs /api/triggers/cases/:id/run with the per-run overrides and returns sessionId", async () => {
|
|
211
|
+
let body = null;
|
|
212
|
+
let apiKey = null;
|
|
213
|
+
const payload = { success: true, sessionId: "88888888-8888-4888-9888-888888888888" };
|
|
214
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, async ({ request: req }) => {
|
|
215
|
+
apiKey = req.headers.get("x-api-key");
|
|
216
|
+
body = await req.json();
|
|
217
|
+
return HttpResponse.json(payload, { status: 201 });
|
|
218
|
+
}));
|
|
219
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
220
|
+
const result = await tool.handler({
|
|
221
|
+
caseId,
|
|
222
|
+
model: "sonnet",
|
|
223
|
+
hostnameOverrideIds: ["99999999-9999-4999-9999-999999999999"],
|
|
224
|
+
apps: [{ uploadArn: "tok-1", uploadType: "own_app", appName: null }],
|
|
225
|
+
});
|
|
226
|
+
expect(result.isError).toBeUndefined();
|
|
227
|
+
expect(parsePayload(result)).toEqual(payload);
|
|
228
|
+
expect(apiKey).toBe("test-key");
|
|
229
|
+
expect(body).toEqual({
|
|
230
|
+
model: "sonnet",
|
|
231
|
+
hostnameOverrideIds: ["99999999-9999-4999-9999-999999999999"],
|
|
232
|
+
apps: [{ uploadArn: "tok-1", uploadType: "own_app", appName: null }],
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
it("sends an empty body when only caseId is given, so the saved config applies", async () => {
|
|
236
|
+
let body = null;
|
|
237
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, async ({ request: req }) => {
|
|
238
|
+
body = await req.json();
|
|
239
|
+
return HttpResponse.json({ success: true, sessionId: "s" }, { status: 201 });
|
|
240
|
+
}));
|
|
241
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
242
|
+
const result = await tool.handler({ caseId });
|
|
243
|
+
expect(result.isError).toBeUndefined();
|
|
244
|
+
expect(body).toEqual({});
|
|
245
|
+
});
|
|
246
|
+
it("surfaces the apiEnabled rejection as the error message", async () => {
|
|
247
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, () => HttpResponse.json({ error: "API access is not enabled for this case" }, { status: 403 })));
|
|
248
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
249
|
+
const result = await tool.handler({ caseId });
|
|
250
|
+
expect(result.isError).toBe(true);
|
|
251
|
+
expect(parsePayload(result).error).toBe("API access is not enabled for this case");
|
|
252
|
+
});
|
|
253
|
+
it("surfaces the insufficient-credits rejection distinctly", async () => {
|
|
254
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, () => HttpResponse.json({ error: "Insufficient credits to start session" }, { status: 403 })));
|
|
255
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
256
|
+
const result = await tool.handler({ caseId });
|
|
257
|
+
expect(result.isError).toBe(true);
|
|
258
|
+
expect(parsePayload(result).error).toBe("Insufficient credits to start session");
|
|
259
|
+
});
|
|
260
|
+
it("maps a 429 to a rate-limit error an agent can back off on", async () => {
|
|
261
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, () => HttpResponse.json({ error: "Rate limit exceeded. Try again later." }, { status: 429, headers: { "Retry-After": "60" } })));
|
|
262
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
263
|
+
const result = await tool.handler({ caseId });
|
|
264
|
+
expect(result.isError).toBe(true);
|
|
265
|
+
const payload = parsePayload(result);
|
|
266
|
+
expect(payload.error).toMatch(/Rate limited/);
|
|
267
|
+
expect(payload.hint).toBe("Back off and retry after 60 seconds.");
|
|
268
|
+
});
|
|
269
|
+
it("returns the API's 404 for a case outside the organization without leaking existence", async () => {
|
|
270
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/cases/${caseId}/run`, () => HttpResponse.json({ error: "Test case not found" }, { status: 404 })));
|
|
271
|
+
const tool = byName(defineTools(), "aximo_run_case");
|
|
272
|
+
const result = await tool.handler({ caseId });
|
|
273
|
+
expect(result.isError).toBe(true);
|
|
274
|
+
expect(parsePayload(result).error).toBe("Test case not found");
|
|
275
|
+
});
|
|
276
|
+
});
|
|
206
277
|
describe("aximo_list_plans", () => {
|
|
207
278
|
const projectId = "44444444-4444-4444-8444-444444444444";
|
|
208
279
|
it("calls /api/plans, passing filters through as query params", async () => {
|
|
@@ -304,6 +375,63 @@ describe("aximo_update_plan", () => {
|
|
|
304
375
|
expect(called).toBe(false);
|
|
305
376
|
});
|
|
306
377
|
});
|
|
378
|
+
describe("aximo_run_plan", () => {
|
|
379
|
+
const planId = "aaaaaaaa-aaaa-4aaa-9aaa-aaaaaaaaaaaa";
|
|
380
|
+
it("POSTs /api/triggers/plans/:id/run with the per-run overrides and returns planRunId", async () => {
|
|
381
|
+
let body = null;
|
|
382
|
+
const payload = {
|
|
383
|
+
success: true,
|
|
384
|
+
planRunId: "bbbbbbbb-bbbb-4bbb-9bbb-bbbbbbbbbbbb",
|
|
385
|
+
totalCases: 3,
|
|
386
|
+
executionMode: "sequential",
|
|
387
|
+
};
|
|
388
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/plans/${planId}/run`, async ({ request: req }) => {
|
|
389
|
+
body = await req.json();
|
|
390
|
+
return HttpResponse.json(payload, { status: 201 });
|
|
391
|
+
}));
|
|
392
|
+
const tool = byName(defineTools(), "aximo_run_plan");
|
|
393
|
+
const result = await tool.handler({
|
|
394
|
+
planId,
|
|
395
|
+
executionMode: "sequential",
|
|
396
|
+
mobileConfig: { deviceArn: "tok-device", deviceName: "Pixel 8", platform: "ANDROID" },
|
|
397
|
+
desktopConfig: { devices: [{ userDeviceId: "cccccccc-cccc-4ccc-9ccc-cccccccccccc" }] },
|
|
398
|
+
variables: [{ name: "BASE_URL", value: "https://staging.example.com" }],
|
|
399
|
+
});
|
|
400
|
+
expect(result.isError).toBeUndefined();
|
|
401
|
+
expect(parsePayload(result)).toEqual(payload);
|
|
402
|
+
expect(body).toEqual({
|
|
403
|
+
executionMode: "sequential",
|
|
404
|
+
mobileConfig: { deviceArn: "tok-device", deviceName: "Pixel 8", platform: "ANDROID" },
|
|
405
|
+
desktopConfig: { devices: [{ userDeviceId: "cccccccc-cccc-4ccc-9ccc-cccccccccccc" }] },
|
|
406
|
+
variables: [{ name: "BASE_URL", value: "https://staging.example.com" }],
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
it("sends an empty body when only planId is given, so the plan's stored settings apply", async () => {
|
|
410
|
+
let body = null;
|
|
411
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/plans/${planId}/run`, async ({ request: req }) => {
|
|
412
|
+
body = await req.json();
|
|
413
|
+
return HttpResponse.json({ success: true, planRunId: "r", totalCases: 1 }, { status: 201 });
|
|
414
|
+
}));
|
|
415
|
+
const tool = byName(defineTools(), "aximo_run_plan");
|
|
416
|
+
const result = await tool.handler({ planId });
|
|
417
|
+
expect(result.isError).toBeUndefined();
|
|
418
|
+
expect(body).toEqual({});
|
|
419
|
+
});
|
|
420
|
+
it("surfaces the apiEnabled rejection as the error message", async () => {
|
|
421
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/plans/${planId}/run`, () => HttpResponse.json({ error: "API access is not enabled for this plan" }, { status: 403 })));
|
|
422
|
+
const tool = byName(defineTools(), "aximo_run_plan");
|
|
423
|
+
const result = await tool.handler({ planId });
|
|
424
|
+
expect(result.isError).toBe(true);
|
|
425
|
+
expect(parsePayload(result).error).toBe("API access is not enabled for this plan");
|
|
426
|
+
});
|
|
427
|
+
it("returns the API's 404 for a plan outside the organization without leaking existence", async () => {
|
|
428
|
+
server.use(http.post(`${TEST_BASE_URL}/api/triggers/plans/${planId}/run`, () => HttpResponse.json({ error: "Test plan not found" }, { status: 404 })));
|
|
429
|
+
const tool = byName(defineTools(), "aximo_run_plan");
|
|
430
|
+
const result = await tool.handler({ planId });
|
|
431
|
+
expect(result.isError).toBe(true);
|
|
432
|
+
expect(parsePayload(result).error).toBe("Test plan not found");
|
|
433
|
+
});
|
|
434
|
+
});
|
|
307
435
|
describe("aximo_list_plan_runs", () => {
|
|
308
436
|
const planId = "77777777-7777-4777-9777-777777777777";
|
|
309
437
|
it("calls /api/triggers/plans/:id/runs, passing limit and cursor through", async () => {
|
|
@@ -471,14 +599,23 @@ describe("call() error mapping", () => {
|
|
|
471
599
|
const payload = parsePayload(result);
|
|
472
600
|
expect(payload.error).toBe("Session was archived");
|
|
473
601
|
});
|
|
474
|
-
it("flags 429 with
|
|
602
|
+
it("flags 429 with a back-off hint when no Retry-After header is present", async () => {
|
|
475
603
|
server.use(http.get(`${TEST_BASE_URL}/api/sessions/${sessionId}/timeline`, () => HttpResponse.json({ error: "Too many requests" }, { status: 429 })));
|
|
476
604
|
const tool = byName(defineTools(), "aximo_get_session_timeline");
|
|
477
605
|
const result = await tool.handler({ sessionId });
|
|
478
606
|
expect(result.isError).toBe(true);
|
|
479
607
|
const payload = parsePayload(result);
|
|
480
608
|
expect(payload.error).toBe("Rate limited");
|
|
481
|
-
expect(payload.hint).toMatch(/
|
|
609
|
+
expect(payload.hint).toMatch(/back off/i);
|
|
610
|
+
});
|
|
611
|
+
it("carries the Retry-After value into the 429 hint so an agent knows how long to wait", async () => {
|
|
612
|
+
server.use(http.get(`${TEST_BASE_URL}/api/sessions/${sessionId}/timeline`, () => HttpResponse.json({ error: "Too many requests" }, { status: 429, headers: { "Retry-After": "42" } })));
|
|
613
|
+
const tool = byName(defineTools(), "aximo_get_session_timeline");
|
|
614
|
+
const result = await tool.handler({ sessionId });
|
|
615
|
+
expect(result.isError).toBe(true);
|
|
616
|
+
const payload = parsePayload(result);
|
|
617
|
+
expect(payload.error).toBe("Rate limited");
|
|
618
|
+
expect(payload.hint).toBe("Back off and retry after 42 seconds.");
|
|
482
619
|
});
|
|
483
620
|
it("distinguishes a 500 with the HTTP-prefixed message", async () => {
|
|
484
621
|
server.use(http.get(`${TEST_BASE_URL}/api/sessions/${sessionId}/timeline`, () => HttpResponse.json({ error: "Internal server error" }, { status: 500 })));
|