Skip to content

Commit e8c3902

Browse files
Improved coverage for operator package
1 parent 73e4626 commit e8c3902

File tree

5 files changed

+707
-0
lines changed

5 files changed

+707
-0
lines changed

operator/clients/factory_test.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ package clients
55
import (
66
"io"
77
"os"
8+
"path/filepath"
89
"testing"
910

11+
"github.com/stretchr/testify/assert"
12+
"k8s.io/client-go/tools/clientcmd"
13+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
14+
1015
. "github.com/netapp/trident/logging"
1116
)
1217

@@ -15,3 +20,245 @@ func TestMain(m *testing.M) {
1520
InitLogOutput(io.Discard)
1621
os.Exit(m.Run())
1722
}
23+
24+
// createValidTempKubeConfig creates a valid temporary kubeconfig file for testing
25+
func createValidTempKubeConfig(t *testing.T) string {
26+
tempDir := t.TempDir()
27+
kubeconfigPath := filepath.Join(tempDir, "kubeconfig")
28+
29+
config := &clientcmdapi.Config{
30+
APIVersion: "v1",
31+
Kind: "Config",
32+
Clusters: map[string]*clientcmdapi.Cluster{
33+
"test-cluster": {
34+
Server: "https://127.0.0.1:6443",
35+
InsecureSkipTLSVerify: true,
36+
},
37+
},
38+
Contexts: map[string]*clientcmdapi.Context{
39+
"test-context": {
40+
Cluster: "test-cluster",
41+
AuthInfo: "test-user",
42+
Namespace: "test-namespace",
43+
},
44+
},
45+
CurrentContext: "test-context",
46+
AuthInfos: map[string]*clientcmdapi.AuthInfo{
47+
"test-user": {
48+
Token: "test-token",
49+
},
50+
},
51+
}
52+
53+
err := clientcmd.WriteToFile(*config, kubeconfigPath)
54+
assert.NoError(t, err, "Failed to write kubeconfig file")
55+
56+
return kubeconfigPath
57+
}
58+
59+
// createInvalidKubeConfig creates an invalid kubeconfig file for testing
60+
func createInvalidKubeConfig(t *testing.T) string {
61+
tempDir := t.TempDir()
62+
kubeconfigPath := filepath.Join(tempDir, "bad-kubeconfig")
63+
err := os.WriteFile(kubeconfigPath, []byte("invalid yaml content"), 0644)
64+
assert.NoError(t, err, "Failed to write bad kubeconfig file")
65+
return kubeconfigPath
66+
}
67+
68+
// createEmptyKubeConfig creates an empty kubeconfig file for testing
69+
func createEmptyKubeConfig(t *testing.T) string {
70+
tempDir := t.TempDir()
71+
kubeconfigPath := filepath.Join(tempDir, "empty-kubeconfig")
72+
err := os.WriteFile(kubeconfigPath, []byte(""), 0644)
73+
assert.NoError(t, err, "Failed to write empty kubeconfig file")
74+
return kubeconfigPath
75+
}
76+
77+
func TestCreateK8SClients(t *testing.T) {
78+
validKubeConfig := createValidTempKubeConfig(t)
79+
80+
tests := []struct {
81+
name string
82+
apiServerIP string
83+
kubeConfigPath string
84+
expectError bool
85+
errorContains string
86+
description string
87+
}{
88+
{
89+
name: "ValidKubeConfigWithInvalidServer",
90+
apiServerIP: "https://invalid:6443",
91+
kubeConfigPath: validKubeConfig,
92+
expectError: true,
93+
description: "Valid kubeconfig but invalid server should fail",
94+
},
95+
{
96+
name: "NonExistentKubeConfig",
97+
apiServerIP: "",
98+
kubeConfigPath: "/non/existent/path",
99+
expectError: true,
100+
description: "Non-existent kubeconfig file should fail",
101+
},
102+
{
103+
name: "InClusterConfigOutsideCluster",
104+
apiServerIP: "",
105+
kubeConfigPath: "",
106+
expectError: true,
107+
description: "In-cluster config outside cluster should fail",
108+
},
109+
{
110+
name: "EmptyAPIWithKubeConfig",
111+
apiServerIP: "",
112+
kubeConfigPath: validKubeConfig,
113+
expectError: true,
114+
description: "Empty API with kubeconfig should trigger ex-cluster path",
115+
},
116+
{
117+
name: "APIWithEmptyKubeConfig",
118+
apiServerIP: "https://test:6443",
119+
kubeConfigPath: "",
120+
expectError: true,
121+
description: "API server with empty kubeconfig should trigger in-cluster path",
122+
},
123+
{
124+
name: "BothNonEmpty",
125+
apiServerIP: "https://test:6443",
126+
kubeConfigPath: "/invalid/path",
127+
expectError: true,
128+
description: "Both non-empty should trigger ex-cluster path",
129+
},
130+
}
131+
132+
for _, tt := range tests {
133+
t.Run(tt.name, func(t *testing.T) {
134+
clients, err := CreateK8SClients(tt.apiServerIP, tt.kubeConfigPath)
135+
136+
if tt.expectError {
137+
assert.Error(t, err, tt.description)
138+
assert.Nil(t, clients, "Clients should be nil on error")
139+
if tt.errorContains != "" {
140+
assert.Contains(t, err.Error(), tt.errorContains, "Error message should contain expected text")
141+
}
142+
} else {
143+
assert.NoError(t, err, tt.description)
144+
assert.NotNil(t, clients, "Clients should not be nil on success")
145+
}
146+
})
147+
}
148+
}
149+
150+
func TestCreateK8SClientsExCluster(t *testing.T) {
151+
validKubeConfig := createValidTempKubeConfig(t)
152+
invalidKubeConfig := createInvalidKubeConfig(t)
153+
emptyKubeConfig := createEmptyKubeConfig(t)
154+
155+
tests := []struct {
156+
name string
157+
apiServerIP string
158+
kubeConfigPath string
159+
expectError bool
160+
errorContains string
161+
description string
162+
}{
163+
{
164+
name: "InvalidKubeConfigPath",
165+
apiServerIP: "",
166+
kubeConfigPath: "/invalid/path",
167+
expectError: true,
168+
description: "Invalid kubeconfig path should fail",
169+
},
170+
{
171+
name: "ValidConfigWithValidServer",
172+
apiServerIP: "https://127.0.0.1:6443",
173+
kubeConfigPath: validKubeConfig,
174+
expectError: true,
175+
errorContains: "could not initialize Kubernetes client",
176+
description: "Valid config should parse but fail at K8S client creation in test environment",
177+
},
178+
{
179+
name: "InvalidKubeConfigContent",
180+
apiServerIP: "",
181+
kubeConfigPath: invalidKubeConfig,
182+
expectError: true,
183+
description: "Invalid kubeconfig content should fail",
184+
},
185+
{
186+
name: "EmptyKubeConfig",
187+
apiServerIP: "",
188+
kubeConfigPath: emptyKubeConfig,
189+
expectError: true,
190+
description: "Empty kubeconfig should fail",
191+
},
192+
{
193+
name: "EmptyAPIServerWithInvalidPath",
194+
apiServerIP: "",
195+
kubeConfigPath: "/invalid/path",
196+
expectError: true,
197+
description: "Empty API server with invalid path should fail",
198+
},
199+
}
200+
201+
for _, tt := range tests {
202+
t.Run(tt.name, func(t *testing.T) {
203+
clients, err := createK8SClientsExCluster(tt.apiServerIP, tt.kubeConfigPath)
204+
205+
if tt.expectError {
206+
assert.Error(t, err, tt.description)
207+
if tt.errorContains != "" {
208+
assert.Contains(t, err.Error(), tt.errorContains, "Error message should contain expected text")
209+
}
210+
// For the valid config case, clients might not be nil if config parsing succeeded
211+
if tt.errorContains != "could not initialize Kubernetes client" {
212+
assert.Nil(t, clients, "Clients should be nil on error")
213+
}
214+
} else {
215+
assert.NoError(t, err, tt.description)
216+
assert.NotNil(t, clients, "Clients should not be nil on success")
217+
assert.NotNil(t, clients.KubeConfig, "KubeConfig should not be nil")
218+
}
219+
})
220+
}
221+
}
222+
223+
func TestCreateK8SClientsInCluster(t *testing.T) {
224+
// Test in-cluster creation (will fail due to missing namespace file in test environment)
225+
clients, err := createK8SClientsInCluster()
226+
227+
assert.Error(t, err, "Expected error for in-cluster config outside cluster")
228+
assert.Nil(t, clients, "Clients should be nil on error")
229+
}
230+
231+
func TestClientsStruct(t *testing.T) {
232+
tests := []struct {
233+
name string
234+
namespace string
235+
}{
236+
{
237+
name: "TestNamespace",
238+
namespace: "test-namespace",
239+
},
240+
{
241+
name: "EmptyNamespace",
242+
namespace: "",
243+
},
244+
{
245+
name: "DefaultNamespace",
246+
namespace: "default",
247+
},
248+
}
249+
250+
for _, tt := range tests {
251+
t.Run(tt.name, func(t *testing.T) {
252+
clients := &Clients{
253+
Namespace: tt.namespace,
254+
}
255+
256+
assert.Equal(t, tt.namespace, clients.Namespace, "Namespace should be set correctly")
257+
assert.Nil(t, clients.KubeClient, "KubeClient should be nil initially")
258+
assert.Nil(t, clients.CRDClient, "CRDClient should be nil initially")
259+
assert.Nil(t, clients.TridentCRDClient, "TridentCRDClient should be nil initially")
260+
assert.Nil(t, clients.SnapshotClient, "SnapshotClient should be nil initially")
261+
assert.Nil(t, clients.K8SVersion, "K8SVersion should be nil initially")
262+
})
263+
}
264+
}

