...

Source file src/sigs.k8s.io/cli-utils/pkg/printers/table/collector_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/printers/table

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package table
     5  
     6  import (
     7  	"errors"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"k8s.io/apimachinery/pkg/runtime/schema"
    12  	"k8s.io/apimachinery/pkg/util/validation/field"
    13  	"sigs.k8s.io/cli-utils/pkg/apply/event"
    14  	pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    15  	"sigs.k8s.io/cli-utils/pkg/object"
    16  	"sigs.k8s.io/cli-utils/pkg/object/graph"
    17  	"sigs.k8s.io/cli-utils/pkg/object/validation"
    18  )
    19  
    20  var (
    21  	depID = object.ObjMetadata{
    22  		GroupKind: schema.GroupKind{
    23  			Group: "apps",
    24  			Kind:  "Deployment",
    25  		},
    26  		Name:      "foo",
    27  		Namespace: "default",
    28  	}
    29  	depID2 = object.ObjMetadata{
    30  		GroupKind: schema.GroupKind{
    31  			Group: "apps",
    32  			Kind:  "Deployment",
    33  		},
    34  		Name:      "bar",
    35  		Namespace: "default",
    36  	}
    37  	customID = object.ObjMetadata{
    38  		GroupKind: schema.GroupKind{
    39  			Group: "custom.io",
    40  			Kind:  "Custom",
    41  		},
    42  		Name: "Custom",
    43  	}
    44  )
    45  
    46  const testMessage = "test message for ResourceStatus"
    47  
    48  func TestResourceStateCollector_New(t *testing.T) {
    49  	testCases := map[string]struct {
    50  		resourceGroups []event.ActionGroup
    51  		resourceInfos  map[object.ObjMetadata]*resourceInfo
    52  	}{
    53  		"no resources": {
    54  			resourceGroups: []event.ActionGroup{},
    55  			resourceInfos:  map[object.ObjMetadata]*resourceInfo{},
    56  		},
    57  		"several resources for apply": {
    58  			resourceGroups: []event.ActionGroup{
    59  				{
    60  					Action: event.ApplyAction,
    61  					Identifiers: object.ObjMetadataSet{
    62  						depID, customID,
    63  					},
    64  				},
    65  			},
    66  			resourceInfos: map[object.ObjMetadata]*resourceInfo{
    67  				depID: {
    68  					ResourceAction: event.ApplyAction,
    69  				},
    70  				customID: {
    71  					ResourceAction: event.ApplyAction,
    72  				},
    73  			},
    74  		},
    75  		"several resources for prune": {
    76  			resourceGroups: []event.ActionGroup{
    77  				{
    78  					Action: event.ApplyAction,
    79  					Identifiers: object.ObjMetadataSet{
    80  						customID,
    81  					},
    82  				},
    83  				{
    84  					Action: event.PruneAction,
    85  					Identifiers: object.ObjMetadataSet{
    86  						depID,
    87  					},
    88  				},
    89  			},
    90  			resourceInfos: map[object.ObjMetadata]*resourceInfo{
    91  				depID: {
    92  					ResourceAction: event.PruneAction,
    93  				},
    94  				customID: {
    95  					ResourceAction: event.ApplyAction,
    96  				},
    97  			},
    98  		},
    99  	}
   100  
   101  	for tn, tc := range testCases {
   102  		t.Run(tn, func(t *testing.T) {
   103  			rsc := newResourceStateCollector(tc.resourceGroups)
   104  
   105  			assert.Equal(t, len(tc.resourceInfos), len(rsc.resourceInfos))
   106  			for expID, expRi := range tc.resourceInfos {
   107  				actRi, found := rsc.resourceInfos[expID]
   108  				if !found {
   109  					t.Errorf("expected to find id %v, but didn't", expID)
   110  				}
   111  				assert.Equal(t, expRi.ResourceAction, actRi.ResourceAction)
   112  			}
   113  		})
   114  	}
   115  }
   116  
   117  func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
   118  	testCases := map[string]struct {
   119  		resourceGroups []event.ActionGroup
   120  		statusEvent    event.StatusEvent
   121  	}{
   122  		"nil StatusEvent.Resource does not crash": {
   123  			resourceGroups: []event.ActionGroup{},
   124  			statusEvent: event.StatusEvent{
   125  				Resource: nil,
   126  			},
   127  		},
   128  		"unfound Resource identifier does not crash": {
   129  			resourceGroups: []event.ActionGroup{
   130  				{
   131  					Action:      event.ApplyAction,
   132  					Identifiers: object.ObjMetadataSet{depID},
   133  				},
   134  			},
   135  			statusEvent: event.StatusEvent{
   136  				PollResourceInfo: &pe.ResourceStatus{
   137  					Identifier: customID, // Does not match identifier in resourceGroups
   138  				},
   139  			},
   140  		},
   141  		"basic status event for applying two resources updates resourceStatus": {
   142  			resourceGroups: []event.ActionGroup{
   143  				{
   144  					Action: event.ApplyAction,
   145  					Identifiers: object.ObjMetadataSet{
   146  						depID, customID,
   147  					},
   148  				},
   149  			},
   150  			statusEvent: event.StatusEvent{
   151  				PollResourceInfo: &pe.ResourceStatus{
   152  					Identifier: depID,
   153  					Message:    testMessage,
   154  				},
   155  			},
   156  		},
   157  		"several resources for prune": {
   158  			resourceGroups: []event.ActionGroup{
   159  				{
   160  					Action: event.ApplyAction,
   161  					Identifiers: object.ObjMetadataSet{
   162  						customID,
   163  					},
   164  				},
   165  				{
   166  					Action: event.PruneAction,
   167  					Identifiers: object.ObjMetadataSet{
   168  						depID,
   169  					},
   170  				},
   171  			},
   172  			statusEvent: event.StatusEvent{
   173  				PollResourceInfo: &pe.ResourceStatus{
   174  					Identifier: depID,
   175  					Message:    testMessage,
   176  				},
   177  			},
   178  		},
   179  	}
   180  
   181  	for tn, tc := range testCases {
   182  		t.Run(tn, func(t *testing.T) {
   183  			rsc := newResourceStateCollector(tc.resourceGroups)
   184  			rsc.processStatusEvent(tc.statusEvent)
   185  			id, found := getID(tc.statusEvent)
   186  			if found {
   187  				resourceInfo, found := rsc.resourceInfos[id]
   188  				if found {
   189  					// Validate the ResourceStatus was set from StatusEvent
   190  					if resourceInfo.resourceStatus != tc.statusEvent.PollResourceInfo {
   191  						t.Errorf("status event not processed for %s", id)
   192  					}
   193  				}
   194  			}
   195  		})
   196  	}
   197  }
   198  
   199  func TestResourceStateCollector_ProcessValidationEvent(t *testing.T) {
   200  	testCases := map[string]struct {
   201  		resourceGroups []event.ActionGroup
   202  		event          event.ValidationEvent
   203  		expectedError  error
   204  	}{
   205  		"zero objects, return error": {
   206  			event: event.ValidationEvent{
   207  				Identifiers: object.ObjMetadataSet{},
   208  				Error:       errors.New("unexpected"),
   209  			},
   210  			expectedError: errors.New("invalid validation event: no identifiers: unexpected"),
   211  		},
   212  		"one object, missing namespace": {
   213  			resourceGroups: []event.ActionGroup{
   214  				{
   215  					Action:      event.ApplyAction,
   216  					Identifiers: object.ObjMetadataSet{depID},
   217  				},
   218  			},
   219  			event: event.ValidationEvent{
   220  				Identifiers: object.ObjMetadataSet{depID},
   221  				Error: validation.NewError(
   222  					field.Required(field.NewPath("metadata", "namespace"), "namespace is required"),
   223  					depID,
   224  				),
   225  			},
   226  		},
   227  		"two objects, cyclic dependency": {
   228  			event: event.ValidationEvent{
   229  				Identifiers: object.ObjMetadataSet{depID, depID2},
   230  				Error: validation.NewError(
   231  					graph.CyclicDependencyError{
   232  						Edges: []graph.Edge{
   233  							{
   234  								From: depID,
   235  								To:   depID2,
   236  							},
   237  							{
   238  								From: depID2,
   239  								To:   depID,
   240  							},
   241  						},
   242  					},
   243  					depID,
   244  					depID2,
   245  				),
   246  			},
   247  		},
   248  	}
   249  
   250  	for tn, tc := range testCases {
   251  		t.Run(tn, func(t *testing.T) {
   252  			rsc := newResourceStateCollector(tc.resourceGroups)
   253  			err := rsc.processValidationEvent(tc.event)
   254  			if tc.expectedError != nil {
   255  				assert.EqualError(t, err, tc.expectedError.Error())
   256  				return
   257  			}
   258  			for _, id := range tc.event.Identifiers {
   259  				resourceInfo, found := rsc.resourceInfos[id]
   260  				if found {
   261  					assert.Equal(t, &pe.ResourceStatus{
   262  						Identifier: id,
   263  						Status:     InvalidStatus,
   264  						Message:    tc.event.Error.Error(),
   265  					}, resourceInfo.resourceStatus)
   266  				}
   267  			}
   268  		})
   269  	}
   270  }
   271  
   272  func getID(e event.StatusEvent) (object.ObjMetadata, bool) {
   273  	if e.Resource == nil {
   274  		return object.ObjMetadata{}, false
   275  	}
   276  	return e.Identifier, true
   277  }
   278  

View as plain text