fix(kube): workload list actions use item.namespace not filter prop
Some checks failed
PR Review Automation / review (pull_request) Has been cancelled
Test / rust-fmt-check (pull_request) Has been cancelled
Test / rust-clippy (pull_request) Has been cancelled
Test / frontend-typecheck (pull_request) Has been cancelled
Test / rust-tests (pull_request) Has been cancelled
Test / frontend-tests (pull_request) Has been cancelled

Deployment/StatefulSet/DaemonSet action handlers were passing
namespace='all' to kubectl when All Namespaces was selected.
Actions now use the resource's own .namespace field for openEdit,
handleRestart, handleRollback, handleDelete, ScaleModal, and
EditResourceModal.

Adds 21 TDD tests in WorkloadListActions.test.tsx covering all
action handlers across DeploymentList, StatefulSetList, DaemonSetList,
ReplicaSetList, JobList, and CronJobList. Tests verify IPC calls
receive the item's actual namespace even when the filter prop is 'all'.
This commit is contained in:
Shaun Arman 2026-06-08 22:02:00 -05:00
parent 05d8b28159
commit 7dfda91cd8
4 changed files with 713 additions and 18 deletions

View File

@ -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}

View File

@ -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}

View File

@ -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}

View 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",
});
});
});
});