Adds tests.
This commit is contained in:
750
internal/tools/kubectl_test.go
Normal file
750
internal/tools/kubectl_test.go
Normal file
@@ -0,0 +1,750 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewKubectl(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeconfigPath string
|
||||
}{
|
||||
{
|
||||
name: "creates Kubectl with kubeconfig path",
|
||||
kubeconfigPath: "/path/to/kubeconfig",
|
||||
},
|
||||
{
|
||||
name: "creates Kubectl with empty path",
|
||||
kubeconfigPath: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
k := NewKubectl(tt.kubeconfigPath)
|
||||
if k == nil {
|
||||
t.Fatal("NewKubectl() returned nil")
|
||||
}
|
||||
if k.kubeconfigPath != tt.kubeconfigPath {
|
||||
t.Errorf("kubeconfigPath = %q, want %q", k.kubeconfigPath, tt.kubeconfigPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlDeploymentExists(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
depName string
|
||||
namespace string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "check deployment exists",
|
||||
depName: "test-deployment",
|
||||
namespace: "default",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
exists := k.DeploymentExists(tt.depName, tt.namespace)
|
||||
_ = exists // Result depends on actual cluster state
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetPods(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
detailed bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get pods basic",
|
||||
namespace: "default",
|
||||
detailed: false,
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "get pods detailed",
|
||||
namespace: "kube-system",
|
||||
detailed: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
pods, err := k.GetPods(tt.namespace, tt.detailed)
|
||||
|
||||
if err == nil {
|
||||
if pods == nil {
|
||||
t.Error("GetPods() returned nil slice without error")
|
||||
}
|
||||
// Verify pod structure
|
||||
for i, pod := range pods {
|
||||
if pod.Name == "" {
|
||||
t.Errorf("pod[%d].Name is empty", i)
|
||||
}
|
||||
if pod.Status == "" {
|
||||
t.Errorf("pod[%d].Status is empty", i)
|
||||
}
|
||||
if pod.Ready == "" {
|
||||
t.Errorf("pod[%d].Ready is empty", i)
|
||||
}
|
||||
if pod.Age == "" {
|
||||
t.Errorf("pod[%d].Age is empty", i)
|
||||
}
|
||||
if tt.detailed && pod.Containers == nil {
|
||||
t.Errorf("pod[%d].Containers is nil in detailed mode", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetFirstPodName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get first pod name",
|
||||
namespace: "kube-system",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
podName, err := k.GetFirstPodName(tt.namespace)
|
||||
|
||||
if err == nil {
|
||||
if podName == "" {
|
||||
t.Error("GetFirstPodName() returned empty string without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetPodContainers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
podName string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get pod containers",
|
||||
namespace: "kube-system",
|
||||
podName: "coredns-123",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
containers, err := k.GetPodContainers(tt.namespace, tt.podName)
|
||||
|
||||
if err == nil {
|
||||
if containers == nil {
|
||||
t.Error("GetPodContainers() returned nil slice without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetDeployment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
depName string
|
||||
namespace string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get deployment info",
|
||||
depName: "test-deployment",
|
||||
namespace: "default",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
depInfo, err := k.GetDeployment(tt.depName, tt.namespace)
|
||||
|
||||
if err == nil {
|
||||
if depInfo == nil {
|
||||
t.Error("GetDeployment() returned nil without error")
|
||||
}
|
||||
// Desired should be non-negative
|
||||
if depInfo.Desired < 0 {
|
||||
t.Errorf("Desired = %d, should be non-negative", depInfo.Desired)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetReplicas(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get replicas for namespace",
|
||||
namespace: "default",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
replicaInfo, err := k.GetReplicas(tt.namespace)
|
||||
|
||||
if err == nil {
|
||||
if replicaInfo == nil {
|
||||
t.Error("GetReplicas() returned nil without error")
|
||||
}
|
||||
// All values should be non-negative
|
||||
if replicaInfo.Desired < 0 {
|
||||
t.Error("Desired < 0")
|
||||
}
|
||||
if replicaInfo.Current < 0 {
|
||||
t.Error("Current < 0")
|
||||
}
|
||||
if replicaInfo.Ready < 0 {
|
||||
t.Error("Ready < 0")
|
||||
}
|
||||
if replicaInfo.Available < 0 {
|
||||
t.Error("Available < 0")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetResources(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get resources for namespace",
|
||||
namespace: "default",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
usage, err := k.GetResources(tt.namespace)
|
||||
|
||||
if err == nil {
|
||||
if usage == nil {
|
||||
t.Error("GetResources() returned nil without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetRecentEvents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
limit int
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get recent events",
|
||||
namespace: "default",
|
||||
limit: 10,
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "get all events with zero limit",
|
||||
namespace: "default",
|
||||
limit: 0,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
events, err := k.GetRecentEvents(tt.namespace, tt.limit)
|
||||
|
||||
if err == nil {
|
||||
if events == nil {
|
||||
t.Error("GetRecentEvents() returned nil slice without error")
|
||||
}
|
||||
if tt.limit > 0 && len(events) > tt.limit {
|
||||
t.Errorf("len(events) = %d, want <= %d", len(events), tt.limit)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlGetLogs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
podName string
|
||||
opts LogOptions
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get logs with tail",
|
||||
namespace: "kube-system",
|
||||
podName: "coredns-123",
|
||||
opts: LogOptions{Tail: 100},
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "get logs with container",
|
||||
namespace: "kube-system",
|
||||
podName: "coredns-123",
|
||||
opts: LogOptions{Container: "coredns", Tail: 50},
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "get previous logs",
|
||||
namespace: "default",
|
||||
podName: "test-pod",
|
||||
opts: LogOptions{Previous: true, Tail: 100},
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
logs, err := k.GetLogs(tt.namespace, tt.podName, tt.opts)
|
||||
|
||||
if err == nil {
|
||||
if logs == nil {
|
||||
t.Error("GetLogs() returned nil slice without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubectlStreamLogs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
podName string
|
||||
opts LogOptions
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "stream logs",
|
||||
namespace: "default",
|
||||
podName: "test-pod",
|
||||
opts: LogOptions{Tail: 10},
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires kubectl and running cluster")
|
||||
}
|
||||
|
||||
k := NewKubectl("")
|
||||
cmd, err := k.StreamLogs(tt.namespace, tt.podName, tt.opts)
|
||||
|
||||
if err == nil {
|
||||
if cmd == nil {
|
||||
t.Error("StreamLogs() returned nil command without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatAge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
duration time.Duration
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "seconds",
|
||||
duration: 45 * time.Second,
|
||||
want: "45s",
|
||||
},
|
||||
{
|
||||
name: "minutes",
|
||||
duration: 5 * time.Minute,
|
||||
want: "5m",
|
||||
},
|
||||
{
|
||||
name: "hours",
|
||||
duration: 3 * time.Hour,
|
||||
want: "3h",
|
||||
},
|
||||
{
|
||||
name: "days",
|
||||
duration: 48 * time.Hour,
|
||||
want: "2d",
|
||||
},
|
||||
{
|
||||
name: "less than minute",
|
||||
duration: 30 * time.Second,
|
||||
want: "30s",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatAge(tt.duration)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatAge(%v) = %q, want %q", tt.duration, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseResourceQuantity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
quantity string
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "millicores",
|
||||
quantity: "500m",
|
||||
want: 500,
|
||||
},
|
||||
{
|
||||
name: "cores as plain number",
|
||||
quantity: "2",
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "Ki suffix",
|
||||
quantity: "100Ki",
|
||||
want: 100 * 1024,
|
||||
},
|
||||
{
|
||||
name: "Mi suffix",
|
||||
quantity: "512Mi",
|
||||
want: 512 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
name: "Gi suffix",
|
||||
quantity: "2Gi",
|
||||
want: 2 * 1024 * 1024 * 1024,
|
||||
},
|
||||
{
|
||||
name: "K suffix",
|
||||
quantity: "100K",
|
||||
want: 100 * 1000,
|
||||
},
|
||||
{
|
||||
name: "M suffix",
|
||||
quantity: "500M",
|
||||
want: 500 * 1000 * 1000,
|
||||
},
|
||||
{
|
||||
name: "G suffix",
|
||||
quantity: "1G",
|
||||
want: 1 * 1000 * 1000 * 1000,
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
quantity: "",
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "whitespace",
|
||||
quantity: " ",
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseResourceQuantity(tt.quantity)
|
||||
if got != tt.want {
|
||||
t.Errorf("parseResourceQuantity(%q) = %d, want %d", tt.quantity, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatCPU(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
millicores int64
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
millicores: 0,
|
||||
want: "0",
|
||||
},
|
||||
{
|
||||
name: "millicores",
|
||||
millicores: 500,
|
||||
want: "500m",
|
||||
},
|
||||
{
|
||||
name: "one core",
|
||||
millicores: 1000,
|
||||
want: "1.0",
|
||||
},
|
||||
{
|
||||
name: "two and half cores",
|
||||
millicores: 2500,
|
||||
want: "2.5",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatCPU(tt.millicores)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatCPU(%d) = %q, want %q", tt.millicores, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatMemory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bytes int64
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
bytes: 0,
|
||||
want: "0",
|
||||
},
|
||||
{
|
||||
name: "bytes",
|
||||
bytes: 512,
|
||||
want: "512B",
|
||||
},
|
||||
{
|
||||
name: "kibibytes",
|
||||
bytes: 1024,
|
||||
want: "1.0Ki",
|
||||
},
|
||||
{
|
||||
name: "mebibytes",
|
||||
bytes: 1024 * 1024,
|
||||
want: "1.0Mi",
|
||||
},
|
||||
{
|
||||
name: "gibibytes",
|
||||
bytes: 2 * 1024 * 1024 * 1024,
|
||||
want: "2.0Gi",
|
||||
},
|
||||
{
|
||||
name: "tebibytes",
|
||||
bytes: 1024 * 1024 * 1024 * 1024,
|
||||
want: "1.0Ti",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := formatMemory(tt.bytes)
|
||||
if got != tt.want {
|
||||
t.Errorf("formatMemory(%d) = %q, want %q", tt.bytes, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodInfoStruct(t *testing.T) {
|
||||
t.Run("PodInfo has required fields", func(t *testing.T) {
|
||||
pod := PodInfo{
|
||||
Name: "test-pod",
|
||||
Status: "Running",
|
||||
Ready: "1/1",
|
||||
Restarts: 0,
|
||||
Age: "5m",
|
||||
Node: "node-1",
|
||||
IP: "10.0.0.1",
|
||||
}
|
||||
|
||||
if pod.Name != "test-pod" {
|
||||
t.Errorf("Name = %q, want %q", pod.Name, "test-pod")
|
||||
}
|
||||
if pod.Status != "Running" {
|
||||
t.Errorf("Status = %q, want %q", pod.Status, "Running")
|
||||
}
|
||||
if pod.Ready != "1/1" {
|
||||
t.Errorf("Ready = %q, want %q", pod.Ready, "1/1")
|
||||
}
|
||||
if pod.Restarts != 0 {
|
||||
t.Errorf("Restarts = %d, want %d", pod.Restarts, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestContainerInfoStruct(t *testing.T) {
|
||||
t.Run("ContainerInfo has required fields", func(t *testing.T) {
|
||||
container := ContainerInfo{
|
||||
Name: "test-container",
|
||||
Image: "nginx:latest",
|
||||
Ready: true,
|
||||
RestartCount: 0,
|
||||
State: ContainerState{
|
||||
Status: "running",
|
||||
Since: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
if container.Name != "test-container" {
|
||||
t.Errorf("Name = %q, want %q", container.Name, "test-container")
|
||||
}
|
||||
if !container.Ready {
|
||||
t.Error("Ready should be true")
|
||||
}
|
||||
if container.State.Status != "running" {
|
||||
t.Errorf("State.Status = %q, want %q", container.State.Status, "running")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeploymentInfoStruct(t *testing.T) {
|
||||
t.Run("DeploymentInfo has required fields", func(t *testing.T) {
|
||||
dep := DeploymentInfo{
|
||||
Desired: 3,
|
||||
Current: 3,
|
||||
Ready: 3,
|
||||
Available: 3,
|
||||
}
|
||||
|
||||
if dep.Desired != 3 {
|
||||
t.Errorf("Desired = %d, want %d", dep.Desired, 3)
|
||||
}
|
||||
if dep.Current != 3 {
|
||||
t.Errorf("Current = %d, want %d", dep.Current, 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestResourceMetricStruct(t *testing.T) {
|
||||
t.Run("ResourceMetric has required fields", func(t *testing.T) {
|
||||
metric := ResourceMetric{
|
||||
Used: "1.5",
|
||||
Requested: "2.0",
|
||||
Limit: "4.0",
|
||||
Percentage: 37.5,
|
||||
}
|
||||
|
||||
if metric.Used != "1.5" {
|
||||
t.Errorf("Used = %q, want %q", metric.Used, "1.5")
|
||||
}
|
||||
if metric.Percentage != 37.5 {
|
||||
t.Errorf("Percentage = %f, want %f", metric.Percentage, 37.5)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogOptionsStruct(t *testing.T) {
|
||||
t.Run("LogOptions has all option fields", func(t *testing.T) {
|
||||
opts := LogOptions{
|
||||
Container: "nginx",
|
||||
Tail: 100,
|
||||
Previous: true,
|
||||
Since: "5m",
|
||||
SinceSeconds: 300,
|
||||
}
|
||||
|
||||
if opts.Container != "nginx" {
|
||||
t.Errorf("Container = %q, want %q", opts.Container, "nginx")
|
||||
}
|
||||
if opts.Tail != 100 {
|
||||
t.Errorf("Tail = %d, want %d", opts.Tail, 100)
|
||||
}
|
||||
if !opts.Previous {
|
||||
t.Error("Previous should be true")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestKubernetesEventStruct(t *testing.T) {
|
||||
t.Run("KubernetesEvent has required fields", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
event := KubernetesEvent{
|
||||
Type: "Warning",
|
||||
Reason: "BackOff",
|
||||
Message: "Back-off restarting failed container",
|
||||
Count: 5,
|
||||
FirstSeen: now.Add(-5 * time.Minute),
|
||||
LastSeen: now,
|
||||
Object: "Pod/test-pod",
|
||||
}
|
||||
|
||||
if event.Type != "Warning" {
|
||||
t.Errorf("Type = %q, want %q", event.Type, "Warning")
|
||||
}
|
||||
if event.Count != 5 {
|
||||
t.Errorf("Count = %d, want %d", event.Count, 5)
|
||||
}
|
||||
})
|
||||
}
|
||||
558
internal/tools/talosctl_test.go
Normal file
558
internal/tools/talosctl_test.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewTalosctl(t *testing.T) {
|
||||
t.Run("creates Talosctl instance without config", func(t *testing.T) {
|
||||
tc := NewTalosctl()
|
||||
if tc == nil {
|
||||
t.Fatal("NewTalosctl() returned nil")
|
||||
}
|
||||
if tc.talosconfigPath != "" {
|
||||
t.Error("talosconfigPath should be empty for NewTalosctl()")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("creates Talosctl instance with config", func(t *testing.T) {
|
||||
configPath := "/path/to/talosconfig"
|
||||
tc := NewTalosconfigWithConfig(configPath)
|
||||
if tc == nil {
|
||||
t.Fatal("NewTalosconfigWithConfig() returned nil")
|
||||
}
|
||||
if tc.talosconfigPath != configPath {
|
||||
t.Errorf("talosconfigPath = %q, want %q", tc.talosconfigPath, configPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTalosconfigBuildArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
talosconfigPath string
|
||||
baseArgs []string
|
||||
wantPrefix []string
|
||||
}{
|
||||
{
|
||||
name: "no talosconfig adds no prefix",
|
||||
talosconfigPath: "",
|
||||
baseArgs: []string{"version", "--short"},
|
||||
wantPrefix: nil,
|
||||
},
|
||||
{
|
||||
name: "with talosconfig adds prefix",
|
||||
talosconfigPath: "/path/to/talosconfig",
|
||||
baseArgs: []string{"version", "--short"},
|
||||
wantPrefix: []string{"--talosconfig", "/path/to/talosconfig"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tc := &Talosctl{talosconfigPath: tt.talosconfigPath}
|
||||
got := tc.buildArgs(tt.baseArgs)
|
||||
|
||||
if tt.wantPrefix == nil {
|
||||
// Should return baseArgs unchanged
|
||||
if len(got) != len(tt.baseArgs) {
|
||||
t.Errorf("buildArgs() length = %d, want %d", len(got), len(tt.baseArgs))
|
||||
}
|
||||
for i, arg := range tt.baseArgs {
|
||||
if i >= len(got) || got[i] != arg {
|
||||
t.Errorf("buildArgs()[%d] = %q, want %q", i, got[i], arg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Should have prefix + baseArgs
|
||||
expectedLen := len(tt.wantPrefix) + len(tt.baseArgs)
|
||||
if len(got) != expectedLen {
|
||||
t.Errorf("buildArgs() length = %d, want %d", len(got), expectedLen)
|
||||
}
|
||||
// Check prefix
|
||||
for i, arg := range tt.wantPrefix {
|
||||
if i >= len(got) || got[i] != arg {
|
||||
t.Errorf("buildArgs() prefix[%d] = %q, want %q", i, got[i], arg)
|
||||
}
|
||||
}
|
||||
// Check baseArgs follow prefix
|
||||
for i, arg := range tt.baseArgs {
|
||||
idx := len(tt.wantPrefix) + i
|
||||
if idx >= len(got) || got[idx] != arg {
|
||||
t.Errorf("buildArgs()[%d] = %q, want %q", idx, got[idx], arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGenConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
clusterName string
|
||||
endpoint string
|
||||
outputDir string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "gen config with valid params",
|
||||
clusterName: "test-cluster",
|
||||
endpoint: "https://192.168.1.100:6443",
|
||||
outputDir: "testdata",
|
||||
skipTest: true, // Skip actual execution
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
tc := NewTalosctl()
|
||||
err := tc.GenConfig(tt.clusterName, tt.endpoint, tmpDir)
|
||||
|
||||
// This will fail without talosctl, but tests the method signature
|
||||
if err == nil {
|
||||
// If it somehow succeeds, verify files were created
|
||||
expectedFiles := []string{
|
||||
"controlplane.yaml",
|
||||
"worker.yaml",
|
||||
"talosconfig",
|
||||
}
|
||||
for _, file := range expectedFiles {
|
||||
path := filepath.Join(tmpDir, file)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
t.Errorf("Expected file not created: %s", file)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigApplyConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
configFile string
|
||||
insecure bool
|
||||
talosconfigPath string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "apply config with all params",
|
||||
nodeIP: "192.168.1.100",
|
||||
configFile: "/path/to/config.yaml",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "apply config with talosconfig",
|
||||
nodeIP: "192.168.1.100",
|
||||
configFile: "/path/to/config.yaml",
|
||||
insecure: false,
|
||||
talosconfigPath: "/path/to/talosconfig",
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
err := tc.ApplyConfig(tt.nodeIP, tt.configFile, tt.insecure, tt.talosconfigPath)
|
||||
|
||||
// Will fail without talosctl, but tests method signature
|
||||
_ = err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetDisks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get disks in insecure mode",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
disks, err := tc.GetDisks(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
// If successful, verify return type
|
||||
if disks == nil {
|
||||
t.Error("GetDisks() returned nil slice without error")
|
||||
}
|
||||
// Each disk should have path and size
|
||||
for i, disk := range disks {
|
||||
if disk.Path == "" {
|
||||
t.Errorf("disk[%d].Path is empty", i)
|
||||
}
|
||||
if disk.Size <= 0 {
|
||||
t.Errorf("disk[%d].Size = %d, want > 0", i, disk.Size)
|
||||
}
|
||||
// Size should be > 10GB per filtering
|
||||
if disk.Size <= 10000000000 {
|
||||
t.Errorf("disk[%d].Size = %d, should be filtered (> 10GB)", i, disk.Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetLinks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get links in insecure mode",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
links, err := tc.GetLinks(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
if links == nil {
|
||||
t.Error("GetLinks() returned nil slice without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetRoutes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get routes in insecure mode",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
routes, err := tc.GetRoutes(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
if routes == nil {
|
||||
t.Error("GetRoutes() returned nil slice without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetDefaultInterface(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get default interface",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
iface, err := tc.GetDefaultInterface(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
if iface == "" {
|
||||
t.Error("GetDefaultInterface() returned empty string without error")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetPhysicalInterface(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get physical interface",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
iface, err := tc.GetPhysicalInterface(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
if iface == "" {
|
||||
t.Error("GetPhysicalInterface() returned empty string without error")
|
||||
}
|
||||
// Should not be loopback
|
||||
if iface == "lo" {
|
||||
t.Error("GetPhysicalInterface() returned loopback interface")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigGetVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nodeIP string
|
||||
insecure bool
|
||||
want string // Expected for maintenance mode or version string
|
||||
skipTest bool
|
||||
}{
|
||||
{
|
||||
name: "get version in insecure mode",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: true,
|
||||
skipTest: true,
|
||||
},
|
||||
{
|
||||
name: "get version in secure mode",
|
||||
nodeIP: "192.168.1.100",
|
||||
insecure: false,
|
||||
skipTest: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.skipTest {
|
||||
t.Skip("Skipping test that requires talosctl binary and running node")
|
||||
}
|
||||
|
||||
tc := NewTalosctl()
|
||||
version, err := tc.GetVersion(tt.nodeIP, tt.insecure)
|
||||
|
||||
if err == nil {
|
||||
if version == "" {
|
||||
t.Error("GetVersion() returned empty string without error")
|
||||
}
|
||||
// Version should be either "maintenance" or start with "v"
|
||||
if version != "maintenance" && version[0] != 'v' {
|
||||
t.Errorf("GetVersion() = %q, expected 'maintenance' or version starting with 'v'", version)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTalosconfigValidate(t *testing.T) {
|
||||
t.Run("validate checks for talosctl", func(t *testing.T) {
|
||||
tc := NewTalosctl()
|
||||
err := tc.Validate()
|
||||
|
||||
// This will pass if talosctl is installed, fail otherwise
|
||||
// We can't guarantee talosctl is installed in all test environments
|
||||
_ = err
|
||||
})
|
||||
}
|
||||
|
||||
func TestDiskInfoStruct(t *testing.T) {
|
||||
t.Run("DiskInfo has required fields", func(t *testing.T) {
|
||||
disk := DiskInfo{
|
||||
Path: "/dev/sda",
|
||||
Size: 1000000000000, // 1TB
|
||||
}
|
||||
|
||||
if disk.Path != "/dev/sda" {
|
||||
t.Errorf("Path = %q, want %q", disk.Path, "/dev/sda")
|
||||
}
|
||||
if disk.Size != 1000000000000 {
|
||||
t.Errorf("Size = %d, want %d", disk.Size, 1000000000000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTalosconfigResourceJSONParsing(t *testing.T) {
|
||||
// This test verifies the logic of getResourceJSON without actually calling talosctl
|
||||
t.Run("getResourceJSON uses correct command structure", func(t *testing.T) {
|
||||
tc := &Talosctl{talosconfigPath: "/path/to/talosconfig"}
|
||||
|
||||
// We can't easily test the actual command execution without mocking,
|
||||
// but we can verify buildArgs works correctly
|
||||
baseArgs := []string{"get", "disks", "--nodes", "192.168.1.100", "-o", "json"}
|
||||
finalArgs := tc.buildArgs(baseArgs)
|
||||
|
||||
// Should have talosconfig prepended
|
||||
if len(finalArgs) < 2 || finalArgs[0] != "--talosconfig" {
|
||||
t.Error("buildArgs() should prepend --talosconfig")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestTalosconfigInterfaceFiltering(t *testing.T) {
|
||||
// Test the logic for filtering physical interfaces
|
||||
tests := []struct {
|
||||
name string
|
||||
interfaceName string
|
||||
linkType string
|
||||
operState string
|
||||
shouldAccept bool
|
||||
}{
|
||||
{
|
||||
name: "eth0 up and ethernet",
|
||||
interfaceName: "eth0",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: true,
|
||||
},
|
||||
{
|
||||
name: "eno1 up and ethernet",
|
||||
interfaceName: "eno1",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: true,
|
||||
},
|
||||
{
|
||||
name: "loopback should be filtered",
|
||||
interfaceName: "lo",
|
||||
linkType: "loopback",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
{
|
||||
name: "cni interface should be filtered",
|
||||
interfaceName: "cni0",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
{
|
||||
name: "flannel interface should be filtered",
|
||||
interfaceName: "flannel.1",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
{
|
||||
name: "docker interface should be filtered",
|
||||
interfaceName: "docker0",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
{
|
||||
name: "bridge interface should be filtered",
|
||||
interfaceName: "br-1234",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
{
|
||||
name: "veth interface should be filtered",
|
||||
interfaceName: "veth123",
|
||||
linkType: "ether",
|
||||
operState: "up",
|
||||
shouldAccept: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// This simulates the filtering logic in GetPhysicalInterface
|
||||
id := tt.interfaceName
|
||||
linkType := tt.linkType
|
||||
operState := tt.operState
|
||||
|
||||
shouldAccept := (linkType == "ether" && operState == "up" &&
|
||||
id != "lo" &&
|
||||
(id[:3] == "eth" || id[:2] == "en") &&
|
||||
!containsAny(id, []string{"cni", "flannel", "docker", "br-", "veth"}))
|
||||
|
||||
if shouldAccept != tt.shouldAccept {
|
||||
t.Errorf("Interface %q filtering = %v, want %v", id, shouldAccept, tt.shouldAccept)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function for interface filtering test
|
||||
func containsAny(s string, substrs []string) bool {
|
||||
for _, substr := range substrs {
|
||||
if len(substr) > 0 {
|
||||
if substr[len(substr)-1] == '-' {
|
||||
// Prefix match for things like "br-"
|
||||
if len(s) >= len(substr) && s[:len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// Contains match
|
||||
if len(s) >= len(substr) {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
469
internal/tools/yq_test.go
Normal file
469
internal/tools/yq_test.go
Normal file
@@ -0,0 +1,469 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewYQ(t *testing.T) {
|
||||
t.Run("creates YQ instance with default path", func(t *testing.T) {
|
||||
yq := NewYQ()
|
||||
if yq == nil {
|
||||
t.Fatal("NewYQ() returned nil")
|
||||
}
|
||||
if yq.yqPath == "" {
|
||||
t.Error("yqPath should not be empty")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestYQGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) (string, string)
|
||||
expression string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "get simple value",
|
||||
setup: func(tmpDir string) (string, string) {
|
||||
yamlContent := `name: test
|
||||
version: "1.0"
|
||||
`
|
||||
filePath := filepath.Join(tmpDir, "test.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath, ".name"
|
||||
},
|
||||
want: "test",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "get nested value",
|
||||
setup: func(tmpDir string) (string, string) {
|
||||
yamlContent := `person:
|
||||
name: John
|
||||
age: 30
|
||||
`
|
||||
filePath := filepath.Join(tmpDir, "nested.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath, ".person.name"
|
||||
},
|
||||
want: "John",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "non-existent file returns error",
|
||||
setup: func(tmpDir string) (string, string) {
|
||||
return filepath.Join(tmpDir, "nonexistent.yaml"), ".name"
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filePath, expression := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
got, err := yq.Get(filePath, expression)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr && got != tt.want {
|
||||
t.Errorf("Get() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYQSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) string
|
||||
expression string
|
||||
value string
|
||||
verify func(t *testing.T, filePath string)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "set simple value",
|
||||
setup: func(tmpDir string) string {
|
||||
yamlContent := `name: old`
|
||||
filePath := filepath.Join(tmpDir, "test.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
expression: ".name",
|
||||
value: "new",
|
||||
verify: func(t *testing.T, filePath string) {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(content), "new") {
|
||||
t.Errorf("File does not contain expected value 'new': %s", content)
|
||||
}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "set value with special characters",
|
||||
setup: func(tmpDir string) string {
|
||||
yamlContent := `message: hello`
|
||||
filePath := filepath.Join(tmpDir, "special.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
expression: ".message",
|
||||
value: `hello "world"`,
|
||||
verify: func(t *testing.T, filePath string) {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Should contain escaped quotes
|
||||
if !strings.Contains(string(content), "hello") {
|
||||
t.Errorf("File does not contain expected value: %s", content)
|
||||
}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "expression without leading dot gets dot prepended",
|
||||
setup: func(tmpDir string) string {
|
||||
yamlContent := `key: value`
|
||||
filePath := filepath.Join(tmpDir, "nodot.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
expression: "key",
|
||||
value: "newvalue",
|
||||
verify: func(t *testing.T, filePath string) {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(content), "newvalue") {
|
||||
t.Errorf("File does not contain expected value: %s", content)
|
||||
}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filePath := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
err := yq.Set(filePath, tt.expression, tt.value)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Set() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr && tt.verify != nil {
|
||||
tt.verify(t, filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYQDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) string
|
||||
expression string
|
||||
verify func(t *testing.T, filePath string)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "delete simple key",
|
||||
setup: func(tmpDir string) string {
|
||||
yamlContent := `name: test
|
||||
version: "1.0"
|
||||
`
|
||||
filePath := filepath.Join(tmpDir, "delete.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
expression: ".name",
|
||||
verify: func(t *testing.T, filePath string) {
|
||||
content, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(content), "name:") {
|
||||
t.Errorf("Key 'name' was not deleted: %s", content)
|
||||
}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filePath := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
err := yq.Delete(filePath, tt.expression)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr && tt.verify != nil {
|
||||
tt.verify(t, filePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYQValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid YAML",
|
||||
setup: func(tmpDir string) string {
|
||||
yamlContent := `name: test
|
||||
version: "1.0"
|
||||
nested:
|
||||
key: value
|
||||
`
|
||||
filePath := filepath.Join(tmpDir, "valid.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid YAML",
|
||||
setup: func(tmpDir string) string {
|
||||
invalidYaml := `name: test
|
||||
invalid indentation
|
||||
version: "1.0"
|
||||
`
|
||||
filePath := filepath.Join(tmpDir, "invalid.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(invalidYaml), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "non-existent file",
|
||||
setup: func(tmpDir string) string {
|
||||
return filepath.Join(tmpDir, "nonexistent.yaml")
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
filePath := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
err := yq.Validate(filePath)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYQExec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) (string, []string)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "exec with valid args",
|
||||
setup: func(tmpDir string) (string, []string) {
|
||||
yamlContent := `name: test`
|
||||
filePath := filepath.Join(tmpDir, "exec.yaml")
|
||||
if err := os.WriteFile(filePath, []byte(yamlContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return filePath, []string{"eval", ".name", filePath}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
_, args := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
output, err := yq.Exec(args...)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Exec() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr && len(output) == 0 {
|
||||
t.Error("Exec() returned empty output")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanYQOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "removes trailing newline",
|
||||
input: "value\n",
|
||||
want: "value",
|
||||
},
|
||||
{
|
||||
name: "converts null to empty string",
|
||||
input: "null",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "removes whitespace",
|
||||
input: " value \n",
|
||||
want: "value",
|
||||
},
|
||||
{
|
||||
name: "handles empty string",
|
||||
input: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "handles multiple newlines",
|
||||
input: "value\n\n",
|
||||
want: "value",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CleanYQOutput(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("CleanYQOutput(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestYQMerge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(tmpDir string) (string, string, string)
|
||||
verify func(t *testing.T, outputPath string)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "merge two files",
|
||||
setup: func(tmpDir string) (string, string, string) {
|
||||
file1 := filepath.Join(tmpDir, "file1.yaml")
|
||||
file2 := filepath.Join(tmpDir, "file2.yaml")
|
||||
output := filepath.Join(tmpDir, "output.yaml")
|
||||
|
||||
if err := os.WriteFile(file1, []byte("key1: value1\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(file2, []byte("key2: value2\n"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return file1, file2, output
|
||||
},
|
||||
verify: func(t *testing.T, outputPath string) {
|
||||
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
|
||||
t.Error("Output file was not created")
|
||||
}
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Skip if yq is not available
|
||||
if _, err := os.Stat("/usr/bin/yq"); os.IsNotExist(err) {
|
||||
t.Skip("yq not installed, skipping test")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
file1, file2, output := tt.setup(tmpDir)
|
||||
|
||||
yq := NewYQ()
|
||||
err := yq.Merge(file1, file2, output)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Merge() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr && tt.verify != nil {
|
||||
tt.verify(t, output)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user