From 3c7f905ec5b935259a64f1922945d7ae7887f52c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:57:30 +0000 Subject: [PATCH 1/2] Initial plan From fccab3607e6b3d6788bc031cc399f289a50b815c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:05:49 +0000 Subject: [PATCH 2/2] Add comprehensive unit tests for kubespy components Co-authored-by: mjeffryes <1189194+mjeffryes@users.noreply.github.com> --- cmd/root_test.go | 104 +++++++++++++ k8sconfig/k8sconfig_test.go | 37 +++++ k8sobject/k8sobject_test.go | 262 ++++++++++++++++++++++++++++++++ pods/pods_test.go | 291 ++++++++++++++++++++++++++++++++++++ print/print_test.go | 126 ++++++++++++++++ version/version_test.go | 23 +++ watch/watch_test.go | 230 ++++++++++++++++++++++++++++ 7 files changed, 1073 insertions(+) create mode 100644 cmd/root_test.go create mode 100644 k8sconfig/k8sconfig_test.go create mode 100644 k8sobject/k8sobject_test.go create mode 100644 pods/pods_test.go create mode 100644 print/print_test.go create mode 100644 version/version_test.go create mode 100644 watch/watch_test.go diff --git a/cmd/root_test.go b/cmd/root_test.go new file mode 100644 index 0000000..f8dda4d --- /dev/null +++ b/cmd/root_test.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "testing" +) + +func TestParseObjID(t *testing.T) { + tests := []struct { + name string + objID string + expectedNS string + expectedName string + expectError bool + errorContains string + }{ + { + name: "name only", + objID: "my-pod", + expectedName: "my-pod", + expectError: true, // This will fail in test environment without kubeconfig + }, + { + name: "name only - with kubeconfig error", + objID: "test-pod", + expectedName: "test-pod", + expectError: true, + errorContains: "configuration", // Should contain "configuration" in error message + }, + { + name: "namespace and name", + objID: "my-namespace/my-pod", + expectedNS: "my-namespace", + expectedName: "my-pod", + expectError: false, + }, + { + name: "too many slashes", + objID: "ns/pod/extra", + expectError: true, + errorContains: "Object ID must be of the form", + }, + { + name: "empty slashes", + objID: "ns//pod", + expectError: true, + errorContains: "Object ID must be of the form", + }, + { + name: "empty name with namespace", + objID: "my-namespace/", + expectedNS: "my-namespace", + expectedName: "", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + namespace, name, err := parseObjID(tt.objID) + + if tt.expectError { + if err == nil { + t.Errorf("Expected error for objID %q, but got none", tt.objID) + return + } + if tt.errorContains != "" && !containsString(err.Error(), tt.errorContains) { + t.Errorf("Expected error to contain %q, got %q", tt.errorContains, err.Error()) + } + return + } + + if err != nil { + t.Errorf("Unexpected error for objID %q: %v", tt.objID, err) + return + } + + if name != tt.expectedName { + t.Errorf("Expected name %q, got %q", tt.expectedName, name) + } + + // For name-only cases, we can't predict the namespace since it depends on kubeconfig + // In test environment, this will likely fail, so we skip this check + if tt.objID != "my-pod" && namespace != tt.expectedNS { + t.Errorf("Expected namespace %q, got %q", tt.expectedNS, namespace) + } + }) + } +} + +// Helper function to check if a string contains a substring +func containsString(s, substr string) bool { + return len(s) >= len(substr) && s[len(s)-len(substr):] == substr || + len(s) > len(substr) && s[:len(substr)] == substr || + len(s) > len(substr) && findSubstring(s, substr) +} + +func findSubstring(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} \ No newline at end of file diff --git a/k8sconfig/k8sconfig_test.go b/k8sconfig/k8sconfig_test.go new file mode 100644 index 0000000..f36662c --- /dev/null +++ b/k8sconfig/k8sconfig_test.go @@ -0,0 +1,37 @@ +package k8sconfig + +import ( + "testing" + + "k8s.io/client-go/tools/clientcmd" +) + +func TestNew(t *testing.T) { + // Test that New() returns a ClientConfig + config := New() + + if config == nil { + t.Error("Expected New() to return a non-nil ClientConfig") + } + + // Test that the returned config implements the ClientConfig interface + _, ok := config.(clientcmd.ClientConfig) + if !ok { + t.Error("Expected New() to return a ClientConfig implementation") + } +} + +func TestNewReturnsInteractiveDeferredLoadingClientConfig(t *testing.T) { + config := New() + + // We can't easily test the internal structure of the config without breaking encapsulation, + // but we can test that it behaves like a ClientConfig by calling some of its methods + // without causing panics + + // Test that ConfigAccess returns something (may be nil in test environment) + configAccess := config.ConfigAccess() + _ = configAccess // Just ensure it doesn't panic + + // Test that RawConfig returns something (may error in test environment but shouldn't panic) + _, _ = config.RawConfig() +} \ No newline at end of file diff --git a/k8sobject/k8sobject_test.go b/k8sobject/k8sobject_test.go new file mode 100644 index 0000000..c296a06 --- /dev/null +++ b/k8sobject/k8sobject_test.go @@ -0,0 +1,262 @@ +package k8sobject + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestOwnedBy(t *testing.T) { + tests := []struct { + name string + object *unstructured.Unstructured + apiVersion interface{} + kind interface{} + ownerName interface{} + expectedResult bool + }{ + { + name: "object owned by deployment", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "my-deployment", + }, + }, + }, + }, + }, + apiVersion: "apps/v1", + kind: "Deployment", + ownerName: "my-deployment", + expectedResult: true, + }, + { + name: "object not owned by specified deployment", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "other-deployment", + }, + }, + }, + }, + }, + apiVersion: "apps/v1", + kind: "Deployment", + ownerName: "my-deployment", + expectedResult: false, + }, + { + name: "object with no owner references", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{}, + }, + }, + apiVersion: "apps/v1", + kind: "Deployment", + ownerName: "my-deployment", + expectedResult: false, + }, + { + name: "object with invalid owner references", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": "invalid", + }, + }, + }, + apiVersion: "apps/v1", + kind: "Deployment", + ownerName: "my-deployment", + expectedResult: false, + }, + { + name: "object with owner reference wrong kind", + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "ReplicaSet", + "name": "my-deployment", + }, + }, + }, + }, + }, + apiVersion: "apps/v1", + kind: "Deployment", + ownerName: "my-deployment", + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := OwnedBy(tt.object, tt.apiVersion, tt.kind, tt.ownerName) + if result != tt.expectedResult { + t.Errorf("Expected %v, got %v", tt.expectedResult, result) + } + }) + } +} + +func TestPodConditions(t *testing.T) { + tests := []struct { + name string + pod *unstructured.Unstructured + expectedConditions int + }{ + { + name: "pod with conditions", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{ + "conditions": []interface{}{ + map[string]interface{}{ + "type": "Ready", + "status": "True", + }, + map[string]interface{}{ + "type": "PodScheduled", + "status": "True", + }, + }, + }, + }, + }, + expectedConditions: 2, + }, + { + name: "pod with no conditions", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{}, + }, + }, + expectedConditions: 0, + }, + { + name: "pod with no status", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + expectedConditions: 0, + }, + { + name: "pod with invalid status", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": "invalid", + }, + }, + expectedConditions: 0, + }, + { + name: "pod with invalid conditions", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{ + "conditions": "invalid", + }, + }, + }, + expectedConditions: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conditions := PodConditions(tt.pod) + if len(conditions) != tt.expectedConditions { + t.Errorf("Expected %d conditions, got %d", tt.expectedConditions, len(conditions)) + } + }) + } +} + +func TestPodContainerStatuses(t *testing.T) { + tests := []struct { + name string + pod *unstructured.Unstructured + expectedContainerStatuses int + }{ + { + name: "pod with container statuses", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{ + "containerStatuses": []interface{}{ + map[string]interface{}{ + "name": "container1", + "ready": true, + }, + map[string]interface{}{ + "name": "container2", + "ready": false, + }, + }, + }, + }, + }, + expectedContainerStatuses: 2, + }, + { + name: "pod with no container statuses", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{}, + }, + }, + expectedContainerStatuses: 0, + }, + { + name: "pod with no status", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + expectedContainerStatuses: 0, + }, + { + name: "pod with invalid status", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": "invalid", + }, + }, + expectedContainerStatuses: 0, + }, + { + name: "pod with invalid container statuses", + pod: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "status": map[string]interface{}{ + "containerStatuses": "invalid", + }, + }, + }, + expectedContainerStatuses: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + containerStatuses := PodContainerStatuses(tt.pod) + if len(containerStatuses) != tt.expectedContainerStatuses { + t.Errorf("Expected %d container statuses, got %d", tt.expectedContainerStatuses, len(containerStatuses)) + } + }) + } +} \ No newline at end of file diff --git a/pods/pods_test.go b/pods/pods_test.go new file mode 100644 index 0000000..3de179f --- /dev/null +++ b/pods/pods_test.go @@ -0,0 +1,291 @@ +package pods + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestGetReady(t *testing.T) { + tests := []struct { + name string + endpoints *unstructured.Unstructured + expectedCount int + expectedFormat bool // Whether to check if output contains expected formatting + }{ + { + name: "endpoints with ready addresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "addresses": []interface{}{ + map[string]interface{}{ + "ip": "192.168.1.10", + "targetRef": map[string]interface{}{ + "name": "pod-1", + }, + }, + map[string]interface{}{ + "ip": "192.168.1.11", + "targetRef": map[string]interface{}{ + "name": "pod-2", + }, + }, + }, + }, + }, + }, + }, + expectedCount: 2, + expectedFormat: true, + }, + { + name: "endpoints with no subsets", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + expectedCount: 0, + }, + { + name: "endpoints with invalid subsets", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": "invalid", + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with no addresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{}, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with invalid addresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "addresses": "invalid", + }, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with address missing targetRef", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "addresses": []interface{}{ + map[string]interface{}{ + "ip": "192.168.1.10", + }, + }, + }, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with address missing IP", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "addresses": []interface{}{ + map[string]interface{}{ + "targetRef": map[string]interface{}{ + "name": "pod-1", + }, + }, + }, + }, + }, + }, + }, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ready := GetReady(tt.endpoints) + if len(ready) != tt.expectedCount { + t.Errorf("Expected %d ready pods, got %d", tt.expectedCount, len(ready)) + } + + if tt.expectedFormat && len(ready) > 0 { + // Check that the format contains expected elements + for _, readyPod := range ready { + if !containsString(readyPod, "Ready") { + t.Errorf("Expected ready pod format to contain 'Ready', got: %s", readyPod) + } + if !containsString(readyPod, "@") { + t.Errorf("Expected ready pod format to contain '@', got: %s", readyPod) + } + } + } + }) + } +} + +func TestGetUnready(t *testing.T) { + tests := []struct { + name string + endpoints *unstructured.Unstructured + expectedCount int + expectedFormat bool // Whether to check if output contains expected formatting + }{ + { + name: "endpoints with unready addresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "notReadyAddresses": []interface{}{ + map[string]interface{}{ + "ip": "192.168.1.10", + "targetRef": map[string]interface{}{ + "name": "pod-1", + }, + }, + map[string]interface{}{ + "ip": "192.168.1.11", + "targetRef": map[string]interface{}{ + "name": "pod-2", + }, + }, + }, + }, + }, + }, + }, + expectedCount: 2, + expectedFormat: true, + }, + { + name: "endpoints with no subsets", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + expectedCount: 0, + }, + { + name: "endpoints with invalid subsets", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": "invalid", + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with no notReadyAddresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{}, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with invalid notReadyAddresses", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "notReadyAddresses": "invalid", + }, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with address missing targetRef", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "notReadyAddresses": []interface{}{ + map[string]interface{}{ + "ip": "192.168.1.10", + }, + }, + }, + }, + }, + }, + expectedCount: 0, + }, + { + name: "endpoints with address missing IP", + endpoints: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "subsets": []interface{}{ + map[string]interface{}{ + "notReadyAddresses": []interface{}{ + map[string]interface{}{ + "targetRef": map[string]interface{}{ + "name": "pod-1", + }, + }, + }, + }, + }, + }, + }, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + unready := GetUnready(tt.endpoints) + if len(unready) != tt.expectedCount { + t.Errorf("Expected %d unready pods, got %d", tt.expectedCount, len(unready)) + } + + if tt.expectedFormat && len(unready) > 0 { + // Check that the format contains expected elements + for _, unreadyPod := range unready { + if !containsString(unreadyPod, "Not live") { + t.Errorf("Expected unready pod format to contain 'Not live', got: %s", unreadyPod) + } + if !containsString(unreadyPod, "@") { + t.Errorf("Expected unready pod format to contain '@', got: %s", unreadyPod) + } + } + } + }) + } +} + +// Helper function to check if a string contains a substring +func containsString(s, substr string) bool { + if len(substr) == 0 { + return true + } + if len(s) < len(substr) { + return false + } + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} \ No newline at end of file diff --git a/print/print_test.go b/print/print_test.go new file mode 100644 index 0000000..a8b0cd9 --- /dev/null +++ b/print/print_test.go @@ -0,0 +1,126 @@ +package print + +import ( + "bytes" + "testing" +) + +func TestSuccessStatusEvent(t *testing.T) { + tests := []struct { + name string + format string + args []interface{} + expected string + }{ + { + name: "simple message", + format: "Test completed successfully", + args: nil, + expected: " ✅ Test completed successfully\n", + }, + { + name: "message with formatting", + format: "Pod %s is ready", + args: []interface{}{"test-pod"}, + expected: " ✅ Pod test-pod is ready\n", + }, + { + name: "message with multiple args", + format: "Service %s created in namespace %s", + args: []interface{}{"my-service", "default"}, + expected: " ✅ Service my-service created in namespace default\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + SuccessStatusEvent(&buf, tt.format, tt.args...) + + result := buf.String() + if result != tt.expected { + t.Errorf("Expected %q, got %q", tt.expected, result) + } + }) + } +} + +func TestFailureStatusEvent(t *testing.T) { + tests := []struct { + name string + format string + args []interface{} + expected string + }{ + { + name: "simple message", + format: "Test failed", + args: nil, + expected: " ❌ Test failed\n", + }, + { + name: "message with formatting", + format: "Pod %s failed to start", + args: []interface{}{"test-pod"}, + expected: " ❌ Pod test-pod failed to start\n", + }, + { + name: "message with multiple args", + format: "Service %s failed in namespace %s", + args: []interface{}{"my-service", "default"}, + expected: " ❌ Service my-service failed in namespace default\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + FailureStatusEvent(&buf, tt.format, tt.args...) + + result := buf.String() + if result != tt.expected { + t.Errorf("Expected %q, got %q", tt.expected, result) + } + }) + } +} + +func TestPendingStatusEvent(t *testing.T) { + tests := []struct { + name string + format string + args []interface{} + expected string + }{ + { + name: "simple message", + format: "Test pending", + args: nil, + expected: " ⌛ Test pending\n", + }, + { + name: "message with formatting", + format: "Pod %s is pending", + args: []interface{}{"test-pod"}, + expected: " ⌛ Pod test-pod is pending\n", + }, + { + name: "message with multiple args", + format: "Service %s pending in namespace %s", + args: []interface{}{"my-service", "default"}, + expected: " ⌛ Service my-service pending in namespace default\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + PendingStatusEvent(&buf, tt.format, tt.args...) + + result := buf.String() + if result != tt.expected { + t.Errorf("Expected %q, got %q", tt.expected, result) + } + }) + } +} \ No newline at end of file diff --git a/version/version_test.go b/version/version_test.go new file mode 100644 index 0000000..3030ff2 --- /dev/null +++ b/version/version_test.go @@ -0,0 +1,23 @@ +package version + +import "testing" + +func TestVersion(t *testing.T) { + // Test default version + if Version == "" { + t.Error("Version should have a default value") + } + + // The default version should be "dev" + expectedDefault := "dev" + if Version != expectedDefault { + t.Errorf("Expected default version to be %q, got %q", expectedDefault, Version) + } +} + +func TestVersionIsString(t *testing.T) { + // Ensure Version is a string type + if _, ok := interface{}(Version).(string); !ok { + t.Error("Version should be of type string") + } +} \ No newline at end of file diff --git a/watch/watch_test.go b/watch/watch_test.go new file mode 100644 index 0000000..97d3f00 --- /dev/null +++ b/watch/watch_test.go @@ -0,0 +1,230 @@ +package watch + +import ( + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestAll(t *testing.T) { + namespace := "test-namespace" + opts := All(namespace) + + if opts.watchType != watchAll { + t.Errorf("Expected watchType to be %v, got %v", watchAll, opts.watchType) + } + + if opts.namespace != namespace { + t.Errorf("Expected namespace to be %q, got %q", namespace, opts.namespace) + } +} + +func TestThisObject(t *testing.T) { + namespace := "test-namespace" + name := "test-object" + opts := ThisObject(namespace, name) + + if opts.watchType != watchByName { + t.Errorf("Expected watchType to be %v, got %v", watchByName, opts.watchType) + } + + if opts.namespace != namespace { + t.Errorf("Expected namespace to be %q, got %q", namespace, opts.namespace) + } + + if opts.name != name { + t.Errorf("Expected name to be %q, got %q", name, opts.name) + } +} + +func TestObjectsOwnedBy(t *testing.T) { + namespace := "test-namespace" + ownerName := "test-owner" + opts := ObjectsOwnedBy(namespace, ownerName) + + if opts.watchType != watchByOwner { + t.Errorf("Expected watchType to be %v, got %v", watchByOwner, opts.watchType) + } + + if opts.namespace != namespace { + t.Errorf("Expected namespace to be %q, got %q", namespace, opts.namespace) + } + + if opts.ownerName != ownerName { + t.Errorf("Expected ownerName to be %q, got %q", ownerName, opts.ownerName) + } +} + +func TestOptsCheck(t *testing.T) { + tests := []struct { + name string + opts Opts + object *unstructured.Unstructured + expectedResult bool + }{ + { + name: "watchByName - matching name", + opts: Opts{ + watchType: watchByName, + name: "test-pod", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "test-pod", + }, + }, + }, + expectedResult: true, + }, + { + name: "watchByName - non-matching name", + opts: Opts{ + watchType: watchByName, + name: "test-pod", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "other-pod", + }, + }, + }, + expectedResult: false, + }, + { + name: "watchAll - always true", + opts: Opts{ + watchType: watchAll, + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "any-object", + }, + }, + }, + expectedResult: true, + }, + { + name: "watchByOwner - apps/v1 deployment owner match", + opts: Opts{ + watchType: watchByOwner, + ownerName: "test-deployment", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "test-deployment", + }, + }, + }, + }, + }, + expectedResult: true, + }, + { + name: "watchByOwner - extensions/v1beta1 deployment owner match", + opts: Opts{ + watchType: watchByOwner, + ownerName: "test-deployment", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "extensions/v1beta1", + "kind": "Deployment", + "name": "test-deployment", + }, + }, + }, + }, + }, + expectedResult: true, + }, + { + name: "watchByOwner - apps/v1beta1 deployment owner match", + opts: Opts{ + watchType: watchByOwner, + ownerName: "test-deployment", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1beta1", + "kind": "Deployment", + "name": "test-deployment", + }, + }, + }, + }, + }, + expectedResult: true, + }, + { + name: "watchByOwner - no owner match", + opts: Opts{ + watchType: watchByOwner, + ownerName: "test-deployment", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []interface{}{ + map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "other-deployment", + }, + }, + }, + }, + }, + expectedResult: false, + }, + { + name: "watchByOwner - no owner references", + opts: Opts{ + watchType: watchByOwner, + ownerName: "test-deployment", + }, + object: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{}, + }, + }, + expectedResult: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.opts.Check(tt.object) + if result != tt.expectedResult { + t.Errorf("Expected %v, got %v", tt.expectedResult, result) + } + }) + } +} + +func TestOptsCheckPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic for unknown watch type, but didn't panic") + } + }() + + opts := Opts{ + watchType: "invalid", + } + object := &unstructured.Unstructured{} + opts.Check(object) +} \ No newline at end of file