@autifyhq/aximo-cli 0.1.0 → 0.3.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.
Files changed (42) hide show
  1. package/README.md +87 -50
  2. package/dist/cli.js +15 -0
  3. package/dist/commands/case/case.test.d.ts +1 -0
  4. package/dist/commands/case/case.test.js +86 -0
  5. package/dist/commands/case/create.d.ts +53 -0
  6. package/dist/commands/case/create.js +71 -0
  7. package/dist/commands/case/index.d.ts +2 -0
  8. package/dist/commands/case/index.js +7 -0
  9. package/dist/commands/device/index.js +4 -1
  10. package/dist/commands/device/list.js +4 -1
  11. package/dist/commands/device/register.js +1 -1
  12. package/dist/commands/device/status.js +4 -1
  13. package/dist/commands/mcp/index.d.ts +2 -0
  14. package/dist/commands/mcp/index.js +29 -0
  15. package/dist/commands/mcp/server.d.ts +3 -0
  16. package/dist/commands/mcp/server.js +19 -0
  17. package/dist/commands/mcp/server.test.d.ts +1 -0
  18. package/dist/commands/mcp/server.test.js +89 -0
  19. package/dist/commands/mcp/tools.d.ts +18 -0
  20. package/dist/commands/mcp/tools.js +268 -0
  21. package/dist/commands/mcp/tools.test.d.ts +1 -0
  22. package/dist/commands/mcp/tools.test.js +269 -0
  23. package/dist/commands/plan/add-case.d.ts +30 -0
  24. package/dist/commands/plan/add-case.js +58 -0
  25. package/dist/commands/plan/create.d.ts +31 -0
  26. package/dist/commands/plan/create.js +56 -0
  27. package/dist/commands/plan/index.d.ts +2 -0
  28. package/dist/commands/plan/index.js +9 -0
  29. package/dist/commands/plan/plan.test.d.ts +1 -0
  30. package/dist/commands/plan/plan.test.js +104 -0
  31. package/dist/commands/plan/remove-case.d.ts +26 -0
  32. package/dist/commands/plan/remove-case.js +43 -0
  33. package/dist/commands/schema.js +42 -3
  34. package/dist/commands/session/index.js +1 -0
  35. package/dist/commands/session/save-as-case.d.ts +43 -0
  36. package/dist/commands/session/save-as-case.js +69 -0
  37. package/dist/commands/session/save-as-case.test.d.ts +1 -0
  38. package/dist/commands/session/save-as-case.test.js +58 -0
  39. package/dist/index.d.ts +12 -7
  40. package/dist/index.js +12 -7
  41. package/dist/types.d.ts +147 -0
  42. package/package.json +4 -2
