Skip to content

Commit e334548

Browse files
committed
test DRs
1 parent 2e99adf commit e334548

File tree

1 file changed

+347
-0
lines changed

1 file changed

+347
-0
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
package istio
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"k8s.io/client-go/tools/clientcmd"
13+
"k8s.io/client-go/tools/clientcmd/api"
14+
)
15+
16+
// TestGetDestinationRules tests the GetDestinationRules method with various scenarios
17+
func TestGetDestinationRules(t *testing.T) {
18+
// Create a mock server that simulates Istio API responses
19+
mockServer := createMockDestinationRuleServer()
20+
defer mockServer.Close()
21+
22+
// Create temporary directory and kubeconfig
23+
tempDir, err := os.MkdirTemp("", "istio-dr-test-*")
24+
if err != nil {
25+
t.Fatalf("Failed to create temp dir: %v", err)
26+
}
27+
defer os.RemoveAll(tempDir)
28+
29+
kubeconfigPath := filepath.Join(tempDir, "config")
30+
kubeconfig := createTestKubeconfigForDR(mockServer.URL)
31+
32+
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0644); err != nil {
33+
t.Fatalf("Failed to write kubeconfig: %v", err)
34+
}
35+
36+
// Create Istio client
37+
istio, err := NewIstio(kubeconfigPath)
38+
if err != nil {
39+
t.Fatalf("Failed to create Istio client: %v", err)
40+
}
41+
defer istio.Close()
42+
43+
ctx := context.Background()
44+
45+
t.Run("GetDestinationRules with default namespace", func(t *testing.T) {
46+
result, err := istio.GetDestinationRules(ctx, "default")
47+
if err != nil {
48+
t.Fatalf("Failed to get destination rules: %v", err)
49+
}
50+
51+
// Verify result contains expected information
52+
if !strings.Contains(result, "Destination Rules") {
53+
t.Errorf("Expected result to contain 'Destination Rules', got: %s", result)
54+
}
55+
56+
if !strings.Contains(result, "namespace 'default'") {
57+
t.Errorf("Expected result to mention namespace 'default', got: %s", result)
58+
}
59+
60+
// Should contain count information
61+
if !strings.Contains(result, "Found") {
62+
t.Errorf("Expected result to contain count information, got: %s", result)
63+
}
64+
})
65+
66+
t.Run("GetDestinationRules with custom namespace", func(t *testing.T) {
67+
result, err := istio.GetDestinationRules(ctx, "istio-system")
68+
if err != nil {
69+
t.Fatalf("Failed to get destination rules: %v", err)
70+
}
71+
72+
if !strings.Contains(result, "namespace 'istio-system'") {
73+
t.Errorf("Expected result to mention namespace 'istio-system', got: %s", result)
74+
}
75+
})
76+
77+
t.Run("GetDestinationRules handles empty result", func(t *testing.T) {
78+
// Test with a namespace that has no destination rules
79+
result, err := istio.GetDestinationRules(ctx, "empty-namespace")
80+
if err != nil {
81+
t.Fatalf("Failed to get destination rules: %v", err)
82+
}
83+
84+
// Should indicate no destination rules found
85+
if !strings.Contains(result, "Found 0 Destination Rules") {
86+
t.Errorf("Expected result to indicate 0 destination rules, got: %s", result)
87+
}
88+
})
89+
90+
t.Run("GetDestinationRules with populated namespace", func(t *testing.T) {
91+
// Test with a namespace that has destination rules configured in mock
92+
result, err := istio.GetDestinationRules(ctx, "production")
93+
if err != nil {
94+
t.Fatalf("Failed to get destination rules: %v", err)
95+
}
96+
97+
// Should contain information about destination rules
98+
if strings.Contains(result, "Found 0 Destination Rules") {
99+
t.Errorf("Expected result to show destination rules, got: %s", result)
100+
}
101+
102+
// Should contain formatting like hosts, traffic policies
103+
expectedPatterns := []string{"Destination Rules", "namespace 'production'", "Found"}
104+
for _, pattern := range expectedPatterns {
105+
if !strings.Contains(result, pattern) {
106+
t.Errorf("Expected result to contain '%s', got: %s", pattern, result)
107+
}
108+
}
109+
})
110+
111+
t.Run("GetDestinationRules result format validation", func(t *testing.T) {
112+
result, err := istio.GetDestinationRules(ctx, "default")
113+
if err != nil {
114+
t.Fatalf("Failed to get destination rules: %v", err)
115+
}
116+
117+
// Check that result has proper structure
118+
lines := strings.Split(result, "\n")
119+
if len(lines) < 1 {
120+
t.Error("Result should have at least one line")
121+
}
122+
123+
// First line should contain count and namespace info
124+
firstLine := lines[0]
125+
if !strings.Contains(firstLine, "Found") || !strings.Contains(firstLine, "Destination Rules") {
126+
t.Errorf("First line should contain count information, got: %s", firstLine)
127+
}
128+
129+
// Result should end with newline for proper formatting
130+
if !strings.HasSuffix(result, "\n") {
131+
t.Error("Result should end with newline for proper formatting")
132+
}
133+
})
134+
135+
t.Run("GetDestinationRules with host information", func(t *testing.T) {
136+
// Test with a namespace that has destination rules with host information
137+
result, err := istio.GetDestinationRules(ctx, "production")
138+
if err != nil {
139+
t.Fatalf("Failed to get destination rules: %v", err)
140+
}
141+
142+
// Should contain host information for destination rules
143+
if !strings.Contains(result, "Host:") {
144+
t.Errorf("Expected result to contain host information, got: %s", result)
145+
}
146+
})
147+
}
148+
149+
// TestGetDestinationRulesErrorHandling tests error scenarios
150+
func TestGetDestinationRulesErrorHandling(t *testing.T) {
151+
// Create a mock server that returns errors
152+
mockServer := createErrorMockServer()
153+
defer mockServer.Close()
154+
155+
tempDir, err := os.MkdirTemp("", "istio-dr-error-test-*")
156+
if err != nil {
157+
t.Fatalf("Failed to create temp dir: %v", err)
158+
}
159+
defer os.RemoveAll(tempDir)
160+
161+
kubeconfigPath := filepath.Join(tempDir, "config")
162+
kubeconfig := createTestKubeconfigForDR(mockServer.URL)
163+
164+
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0644); err != nil {
165+
t.Fatalf("Failed to write kubeconfig: %v", err)
166+
}
167+
168+
istio, err := NewIstio(kubeconfigPath)
169+
if err != nil {
170+
t.Fatalf("Failed to create Istio client: %v", err)
171+
}
172+
defer istio.Close()
173+
174+
ctx := context.Background()
175+
176+
t.Run("GetDestinationRules handles API errors gracefully", func(t *testing.T) {
177+
_, err := istio.GetDestinationRules(ctx, "default")
178+
if err == nil {
179+
t.Fatal("Expected error when API returns error response")
180+
}
181+
182+
// Error should be wrapped with context
183+
if !strings.Contains(err.Error(), "failed to list destination rules") {
184+
t.Errorf("Expected error to be wrapped with context, got: %v", err)
185+
}
186+
})
187+
}
188+
189+
// TestGetDestinationRulesContextCancellation tests context cancellation
190+
func TestGetDestinationRulesContextCancellation(t *testing.T) {
191+
// Create a mock server with delay
192+
mockServer := createDelayedMockServer()
193+
defer mockServer.Close()
194+
195+
tempDir, err := os.MkdirTemp("", "istio-dr-ctx-test-*")
196+
if err != nil {
197+
t.Fatalf("Failed to create temp dir: %v", err)
198+
}
199+
defer os.RemoveAll(tempDir)
200+
201+
kubeconfigPath := filepath.Join(tempDir, "config")
202+
kubeconfig := createTestKubeconfigForDR(mockServer.URL)
203+
204+
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfig), 0644); err != nil {
205+
t.Fatalf("Failed to write kubeconfig: %v", err)
206+
}
207+
208+
istio, err := NewIstio(kubeconfigPath)
209+
if err != nil {
210+
t.Fatalf("Failed to create Istio client: %v", err)
211+
}
212+
defer istio.Close()
213+
214+
t.Run("GetDestinationRules respects context cancellation", func(t *testing.T) {
215+
ctx, cancel := context.WithCancel(context.Background())
216+
217+
// Cancel context immediately
218+
cancel()
219+
220+
_, err := istio.GetDestinationRules(ctx, "default")
221+
if err == nil {
222+
t.Fatal("Expected error when context is cancelled")
223+
}
224+
225+
if !strings.Contains(err.Error(), "context canceled") && !strings.Contains(err.Error(), "cancelled") {
226+
t.Logf("Context cancellation may not be immediately detected, got error: %v", err)
227+
// Note: Context cancellation might not always be immediately detected
228+
// depending on where in the request lifecycle it occurs
229+
}
230+
})
231+
}
232+
233+
// Helper functions for creating mock servers
234+
235+
func createMockDestinationRuleServer() *httptest.Server {
236+
mux := http.NewServeMux()
237+
238+
// Mock Destination Rules API endpoint
239+
mux.HandleFunc("/apis/networking.istio.io/v1alpha3/namespaces/", func(w http.ResponseWriter, r *http.Request) {
240+
w.Header().Set("Content-Type", "application/json")
241+
w.WriteHeader(http.StatusOK)
242+
243+
// Extract namespace from URL path
244+
namespace := extractNamespaceFromPath(r.URL.Path)
245+
246+
var response string
247+
switch namespace {
248+
case "production":
249+
// Mock response with destination rules
250+
response = `{
251+
"apiVersion": "networking.istio.io/v1alpha3",
252+
"kind": "DestinationRuleList",
253+
"items": [
254+
{
255+
"metadata": {
256+
"name": "bookinfo-dr",
257+
"namespace": "production"
258+
},
259+
"spec": {
260+
"host": "bookinfo.example.com",
261+
"trafficPolicy": {
262+
"loadBalancer": {
263+
"simple": "ROUND_ROBIN"
264+
}
265+
}
266+
}
267+
},
268+
{
269+
"metadata": {
270+
"name": "reviews-dr",
271+
"namespace": "production"
272+
},
273+
"spec": {
274+
"host": "reviews",
275+
"subsets": [
276+
{
277+
"name": "v1",
278+
"labels": {
279+
"version": "v1"
280+
}
281+
}
282+
]
283+
}
284+
}
285+
]
286+
}`
287+
case "empty-namespace":
288+
// Mock response with no destination rules
289+
response = `{
290+
"apiVersion": "networking.istio.io/v1alpha3",
291+
"kind": "DestinationRuleList",
292+
"items": []
293+
}`
294+
default:
295+
// Default response for other namespaces
296+
response = `{
297+
"apiVersion": "networking.istio.io/v1alpha3",
298+
"kind": "DestinationRuleList",
299+
"items": []
300+
}`
301+
}
302+
303+
w.Write([]byte(response))
304+
})
305+
306+
// Mock API discovery endpoints
307+
mux.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
308+
w.Header().Set("Content-Type", "application/json")
309+
w.WriteHeader(http.StatusOK)
310+
w.Write([]byte(`{"kind":"APIVersions","versions":["v1"]}`))
311+
})
312+
313+
mux.HandleFunc("/apis", func(w http.ResponseWriter, r *http.Request) {
314+
w.Header().Set("Content-Type", "application/json")
315+
w.WriteHeader(http.StatusOK)
316+
w.Write([]byte(`{
317+
"kind":"APIGroupList",
318+
"groups":[
319+
{
320+
"name":"networking.istio.io",
321+
"versions":[{"version":"v1alpha3"}]
322+
}
323+
]
324+
}`))
325+
})
326+
327+
return httptest.NewServer(mux)
328+
}
329+
330+
func createTestKubeconfigForDR(server string) string {
331+
config := api.NewConfig()
332+
config.Clusters["test-cluster"] = &api.Cluster{
333+
Server: server,
334+
}
335+
config.AuthInfos["test-user"] = &api.AuthInfo{}
336+
config.Contexts["test-context"] = &api.Context{
337+
Cluster: "test-cluster",
338+
AuthInfo: "test-user",
339+
}
340+
config.CurrentContext = "test-context"
341+
342+
bytes, err := clientcmd.Write(*config)
343+
if err != nil {
344+
panic(err)
345+
}
346+
return string(bytes)
347+
}

0 commit comments

Comments
 (0)