@@ -230,6 +230,116 @@ func TestGetDestinationRulesContextCancellation(t *testing.T) {
230230 })
231231}
232232
233+ // TestGetDestinationRule tests the GetDestinationRule method with various scenarios
234+ func TestGetDestinationRule (t * testing.T ) {
235+ // Create a mock server that simulates Istio API responses
236+ mockServer := createMockDestinationRuleServer ()
237+ defer mockServer .Close ()
238+
239+ // Create temporary directory and kubeconfig
240+ tempDir , err := os .MkdirTemp ("" , "istio-dr-test-*" )
241+ if err != nil {
242+ t .Fatalf ("Failed to create temp dir: %v" , err )
243+ }
244+ defer os .RemoveAll (tempDir )
245+
246+ kubeconfigPath := filepath .Join (tempDir , "config" )
247+ kubeconfig := createTestKubeconfigForDR (mockServer .URL )
248+
249+ if err := os .WriteFile (kubeconfigPath , []byte (kubeconfig ), 0644 ); err != nil {
250+ t .Fatalf ("Failed to write kubeconfig: %v" , err )
251+ }
252+
253+ // Create Istio client
254+ istio , err := NewIstio (kubeconfigPath )
255+ if err != nil {
256+ t .Fatalf ("Failed to create Istio client: %v" , err )
257+ }
258+ defer istio .Close ()
259+
260+ ctx := context .Background ()
261+
262+ t .Run ("GetDestinationRule with specific DestinationRule name" , func (t * testing.T ) {
263+ result , err := istio .GetDestinationRule (ctx , "production" , "payment-dr" )
264+ if err != nil {
265+ t .Fatalf ("Failed to get destination rule: %v" , err )
266+ }
267+
268+ // Verify result contains expected information
269+ if ! strings .Contains (result , "Destination Rule 'payment-dr'" ) {
270+ t .Errorf ("Expected result to contain 'Destination Rule 'payment-dr'', got: %s" , result )
271+ }
272+
273+ if ! strings .Contains (result , "namespace 'production'" ) {
274+ t .Errorf ("Expected result to mention namespace 'production', got: %s" , result )
275+ }
276+ })
277+
278+ t .Run ("GetDestinationRule result format validation" , func (t * testing.T ) {
279+ result , err := istio .GetDestinationRule (ctx , "production" , "payment-dr" )
280+ if err != nil {
281+ t .Fatalf ("Failed to get destination rule: %v" , err )
282+ }
283+
284+ // Check that result has proper structure
285+ lines := strings .Split (result , "\n " )
286+ if len (lines ) < 1 {
287+ t .Error ("Result should have at least one line" )
288+ }
289+
290+ // First line should contain DestinationRule name and namespace info
291+ firstLine := lines [0 ]
292+ if ! strings .Contains (firstLine , "Destination Rule 'payment-dr'" ) || ! strings .Contains (firstLine , "namespace 'production'" ) {
293+ t .Errorf ("First line should contain DestinationRule information, got: %s" , firstLine )
294+ }
295+
296+ // Result should end with newline for proper formatting
297+ if ! strings .HasSuffix (result , "\n " ) {
298+ t .Error ("Result should end with newline for proper formatting" )
299+ }
300+ })
301+ }
302+
303+ // TestGetDestinationRuleErrorHandling tests error scenarios
304+ func TestGetDestinationRuleErrorHandling (t * testing.T ) {
305+ // Create a mock server that returns errors
306+ mockServer := createErrorMockServer ()
307+ defer mockServer .Close ()
308+
309+ tempDir , err := os .MkdirTemp ("" , "istio-dr-error-test-*" )
310+ if err != nil {
311+ t .Fatalf ("Failed to create temp dir: %v" , err )
312+ }
313+ defer os .RemoveAll (tempDir )
314+
315+ kubeconfigPath := filepath .Join (tempDir , "config" )
316+ kubeconfig := createTestKubeconfigForDR (mockServer .URL )
317+
318+ if err := os .WriteFile (kubeconfigPath , []byte (kubeconfig ), 0644 ); err != nil {
319+ t .Fatalf ("Failed to write kubeconfig: %v" , err )
320+ }
321+
322+ istio , err := NewIstio (kubeconfigPath )
323+ if err != nil {
324+ t .Fatalf ("Failed to create Istio client: %v" , err )
325+ }
326+ defer istio .Close ()
327+
328+ ctx := context .Background ()
329+
330+ t .Run ("GetDestinationRule handles API errors gracefully" , func (t * testing.T ) {
331+ _ , err := istio .GetDestinationRule (ctx , "default" , "nonexistent-dr" )
332+ if err == nil {
333+ t .Fatal ("Expected error when DestinationRule doesn't exist" )
334+ }
335+
336+ // Error should be wrapped with context
337+ if ! strings .Contains (err .Error (), "failed to get destination rule" ) {
338+ t .Errorf ("Expected error to be wrapped with context, got: %v" , err )
339+ }
340+ })
341+ }
342+
233343// Helper functions for creating mock servers
234344
235345func createMockDestinationRuleServer () * httptest.Server {
@@ -240,14 +350,72 @@ func createMockDestinationRuleServer() *httptest.Server {
240350 w .Header ().Set ("Content-Type" , "application/json" )
241351 w .WriteHeader (http .StatusOK )
242352
243- // Extract namespace from URL path
353+ // Extract namespace and resource name from URL path
244354 namespace := extractNamespaceFromPath (r .URL .Path )
355+ resourceName := extractResourceNameFromPath (r .URL .Path )
245356
246357 var response string
247- switch namespace {
248- case "production" :
249- // Mock response with destination rules
250- response = `{
358+
359+ // Handle GET request for specific DestinationRule
360+ if resourceName != "" {
361+ switch {
362+ case namespace == "production" && resourceName == "payment-dr" :
363+ response = `{
364+ "apiVersion": "networking.istio.io/v1alpha3",
365+ "kind": "DestinationRule",
366+ "metadata": {
367+ "name": "payment-dr",
368+ "namespace": "production"
369+ },
370+ "spec": {
371+ "host": "api.payment.com",
372+ "trafficPolicy": {
373+ "loadBalancer": {
374+ "simple": "ROUND_ROBIN"
375+ },
376+ "connectionPool": {
377+ "tcp": {
378+ "maxConnections": 10
379+ }
380+ },
381+ "outlierDetection": {
382+ "consecutiveErrors": 3
383+ }
384+ },
385+ "subsets": [
386+ {
387+ "name": "v1",
388+ "labels": {
389+ "version": "v1"
390+ }
391+ }
392+ ]
393+ }
394+ }`
395+ default :
396+ // Return 404 for non-existent DestinationRule
397+ w .WriteHeader (http .StatusNotFound )
398+ response = `{
399+ "kind": "Status",
400+ "apiVersion": "v1",
401+ "metadata": {},
402+ "status": "Failure",
403+ "message": "destinationrules.networking.istio.io \"` + resourceName + `\" not found",
404+ "reason": "NotFound",
405+ "details": {
406+ "name": "` + resourceName + `",
407+ "group": "networking.istio.io",
408+ "kind": "destinationrules"
409+ },
410+ "code": 404
411+ }`
412+ }
413+ } else {
414+ // Handle LIST request for all DestinationRules
415+ switch namespace {
416+ case "production" :
417+ // Mock response with destination rules
418+ response = `{
251419 "apiVersion": "networking.istio.io/v1alpha3",
252420 "kind": "DestinationRuleList",
253421 "items": [
@@ -284,20 +452,21 @@ func createMockDestinationRuleServer() *httptest.Server {
284452 }
285453 ]
286454 }`
287- case "empty-namespace" :
288- // Mock response with no destination rules
289- response = `{
455+ case "empty-namespace" :
456+ // Mock response with no destination rules
457+ response = `{
290458 "apiVersion": "networking.istio.io/v1alpha3",
291459 "kind": "DestinationRuleList",
292460 "items": []
293461 }`
294- default :
295- // Default response for other namespaces
296- response = `{
462+ default :
463+ // Default response for other namespaces
464+ response = `{
297465 "apiVersion": "networking.istio.io/v1alpha3",
298466 "kind": "DestinationRuleList",
299467 "items": []
300468 }`
469+ }
301470 }
302471
303472 w .Write ([]byte (response ))
0 commit comments