@@ -0,0 +1,30 @@
1
+ import { type ClientDeps } from "../../client.js";
2
+ import { type DryRunResult } from "../../mutate.js";
3
+ import type { Envelope, PlanCaseResult } from "../../types.js";
4
+ export interface PlanAddCaseOptions {
5
+ planId?: unknown;
6
+ caseId?: unknown;
7
+ json?: unknown;
8
+ dryRun?: unknown;
9
+ }
10
+ export declare function runPlanAddCase(options: PlanAddCaseOptions, deps?: ClientDeps): Promise<Envelope<PlanCaseResult | DryRunResult>>;
11
+ declare const _default: import("citty").CommandDef<{
12
+ readonly planId: {
13
+ readonly type: "positional";
14
+ readonly description: "Plan ID";
15
+ readonly required: true;
16
+ };
17
+ readonly "case-id": {
18
+ readonly type: "string";
19
+ readonly description: "Case ID to add";
20
+ };
21
+ readonly json: {
22
+ readonly type: "string";
23
+ readonly description: "Raw JSON request body (replaces individual flags)";
24
+ };
25
+ readonly "dry-run": {
26
+ readonly type: "boolean";
27
+ readonly description: "Validate and show request without calling API";
28
+ };
29
+ }>;
30
+ export default _default;
@@ -0,0 +1,58 @@
1
+ import { defineCommand } from "citty";
2
+ import { request } from "../../client.js";
3
+ import { handleApiError } from "../../errors.js";
4
+ import { dryRunEnvelope, resolveJsonOverride } from "../../mutate.js";
5
+ import { formatSuccess, printResult, validationError } from "../../output.js";
6
+ import { requireId, requireString } from "../../validate.js";
7
+ export async function runPlanAddCase(options, deps) {
8
+ let planId;
9
+ let body;
10
+ const override = resolveJsonOverride(options.json);
11
+ if (override && "error" in override)
12
+ return override.error;
13
+ try {
14
+ planId = requireId("planId", options.planId);
15
+ }
16
+ catch (err) {
17
+ return validationError(err);
18
+ }
19
+ if (override) {
20
+ body = override.body;
21
+ }
22
+ else {
23
+ try {
24
+ body = { caseId: requireString("caseId", options.caseId) };
25
+ }
26
+ catch (err) {
27
+ return validationError(err);
28
+ }
29
+ }
30
+ const path = `/api/plans/${encodeURIComponent(planId)}/cases`;
31
+ if (options.dryRun) {
32
+ return dryRunEnvelope("POST", path, body);
33
+ }
34
+ try {
35
+ const res = await request(path, { method: "POST", body }, deps ?? {});
36
+ return formatSuccess(res);
37
+ }
38
+ catch (err) {
39
+ return handleApiError(err);
40
+ }
41
+ }
42
+ export default defineCommand({
43
+ meta: { name: "add-case", description: "Add a case to a plan" },
44
+ args: {
45
+ planId: { type: "positional", description: "Plan ID", required: true },
46
+ "case-id": { type: "string", description: "Case ID to add" },
47
+ json: { type: "string", description: "Raw JSON request body (replaces individual flags)" },
48
+ "dry-run": { type: "boolean", description: "Validate and show request without calling API" },
49
+ },
50
+ async run({ args }) {
51
+ printResult(await runPlanAddCase({
52
+ planId: args.planId,
53
+ caseId: args["case-id"],
54
+ json: args.json,
55
+ dryRun: args["dry-run"],
56
+ }));
57
+ },
58
+ });
@@ -0,0 +1,31 @@
1
+ import { type ClientDeps } from "../../client.js";
2
+ import { type DryRunResult } from "../../mutate.js";
3
+ import type { CreatePlanResult, Envelope } from "../../types.js";
4
+ export interface CreatePlanOptions {
5
+ projectId?: unknown;
6
+ title?: unknown;
7
+ description?: unknown;
8
+ json?: unknown;
9
+ dryRun?: unknown;
10
+ }
11
+ export declare function runPlanCreate(options: CreatePlanOptions, deps?: ClientDeps): Promise<Envelope<CreatePlanResult | DryRunResult>>;
12
+ declare const _default: import("citty").CommandDef<{
13
+ readonly "project-id": {
14
+ readonly type: "string";
15
+ };
16
+ readonly title: {
17
+ readonly type: "string";
18
+ };
19
+ readonly description: {
20
+ readonly type: "string";
21
+ };
22
+ readonly json: {
23
+ readonly type: "string";
24
+ readonly description: "Raw JSON request body (replaces individual flags)";
25
+ };
26
+ readonly "dry-run": {
27
+ readonly type: "boolean";
28
+ readonly description: "Validate and show request without calling API";
29
+ };
30
+ }>;
31
+ export default _default;
@@ -0,0 +1,56 @@
1
+ import { defineCommand } from "citty";
2
+ import { request } from "../../client.js";
3
+ import { handleApiError } from "../../errors.js";
4
+ import { dryRunEnvelope, resolveJsonOverride } from "../../mutate.js";
5
+ import { formatSuccess, printResult, validationError } from "../../output.js";
6
+ import { optionalString, requireString } from "../../validate.js";
7
+ export async function runPlanCreate(options, deps) {
8
+ let body;
9
+ const override = resolveJsonOverride(options.json);
10
+ if (override && "error" in override)
11
+ return override.error;
12
+ if (override) {
13
+ body = override.body;
14
+ }
15
+ else {
16
+ try {
17
+ body = {
18
+ projectId: requireString("projectId", options.projectId),
19
+ title: requireString("title", options.title),
20
+ description: optionalString("description", options.description),
21
+ };
22
+ }
23
+ catch (err) {
24
+ return validationError(err);
25
+ }
26
+ }
27
+ if (options.dryRun) {
28
+ return dryRunEnvelope("POST", "/api/plans", body);
29
+ }
30
+ try {
31
+ const res = await request("/api/plans", { method: "POST", body }, deps ?? {});
32
+ return formatSuccess(res);
33
+ }
34
+ catch (err) {
35
+ return handleApiError(err);
36
+ }
37
+ }
38
+ export default defineCommand({
39
+ meta: { name: "create", description: "Create a test plan" },
40
+ args: {
41
+ "project-id": { type: "string" },
42
+ title: { type: "string" },
43
+ description: { type: "string" },
44
+ json: { type: "string", description: "Raw JSON request body (replaces individual flags)" },
45
+ "dry-run": { type: "boolean", description: "Validate and show request without calling API" },
46
+ },
47
+ async run({ args }) {
48
+ printResult(await runPlanCreate({
49
+ projectId: args["project-id"],
50
+ title: args.title,
51
+ description: args.description,
52
+ json: args.json,
53
+ dryRun: args["dry-run"],
54
+ }));
55
+ },
56
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("citty").CommandDef<import("citty").ArgsDef>;
2
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { defineCommand } from "citty";
2
+ export default defineCommand({
3
+ meta: { name: "plan", description: "Test plan commands" },
4
+ subCommands: {
5
+ create: () => import("./create.js").then((m) => m.default),
6
+ "add-case": () => import("./add-case.js").then((m) => m.default),
7
+ "remove-case": () => import("./remove-case.js").then((m) => m.default),
8
+ },
9
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
2
+ import { HttpResponse, http, server, TEST_BASE_URL } from "../../test-utils.js";
3
+ import { runPlanAddCase } from "./add-case.js";
4
+ import { runPlanCreate } from "./create.js";
5
+ import { runPlanRemoveCase } from "./remove-case.js";
6
+ beforeAll(() => {
7
+ process.env["AXIMO_API_KEY"] = "test-key";
8
+ process.env["AXIMO_BASE_URL"] = TEST_BASE_URL;
9
+ server.listen();
10
+ });
11
+ afterEach(() => server.resetHandlers());
12
+ afterAll(() => server.close());
13
+ describe("runPlanCreate", () => {
14
+ it("POSTs body and returns success envelope", async () => {
15
+ let seen;
16
+ server.use(http.post(`${TEST_BASE_URL}/api/plans`, async ({ request: req }) => {
17
+ seen = await req.json();
18
+ return HttpResponse.json({ success: true, planId: "pl1" }, { status: 201 });
19
+ }));
20
+ const result = await runPlanCreate({ projectId: "proj-1", title: "Regression" });
21
+ expect(result.ok).toBe(true);
22
+ if (result.ok && !("dryRun" in result.data))
23
+ expect(result.data.planId).toBe("pl1");
24
+ expect(seen).toMatchObject({ projectId: "proj-1", title: "Regression" });
25
+ });
26
+ it("rejects missing title", async () => {
27
+ const result = await runPlanCreate({ projectId: "p1" });
28
+ expect(result.ok).toBe(false);
29
+ if (!result.ok)
30
+ expect(result.error.code).toBe("validation_error");
31
+ });
32
+ it("--dry-run returns request without calling API", async () => {
33
+ const result = await runPlanCreate({ projectId: "p1", title: "t", dryRun: true });
34
+ expect(result.ok).toBe(true);
35
+ if (result.ok && "dryRun" in result.data) {
36
+ expect(result.data.url).toBe(`${TEST_BASE_URL}/api/plans`);
37
+ }
38
+ });
39
+ });
40
+ describe("runPlanAddCase", () => {
41
+ it("POSTs caseId to the plan-cases path", async () => {
42
+ let seen;
43
+ server.use(http.post(`${TEST_BASE_URL}/api/plans/pl1/cases`, async ({ request: req }) => {
44
+ seen = await req.json();
45
+ return HttpResponse.json({ success: true }, { status: 201 });
46
+ }));
47
+ const result = await runPlanAddCase({ planId: "pl1", caseId: "c1" });
48
+ expect(result.ok).toBe(true);
49
+ expect(seen).toEqual({ caseId: "c1" });
50
+ });
51
+ it("rejects missing caseId", async () => {
52
+ const result = await runPlanAddCase({ planId: "pl1" });
53
+ expect(result.ok).toBe(false);
54
+ if (!result.ok)
55
+ expect(result.error.code).toBe("validation_error");
56
+ });
57
+ it("rejects an injected plan id (requireId)", async () => {
58
+ const result = await runPlanAddCase({ planId: "pl1/../x", caseId: "c1" });
59
+ expect(result.ok).toBe(false);
60
+ if (!result.ok)
61
+ expect(result.error.code).toBe("validation_error");
62
+ });
63
+ it("maps 409 to validation_error", async () => {
64
+ server.use(http.post(`${TEST_BASE_URL}/api/plans/pl1/cases`, () => HttpResponse.json({ error: "Case already in plan" }, { status: 409 })));
65
+ const result = await runPlanAddCase({ planId: "pl1", caseId: "c1" });
66
+ expect(result.ok).toBe(false);
67
+ if (!result.ok)
68
+ expect(result.error.code).toBe("validation_error");
69
+ });
70
+ it("--dry-run returns request without calling API", async () => {
71
+ const result = await runPlanAddCase({ planId: "pl1", caseId: "c1", dryRun: true });
72
+ expect(result.ok).toBe(true);
73
+ if (result.ok && "dryRun" in result.data) {
74
+ expect(result.data.method).toBe("POST");
75
+ expect(result.data.url).toBe(`${TEST_BASE_URL}/api/plans/pl1/cases`);
76
+ }
77
+ });
78
+ });
79
+ describe("runPlanRemoveCase", () => {
80
+ it("DELETEs the plan-case path", async () => {
81
+ let called = false;
82
+ server.use(http.delete(`${TEST_BASE_URL}/api/plans/pl1/cases/c1`, () => {
83
+ called = true;
84
+ return HttpResponse.json({ success: true });
85
+ }));
86
+ const result = await runPlanRemoveCase({ planId: "pl1", caseId: "c1" });
87
+ expect(result.ok).toBe(true);
88
+ expect(called).toBe(true);
89
+ });
90
+ it("rejects missing caseId", async () => {
91
+ const result = await runPlanRemoveCase({ planId: "pl1" });
92
+ expect(result.ok).toBe(false);
93
+ if (!result.ok)
94
+ expect(result.error.code).toBe("validation_error");
95
+ });
96
+ it("--dry-run returns a DELETE request without calling API", async () => {
97
+ const result = await runPlanRemoveCase({ planId: "pl1", caseId: "c1", dryRun: true });
98
+ expect(result.ok).toBe(true);
99
+ if (result.ok && "dryRun" in result.data) {
100
+ expect(result.data.method).toBe("DELETE");
101
+ expect(result.data.url).toBe(`${TEST_BASE_URL}/api/plans/pl1/cases/c1`);
102
+ }
103
+ });
104
+ });
@@ -0,0 +1,26 @@
1
+ import { type ClientDeps } from "../../client.js";
2
+ import { type DryRunResult } from "../../mutate.js";
3
+ import type { Envelope, PlanCaseResult } from "../../types.js";
4
+ export interface PlanRemoveCaseOptions {
5
+ planId?: unknown;
6
+ caseId?: unknown;
7
+ dryRun?: unknown;
8
+ }
9
+ export declare function runPlanRemoveCase(options: PlanRemoveCaseOptions, deps?: ClientDeps): Promise<Envelope<PlanCaseResult | DryRunResult>>;
10
+ declare const _default: import("citty").CommandDef<{
11
+ readonly planId: {
12
+ readonly type: "positional";
13
+ readonly description: "Plan ID";
14
+ readonly required: true;
15
+ };
16
+ readonly caseId: {
17
+ readonly type: "positional";
18
+ readonly description: "Case ID";
19
+ readonly required: true;
20
+ };
21
+ readonly "dry-run": {
22
+ readonly type: "boolean";
23
+ readonly description: "Validate and show request without calling API";
24
+ };
25
+ }>;
26
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import { defineCommand } from "citty";
2
+ import { request } from "../../client.js";
3
+ import { handleApiError } from "../../errors.js";
4
+ import { dryRunEnvelope } from "../../mutate.js";
5
+ import { formatSuccess, printResult, validationError } from "../../output.js";
6
+ import { requireId } from "../../validate.js";
7
+ export async function runPlanRemoveCase(options, deps) {
8
+ let planId;
9
+ let caseId;
10
+ try {
11
+ planId = requireId("planId", options.planId);
12
+ caseId = requireId("caseId", options.caseId);
13
+ }
14
+ catch (err) {
15
+ return validationError(err);
16
+ }
17
+ const path = `/api/plans/${encodeURIComponent(planId)}/cases/${encodeURIComponent(caseId)}`;
18
+ if (options.dryRun) {
19
+ return dryRunEnvelope("DELETE", path);
20
+ }
21
+ try {
22
+ const res = await request(path, { method: "DELETE" }, deps ?? {});
23
+ return formatSuccess(res);
24
+ }
25
+ catch (err) {
26
+ return handleApiError(err);
27
+ }
28
+ }
29
+ export default defineCommand({
30
+ meta: { name: "remove-case", description: "Remove a case from a plan" },
31
+ args: {
32
+ planId: { type: "positional", description: "Plan ID", required: true },
33
+ caseId: { type: "positional", description: "Case ID", required: true },
34
+ "dry-run": { type: "boolean", description: "Validate and show request without calling API" },
35
+ },
36
+ async run({ args }) {
37
+ printResult(await runPlanRemoveCase({
38
+ planId: args.planId,
39
+ caseId: args.caseId,
40
+ dryRun: args["dry-run"],
41
+ }));
42
+ },
43
+ });
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from "citty";
2
- import { formatSuccess, formatError, printResult } from "../output.js";
3
2
  import pkg from "../../package.json" with { type: "json" };
3
+ import { formatError, formatSuccess, printResult } from "../output.js";
4
4
  function arg(name, type, description, required = false) {
5
5
  return { name, type, description, required };
6
6
  }
@@ -41,6 +41,45 @@ export function getSchema() {
41
41
  cmd("get", "GET /api/sessions/{sessionId}", [
42
42
  arg("sessionId", "positional", "Session ID", true),
43
43
  ]),
44
+ cmd("save-as-case", "POST /api/sessions/{sessionId}/save-as-case", [
45
+ arg("sessionId", "positional", "Session ID", true),
46
+ arg("title", "string", ""),
47
+ arg("type", "string", "web | mobile | desktop"),
48
+ arg("scenario", "string", ""),
49
+ arg("format", "string", "gherkin | markdown | plain_text"),
50
+ ...MUTATE_ARGS,
51
+ ]),
52
+ ]),
53
+ cmd("case", "Test case commands", [], [
54
+ cmd("create", "POST /api/cases", [
55
+ arg("project-id", "string", "Project ID"),
56
+ arg("scenario", "string", "Test scenario text"),
57
+ arg("type", "string", "web | mobile | desktop"),
58
+ arg("title", "string", ""),
59
+ arg("description", "string", ""),
60
+ arg("format", "string", "gherkin | markdown | plain_text"),
61
+ arg("start-url", "string", ""),
62
+ arg("directory-id", "string", ""),
63
+ ...MUTATE_ARGS,
64
+ ]),
65
+ ]),
66
+ cmd("plan", "Test plan commands", [], [
67
+ cmd("create", "POST /api/plans", [
68
+ arg("project-id", "string", "Project ID"),
69
+ arg("title", "string", "Plan title"),
70
+ arg("description", "string", ""),
71
+ ...MUTATE_ARGS,
72
+ ]),
73
+ cmd("add-case", "POST /api/plans/{planId}/cases", [
74
+ arg("planId", "positional", "Plan ID", true),
75
+ arg("case-id", "string", "Case ID to add"),
76
+ ...MUTATE_ARGS,
77
+ ]),
78
+ cmd("remove-case", "DELETE /api/plans/{planId}/cases/{caseId}", [
79
+ arg("planId", "positional", "Plan ID", true),
80
+ arg("caseId", "positional", "Case ID", true),
81
+ arg("dry-run", "boolean", "Validate and show request without calling API"),
82
+ ]),
44
83
  ]),
45
84
  cmd("trigger", "Trigger commands", [], [
46
85
  cmd("case", "Case trigger commands", [], [
@@ -74,8 +113,8 @@ export function getSchema() {
74
113
  ]),
75
114
  ]),
76
115
  ]),
77
- cmd("device", "Device commands", [], [
78
- cmd("list", "GET /api/devices", [
116
+ cmd("device", "Device Farm mobile device listing (BYOD device commands are not exposed via schema)", [], [
117
+ cmd("list", "GET /api/devices — list Device Farm mobile devices (ANDROID/IOS)", [
79
118
  arg("platform", "string", "ANDROID | IOS"),
80
119
  arg("available-only", "boolean", ""),
81
120
  ]),
@@ -4,5 +4,6 @@ export default defineCommand({
4
4
  subCommands: {
5
5
  create: () => import("./create.js").then((m) => m.default),
6
6
  get: () => import("./get.js").then((m) => m.default),
7
+ "save-as-case": () => import("./save-as-case.js").then((m) => m.default),
7
8
  },
8
9
  });
@@ -0,0 +1,43 @@
1
+ import { type ClientDeps } from "../../client.js";
2
+ import { type DryRunResult } from "../../mutate.js";
3
+ import type { Envelope, SaveSessionAsCaseResult } from "../../types.js";
4
+ export interface SessionSaveAsCaseOptions {
5
+ sessionId?: unknown;
6
+ title?: unknown;
7
+ type?: unknown;
8
+ scenario?: unknown;
9
+ format?: unknown;
10
+ json?: unknown;
11
+ dryRun?: unknown;
12
+ }
13
+ export declare function runSessionSaveAsCase(options: SessionSaveAsCaseOptions, deps?: ClientDeps): Promise<Envelope<SaveSessionAsCaseResult | DryRunResult>>;
14
+ declare const _default: import("citty").CommandDef<{
15
+ readonly sessionId: {
16
+ readonly type: "positional";
17
+ readonly description: "Session ID";
18
+ readonly required: true;
19
+ };
20
+ readonly title: {
21
+ readonly type: "string";
22
+ };
23
+ readonly type: {
24
+ readonly type: "string";
25
+ readonly description: "web | mobile | desktop";
26
+ };
27
+ readonly scenario: {
28
+ readonly type: "string";
29
+ };
30
+ readonly format: {
31
+ readonly type: "string";
32
+ readonly description: "gherkin | markdown | plain_text";
33
+ };
34
+ readonly json: {
35
+ readonly type: "string";
36
+ readonly description: "Raw JSON request body (replaces individual flags)";
37
+ };
38
+ readonly "dry-run": {
39
+ readonly type: "boolean";
40
+ readonly description: "Validate and show request without calling API";
41
+ };
42
+ }>;
43
+ export default _default;
@@ -0,0 +1,69 @@
1
+ import { defineCommand } from "citty";
2
+ import { request } from "../../client.js";
3
+ import { handleApiError } from "../../errors.js";
4
+ import { dryRunEnvelope, resolveJsonOverride } from "../../mutate.js";
5
+ import { formatSuccess, printResult, validationError } from "../../output.js";
6
+ import { optionalString, requireId } from "../../validate.js";
7
+ export async function runSessionSaveAsCase(options, deps) {
8
+ let sessionId;
9
+ let body;
10
+ const override = resolveJsonOverride(options.json);
11
+ if (override && "error" in override)
12
+ return override.error;
13
+ try {
14
+ sessionId = requireId("sessionId", options.sessionId);
15
+ }
16
+ catch (err) {
17
+ return validationError(err);
18
+ }
19
+ if (override) {
20
+ body = override.body;
21
+ }
22
+ else {
23
+ try {
24
+ body = {
25
+ title: optionalString("title", options.title),
26
+ type: optionalString("type", options.type),
27
+ scenario: optionalString("scenario", options.scenario),
28
+ format: optionalString("format", options.format),
29
+ };
30
+ }
31
+ catch (err) {
32
+ return validationError(err);
33
+ }
34
+ }
35
+ const path = `/api/sessions/${encodeURIComponent(sessionId)}/save-as-case`;
36
+ if (options.dryRun) {
37
+ return dryRunEnvelope("POST", path, body);
38
+ }
39
+ try {
40
+ const res = await request(path, { method: "POST", body }, deps ?? {});
41
+ return formatSuccess(res);
42
+ }
43
+ catch (err) {
44
+ return handleApiError(err);
45
+ }
46
+ }
47
+ export default defineCommand({
48
+ meta: { name: "save-as-case", description: "Save a finished session as a test case" },
49
+ args: {
50
+ sessionId: { type: "positional", description: "Session ID", required: true },
51
+ title: { type: "string" },
52
+ type: { type: "string", description: "web | mobile | desktop" },
53
+ scenario: { type: "string" },
54
+ format: { type: "string", description: "gherkin | markdown | plain_text" },
55
+ json: { type: "string", description: "Raw JSON request body (replaces individual flags)" },
56
+ "dry-run": { type: "boolean", description: "Validate and show request without calling API" },
57
+ },
58
+ async run({ args }) {
59
+ printResult(await runSessionSaveAsCase({
60
+ sessionId: args.sessionId,
61
+ title: args.title,
62
+ type: args.type,
63
+ scenario: args.scenario,
64
+ format: args.format,
65
+ json: args.json,
66
+ dryRun: args["dry-run"],
67
+ }));
68
+ },
69
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
2
+ import { HttpResponse, http, server, TEST_BASE_URL } from "../../test-utils.js";
3
+ import { runSessionSaveAsCase } from "./save-as-case.js";
4
+ beforeAll(() => {
5
+ process.env["AXIMO_API_KEY"] = "test-key";
6
+ process.env["AXIMO_BASE_URL"] = TEST_BASE_URL;
7
+ server.listen();
8
+ });
9
+ afterEach(() => server.resetHandlers());
10
+ afterAll(() => server.close());
11
+ describe("runSessionSaveAsCase", () => {
12
+ it("POSTs to the save-as-case path and returns the new case", async () => {
13
+ let seen;
14
+ server.use(http.post(`${TEST_BASE_URL}/api/sessions/s1/save-as-case`, async ({ request: req }) => {
15
+ seen = await req.json();
16
+ return HttpResponse.json({ success: true, caseId: "c1", projectId: "p1" }, { status: 201 });
17
+ }));
18
+ const result = await runSessionSaveAsCase({ sessionId: "s1", title: "Saved" });
19
+ expect(result.ok).toBe(true);
20
+ if (result.ok && !("dryRun" in result.data)) {
21
+ expect(result.data.caseId).toBe("c1");
22
+ expect(result.data.projectId).toBe("p1");
23
+ }
24
+ expect(seen).toMatchObject({ title: "Saved" });
25
+ });
26
+ it("works with no body fields (derives from session)", async () => {
27
+ server.use(http.post(`${TEST_BASE_URL}/api/sessions/s1/save-as-case`, () => HttpResponse.json({ success: true, caseId: "c2", projectId: "p1" }, { status: 201 })));
28
+ const result = await runSessionSaveAsCase({ sessionId: "s1" });
29
+ expect(result.ok).toBe(true);
30
+ });
31
+ it("rejects an empty sessionId", async () => {
32
+ const result = await runSessionSaveAsCase({ sessionId: "" });
33
+ expect(result.ok).toBe(false);
34
+ if (!result.ok)
35
+ expect(result.error.code).toBe("validation_error");
36
+ });
37
+ it("rejects an injected sessionId (requireId)", async () => {
38
+ const result = await runSessionSaveAsCase({ sessionId: "s1?x=1" });
39
+ expect(result.ok).toBe(false);
40
+ if (!result.ok)
41
+ expect(result.error.code).toBe("validation_error");
42
+ });
43
+ it("--dry-run returns request without calling API", async () => {
44
+ const result = await runSessionSaveAsCase({ sessionId: "s1", dryRun: true });
45
+ expect(result.ok).toBe(true);
46
+ if (result.ok && "dryRun" in result.data) {
47
+ expect(result.data.method).toBe("POST");
48
+ expect(result.data.url).toBe(`${TEST_BASE_URL}/api/sessions/s1/save-as-case`);
49
+ }
50
+ });
51
+ it("maps 400 to validation_error", async () => {
52
+ server.use(http.post(`${TEST_BASE_URL}/api/sessions/s1/save-as-case`, () => HttpResponse.json({ error: "Session is still in progress" }, { status: 400 })));
53
+ const result = await runSessionSaveAsCase({ sessionId: "s1" });
54
+ expect(result.ok).toBe(false);
55
+ if (!result.ok)
56
+ expect(result.error.code).toBe("validation_error");
57
+ });
58
+ });
package/dist/index.d.ts CHANGED
@@ -1,19 +1,24 @@
1
- export { request, getApiKey, getBaseUrl } from "./client.js";
2
1
  export type { ClientDeps, RequestOptions } from "./client.js";
3
- export { formatSuccess, formatError, printResult, validationError } from "./output.js";
4
- export { handleApiError } from "./errors.js";
2
+ export { getApiKey, getBaseUrl, request } from "./client.js";
3
+ export { runCaseCreate } from "./commands/case/create.js";
4
+ export { runDeviceList } from "./commands/device/list.js";
5
5
  export { runHealthCheck } from "./commands/health/check.js";
6
+ export { runPlanAddCase } from "./commands/plan/add-case.js";
7
+ export { runPlanCreate } from "./commands/plan/create.js";
8
+ export { runPlanRemoveCase } from "./commands/plan/remove-case.js";
6
9
  export { runProjectList } from "./commands/project/list.js";
7
- export { runDeviceList } from "./commands/device/list.js";
8
10
  export { runSessionCreate } from "./commands/session/create.js";
9
11
  export { runSessionGet } from "./commands/session/get.js";
10
- export { runTriggerCaseRun } from "./commands/trigger/case/run.js";
12
+ export { runSessionSaveAsCase } from "./commands/session/save-as-case.js";
11
13
  export { runTriggerCaseGet } from "./commands/trigger/case/get.js";
12
- export { runTriggerPlanRun } from "./commands/trigger/plan/run.js";
14
+ export { runTriggerCaseRun } from "./commands/trigger/case/run.js";
13
15
  export { runTriggerPlanGet } from "./commands/trigger/plan/get.js";
16
+ export { runTriggerPlanRun } from "./commands/trigger/plan/run.js";
14
17
  export { runUploadCreate } from "./commands/upload/create.js";
15
- export { runUploadList } from "./commands/upload/list.js";
16
18
  export { runUploadGet } from "./commands/upload/get.js";
19
+ export { runUploadList } from "./commands/upload/list.js";
17
20
  export { runUploadPut } from "./commands/upload/put.js";
18
21
  export { runUploadSubmit } from "./commands/upload/submit.js";
22
+ export { handleApiError } from "./errors.js";
23
+ export { formatError, formatSuccess, printResult, validationError } from "./output.js";
19
24
  export type * from "./types.js";