fix(kube): action namespace, race condition, stability, dark mode #86
@ -24,7 +24,7 @@ type ActiveModal =
|
||||
| { type: "delete"; ds: DaemonSetInfo }
|
||||
| null;
|
||||
|
||||
export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: DaemonSetListProps) {
|
||||
export function DaemonSetList({ daemonsets, clusterId, namespace: _namespace, onRefresh }: DaemonSetListProps) {
|
||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||
const [isActing, setIsActing] = useState(false);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
@ -32,7 +32,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
||||
const openEdit = async (ds: DaemonSetInfo) => {
|
||||
setActionError(null);
|
||||
try {
|
||||
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", namespace, ds.name);
|
||||
const yaml = await getResourceYamlCmd(clusterId, "daemonsets", ds.namespace, ds.name);
|
||||
setActiveModal({ type: "edit", ds, yaml });
|
||||
} catch (err) {
|
||||
setActionError(err instanceof Error ? err.message : String(err));
|
||||
@ -43,7 +43,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
||||
if (activeModal?.type !== "restart") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await restartDaemonsetCmd(clusterId, namespace, activeModal.ds.name);
|
||||
await restartDaemonsetCmd(clusterId, activeModal.ds.namespace, activeModal.ds.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} catch (err) {
|
||||
@ -57,7 +57,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
||||
if (activeModal?.type !== "delete") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await deleteResourceCmd(clusterId, "daemonsets", namespace, activeModal.ds.name);
|
||||
await deleteResourceCmd(clusterId, "daemonsets", activeModal.ds.namespace, activeModal.ds.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} finally {
|
||||
@ -146,7 +146,7 @@ export function DaemonSetList({ daemonsets, clusterId, namespace, onRefresh }: D
|
||||
<EditResourceModal
|
||||
isOpen
|
||||
clusterId={clusterId}
|
||||
namespace={namespace}
|
||||
namespace={activeModal.ds.namespace}
|
||||
resourceType="daemonsets"
|
||||
resourceName={activeModal.ds.name}
|
||||
initialYaml={activeModal.yaml}
|
||||
|
||||
@ -29,7 +29,7 @@ type ActiveModal =
|
||||
| { type: "delete"; deployment: DeploymentInfo }
|
||||
| null;
|
||||
|
||||
export function DeploymentList({ deployments, clusterId, namespace, onRefresh }: DeploymentListProps) {
|
||||
export function DeploymentList({ deployments, clusterId, namespace: _namespace, onRefresh }: DeploymentListProps) {
|
||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||
const [isActing, setIsActing] = useState(false);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
@ -37,7 +37,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
const openEdit = async (deployment: DeploymentInfo) => {
|
||||
setActionError(null);
|
||||
try {
|
||||
const yaml = await getResourceYamlCmd(clusterId, "deployments", namespace, deployment.name);
|
||||
const yaml = await getResourceYamlCmd(clusterId, "deployments", deployment.namespace, deployment.name);
|
||||
setActiveModal({ type: "edit", deployment, yaml });
|
||||
} catch (err) {
|
||||
setActionError(err instanceof Error ? err.message : String(err));
|
||||
@ -48,7 +48,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
if (activeModal?.type !== "restart") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await restartDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
|
||||
await restartDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} catch (err) {
|
||||
@ -62,7 +62,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
if (activeModal?.type !== "rollback") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await rollbackDeploymentCmd(clusterId, namespace, activeModal.deployment.name);
|
||||
await rollbackDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} catch (err) {
|
||||
@ -76,7 +76,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
if (activeModal?.type !== "delete") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await deleteResourceCmd(clusterId, "deployments", namespace, activeModal.deployment.name);
|
||||
await deleteResourceCmd(clusterId, "deployments", activeModal.deployment.namespace, activeModal.deployment.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} finally {
|
||||
@ -165,7 +165,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
resourceName={activeModal.deployment.name}
|
||||
currentReplicas={activeModal.deployment.replicas}
|
||||
onScale={(replicas) =>
|
||||
scaleDeploymentCmd(clusterId, namespace, activeModal.deployment.name, replicas).then(() => {
|
||||
scaleDeploymentCmd(clusterId, activeModal.deployment.namespace, activeModal.deployment.name, replicas).then(() => {
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
})
|
||||
@ -201,7 +201,7 @@ export function DeploymentList({ deployments, clusterId, namespace, onRefresh }:
|
||||
<EditResourceModal
|
||||
isOpen
|
||||
clusterId={clusterId}
|
||||
namespace={namespace}
|
||||
namespace={activeModal.deployment.namespace}
|
||||
resourceType="deployments"
|
||||
resourceName={activeModal.deployment.name}
|
||||
initialYaml={activeModal.yaml}
|
||||
|
||||
@ -27,7 +27,7 @@ type ActiveModal =
|
||||
| { type: "delete"; ss: StatefulSetInfo }
|
||||
| null;
|
||||
|
||||
export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh }: StatefulSetListProps) {
|
||||
export function StatefulSetList({ statefulsets, clusterId, namespace: _namespace, onRefresh }: StatefulSetListProps) {
|
||||
const [activeModal, setActiveModal] = useState<ActiveModal>(null);
|
||||
const [isActing, setIsActing] = useState(false);
|
||||
const [actionError, setActionError] = useState<string | null>(null);
|
||||
@ -35,7 +35,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
||||
const openEdit = async (ss: StatefulSetInfo) => {
|
||||
setActionError(null);
|
||||
try {
|
||||
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", namespace, ss.name);
|
||||
const yaml = await getResourceYamlCmd(clusterId, "statefulsets", ss.namespace, ss.name);
|
||||
setActiveModal({ type: "edit", ss, yaml });
|
||||
} catch (err) {
|
||||
setActionError(err instanceof Error ? err.message : String(err));
|
||||
@ -46,7 +46,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
||||
if (activeModal?.type !== "restart") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await restartStatefulsetCmd(clusterId, namespace, activeModal.ss.name);
|
||||
await restartStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} catch (err) {
|
||||
@ -60,7 +60,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
||||
if (activeModal?.type !== "delete") return;
|
||||
setIsActing(true);
|
||||
try {
|
||||
await deleteResourceCmd(clusterId, "statefulsets", namespace, activeModal.ss.name);
|
||||
await deleteResourceCmd(clusterId, "statefulsets", activeModal.ss.namespace, activeModal.ss.name);
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
} finally {
|
||||
@ -140,7 +140,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
||||
resourceName={activeModal.ss.name}
|
||||
currentReplicas={activeModal.ss.replicas}
|
||||
onScale={(replicas) =>
|
||||
scaleStatefulsetCmd(clusterId, namespace, activeModal.ss.name, replicas).then(() => {
|
||||
scaleStatefulsetCmd(clusterId, activeModal.ss.namespace, activeModal.ss.name, replicas).then(() => {
|
||||
setActiveModal(null);
|
||||
onRefresh?.();
|
||||
})
|
||||
@ -164,7 +164,7 @@ export function StatefulSetList({ statefulsets, clusterId, namespace, onRefresh
|
||||
<EditResourceModal
|
||||
isOpen
|
||||
clusterId={clusterId}
|
||||
namespace={namespace}
|
||||
namespace={activeModal.ss.namespace}
|
||||
resourceType="statefulsets"
|
||||
resourceName={activeModal.ss.name}
|
||||
initialYaml={activeModal.yaml}
|
||||
|
||||
695
tests/unit/WorkloadListActions.test.tsx
Normal file
695
tests/unit/WorkloadListActions.test.tsx
Normal file
@ -0,0 +1,695 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { DeploymentList } from "@/components/Kubernetes/DeploymentList";
|
||||
import { StatefulSetList } from "@/components/Kubernetes/StatefulSetList";
|
||||
import { DaemonSetList } from "@/components/Kubernetes/DaemonSetList";
|
||||
import { ReplicaSetList } from "@/components/Kubernetes/ReplicaSetList";
|
||||
import { JobList } from "@/components/Kubernetes/JobList";
|
||||
import { CronJobList } from "@/components/Kubernetes/CronJobList";
|
||||
import type {
|
||||
DeploymentInfo,
|
||||
StatefulSetInfo,
|
||||
DaemonSetInfo,
|
||||
ReplicaSetInfo,
|
||||
JobInfo,
|
||||
CronJobInfo,
|
||||
} from "@/lib/tauriCommands";
|
||||
|
||||
type MockedInvoke = typeof invoke & {
|
||||
mockResolvedValue: (v: unknown) => void;
|
||||
mockImplementation: (fn: (cmd: string) => Promise<unknown>) => void;
|
||||
};
|
||||
|
||||
const mockInvoke = invoke as MockedInvoke;
|
||||
|
||||
// Helper: open the action menu for the first Actions button, then click a menu item by label
|
||||
async function openMenuAndClick(label: string) {
|
||||
const btn = screen.getAllByRole("button", { name: /actions/i })[0];
|
||||
fireEvent.click(btn);
|
||||
const item = await screen.findByRole("button", { name: new RegExp(label, "i") });
|
||||
fireEvent.click(item);
|
||||
}
|
||||
|
||||
// ─── DeploymentList ──────────────────────────────────────────────────────────
|
||||
|
||||
describe("DeploymentList — actions use item.namespace not filter prop", () => {
|
||||
const deployment: DeploymentInfo = {
|
||||
name: "nginx",
|
||||
namespace: "kube-system",
|
||||
ready: "1/1",
|
||||
up_to_date: "1",
|
||||
available: "1",
|
||||
replicas: 1,
|
||||
age: "1d",
|
||||
labels: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<DeploymentList
|
||||
deployments={[deployment]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "deployments",
|
||||
namespace: "kube-system",
|
||||
resourceName: "nginx",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<DeploymentList
|
||||
deployments={[deployment]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "deployments",
|
||||
namespace: "kube-system",
|
||||
resourceName: "nginx",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("ScaleModal onScale calls scaleDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockResolvedValue(undefined);
|
||||
|
||||
render(
|
||||
<DeploymentList
|
||||
deployments={[deployment]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Scale");
|
||||
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||
fireEvent.click(scaleBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("scale_deployment", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
deploymentName: "nginx",
|
||||
replicas: expect.any(Number),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleRestart calls restartDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "restart_deployment") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<DeploymentList
|
||||
deployments={[deployment]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Restart");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("restart_deployment", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
deploymentName: "nginx",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleRollback calls rollbackDeploymentCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "rollback_deployment") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<DeploymentList
|
||||
deployments={[deployment]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Rollback");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|rollback/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("rollback_deployment", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
deploymentName: "nginx",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── StatefulSetList ─────────────────────────────────────────────────────────
|
||||
|
||||
describe("StatefulSetList — actions use item.namespace not filter prop", () => {
|
||||
const ss: StatefulSetInfo = {
|
||||
name: "postgres",
|
||||
namespace: "kube-system",
|
||||
ready: "1/1",
|
||||
replicas: 1,
|
||||
age: "2d",
|
||||
labels: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<StatefulSetList
|
||||
statefulsets={[ss]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "statefulsets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "postgres",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<StatefulSetList
|
||||
statefulsets={[ss]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "statefulsets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "postgres",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("ScaleModal onScale calls scaleStatefulsetCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockResolvedValue(undefined);
|
||||
|
||||
render(
|
||||
<StatefulSetList
|
||||
statefulsets={[ss]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Scale");
|
||||
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||
fireEvent.click(scaleBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("scale_statefulset", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "postgres",
|
||||
replicas: expect.any(Number),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleRestart calls restartStatefulsetCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "restart_statefulset") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<StatefulSetList
|
||||
statefulsets={[ss]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Restart");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("restart_statefulset", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "postgres",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── DaemonSetList ───────────────────────────────────────────────────────────
|
||||
|
||||
describe("DaemonSetList — actions use item.namespace not filter prop", () => {
|
||||
const ds: DaemonSetInfo = {
|
||||
name: "fluentd",
|
||||
namespace: "kube-system",
|
||||
desired: 3,
|
||||
current: 3,
|
||||
ready: 3,
|
||||
up_to_date: 3,
|
||||
available: 3,
|
||||
age: "5d",
|
||||
labels: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<DaemonSetList
|
||||
daemonsets={[ds]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "daemonsets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "fluentd",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<DaemonSetList
|
||||
daemonsets={[ds]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "daemonsets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "fluentd",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleRestart calls restartDaemonsetCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "restart_daemonset") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<DaemonSetList
|
||||
daemonsets={[ds]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Restart");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm|restart/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("restart_daemonset", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "fluentd",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── ReplicaSetList ──────────────────────────────────────────────────────────
|
||||
|
||||
describe("ReplicaSetList — actions use item.namespace not filter prop", () => {
|
||||
const rs: ReplicaSetInfo = {
|
||||
name: "nginx-abc12",
|
||||
namespace: "kube-system",
|
||||
replicas: 2,
|
||||
ready: "2",
|
||||
age: "3d",
|
||||
labels: { app: "nginx" },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: apps/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<ReplicaSetList
|
||||
replicaSets={[rs]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "replicasets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "nginx-abc12",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<ReplicaSetList
|
||||
replicaSets={[rs]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "replicasets",
|
||||
namespace: "kube-system",
|
||||
resourceName: "nginx-abc12",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("ScaleModal onScale calls scaleReplicasetCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockResolvedValue(undefined);
|
||||
|
||||
render(
|
||||
<ReplicaSetList
|
||||
replicaSets={[rs]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Scale");
|
||||
const scaleBtn = await screen.findByRole("button", { name: /scale/i });
|
||||
fireEvent.click(scaleBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("scale_replicaset", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "nginx-abc12",
|
||||
replicas: expect.any(Number),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── JobList ─────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("JobList — actions use item.namespace not filter prop", () => {
|
||||
const job: JobInfo = {
|
||||
name: "db-migrate",
|
||||
namespace: "kube-system",
|
||||
completions: "1/1",
|
||||
duration: "45s",
|
||||
age: "1d",
|
||||
labels: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<JobList
|
||||
jobs={[job]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "jobs",
|
||||
namespace: "kube-system",
|
||||
resourceName: "db-migrate",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<JobList
|
||||
jobs={[job]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "jobs",
|
||||
namespace: "kube-system",
|
||||
resourceName: "db-migrate",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ─── CronJobList ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe("CronJobList — actions use item.namespace not filter prop", () => {
|
||||
const cj: CronJobInfo = {
|
||||
name: "backup",
|
||||
namespace: "kube-system",
|
||||
schedule: "0 2 * * *",
|
||||
active: 0,
|
||||
last_schedule: "1h",
|
||||
age: "10d",
|
||||
labels: {},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockInvoke.mockResolvedValue("apiVersion: batch/v1");
|
||||
});
|
||||
|
||||
it("openEdit calls getResourceYamlCmd with item.namespace, not 'all'", async () => {
|
||||
render(
|
||||
<CronJobList
|
||||
cronJobs={[cj]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Edit");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("get_resource_yaml", {
|
||||
clusterId: "c1",
|
||||
resourceType: "cronjobs",
|
||||
namespace: "kube-system",
|
||||
resourceName: "backup",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleDelete calls deleteResourceCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "delete_resource") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<CronJobList
|
||||
cronJobs={[cj]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Delete");
|
||||
const confirmBtn = await screen.findByRole("button", { name: /delete|confirm/i });
|
||||
fireEvent.click(confirmBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("delete_resource", {
|
||||
clusterId: "c1",
|
||||
resourceType: "cronjobs",
|
||||
namespace: "kube-system",
|
||||
resourceName: "backup",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleSuspend calls suspendCronjobCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "suspend_cronjob") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<CronJobList
|
||||
cronJobs={[cj]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Suspend");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("suspend_cronjob", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "backup",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("handleTrigger calls triggerCronjobCmd with item.namespace, not 'all'", async () => {
|
||||
mockInvoke.mockImplementation(async (cmd: string) => {
|
||||
if (cmd === "trigger_cronjob") return undefined;
|
||||
return "yaml";
|
||||
});
|
||||
|
||||
render(
|
||||
<CronJobList
|
||||
cronJobs={[cj]}
|
||||
clusterId="c1"
|
||||
namespace="all"
|
||||
onRefresh={vi.fn()}
|
||||
/>
|
||||
);
|
||||
|
||||
await openMenuAndClick("Trigger");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith("trigger_cronjob", {
|
||||
clusterId: "c1",
|
||||
namespace: "kube-system",
|
||||
name: "backup",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user