kiwivm-cli 0.1.0 → 0.2.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 (52) hide show
  1. package/README.md +62 -36
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +370 -50
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/src/commands/admin.test.ts +135 -22
  7. package/src/commands/admin.ts +57 -19
  8. package/src/commands/backup.test.ts +26 -23
  9. package/src/commands/backup.ts +13 -15
  10. package/src/commands/help.test.ts +27 -7
  11. package/src/commands/help.ts +61 -26
  12. package/src/commands/info.test.ts +47 -43
  13. package/src/commands/info.ts +11 -13
  14. package/src/commands/iso.test.ts +58 -0
  15. package/src/commands/iso.ts +21 -0
  16. package/src/commands/migrate.test.ts +105 -0
  17. package/src/commands/migrate.ts +38 -0
  18. package/src/commands/network.test.ts +107 -30
  19. package/src/commands/network.ts +56 -18
  20. package/src/commands/power.test.ts +58 -40
  21. package/src/commands/power.ts +27 -16
  22. package/src/commands/shell.test.ts +66 -0
  23. package/src/commands/shell.ts +25 -0
  24. package/src/commands/snapshot.test.ts +141 -71
  25. package/src/commands/snapshot.ts +85 -33
  26. package/src/commands/stats.test.ts +81 -0
  27. package/src/commands/stats.ts +25 -0
  28. package/src/commands/system.test.ts +109 -40
  29. package/src/commands/system.ts +55 -23
  30. package/src/index.test.ts +435 -148
  31. package/src/index.ts +129 -57
  32. package/src/types.ts +57 -1
  33. package/dist/admin-fOud1ZmX.mjs +0 -15
  34. package/dist/admin-fOud1ZmX.mjs.map +0 -1
  35. package/dist/backup-D1UJ4aap.mjs +0 -12
  36. package/dist/backup-D1UJ4aap.mjs.map +0 -1
  37. package/dist/help-Dk-WApoi.mjs +0 -40
  38. package/dist/help-Dk-WApoi.mjs.map +0 -1
  39. package/dist/info-DKExtFYH.mjs +0 -13
  40. package/dist/info-DKExtFYH.mjs.map +0 -1
  41. package/dist/monitoring-BSuv8fj9.mjs +0 -13
  42. package/dist/monitoring-BSuv8fj9.mjs.map +0 -1
  43. package/dist/network-1ycEIJqT.mjs +0 -15
  44. package/dist/network-1ycEIJqT.mjs.map +0 -1
  45. package/dist/power-CDg0Mx1A.mjs +0 -14
  46. package/dist/power-CDg0Mx1A.mjs.map +0 -1
  47. package/dist/snapshot-LO_ufoj5.mjs +0 -23
  48. package/dist/snapshot-LO_ufoj5.mjs.map +0 -1
  49. package/dist/system-Bl-dsqX9.mjs +0 -21
  50. package/dist/system-Bl-dsqX9.mjs.map +0 -1
  51. package/src/commands/monitoring.test.ts +0 -82
  52. package/src/commands/monitoring.ts +0 -20
@@ -1,32 +1,67 @@
1
- const HELP = `Usage: kiwivm-cli <category> <action> [--flags...]
1
+ const HELP = `Usage: kiwivm-cli <command> [<subcommand>] [args...] [--flags...]
2
2
 
3
3
  Auth: --veid <VEID> --api-key <KEY> (or KIWIVM_VEID / KIWIVM_API_KEY env vars)
4
4
 
