Update package.json, fix linting issues

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