operator/clients/operator_crd_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,34 @@ func TestOperatorCRDClient_UpdateTridentConfiguratorStatus_Error(t *testing.T) {
172172
assert.Error(t, err, "no error returned for failed TridentConfigurator CR status update")
173173
assert.True(t, updated, "status of TridentConfigurator CR was not updated")
174174
}
175+
176+
func TestOperatorCRDClient_GetControllingTorcCR_MultipleNoneInstalled(t *testing.T) {
177+
client, fakeClient := getMockOperatorClient()
178+
179+
fakeClient.PrependReactor("list", "tridentorchestrators", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
180+
return true, &operatorV1.TridentOrchestratorList{
181+
Items: []operatorV1.TridentOrchestrator{
182+
{Status: operatorV1.TridentOrchestratorStatus{Status: string(operatorV1.AppStatusFailed)}},
183+
{Status: operatorV1.TridentOrchestratorStatus{Status: string(operatorV1.AppStatusError)}},
184+
},
185+
}, nil
186+
})
187+
188+
_, err := client.GetControllingTorcCR()
189+
190+
assert.Error(t, err, "no error returned when no TridentOrchestrator is installed")
191+
assert.Contains(t, err.Error(), "trident is not installed yet", "Error should indicate trident is not installed")
192+
}
193+
194+
func TestOperatorCRDClient_GetControllingTorcCR_ListError(t *testing.T) {
195+
client, fakeClient := getMockOperatorClient()
196+
197+
fakeClient.PrependReactor("list", "tridentorchestrators", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
198+
return true, nil, fmt.Errorf("failed to list orchestrators")
199+
})
200+
201+
_, err := client.GetControllingTorcCR()
202+
203+
assert.Error(t, err, "no error returned for list failure")
204+
assert.Contains(t, err.Error(), "failed to list orchestrators", "Error should be from list operation")
205+
}