5
- Categories:
6
-
7
- power start | stop | restart | kill
8
- info (no action) | live
9
- snapshot create | list | delete | restore | sticky | export | import
10
- backup list | copy
11
- system hostname | rdns | password | sshkey | os | reinstall
12
- network ipv6-add | ipv6-delete | private-list | private-assign | private-delete
13
- monitoring usage | audit | rate-limit
14
- admin suspensions | unsuspend | resolve | resolve-violation
15
-
16
- Flags:
17
-
18
- --description Snapshot description (snapshot create)
19
- --snapshot Snapshot file name (snapshot delete/restore/sticky/export)
20
- --sticky 0 or 1 (snapshot sticky)
21
- --source-veid Source VEID (snapshot import)
22
- --source-token Source token (snapshot import)
23
- --backup-token Backup token (backup copy)
24
- --new-hostname New hostname (system hostname)
25
- --ip IP address (system rdns, network ipv6-delete/private-assign/private-delete)
26
- --ptr PTR/rDNS record (system rdns)
27
- --ssh-keys SSH keys (system sshkey update)
28
- --os OS template name (system reinstall)
29
- --record-id Record ID (admin unsuspend/resolve-violation)
5
+ Commands:
6
+
7
+ start Start the VPS
8
+ stop Stop the VPS
9
+ restart Reboot the VPS
10
+ kill Force-stop a stuck VPS
11
+
12
+ info Get service info (plan, IPs, bandwidth, etc.)
13
+ status Get live status (CPU, RAM, disk, uptime)
14
+
15
+ snapshot list List all snapshots
16
+ snapshot create [--desc] Create a new snapshot
17
+ snapshot delete <token> Delete a snapshot
18
+ snapshot restore <token> Restore from snapshot
19
+ snapshot sticky <token> Toggle sticky (--on | --off)
20
+ snapshot export <token> Generate transfer token
21
+ snapshot import <veid> <token> Import snapshot from another instance
22
+
23
+ backup list List automatic backups
24
+ backup copy <token> Convert backup to restorable snapshot
25
+
26
+ os list List available OS templates
27
+ os reinstall <template> Reinstall OS
28
+
29
+ hostname <name> Set new hostname
30
+ password Reset root password
31
+
32
+ ssh-key Show SSH keys
33
+ ssh-key set <keys> Set SSH keys
34
+
35
+ rdns set <ip> <ptr> Set reverse DNS record
36
+
37
+ ipv6 add Assign new IPv6 /64 subnet
38
+ ipv6 delete <subnet> Release IPv6 subnet
39
+
40
+ private-ip list List available private IPs
41
+ private-ip assign [<ip>] Assign private IP (random if omitted)
42
+ private-ip delete <ip> Release private IP
43
+
44
+ iso mount <name> Mount ISO for boot (VM must be off)
45
+ iso unmount Unmount ISO, boot from disk
46
+
47
+ shell exec <command> Execute command synchronously
48
+ shell script <script> Execute script asynchronously
49
+
50
+ migrate locations List available migration locations
51
+ migrate start <location> Start migration to new location
52
+ clone <ip> <password> [--port] Clone from external server (OVZ only)
53
+
54
+ stats usage Get detailed usage statistics
55
+ stats audit Get audit log
56
+ stats rate-limit Check API rate limit status
57
+
58
+ suspensions View suspension details
59
+ unsuspend <record-id> Clear abuse issue and unsuspend
60
+ violations View policy violations
61
+ violations resolve <record-id> Resolve policy violation
62
+
63
+ notifications Get notification preferences
64
+ notifications set <json> Update notification preferences
30
65
 
31
66
  Output: JSON to stdout. Errors to stderr with exit code 1.
