mirror of
https://github.com/trycua/computer.git
synced 2026-05-08 08:09:43 -05:00
Update package.json, fix linting issues
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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,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", () => {
|
||||||
|
|||||||
@@ -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"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user