tftsr-devops_investigation/tests/unit/kubernetesCommands.test.ts
Shaun Arman ef3709ffe9
Some checks failed
PR Review Automation / review (pull_request) Has been cancelled
Test / frontend-tests (pull_request) Successful in 1m37s
Test / frontend-typecheck (pull_request) Successful in 1m46s
Test / rust-fmt-check (pull_request) Failing after 10m52s
Test / rust-clippy (pull_request) Successful in 12m34s
Test / rust-tests (pull_request) Successful in 14m8s
fix(kube): bridge kubeconfig storage to in-memory cluster map and fix UI issues
Resolves four bugs in the Kubernetes management interface:

1. **Cluster not found error** - commands/kube.rs::list_nodes (and all other
   kube resource commands) look up clusters from state.clusters (in-memory map)
   which was never populated from the kubeconfig_files table. Add a new
   connect_cluster_from_kubeconfig Tauri command that reads the encrypted
   kubeconfig from the DB, decrypts it, and inserts a ClusterClient into
   state.clusters. Wire it into KubernetesPage on initial load and cluster
   change so the in-memory map is always populated before any kube command runs.

2. **Dropdown selection has no effect** - same root cause as #1; activating a
   kubeconfig only updated the DB flag but never loaded the client into memory.
   handleClusterChange now calls connectClusterFromKubeconfigCmd after activation.

3. **GUID shown instead of cluster name** - ClusterOverview displayed the raw
   internal UUID as the page subtitle. Now accepts a clusterName prop (populated
   from kubeconfig.context) and renders that instead. ClusterDetails similarly
   changed to show kubeconfig.context in the header, not the UUID.

4. **Bell icon not clickable** - Hotbar bell button had no onClick handler. Add
   optional onNotifications / notificationCount props; badge count is now dynamic
   rather than hardcoded. KubernetesPage wires up a notifications dialog showing
   active cluster context and a link to the Events section.

All changes follow TDD: failing tests written first, then implementation.
2026-06-07 17:39:07 -05:00

171 lines
4.9 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { invoke } from "@tauri-apps/api/core";
import * as tauriCommands from "@/lib/tauriCommands";
// Mock Tauri invoke
vi.mock("@tauri-apps/api/core");
type MockedFunction<T = (...args: unknown[]) => unknown> = T & {
mockResolvedValue: (value: unknown) => void;
mockRejectedValue: (error: Error) => void;
};
describe("Kubernetes Management Commands", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("addClusterCmd", () => {
it("should call invoke with correct parameters", async () => {
(invoke as MockedFunction).mockResolvedValue({
id: "cluster-1",
name: "production",
context: "prod-context",
cluster_url: "https://prod.example.com",
});
const result = await tauriCommands.addClusterCmd(
"cluster-1",
"production",
"kubeconfig-content"
);
expect(invoke).toHaveBeenCalledWith("add_cluster", {
id: "cluster-1",
name: "production",
kubeconfig_content: "kubeconfig-content",
});
expect(result).toEqual({
id: "cluster-1",
name: "production",
context: "prod-context",
cluster_url: "https://prod.example.com",
});
});
});
describe("removeClusterCmd", () => {
it("should call invoke with cluster id", async () => {
(invoke as MockedFunction).mockResolvedValue(undefined);
await tauriCommands.removeClusterCmd("cluster-1");
expect(invoke).toHaveBeenCalledWith("remove_cluster", { id: "cluster-1" });
});
});
describe("listClustersCmd", () => {
it("should call invoke and return cluster list", async () => {
(invoke as MockedFunction).mockResolvedValue([
{
id: "cluster-1",
name: "production",
context: "prod-context",
cluster_url: "https://prod.example.com",
},
]);
const result = await tauriCommands.listClustersCmd();
expect(invoke).toHaveBeenCalledWith("list_clusters");
expect(result).toHaveLength(1);
expect(result[0].name).toBe("production");
});
});
describe("startPortForwardCmd", () => {
it("should call invoke with port forward request", async () => {
(invoke as MockedFunction).mockResolvedValue({
id: "pf-1",
cluster_id: "cluster-1",
namespace: "default",
pod: "nginx-abc123",
container_port: 80,
local_port: 8080,
status: "Active",
});
const request = {
cluster_id: "cluster-1",
namespace: "default",
pod: "nginx-abc123",
container_port: 80,
};
const result = await tauriCommands.startPortForwardCmd(request);
expect(invoke).toHaveBeenCalledWith("start_port_forward", { request });
expect(result).toEqual({
id: "pf-1",
cluster_id: "cluster-1",
namespace: "default",
pod: "nginx-abc123",
container_port: 80,
local_port: 8080,
status: "Active",
});
});
});
describe("stopPortForwardCmd", () => {
it("should call invoke with session id", async () => {
(invoke as MockedFunction).mockResolvedValue(undefined);
await tauriCommands.stopPortForwardCmd("pf-1");
expect(invoke).toHaveBeenCalledWith("stop_port_forward", { id: "pf-1" });
});
});
describe("listPortForwardsCmd", () => {
it("should call invoke and return port forwards list", async () => {
(invoke as MockedFunction).mockResolvedValue([
{
id: "pf-1",
cluster_id: "cluster-1",
namespace: "default",
pod: "nginx-abc123",
container_port: 80,
local_port: 8080,
status: "Active",
},
]);
const result = await tauriCommands.listPortForwardsCmd();
expect(invoke).toHaveBeenCalledWith("list_port_forwards");
expect(result).toHaveLength(1);
expect(result[0].pod).toBe("nginx-abc123");
});
});
describe("connectClusterFromKubeconfigCmd", () => {
it("should call invoke with kubeconfig id", async () => {
(invoke as MockedFunction).mockResolvedValue(undefined);
await tauriCommands.connectClusterFromKubeconfigCmd("kubeconfig-uuid-123");
expect(invoke).toHaveBeenCalledWith("connect_cluster_from_kubeconfig", {
id: "kubeconfig-uuid-123",
});
});
it("should resolve with void on success", async () => {
(invoke as MockedFunction).mockResolvedValue(undefined);
const result = await tauriCommands.connectClusterFromKubeconfigCmd("some-id");
expect(result).toBeUndefined();
});
it("should propagate errors from invoke", async () => {
(invoke as MockedFunction).mockRejectedValue(
new Error("Kubeconfig not found")
);
await expect(
tauriCommands.connectClusterFromKubeconfigCmd("bad-id")
).rejects.toThrow("Kubeconfig not found");
});
});
});