operator/clients/trident_crd_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,29 @@ func TestTridentCRDClient_DeleteTridentBackendConfig_Error(t *testing.T) {
142142

143143
assert.Error(t, err, "delete backend config did not fail")
144144
}
145+
146+
func TestTridentCRDClient_ListTridentBackend_Success(t *testing.T) {
147+
client, fakeClient := getMockTridentClient()
148+
149+
fakeClient.PrependReactor("list", "tridentbackends", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
150+
return true, &tridentV1.TridentBackendList{}, nil
151+
})
152+
153+
backendList, err := client.ListTridentBackend("test-namespace")
154+
155+
assert.NoError(t, err, "failed to list trident backends")
156+
assert.NotNil(t, backendList, "expected TridentBackendList, got nil")
157+
}
158+
159+
func TestTridentCRDClient_ListTridentBackend_Error(t *testing.T) {
160+
client, fakeClient := getMockTridentClient()
161+
162+
fakeClient.PrependReactor("list", "tridentbackends", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
163+
return true, nil, fmt.Errorf("random error")
164+
})
165+
166+
backendList, err := client.ListTridentBackend("test-namespace")
167+
168+
assert.Error(t, err, "list trident backends did not fail")
169+
assert.Nil(t, backendList, "expected nil, got TridentBackendList")
170+
}

0 commit comments

Comments
 (0)