32
67
  `;
@@ -1,67 +1,71 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { KiwiVMClient } from "../client.ts";
3
- import { run } from "./info.ts";
3
+ import { info, status } from "./info.ts";
4
4
 
5
5
  function mockClient() {
6
6
  const call = vi.fn();
7
7
  return { client: { call } as unknown as KiwiVMClient, call };
8
8
  }
9
9
 
10
- describe("info command", () => {
11
- it("calls getServiceInfo for basic info (empty action)", async () => {
12
- const { client, call } = mockClient();
13
- call.mockResolvedValueOnce({
14
- error: 0,
15
- hostname: "my-vps",
16
- });
17
-
18
- const result = await run("", {}, client);
10
+ describe("info handlers", () => {
11
+ describe("info", () => {
12
+ it("calls client.call('getServiceInfo')", async () => {
13
+ const { client, call } = mockClient();
14
+ call.mockResolvedValueOnce({
15
+ error: 0,
16
+ hostname: "my-vps",
17
+ plan_disk: 50,
18
+ plan_ram: 1024,
19
+ });
19
20
 
20
- expect(client.call).toHaveBeenCalledExactlyOnceWith("getServiceInfo");
21
- expect(result).toMatchObject({ hostname: "my-vps" });
22
- });
21
+ const result = await info([], {}, client);
23
22
 
24
- it("calls getServiceInfo when action is omitted", async () => {
25
- const { client, call } = mockClient();
26
- call.mockResolvedValueOnce({ error: 0 });
23
+ expect(call).toHaveBeenCalledExactlyOnceWith("getServiceInfo");
24
+ expect(result).toMatchObject({ hostname: "my-vps" });
25
+ });
27
26
 
28
- await run("", {}, client);
27
+ it("returns the complete API response", async () => {
28
+ const { client, call } = mockClient();
29
+ const apiResponse = {
30
+ error: 0,
31
+ hostname: "my-vps",
32
+ plan_disk: 50,
33
+ plan_ram: 1024,
34
+ };
35
+ call.mockResolvedValueOnce(apiResponse);
29
36
 
30
- expect(client.call).toHaveBeenCalledExactlyOnceWith("getServiceInfo");
31
- });
37
+ const result = await info([], {}, client);
32
38
 
33
- it("calls getLiveServiceInfo for live action", async () => {
34
- const { client, call } = mockClient();
35
- call.mockResolvedValueOnce({
36
- error: 0,
37
- ve_status: "Running",
39
+ expect(result).toEqual(apiResponse);
38
40
  });
39
41
 
40
- const result = await run("live", {}, client);
42
+ it("propagates errors from the client", async () => {
43
+ const { client, call } = mockClient();
44
+ call.mockRejectedValueOnce(new Error("Network error"));
41
45
 
42
- expect(client.call).toHaveBeenCalledExactlyOnceWith("getLiveServiceInfo");
43
- expect(result).toMatchObject({ ve_status: "Running" });
46
+ await expect(info([], {}, client)).rejects.toThrow("Network error");
47
+ });
44
48
  });
45
49
 
46
- it("returns the complete API response", async () => {
47
- const { client, call } = mockClient();
48
- const apiResponse = {
49
- error: 0,
50
- hostname: "my-vps",
51
- plan_disk: 50,
52
- plan_ram: 1024,
53
- };
54
- call.mockResolvedValueOnce(apiResponse);
50
+ describe("status", () => {
51
+ it("calls client.call('getLiveServiceInfo')", async () => {
52
+ const { client, call } = mockClient();
53
+ call.mockResolvedValueOnce({
54
+ error: 0,
55
+ ve_status: "Running",
56
+ });
55
57
 
56
- const result = await run("", {}, client);
58
+ const result = await status([], {}, client);
57
59
 
58
- expect(result).toEqual(apiResponse);
59
- });
60
+ expect(call).toHaveBeenCalledExactlyOnceWith("getLiveServiceInfo");
61
+ expect(result).toMatchObject({ ve_status: "Running" });
62
+ });
60
63
 
61
- it("propagates errors from the client", async () => {
62
- const { client, call } = mockClient();
63
- call.mockRejectedValueOnce(new Error("Network error"));
64
+ it("propagates errors from the client", async () => {
65
+ const { client, call } = mockClient();
66
+ call.mockRejectedValueOnce(new Error("Network error"));
64
67
 
65
- await expect(run("live", {}, client)).rejects.toThrow("Network error");
68
+ await expect(status([], {}, client)).rejects.toThrow("Network error");
69
+ });
66
70
  });
67
71
  });
@@ -1,20 +1,18 @@
1
1
  import type { KiwiVMClient } from "../client.ts";
2
2
  import type { LiveServiceInfo, ServiceInfo } from "../types.ts";
3
3
 
4
- export async function run(
5
- action: string,
4
+ export async function info(
5
+ _args: string[],
6
6
  _flags: Record<string, string>,
7
7
  client: KiwiVMClient,
8
8
  ): Promise<unknown> {
9
- switch (action) {
10
- case "live":
11
- return client.call<LiveServiceInfo>("getLiveServiceInfo");
12
- case "":
13
- case "info":
14
- return client.call<ServiceInfo>("getServiceInfo");
15
- default:
16
- throw new Error(
17
- `Unknown info action: ${action}. Valid: (no action), live`,
18
- );
19
- }
9
+ return client.call<ServiceInfo>("getServiceInfo");
10
+ }
11
+
12
+ export async function status(
13
+ _args: string[],
14
+ _flags: Record<string, string>,
15
+ client: KiwiVMClient,
16
+ ): Promise<unknown> {
17
+ return client.call<LiveServiceInfo>("getLiveServiceInfo");
20
18
  }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { KiwiVMClient } from "../client.ts";
3
+ import { mount, unmount } from "./iso.ts";
4
+
5
+ function mockClient() {
6
+ const call = vi.fn();
7
+ return { client: { call } as unknown as KiwiVMClient, call };
8
+ }
9
+
10
+ describe("iso handlers", () => {
11
+ describe("mount", () => {
12
+ it("calls iso/mount with iso name from args", async () => {
13
+ const { client, call } = mockClient();
14
+ call.mockResolvedValueOnce({ error: 0 });
15
+
16
+ const result = await mount(["ubuntu.iso"], {}, client);
17
+
18
+ expect(call).toHaveBeenCalledExactlyOnceWith("iso/mount", {
19
+ iso: "ubuntu.iso",
20
+ });
21
+ expect(result).toEqual({ error: 0 });
22
+ });
23
+
24
+ it("throws when no ISO name provided", async () => {
25
+ const { client } = mockClient();
26
+
27
+ await expect(mount([], {}, client)).rejects.toThrow(/name/);
28
+ });
29
+
30
+ it("propagates errors from the client", async () => {
31
+ const { client, call } = mockClient();
32
+ call.mockRejectedValueOnce(new Error("Mount failed"));
33
+
34
+ await expect(mount(["ubuntu.iso"], {}, client)).rejects.toThrow(
35
+ "Mount failed",
36
+ );
37
+ });
38
+ });
39
+
40
+ describe("unmount", () => {
41
+ it("calls iso/unmount with no params", async () => {
42
+ const { client, call } = mockClient();
43
+ call.mockResolvedValueOnce({ error: 0 });
44
+
45
+ const result = await unmount([], {}, client);
46
+
47
+ expect(call).toHaveBeenCalledExactlyOnceWith("iso/unmount");
48
+ expect(result).toEqual({ error: 0 });
49
+ });
50
+
51
+ it("propagates errors from the client", async () => {
52
+ const { client, call } = mockClient();
53
+ call.mockRejectedValueOnce(new Error("Unmount failed"));
54
+
55
+ await expect(unmount([], {}, client)).rejects.toThrow("Unmount failed");
56
+ });
57
+ });
58
+ });
@@ -0,0 +1,21 @@
1
+ import type { KiwiVMClient } from "../client.ts";
2
+
3
+ export async function mount(
4
+ args: string[],
5
+ _flags: Record<string, string>,
6
+ client: KiwiVMClient,
7
+ ): Promise<unknown> {
8
+ const iso = args[0];
9
+ if (!iso) {
10
+ throw new Error("iso mount requires a <name> argument");
11
+ }
12
+ return client.call("iso/mount", { iso });
13
+ }
14
+
15
+ export async function unmount(
16
+ _args: string[],
17
+ _flags: Record<string, string>,
18
+ client: KiwiVMClient,
19
+ ): Promise<unknown> {
20
+ return client.call("iso/unmount");
21
+ }
@@ -0,0 +1,105 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { KiwiVMClient } from "../client.ts";
3
+ import { clone, locations, migrateStart } from "./migrate.ts";
4
+
5
+ function mockClient() {
6
+ const call = vi.fn();
7
+ return { client: { call } as unknown as KiwiVMClient, call };
8
+ }
9
+
10
+ describe("migrate handlers", () => {
11
+ describe("locations", () => {
12
+ it("calls migrate/getLocations", async () => {
13
+ const { client, call } = mockClient();
14
+ call.mockResolvedValueOnce({
15
+ error: 0,
16
+ locations: ["lasvegas", "newyork", "luxembourg"],
17
+ });
18
+
19
+ const result = await locations([], {}, client);
20
+
21
+ expect(call).toHaveBeenCalledExactlyOnceWith("migrate/getLocations");
22
+ expect(result).toMatchObject({
23
+ locations: ["lasvegas", "newyork", "luxembourg"],
24
+ });
25
+ });
26
+
27
+ it("propagates errors from the client", async () => {
28
+ const { client, call } = mockClient();
29
+ call.mockRejectedValueOnce(new Error("API failure"));
30
+
31
+ await expect(locations([], {}, client)).rejects.toThrow("API failure");
32
+ });
33
+ });
34
+
35
+ describe("migrateStart", () => {
36
+ it("calls migrate/start with location from args", async () => {
37
+ const { client, call } = mockClient();
38
+ call.mockResolvedValueOnce({ error: 0 });
39
+
40
+ await migrateStart(["lasvegas"], {}, client);
41
+
42
+ expect(call).toHaveBeenCalledExactlyOnceWith("migrate/start", {
43
+ location: "lasvegas",
44
+ });
45
+ });
46
+
47
+ it("throws when no location provided", async () => {
48
+ const { client } = mockClient();
49
+
50
+ await expect(migrateStart([], {}, client)).rejects.toThrow("location");
51
+ });
52
+
53
+ it("propagates errors from the client", async () => {
54
+ const { client, call } = mockClient();
55
+ call.mockRejectedValueOnce(new Error("Migration not available"));
56
+
57
+ await expect(migrateStart(["lasvegas"], {}, client)).rejects.toThrow(
58
+ "Migration not available",
59
+ );
60
+ });
61
+ });
62
+
63
+ describe("clone", () => {
64
+ it("calls cloneFromExternalServer with IP, default port, and password", async () => {
65
+ const { client, call } = mockClient();
66
+ call.mockResolvedValueOnce({ error: 0 });
67
+
68
+ await clone(["1.2.3.4", "mypassword"], {}, client);
69
+
70
+ expect(call).toHaveBeenCalledExactlyOnceWith("cloneFromExternalServer", {
71
+ externalServerIP: "1.2.3.4",
72
+ externalServerSSHport: "22",
73
+ externalServerRootPassword: "mypassword",
74
+ });
75
+ });
76
+
77
+ it("calls cloneFromExternalServer with custom port from flags", async () => {
78
+ const { client, call } = mockClient();
79
+ call.mockResolvedValueOnce({ error: 0 });
80
+
81
+ await clone(["1.2.3.4", "mypassword"], { port: "2222" }, client);
82
+
83
+ expect(call).toHaveBeenCalledExactlyOnceWith("cloneFromExternalServer", {
84
+ externalServerIP: "1.2.3.4",
85
+ externalServerSSHport: "2222",
86
+ externalServerRootPassword: "mypassword",
87
+ });
88
+ });
89
+
90
+ it("throws when password not provided", async () => {
91
+ const { client } = mockClient();
92
+
93
+ await expect(clone(["1.2.3.4"], {}, client)).rejects.toThrow("password");
94
+ });
95
+
96
+ it("propagates errors from the client", async () => {
97
+ const { client, call } = mockClient();
98
+ call.mockRejectedValueOnce(new Error("Clone failed"));
99
+
100
+ await expect(
101
+ clone(["1.2.3.4", "mypassword"], {}, client),
102
+ ).rejects.toThrow("Clone failed");
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,38 @@
1
+ import type { KiwiVMClient } from "../client.ts";
2
+
3
+ export async function locations(
4
+ _args: string[],
5
+ _flags: Record<string, string>,
6
+ client: KiwiVMClient,
7
+ ): Promise<unknown> {
8
+ return client.call("migrate/getLocations");
9
+ }
10
+
11
+ export async function migrateStart(
12
+ args: string[],
13
+ _flags: Record<string, string>,
14
+ client: KiwiVMClient,
15
+ ): Promise<unknown> {
16
+ const location = args[0];
17
+ if (!location) {
18
+ throw new Error("migrate start requires a <location> argument");
19
+ }
20
+ return client.call("migrate/start", { location });
21
+ }
22
+
23
+ export async function clone(
24
+ args: string[],
25
+ flags: Record<string, string>,
26
+ client: KiwiVMClient,
27
+ ): Promise<unknown> {
28
+ const ip = args[0];
29
+ const password = args[1];
30
+ if (!ip || !password) {
31
+ throw new Error("clone requires both <ip> and <password> arguments");
32
+ }
33
+ return client.call("cloneFromExternalServer", {
34
+ externalServerIP: ip,
35
+ externalServerSSHport: flags["port"] || "22",
36
+ externalServerRootPassword: password,
37
+ });
38
+ }
@@ -1,85 +1,162 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { KiwiVMClient } from "../client.ts";
3
- import { run } from "./network.ts";
3
+ import {
4
+ ipv6Add,
5
+ ipv6Delete,
6
+ privateIpAssign,
7
+ privateIpDelete,
8
+ privateIpList,
9
+ rdnsSet,
10
+ } from "./network.ts";
4
11
 
5
12
  function mockClient() {
6
13
  const call = vi.fn();
7
14
  return { client: { call } as unknown as KiwiVMClient, call };
8
15
  }
9
16
 
10
- describe("network command", () => {
11
- describe("ipv6-add", () => {
17
+ describe("network handlers", () => {
18
+ describe("rdnsSet", () => {
19
+ it("calls setPTR with ip and ptr from args", async () => {
20
+ const { client, call } = mockClient();
21
+ call.mockResolvedValueOnce({ error: 0 });
22
+
23
+ await rdnsSet(["1.2.3.4", "my.domain.com"], {}, client);
24
+
25
+ expect(call).toHaveBeenCalledExactlyOnceWith("setPTR", {
26
+ ip: "1.2.3.4",
27
+ ptr: "my.domain.com",
28
+ });
29
+ });
30
+
31
+ it("propagates errors from the client", async () => {
32
+ const { client, call } = mockClient();
33
+ call.mockRejectedValueOnce(new Error("Invalid PTR"));
34
+
35
+ await expect(
36
+ rdnsSet(["1.2.3.4", "my.domain.com"], {}, client),
37
+ ).rejects.toThrow("Invalid PTR");
38
+ });
39
+ });
40
+
41
+ describe("ipv6Add", () => {
12
42
  it("calls ipv6/add with no extra params", async () => {
13
43
  const { client, call } = mockClient();
14
44
  call.mockResolvedValueOnce({ error: 0 });
15
45
 
16
- const result = await run("ipv6-add", {}, client);
46
+ const result = await ipv6Add([], {}, client);
17
47
 
18
- expect(client.call).toHaveBeenCalledExactlyOnceWith("ipv6/add");
48
+ expect(call).toHaveBeenCalledExactlyOnceWith("ipv6/add");
19
49
  expect(result).toEqual({ error: 0 });
20
50
  });
51
+
52
+ it("propagates errors from the client", async () => {
53
+ const { client, call } = mockClient();
54
+ call.mockRejectedValueOnce(new Error("IPv6 not available"));
55
+
56
+ await expect(ipv6Add([], {}, client)).rejects.toThrow(
57
+ "IPv6 not available",
58
+ );
59
+ });
21
60
  });
22
61
 
23
- describe("ipv6-delete", () => {
24
- it("calls ipv6/delete with ip flag", async () => {
62
+ describe("ipv6Delete", () => {
63
+ it("calls ipv6/delete with ip from args", async () => {
25
64
  const { client, call } = mockClient();
26
65
  call.mockResolvedValueOnce({ error: 0 });
27
66
 
28
- await run("ipv6-delete", { ip: "2001:db8::1" }, client);
67
+ await ipv6Delete(["2001:db8::1"], {}, client);
29
68
 
30
- expect(client.call).toHaveBeenCalledExactlyOnceWith("ipv6/delete", {
69
+ expect(call).toHaveBeenCalledExactlyOnceWith("ipv6/delete", {
31
70
  ip: "2001:db8::1",
32
71
  });
33
72
  });
73
+
74
+ it("propagates errors from the client", async () => {
75
+ const { client, call } = mockClient();
76
+ call.mockRejectedValueOnce(new Error("Invalid IP"));
77
+
78
+ await expect(ipv6Delete(["2001:db8::1"], {}, client)).rejects.toThrow(
79
+ "Invalid IP",
80
+ );
81
+ });
34
82
  });
35
83
 
36
- describe("private-list", () => {
84
+ describe("privateIpList", () => {
37
85
  it("calls privateIp/getAvailableIps", async () => {
38
86
  const { client, call } = mockClient();
39
87
  call.mockResolvedValueOnce({
40
88
  error: 0,
41
- availableIps: ["10.0.0.1"],
89
+ availableIps: ["10.0.0.1", "10.0.0.2"],
42
90
  });
43
91
 
44
- const result = await run("private-list", {}, client);
92
+ const result = await privateIpList([], {}, client);
93
+
94
+ expect(call).toHaveBeenCalledExactlyOnceWith("privateIp/getAvailableIps");
95
+ expect(result).toMatchObject({ availableIps: ["10.0.0.1", "10.0.0.2"] });
96
+ });
97
+
98
+ it("propagates errors from the client", async () => {
99
+ const { client, call } = mockClient();
100
+ call.mockRejectedValueOnce(new Error("Private IP not available"));
45
101
 
46
- expect(client.call).toHaveBeenCalledExactlyOnceWith(
47
- "privateIp/getAvailableIps",
102
+ await expect(privateIpList([], {}, client)).rejects.toThrow(
103
+ "Private IP not available",
48
104
  );
49
- expect(result).toMatchObject({ availableIps: ["10.0.0.1"] });
50
105
  });
51
106
  });
52
107
 
53
- describe("private-assign", () => {
54
- it("calls privateIp/assign with ip flag", async () => {
108
+ describe("privateIpAssign", () => {
109
+ it("calls privateIp/assign with ip from args", async () => {
55
110
  const { client, call } = mockClient();
56
111
  call.mockResolvedValueOnce({ error: 0 });
57
112
 
58
- await run("private-assign", { ip: "10.0.0.1" }, client);
113
+ await privateIpAssign(["10.0.0.5"], {}, client);
59
114
 
60
- expect(client.call).toHaveBeenCalledExactlyOnceWith("privateIp/assign", {
61
- ip: "10.0.0.1",
115
+ expect(call).toHaveBeenCalledExactlyOnceWith("privateIp/assign", {
116
+ ip: "10.0.0.5",
62
117
  });
63
118
  });
64
- });
65
119
 
66
- describe("private-delete", () => {
67
- it("calls privateIp/delete with ip flag", async () => {
120
+ it("calls privateIp/assign with undefined ip when no arg", async () => {
68
121
  const { client, call } = mockClient();
69
122
  call.mockResolvedValueOnce({ error: 0 });
70
123
 
71
- await run("private-delete", { ip: "10.0.0.1" }, client);
124
+ await privateIpAssign([], {}, client);
72
125
 
73
- expect(client.call).toHaveBeenCalledExactlyOnceWith("privateIp/delete", {
74
- ip: "10.0.0.1",
126
+ expect(call).toHaveBeenCalledExactlyOnceWith("privateIp/assign", {
127
+ ip: undefined,
75
128
  });
76
129
  });
130
+
131
+ it("propagates errors from the client", async () => {
132
+ const { client, call } = mockClient();
133
+ call.mockRejectedValueOnce(new Error("Assignment failed"));
134
+
135
+ await expect(privateIpAssign(["10.0.0.5"], {}, client)).rejects.toThrow(
136
+ "Assignment failed",
137
+ );
138
+ });
77
139
  });
78
140
 
79
- it("propagates errors from the client", async () => {
80
- const { client, call } = mockClient();
81
- call.mockRejectedValueOnce(new Error("Invalid IP"));
141
+ describe("privateIpDelete", () => {
142
+ it("calls privateIp/delete with ip from args", async () => {
143
+ const { client, call } = mockClient();
144
+ call.mockResolvedValueOnce({ error: 0 });
145
+
146
+ await privateIpDelete(["10.0.0.5"], {}, client);
147
+
148
+ expect(call).toHaveBeenCalledExactlyOnceWith("privateIp/delete", {
149
+ ip: "10.0.0.5",
150
+ });
151
+ });
152
+
153
+ it("propagates errors from the client", async () => {
154
+ const { client, call } = mockClient();
155
+ call.mockRejectedValueOnce(new Error("Delete failed"));
82
156
 
83
- await expect(run("ipv6-add", {}, client)).rejects.toThrow("Invalid IP");
157
+ await expect(privateIpDelete(["10.0.0.5"], {}, client)).rejects.toThrow(
158
+ "Delete failed",
159
+ );
160
+ });
84
161
  });
85
162
  });