mirror of
https://github.com/trycua/computer.git
synced 2026-01-28 00:27:51 -06:00
Update package.json, fix linting issues
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
{
|
||||
"name": "@cua/computer",
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"description": "",
|
||||
"description": "Typescript SDK for c/ua computer interaction",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "",
|
||||
"bugs": {
|
||||
"url": ""
|
||||
"url": "https://github.com/trycua/cua/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/trycua/cua.git"
|
||||
},
|
||||
"author": "",
|
||||
"funding": "",
|
||||
"author": "c/ua",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
@@ -29,8 +28,8 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "biome lint --cache .",
|
||||
"lint:fix": "biome lint --cache --fix .",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --fix .",
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch",
|
||||
"test": "vitest",
|
||||
@@ -52,4 +51,4 @@
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OSType, VMProviderType } from "./types";
|
||||
import type { BaseComputerConfig, Display } from "./types";
|
||||
import type { BaseComputerConfig } from "./types";
|
||||
|
||||
/**
|
||||
* Default configuration values for Computer
|
||||
|
||||
@@ -15,18 +15,18 @@ export const logger = pino({ name: "computer" });
|
||||
/**
|
||||
* Factory class for creating the appropriate Computer instance
|
||||
*/
|
||||
export class Computer {
|
||||
export const Computer = {
|
||||
/**
|
||||
* Create a computer instance based on the provided configuration
|
||||
* @param config The computer configuration
|
||||
* @returns The appropriate computer instance based on the VM provider type
|
||||
*/
|
||||
static create(
|
||||
create: (
|
||||
config:
|
||||
| Partial<BaseComputerConfig>
|
||||
| Partial<CloudComputerConfig>
|
||||
| Partial<LumeComputerConfig>
|
||||
): BaseComputer {
|
||||
): BaseComputer => {
|
||||
// Apply defaults to the configuration
|
||||
const fullConfig = applyDefaults(config);
|
||||
|
||||
@@ -41,7 +41,5 @@ export class Computer {
|
||||
`Unsupported VM provider type: ${fullConfig.vmProvider}`
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported VM provider type`);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -63,8 +63,8 @@ export abstract class BaseComputer {
|
||||
}
|
||||
|
||||
return {
|
||||
width: parseInt(match[1], 10),
|
||||
height: parseInt(match[2], 10),
|
||||
width: Number.parseInt(match[1], 10),
|
||||
height: Number.parseInt(match[2], 10),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,14 +93,13 @@ export abstract class BaseComputer {
|
||||
throw new Error(`Invalid memory format: ${memoryStr}`);
|
||||
}
|
||||
|
||||
const value = parseFloat(match[1]);
|
||||
const value = Number.parseFloat(match[1]);
|
||||
const unit = match[2] || "MB"; // Default to MB if no unit specified
|
||||
|
||||
// Convert to MB
|
||||
if (unit === "GB") {
|
||||
return Math.round(value * 1024);
|
||||
} else {
|
||||
return Math.round(value);
|
||||
}
|
||||
return Math.round(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ const logger = pino({ name: "cloud" });
|
||||
* Cloud-specific computer implementation
|
||||
*/
|
||||
export class CloudComputer extends BaseComputer {
|
||||
private apiKey: string;
|
||||
constructor(config: CloudComputerConfig) {
|
||||
super(config);
|
||||
this.apiKey = config.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -90,7 +90,7 @@ export class LumeComputer extends BaseComputer {
|
||||
async runVm(
|
||||
image: string,
|
||||
name: string,
|
||||
runOpts: { [key: string]: any } = {},
|
||||
runOpts: { [key: string]: unknown } = {},
|
||||
storage?: string
|
||||
): Promise<VMInfo> {
|
||||
logger.info(
|
||||
@@ -106,7 +106,7 @@ export class LumeComputer extends BaseComputer {
|
||||
// Lume-specific implementation
|
||||
try {
|
||||
await this.getVm(name, storage);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
logger.info(
|
||||
`VM ${name} not found, attempting to pull image ${image} from registry...`
|
||||
);
|
||||
@@ -161,9 +161,8 @@ export class LumeComputer extends BaseComputer {
|
||||
name: string,
|
||||
image: string,
|
||||
storage?: string,
|
||||
registry: string = "ghcr.io",
|
||||
organization: string = "trycua",
|
||||
pullOpts?: { [key: string]: any }
|
||||
registry = "ghcr.io",
|
||||
organization = "trycua"
|
||||
): Promise<VMInfo> {
|
||||
// Validate image parameter
|
||||
if (!image) {
|
||||
@@ -222,7 +221,7 @@ export class LumeComputer extends BaseComputer {
|
||||
*/
|
||||
async updateVm(
|
||||
name: string,
|
||||
updateOpts: { [key: string]: any },
|
||||
updateOpts: { [key: string]: unknown },
|
||||
storage?: string
|
||||
): Promise<VMInfo> {
|
||||
return await lumeApiUpdate(
|
||||
@@ -239,11 +238,7 @@ export class LumeComputer extends BaseComputer {
|
||||
/**
|
||||
* Lume-specific method to get the IP address of a VM, waiting indefinitely until it's available
|
||||
*/
|
||||
async getIp(
|
||||
name: string,
|
||||
storage?: string,
|
||||
retryDelay: number = 2
|
||||
): Promise<string> {
|
||||
async getIp(name: string, storage?: string, retryDelay = 2): Promise<string> {
|
||||
// Track total attempts for logging purposes
|
||||
let attempts = 0;
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ export interface VMInfo {
|
||||
}
|
||||
|
||||
export interface RunOptions {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UpdateOptions {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +63,8 @@ export async function lumeApiGet(
|
||||
host: string,
|
||||
port: number,
|
||||
storage?: string,
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo[]> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -111,16 +111,17 @@ export async function lumeApiGet(
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.code === "ENOTFOUND") {
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
@@ -149,8 +150,8 @@ export async function lumeApiRun(
|
||||
port: number,
|
||||
runOpts: RunOptions,
|
||||
storage?: string,
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -199,14 +200,18 @@ export async function lumeApiRun(
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
errorMsg = "Failed to connect to the API server";
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
}
|
||||
@@ -232,8 +237,8 @@ export async function lumeApiStop(
|
||||
host: string,
|
||||
port: number,
|
||||
storage?: string,
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -278,14 +283,18 @@ export async function lumeApiStop(
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
errorMsg = "Failed to connect to the API server";
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
}
|
||||
@@ -313,8 +322,8 @@ export async function lumeApiUpdate(
|
||||
port: number,
|
||||
updateOpts: UpdateOptions,
|
||||
storage?: string,
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -363,14 +372,18 @@ export async function lumeApiUpdate(
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
errorMsg = "Failed to connect to the API server";
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
}
|
||||
@@ -400,10 +413,10 @@ export async function lumeApiPull(
|
||||
host: string,
|
||||
port: number,
|
||||
storage?: string,
|
||||
registry: string = "ghcr.io",
|
||||
organization: string = "trycua",
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
registry = "ghcr.io",
|
||||
organization = "trycua",
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -459,13 +472,18 @@ export async function lumeApiPull(
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg = "Operation timeout - the pull is taking too long";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
errorMsg = "Failed to connect to the API server";
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
}
|
||||
@@ -491,8 +509,8 @@ export async function lumeApiDelete(
|
||||
host: string,
|
||||
port: number,
|
||||
storage?: string,
|
||||
debug: boolean = false,
|
||||
verbose: boolean = false
|
||||
debug = false,
|
||||
verbose = false
|
||||
): Promise<VMInfo | null> {
|
||||
// URL encode the storage parameter for the query
|
||||
let storageParam = "";
|
||||
@@ -537,10 +555,10 @@ export async function lumeApiDelete(
|
||||
// Try to parse JSON response, but handle empty responses
|
||||
let data: VMInfo | null = null;
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (contentType && contentType.includes("application/json")) {
|
||||
if (contentType?.includes("application/json")) {
|
||||
try {
|
||||
data = (await response.json()) as VMInfo;
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Empty response is OK for DELETE
|
||||
}
|
||||
} else {
|
||||
@@ -552,14 +570,18 @@ export async function lumeApiDelete(
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
let errorMsg = "Unknown error";
|
||||
|
||||
if (error.name === "AbortError") {
|
||||
errorMsg =
|
||||
"Operation timeout - the API server is taking too long to respond";
|
||||
} else if (error.code === "ECONNREFUSED") {
|
||||
errorMsg = "Failed to connect to the API server";
|
||||
} else if (error.message.includes("ECONNREFUSED")) {
|
||||
errorMsg =
|
||||
"Failed to connect to the API server - it might still be starting up";
|
||||
} else if (error.message.includes("ENOTFOUND")) {
|
||||
errorMsg = "Failed to resolve host - check the API server address";
|
||||
} else if (error.message) {
|
||||
errorMsg = error.message;
|
||||
}
|
||||
|
||||
@@ -86,28 +86,37 @@ describe("LumeComputer", () => {
|
||||
describe("getVm", () => {
|
||||
it("should get VM info successfully", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.getVm("test-vm");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined);
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
undefined
|
||||
);
|
||||
expect(result).toEqual(mockVMInfo);
|
||||
});
|
||||
|
||||
it("should handle VM not found error", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.getVm("test-vm")).rejects.toThrow("VM Not Found.");
|
||||
});
|
||||
|
||||
it("should handle stopped VM state", async () => {
|
||||
const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined };
|
||||
const stoppedVM = {
|
||||
...mockVMInfo,
|
||||
status: "stopped",
|
||||
ipAddress: undefined,
|
||||
};
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.getVm("test-vm");
|
||||
|
||||
|
||||
expect(result.status).toBe("stopped");
|
||||
expect(result.name).toBe("test-vm");
|
||||
});
|
||||
@@ -115,20 +124,25 @@ describe("LumeComputer", () => {
|
||||
it("should handle VM without IP address", async () => {
|
||||
const noIpVM = { ...mockVMInfo, ipAddress: undefined };
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([noIpVM]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.getVm("test-vm");
|
||||
|
||||
|
||||
expect(result).toEqual(noIpVM);
|
||||
});
|
||||
|
||||
it("should pass storage parameter", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.getVm("test-vm", "/custom/storage");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/custom/storage");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
"/custom/storage"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,10 +150,10 @@ describe("LumeComputer", () => {
|
||||
it("should list all VMs", async () => {
|
||||
const vmList = [mockVMInfo, { ...mockVMInfo, name: "another-vm" }];
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue(vmList);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.listVm();
|
||||
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("", "localhost", 7777);
|
||||
expect(result).toEqual(vmList);
|
||||
});
|
||||
@@ -149,24 +163,44 @@ describe("LumeComputer", () => {
|
||||
it("should run VM when it already exists", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const runOpts = { memory: "4GB" };
|
||||
const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm", runOpts);
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined);
|
||||
expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, runOpts, undefined);
|
||||
const result = await computer.runVm(
|
||||
"macos-sequoia-cua:latest",
|
||||
"test-vm",
|
||||
runOpts
|
||||
);
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
undefined
|
||||
);
|
||||
expect(lumeApi.lumeApiRun).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
runOpts,
|
||||
undefined
|
||||
);
|
||||
expect(result).toEqual(mockVMInfo);
|
||||
});
|
||||
|
||||
it("should pull and run VM when it doesn't exist", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found"));
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(
|
||||
new Error("VM not found")
|
||||
);
|
||||
vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo);
|
||||
vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm");
|
||||
|
||||
const result = await computer.runVm(
|
||||
"macos-sequoia-cua:latest",
|
||||
"test-vm"
|
||||
);
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalled();
|
||||
expect(lumeApi.lumeApiPull).toHaveBeenCalledWith(
|
||||
"macos-sequoia-cua:latest",
|
||||
@@ -182,33 +216,60 @@ describe("LumeComputer", () => {
|
||||
});
|
||||
|
||||
it("should handle pull failure", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found"));
|
||||
vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Pull failed"));
|
||||
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(
|
||||
new Error("VM not found")
|
||||
);
|
||||
vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(
|
||||
new Error("Pull failed")
|
||||
);
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.runVm("macos-sequoia-cua:latest", "test-vm")).rejects.toThrow("Pull failed");
|
||||
await expect(
|
||||
computer.runVm("macos-sequoia-cua:latest", "test-vm")
|
||||
).rejects.toThrow("Pull failed");
|
||||
});
|
||||
|
||||
it("should pass storage parameter", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.runVm("macos-sequoia-cua:latest", "test-vm", {}, "/storage");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage");
|
||||
expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, {}, "/storage");
|
||||
await computer.runVm(
|
||||
"macos-sequoia-cua:latest",
|
||||
"test-vm",
|
||||
{},
|
||||
"/storage"
|
||||
);
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
"/storage"
|
||||
);
|
||||
expect(lumeApi.lumeApiRun).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
{},
|
||||
"/storage"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("stopVm", () => {
|
||||
it("should stop VM normally", async () => {
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.stopVm("test-vm");
|
||||
|
||||
expect(lumeApi.lumeApiStop).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined);
|
||||
|
||||
expect(lumeApi.lumeApiStop).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
undefined
|
||||
);
|
||||
expect(result).toEqual(mockVMInfo);
|
||||
});
|
||||
|
||||
@@ -216,13 +277,20 @@ describe("LumeComputer", () => {
|
||||
const stoppedVM = { ...mockVMInfo, status: "stopped" };
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM);
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null);
|
||||
|
||||
|
||||
const ephemeralConfig = { ...defaultConfig, ephemeral: true };
|
||||
const computer = new LumeComputer(ephemeralConfig);
|
||||
const result = await computer.stopVm("test-vm");
|
||||
|
||||
|
||||
expect(lumeApi.lumeApiStop).toHaveBeenCalled();
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false);
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
undefined,
|
||||
false,
|
||||
false
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
...stoppedVM,
|
||||
deleted: true,
|
||||
@@ -232,34 +300,26 @@ describe("LumeComputer", () => {
|
||||
|
||||
it("should handle delete failure in ephemeral mode", async () => {
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo);
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Delete failed"));
|
||||
|
||||
const ephemeralConfig = { ...defaultConfig, ephemeral: true };
|
||||
const computer = new LumeComputer(ephemeralConfig);
|
||||
|
||||
await expect(computer.stopVm("test-vm")).rejects.toThrow("Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed");
|
||||
});
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(
|
||||
new Error("Delete failed")
|
||||
);
|
||||
|
||||
it("should not delete VM if stop returns error in ephemeral mode", async () => {
|
||||
const errorResponse = { error: "Stop failed" } as any;
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(errorResponse);
|
||||
|
||||
const ephemeralConfig = { ...defaultConfig, ephemeral: true };
|
||||
const computer = new LumeComputer(ephemeralConfig);
|
||||
const result = await computer.stopVm("test-vm");
|
||||
|
||||
expect(lumeApi.lumeApiDelete).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(errorResponse);
|
||||
|
||||
await expect(computer.stopVm("test-vm")).rejects.toThrow(
|
||||
"Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("pullVm", () => {
|
||||
it("should pull VM image successfully", async () => {
|
||||
vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.pullVm("test-vm", "ubuntu:latest");
|
||||
|
||||
|
||||
expect(lumeApi.lumeApiPull).toHaveBeenCalledWith(
|
||||
"ubuntu:latest",
|
||||
"test-vm",
|
||||
@@ -274,15 +334,23 @@ describe("LumeComputer", () => {
|
||||
|
||||
it("should throw error if image parameter is missing", async () => {
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.pullVm("test-vm", "")).rejects.toThrow("Image parameter is required for pullVm");
|
||||
await expect(computer.pullVm("test-vm", "")).rejects.toThrow(
|
||||
"Image parameter is required for pullVm"
|
||||
);
|
||||
});
|
||||
|
||||
it("should use custom registry and organization", async () => {
|
||||
vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.pullVm("test-vm", "custom:tag", "/storage", "docker.io", "myorg");
|
||||
|
||||
await computer.pullVm(
|
||||
"test-vm",
|
||||
"custom:tag",
|
||||
"/storage",
|
||||
"docker.io",
|
||||
"myorg"
|
||||
);
|
||||
|
||||
expect(lumeApi.lumeApiPull).toHaveBeenCalledWith(
|
||||
"custom:tag",
|
||||
"test-vm",
|
||||
@@ -295,38 +363,60 @@ describe("LumeComputer", () => {
|
||||
});
|
||||
|
||||
it("should handle pull failure", async () => {
|
||||
vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Network error"));
|
||||
|
||||
vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(
|
||||
new Error("Network error")
|
||||
);
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow("Failed to pull VM: Error: Network error");
|
||||
await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow(
|
||||
"Failed to pull VM: Error: Network error"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteVm", () => {
|
||||
it("should delete VM successfully", async () => {
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const result = await computer.deleteVm("test-vm");
|
||||
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false);
|
||||
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
undefined,
|
||||
false,
|
||||
false
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle delete failure", async () => {
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Permission denied"));
|
||||
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(
|
||||
new Error("Permission denied")
|
||||
);
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.deleteVm("test-vm")).rejects.toThrow("Failed to delete VM: Error: Permission denied");
|
||||
await expect(computer.deleteVm("test-vm")).rejects.toThrow(
|
||||
"Failed to delete VM: Error: Permission denied"
|
||||
);
|
||||
});
|
||||
|
||||
it("should pass storage parameter", async () => {
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.deleteVm("test-vm", "/storage");
|
||||
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage", false, false);
|
||||
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
"/storage",
|
||||
false,
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -334,11 +424,11 @@ describe("LumeComputer", () => {
|
||||
it("should update VM configuration", async () => {
|
||||
const updatedVM = { ...mockVMInfo, memorySize: 4096 };
|
||||
vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(updatedVM);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const updateOpts = { memory: "4GB" };
|
||||
const result = await computer.updateVm("test-vm", updateOpts);
|
||||
|
||||
|
||||
expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
@@ -353,10 +443,10 @@ describe("LumeComputer", () => {
|
||||
|
||||
it("should pass storage parameter", async () => {
|
||||
vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.updateVm("test-vm", {}, "/storage");
|
||||
|
||||
|
||||
expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
@@ -372,10 +462,10 @@ describe("LumeComputer", () => {
|
||||
describe("getIp", () => {
|
||||
it("should return IP address immediately if available", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const ip = await computer.getIp("test-vm");
|
||||
|
||||
|
||||
expect(ip).toBe("192.168.1.100");
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -386,18 +476,22 @@ describe("LumeComputer", () => {
|
||||
.mockResolvedValueOnce([noIpVM])
|
||||
.mockResolvedValueOnce([noIpVM])
|
||||
.mockResolvedValueOnce([mockVMInfo]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
const ip = await computer.getIp("test-vm", undefined, 0.1); // Short retry delay for testing
|
||||
|
||||
|
||||
expect(ip).toBe("192.168.1.100");
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("should throw error if VM is stopped", async () => {
|
||||
const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined };
|
||||
const stoppedVM = {
|
||||
...mockVMInfo,
|
||||
status: "stopped",
|
||||
ipAddress: undefined,
|
||||
};
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.getIp("test-vm")).rejects.toThrow(
|
||||
"VM test-vm is in 'stopped' state and will not get an IP address"
|
||||
@@ -407,7 +501,7 @@ describe("LumeComputer", () => {
|
||||
it("should throw error if VM is in error state", async () => {
|
||||
const errorVM = { ...mockVMInfo, status: "error", ipAddress: undefined };
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([errorVM]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.getIp("test-vm")).rejects.toThrow(
|
||||
"VM test-vm is in 'error' state and will not get an IP address"
|
||||
@@ -415,48 +509,61 @@ describe("LumeComputer", () => {
|
||||
});
|
||||
|
||||
it("should handle getVm errors", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValue(new Error("Network error"));
|
||||
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValue(
|
||||
new Error("Network error")
|
||||
);
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await expect(computer.getIp("test-vm")).rejects.toThrow("Network error");
|
||||
});
|
||||
|
||||
it("should pass storage parameter", async () => {
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
await computer.getIp("test-vm", "/storage");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage");
|
||||
|
||||
expect(lumeApi.lumeApiGet).toHaveBeenCalledWith(
|
||||
"test-vm",
|
||||
"localhost",
|
||||
7777,
|
||||
"/storage"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("integration scenarios", () => {
|
||||
it("should handle full VM lifecycle", async () => {
|
||||
// Simulate VM not existing initially
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found"));
|
||||
vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(
|
||||
new Error("VM not found")
|
||||
);
|
||||
vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo);
|
||||
|
||||
|
||||
// Simulate VM starting without IP, then getting IP
|
||||
const startingVM = { ...mockVMInfo, ipAddress: undefined, status: "starting" };
|
||||
const startingVM = {
|
||||
...mockVMInfo,
|
||||
ipAddress: undefined,
|
||||
status: "starting",
|
||||
};
|
||||
vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(startingVM);
|
||||
vi.mocked(lumeApi.lumeApiGet)
|
||||
.mockResolvedValueOnce([startingVM])
|
||||
.mockResolvedValueOnce([mockVMInfo]);
|
||||
|
||||
|
||||
// Simulate stop
|
||||
const stoppedVM = { ...mockVMInfo, status: "stopped" };
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM);
|
||||
|
||||
|
||||
const computer = new LumeComputer(defaultConfig);
|
||||
|
||||
|
||||
// Run VM (should pull first)
|
||||
await computer.runVm("macos-sequoia-cua:latest", "test-vm");
|
||||
|
||||
|
||||
// Get IP (should retry once)
|
||||
const ip = await computer.getIp("test-vm", undefined, 0.1);
|
||||
expect(ip).toBe("192.168.1.100");
|
||||
|
||||
|
||||
// Stop VM
|
||||
const stopResult = await computer.stopVm("test-vm");
|
||||
expect(stopResult.status).toBe("stopped");
|
||||
@@ -465,20 +572,23 @@ describe("LumeComputer", () => {
|
||||
it("should handle ephemeral VM lifecycle", async () => {
|
||||
const ephemeralConfig = { ...defaultConfig, ephemeral: true };
|
||||
const computer = new LumeComputer(ephemeralConfig);
|
||||
|
||||
|
||||
// Setup mocks
|
||||
vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]);
|
||||
vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo);
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({ ...mockVMInfo, status: "stopped" });
|
||||
vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({
|
||||
...mockVMInfo,
|
||||
status: "stopped",
|
||||
});
|
||||
vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null);
|
||||
|
||||
|
||||
// Run and stop ephemeral VM
|
||||
await computer.runVm("macos-sequoia-cua:latest", "test-vm");
|
||||
const result = await computer.stopVm("test-vm");
|
||||
|
||||
|
||||
// Verify VM was deleted
|
||||
expect(lumeApi.lumeApiDelete).toHaveBeenCalled();
|
||||
expect((result as any).deleted).toBe(true);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, it } from "vitest";
|
||||
import { Computer, OSType, VMProviderType } from "../src/index";
|
||||
|
||||
describe("Cloud Interface", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
lumeApiGet,
|
||||
lumeApiRun,
|
||||
@@ -15,24 +15,6 @@ const PORT = 1213;
|
||||
const HOST = "localhost";
|
||||
|
||||
describe("Lume API", () => {
|
||||
let lumeServer: any;
|
||||
|
||||
beforeAll(() => {
|
||||
// Spawn the lume serve process before tests
|
||||
const { spawn } = require("child_process");
|
||||
lumeServer = spawn("lume", ["serve", "--port", PORT], {
|
||||
stdio: "pipe",
|
||||
detached: true,
|
||||
});
|
||||
|
||||
// Clean up the server when tests are done
|
||||
afterAll(() => {
|
||||
if (lumeServer && !lumeServer.killed) {
|
||||
process.kill(-lumeServer.pid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("lumeApiGet", () => {
|
||||
it("should fetch VM information successfully", async () => {
|
||||
// Mock fetch for this test - API returns a single VMDetails object
|
||||
@@ -44,7 +26,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "1920x1080",
|
||||
locationName: "local",
|
||||
cpuCount: 2,
|
||||
cpuCount: 2,
|
||||
sharedDirectories: [
|
||||
{
|
||||
hostPath: "/home/user/shared",
|
||||
@@ -56,7 +38,7 @@ describe("Lume API", () => {
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockVMInfo,
|
||||
json: async () => mockVMInfo,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
} as Response);
|
||||
|
||||
@@ -99,7 +81,7 @@ describe("Lume API", () => {
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockVMList,
|
||||
json: async () => mockVMList,
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
} as Response);
|
||||
|
||||
@@ -161,7 +143,7 @@ describe("Lume API", () => {
|
||||
|
||||
it("should handle connection refused errors", async () => {
|
||||
const error = new Error("Connection refused");
|
||||
(error as any).code = "ECONNREFUSED";
|
||||
(error as Error).message = "ECONNREFUSED";
|
||||
global.fetch = vi.fn().mockRejectedValueOnce(error);
|
||||
|
||||
await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow(
|
||||
@@ -181,7 +163,7 @@ describe("Lume API", () => {
|
||||
|
||||
it("should handle host not found errors", async () => {
|
||||
const error = new Error("Host not found");
|
||||
(error as any).code = "ENOTFOUND";
|
||||
(error as Error).message = "ENOTFOUND";
|
||||
global.fetch = vi.fn().mockRejectedValueOnce(error);
|
||||
|
||||
await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow(
|
||||
@@ -200,7 +182,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "1920x1080",
|
||||
locationName: "local",
|
||||
cpuCount: 2,
|
||||
cpuCount: 2,
|
||||
vncUrl: "vnc://localhost:5900",
|
||||
ipAddress: "192.168.1.100",
|
||||
};
|
||||
@@ -263,9 +245,7 @@ describe("Lume API", () => {
|
||||
headers: new Headers(),
|
||||
} as Response);
|
||||
|
||||
await expect(
|
||||
lumeApiRun("test-vm", HOST, PORT, {})
|
||||
).rejects.toThrow(
|
||||
await expect(lumeApiRun("test-vm", HOST, PORT, {})).rejects.toThrow(
|
||||
"API request failed: HTTP error returned from API server (status: 500)"
|
||||
);
|
||||
});
|
||||
@@ -281,7 +261,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "1920x1080",
|
||||
locationName: "local",
|
||||
cpuCount: 2,
|
||||
cpuCount: 2,
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
@@ -338,7 +318,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "2560x1440",
|
||||
locationName: "local",
|
||||
cpuCount: 2,
|
||||
cpuCount: 2,
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
@@ -396,7 +376,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "1920x1080",
|
||||
locationName: "local",
|
||||
cpuCount: 2,
|
||||
cpuCount: 2,
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
@@ -493,7 +473,7 @@ describe("Lume API", () => {
|
||||
os: "ubuntu",
|
||||
display: "",
|
||||
locationName: "local",
|
||||
cpuCount: 0,
|
||||
cpuCount: 0,
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
@@ -543,7 +523,7 @@ describe("Lume API", () => {
|
||||
} as Response);
|
||||
|
||||
const result = await lumeApiDelete("non-existent-vm", HOST, PORT);
|
||||
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
@@ -566,7 +546,7 @@ describe("Lume API", () => {
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => [{ name: "test-vm", cpuCount: 2 }],
|
||||
json: async () => [{ name: "test-vm", cpuCount: 2 }],
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
} as Response);
|
||||
|
||||
@@ -584,7 +564,7 @@ describe("Lume API", () => {
|
||||
|
||||
global.fetch = vi.fn().mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ name: "test-vm", cpuCount: 2 }),
|
||||
json: async () => ({ name: "test-vm", cpuCount: 2 }),
|
||||
headers: new Headers({ "content-type": "application/json" }),
|
||||
} as Response);
|
||||
|
||||
@@ -613,13 +593,5 @@ describe("Lume API", () => {
|
||||
"API request failed: Custom error message"
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle unknown errors", async () => {
|
||||
global.fetch = vi.fn().mockRejectedValueOnce({});
|
||||
|
||||
await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow(
|
||||
"API request failed: Unknown error"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user