/** * TDD tests: action IPC calls in network/config/storage/access-control list * components must use the item's own .namespace, never the filter prop (which * can be "all" when the user is viewing all namespaces). */ 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 { ServiceList } from "@/components/Kubernetes/ServiceList"; import { IngressList } from "@/components/Kubernetes/IngressList"; import { ConfigMapList } from "@/components/Kubernetes/ConfigMapList"; import { SecretList } from "@/components/Kubernetes/SecretList"; import { HPAList } from "@/components/Kubernetes/HPAList"; import { PVCList } from "@/components/Kubernetes/PVCList"; import { ServiceAccountList } from "@/components/Kubernetes/ServiceAccountList"; import { RoleList } from "@/components/Kubernetes/RoleList"; import { RoleBindingList } from "@/components/Kubernetes/RoleBindingList"; import { NetworkPolicyList } from "@/components/Kubernetes/NetworkPolicyList"; import { ResourceQuotaList } from "@/components/Kubernetes/ResourceQuotaList"; import { LimitRangeList } from "@/components/Kubernetes/LimitRangeList"; import type { ServiceInfo, IngressInfo, ConfigMapInfo, SecretInfo, HorizontalPodAutoscalerInfo, PersistentVolumeClaimInfo, ServiceAccountInfo, RoleInfo, RoleBindingInfo, NetworkPolicyInfo, ResourceQuotaInfo, LimitRangeInfo, } from "@/lib/tauriCommands"; type MockedInvoke = typeof invoke & { mockResolvedValue: (v: unknown) => void; mockImplementation: (fn: (cmd: string, args?: unknown) => Promise) => void; }; const mockInvoke = invoke as MockedInvoke; // ─── helpers ───────────────────────────────────────────────────────────────── /** Open the action menu for the first row whose name cell matches `name`. */ function openActionMenu(name: string) { const cell = screen.getByText(name); const row = cell.closest("tr")!; const btn = row.querySelector("button")!; fireEvent.click(btn); } /** Click the first menu item that contains `label`. */ function clickMenuItem(label: string) { const item = screen.getByText(label); fireEvent.click(item); } // ─── ServiceList ───────────────────────────────────────────────────────────── describe("ServiceList – action IPC uses item.namespace", () => { const svc: ServiceInfo = { name: "my-svc", namespace: "production", type: "ClusterIP", cluster_ip: "10.0.0.1", ports: [], age: "1d", selector: {}, }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("my-svc"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "services", namespace: "production", resourceName: "my-svc", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("my-svc"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "services", namespace: "production", resourceName: "my-svc", }) ); }); }); // ─── IngressList ───────────────────────────────────────────────────────────── describe("IngressList – action IPC uses item.namespace", () => { const ing: IngressInfo = { name: "my-ingress", namespace: "staging", host: "example.com", addresses: [], age: "2d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("my-ingress"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "ingresses", namespace: "staging", resourceName: "my-ingress", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("my-ingress"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "ingresses", namespace: "staging", resourceName: "my-ingress", }) ); }); }); // ─── ConfigMapList ──────────────────────────────────────────────────────────── describe("ConfigMapList – action IPC uses item.namespace", () => { const cm: ConfigMapInfo = { name: "app-config", namespace: "kube-system", data_keys: 3, age: "10d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("app-config"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "configmaps", namespace: "kube-system", resourceName: "app-config", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("app-config"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "configmaps", namespace: "kube-system", resourceName: "app-config", }) ); }); }); // ─── SecretList ─────────────────────────────────────────────────────────────── describe("SecretList – action IPC uses item.namespace", () => { const secret: SecretInfo = { name: "db-creds", namespace: "production", type: "Opaque", data_keys: 2, age: "5d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("db-creds"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "secrets", namespace: "production", resourceName: "db-creds", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("db-creds"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "secrets", namespace: "production", resourceName: "db-creds", }) ); }); }); // ─── HPAList ────────────────────────────────────────────────────────────────── describe("HPAList – action IPC uses item.namespace", () => { const hpa: HorizontalPodAutoscalerInfo = { name: "web-hpa", namespace: "default", min_replicas: 1, max_replicas: 10, current_replicas: 3, desired_replicas: 3, age: "7d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("web-hpa"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "horizontalpodautoscalers", namespace: "default", resourceName: "web-hpa", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("web-hpa"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "horizontalpodautoscalers", namespace: "default", resourceName: "web-hpa", }) ); }); }); // ─── PVCList ────────────────────────────────────────────────────────────────── describe("PVCList – action IPC uses item.namespace", () => { const pvc: PersistentVolumeClaimInfo = { name: "data-pvc", namespace: "staging", status: "Bound", volume: "pv-001", capacity: "10Gi", access_modes: ["ReadWriteOnce"], age: "3d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("data-pvc"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "persistentvolumeclaims", namespace: "staging", resourceName: "data-pvc", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("data-pvc"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "persistentvolumeclaims", namespace: "staging", resourceName: "data-pvc", }) ); }); }); // ─── ServiceAccountList ─────────────────────────────────────────────────────── describe("ServiceAccountList – action IPC uses item.namespace", () => { const sa: ServiceAccountInfo = { name: "app-sa", namespace: "production", secrets: 1, age: "30d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("app-sa"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "serviceaccounts", namespace: "production", resourceName: "app-sa", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("app-sa"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "serviceaccounts", namespace: "production", resourceName: "app-sa", }) ); }); }); // ─── RoleList ───────────────────────────────────────────────────────────────── describe("RoleList – action IPC uses item.namespace", () => { const role: RoleInfo = { name: "pod-reader", namespace: "default", age: "14d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("pod-reader"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "roles", namespace: "default", resourceName: "pod-reader", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("pod-reader"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "roles", namespace: "default", resourceName: "pod-reader", }) ); }); }); // ─── RoleBindingList ────────────────────────────────────────────────────────── describe("RoleBindingList – action IPC uses item.namespace", () => { const rb: RoleBindingInfo = { name: "pod-reader-binding", namespace: "default", role: "pod-reader", age: "10d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("pod-reader-binding"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "rolebindings", namespace: "default", resourceName: "pod-reader-binding", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("pod-reader-binding"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "rolebindings", namespace: "default", resourceName: "pod-reader-binding", }) ); }); }); // ─── NetworkPolicyList ──────────────────────────────────────────────────────── describe("NetworkPolicyList – action IPC uses item.namespace", () => { const np: NetworkPolicyInfo = { name: "deny-all", namespace: "production", pod_selector: "{}", policy_types: ["Ingress"], age: "3d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("deny-all"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "networkpolicies", namespace: "production", resourceName: "deny-all", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("deny-all"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "networkpolicies", namespace: "production", resourceName: "deny-all", }) ); }); }); // ─── ResourceQuotaList ──────────────────────────────────────────────────────── describe("ResourceQuotaList – action IPC uses item.namespace", () => { const rq: ResourceQuotaInfo = { name: "compute-resources", namespace: "default", request_cpu: "4", request_memory: "8Gi", limit_cpu: "8", limit_memory: "16Gi", age: "7d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("compute-resources"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "resourcequotas", namespace: "default", resourceName: "compute-resources", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("compute-resources"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "resourcequotas", namespace: "default", resourceName: "compute-resources", }) ); }); }); // ─── LimitRangeList ─────────────────────────────────────────────────────────── describe("LimitRangeList – action IPC uses item.namespace", () => { const lr: LimitRangeInfo = { name: "cpu-mem-limits", namespace: "default", limit_count: 3, age: "14d", }; beforeEach(() => vi.clearAllMocks()); it("getResourceYamlCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue("yaml: content"); render( ); openActionMenu("cpu-mem-limits"); clickMenuItem("Edit"); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("get_resource_yaml", { clusterId: "c1", resourceType: "limitranges", namespace: "default", resourceName: "cpu-mem-limits", }) ); }); it("deleteResourceCmd receives item.namespace, not filter prop 'all'", async () => { mockInvoke.mockResolvedValue(undefined); render( {}} /> ); openActionMenu("cpu-mem-limits"); clickMenuItem("Delete"); const confirmBtn = await screen.findByRole("button", { name: /confirm|delete/i }); fireEvent.click(confirmBtn); await waitFor(() => expect(mockInvoke).toHaveBeenCalledWith("delete_resource", { clusterId: "c1", resourceType: "limitranges", namespace: "default", resourceName: "cpu-mem-limits", }) ); }); });