314 lines
8.5 KiB
TypeScript
314 lines
8.5 KiB
TypeScript
|
|
import React from "react";
|
||
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||
|
|
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||
|
|
import { invoke } from "@tauri-apps/api/core";
|
||
|
|
import { VMList } from "@/components/Proxmox/VMList";
|
||
|
|
|
||
|
|
vi.mock("@tauri-apps/api/core");
|
||
|
|
vi.mock("sonner", () => ({
|
||
|
|
toast: {
|
||
|
|
success: vi.fn(),
|
||
|
|
error: vi.fn(),
|
||
|
|
info: vi.fn(),
|
||
|
|
},
|
||
|
|
Toaster: () => null,
|
||
|
|
}));
|
||
|
|
|
||
|
|
type MockedInvoke = typeof invoke & {
|
||
|
|
mockResolvedValue: (v: unknown) => void;
|
||
|
|
mockRejectedValue: (e: unknown) => void;
|
||
|
|
mockResolvedValueOnce: (v: unknown) => void;
|
||
|
|
};
|
||
|
|
|
||
|
|
const mockInvoke = invoke as MockedInvoke;
|
||
|
|
|
||
|
|
const stoppedVm = {
|
||
|
|
id: 101,
|
||
|
|
name: "nginx",
|
||
|
|
node: "vmhost2",
|
||
|
|
status: "stopped",
|
||
|
|
cpu: 0,
|
||
|
|
mem: 0,
|
||
|
|
max_mem: 2 * 1024 * 1024 * 1024,
|
||
|
|
disk: 0,
|
||
|
|
max_disk: 20 * 1024 * 1024 * 1024,
|
||
|
|
uptime: 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
const runningVm = {
|
||
|
|
id: 102,
|
||
|
|
name: "docker-01",
|
||
|
|
node: "vmhost2",
|
||
|
|
status: "running",
|
||
|
|
cpu: 0.35,
|
||
|
|
mem: 512 * 1024 * 1024,
|
||
|
|
max_mem: 2 * 1024 * 1024 * 1024,
|
||
|
|
disk: 0,
|
||
|
|
max_disk: 20 * 1024 * 1024 * 1024,
|
||
|
|
uptime: 3600,
|
||
|
|
};
|
||
|
|
|
||
|
|
const pausedVm = {
|
||
|
|
id: 103,
|
||
|
|
name: "test-vm",
|
||
|
|
node: "vmhost2",
|
||
|
|
status: "paused",
|
||
|
|
cpu: 0,
|
||
|
|
mem: 0,
|
||
|
|
max_mem: 1024 * 1024 * 1024,
|
||
|
|
disk: 0,
|
||
|
|
max_disk: 10 * 1024 * 1024 * 1024,
|
||
|
|
uptime: 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
const mockClusters = [{ id: "cluster-1", name: "TFTSR" }];
|
||
|
|
|
||
|
|
function renderVMList(vms = [stoppedVm], clusterId = "cluster-1") {
|
||
|
|
const onRefresh = vi.fn();
|
||
|
|
return {
|
||
|
|
onRefresh,
|
||
|
|
...render(
|
||
|
|
<VMList
|
||
|
|
vms={vms}
|
||
|
|
clusterId={clusterId}
|
||
|
|
clusters={mockClusters as never}
|
||
|
|
onRefresh={onRefresh}
|
||
|
|
/>
|
||
|
|
),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("VMList — column rendering", () => {
|
||
|
|
it("renders VM name, VMID, node, status, CPU, memory and uptime columns", () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
expect(screen.getByText("Name")).toBeDefined();
|
||
|
|
expect(screen.getByText("VM ID")).toBeDefined();
|
||
|
|
expect(screen.getByText("Node")).toBeDefined();
|
||
|
|
expect(screen.getByText("Status")).toBeDefined();
|
||
|
|
expect(screen.getByText("CPU")).toBeDefined();
|
||
|
|
expect(screen.getByText("Memory")).toBeDefined();
|
||
|
|
expect(screen.getByText("Uptime")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("does NOT render the Disk column", () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
expect(screen.queryByText("Disk")).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("displays VM name in the list", () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
expect(screen.getByText("nginx")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("displays the correct VMID", () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
expect(screen.getByText("101")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("displays status badge for stopped VM", () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
expect(screen.getByText("stopped")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("displays status badge for running VM", () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
expect(screen.getByText("running")).toBeDefined();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("VMList — action menu for stopped VM", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
mockInvoke.mockResolvedValue(undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("shows Start action for stopped VM", async () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
expect(screen.getByText("Start")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("does NOT show Stop/Reboot/Shutdown for stopped VM", async () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
expect(screen.queryByText("Stop")).toBeNull();
|
||
|
|
expect(screen.queryByText("Reboot")).toBeNull();
|
||
|
|
expect(screen.queryByText("Shutdown")).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls start_proxmox_vm when Start is clicked", async () => {
|
||
|
|
renderVMList([stoppedVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Start"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("start_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 101,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("VMList — action menu for running VM", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
mockInvoke.mockResolvedValue(undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("shows Stop, Reboot, Shutdown, Suspend actions for running VM", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
expect(screen.getByText("Stop")).toBeDefined();
|
||
|
|
expect(screen.getByText("Reboot")).toBeDefined();
|
||
|
|
expect(screen.getByText("Shutdown")).toBeDefined();
|
||
|
|
expect(screen.getByText("Suspend")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls stop_proxmox_vm when Stop is clicked", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Stop"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("stop_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 102,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls reboot_proxmox_vm when Reboot is clicked", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Reboot"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("reboot_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 102,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls shutdown_proxmox_vm when Shutdown is clicked", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Shutdown"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("shutdown_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 102,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls suspend_proxmox_vm when Suspend is clicked", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Suspend"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("suspend_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 102,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("VMList — action menu for paused VM", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
mockInvoke.mockResolvedValue(undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("shows Resume action for paused VM", async () => {
|
||
|
|
renderVMList([pausedVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
expect(screen.getByText("Resume")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("calls resume_proxmox_vm when Resume is clicked", async () => {
|
||
|
|
renderVMList([pausedVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Resume"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(mockInvoke).toHaveBeenCalledWith("resume_proxmox_vm", {
|
||
|
|
clusterId: "cluster-1",
|
||
|
|
nodeId: "vmhost2",
|
||
|
|
vmId: 103,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("VMList — migrate action", () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
mockInvoke.mockResolvedValue([
|
||
|
|
{ node: "vmhost1", status: "online" },
|
||
|
|
{ node: "vmhost3", status: "online" },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("shows Migrate option in action menu", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
expect(screen.getByText("Migrate")).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("opens migration dialog when Migrate is clicked", async () => {
|
||
|
|
renderVMList([runningVm]);
|
||
|
|
const menuBtn = screen.getAllByRole("button").find(
|
||
|
|
(b) => b.querySelector("svg")
|
||
|
|
);
|
||
|
|
fireEvent.click(menuBtn!);
|
||
|
|
fireEvent.click(screen.getByText("Migrate"));
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(screen.getByText(/Migrate docker-01/i)).toBeDefined();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("VMList — empty state", () => {
|
||
|
|
it("renders empty table body with no VMs", () => {
|
||
|
|
renderVMList([]);
|
||
|
|
expect(screen.getByText("Virtual Machines")).toBeDefined();
|
||
|
|
});
|
||
|
|
});
|