cli-utils/pkg/printers/table/collector_test.go

278 lines
6.5 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package table
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/cli-utils/pkg/apply/event"
pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
"sigs.k8s.io/cli-utils/pkg/object"
"sigs.k8s.io/cli-utils/pkg/object/graph"
"sigs.k8s.io/cli-utils/pkg/object/validation"
)
var (
depID = object.ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "foo",
Namespace: "default",
}
depID2 = object.ObjMetadata{
GroupKind: schema.GroupKind{
Group: "apps",
Kind: "Deployment",
},
Name: "bar",
Namespace: "default",
}
customID = object.ObjMetadata{
GroupKind: schema.GroupKind{
Group: "custom.io",
Kind: "Custom",
},
Name: "Custom",
}
)
const testMessage = "test message for ResourceStatus"
func TestResourceStateCollector_New(t *testing.T) {
testCases := map[string]struct {
resourceGroups []event.ActionGroup
resourceInfos map[object.ObjMetadata]*ResourceInfo
}{
"no resources": {
resourceGroups: []event.ActionGroup{},
resourceInfos: map[object.ObjMetadata]*ResourceInfo{},
},
"several resources for apply": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{
depID, customID,
},
},
},
resourceInfos: map[object.ObjMetadata]*ResourceInfo{
depID: {
ResourceAction: event.ApplyAction,
},
customID: {
ResourceAction: event.ApplyAction,
},
},
},
"several resources for prune": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{
customID,
},
},
{
Action: event.PruneAction,
Identifiers: object.ObjMetadataSet{
depID,
},
},
},
resourceInfos: map[object.ObjMetadata]*ResourceInfo{
depID: {
ResourceAction: event.PruneAction,
},
customID: {
ResourceAction: event.ApplyAction,
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
rsc := newResourceStateCollector(tc.resourceGroups)
assert.Equal(t, len(tc.resourceInfos), len(rsc.resourceInfos))
for expID, expRi := range tc.resourceInfos {
actRi, found := rsc.resourceInfos[expID]
if !found {
t.Errorf("expected to find id %v, but didn't", expID)
}
assert.Equal(t, expRi.ResourceAction, actRi.ResourceAction)
}
})
}
}
func TestResourceStateCollector_ProcessStatusEvent(t *testing.T) {
testCases := map[string]struct {
resourceGroups []event.ActionGroup
statusEvent event.StatusEvent
}{
"nil StatusEvent.Resource does not crash": {
resourceGroups: []event.ActionGroup{},
statusEvent: event.StatusEvent{
Resource: nil,
},
},
"unfound Resource identifier does not crash": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{depID},
},
},
statusEvent: event.StatusEvent{
PollResourceInfo: &pe.ResourceStatus{
Identifier: customID, // Does not match identifier in resourceGroups
},
},
},
"basic status event for applying two resources updates resourceStatus": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{
depID, customID,
},
},
},
statusEvent: event.StatusEvent{
PollResourceInfo: &pe.ResourceStatus{
Identifier: depID,
Message: testMessage,
},
},
},
"several resources for prune": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{
customID,
},
},
{
Action: event.PruneAction,
Identifiers: object.ObjMetadataSet{
depID,
},
},
},
statusEvent: event.StatusEvent{
PollResourceInfo: &pe.ResourceStatus{
Identifier: depID,
Message: testMessage,
},
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
rsc := newResourceStateCollector(tc.resourceGroups)
rsc.processStatusEvent(tc.statusEvent)
id, found := getID(tc.statusEvent)
if found {
resourceInfo, found := rsc.resourceInfos[id]
if found {
// Validate the ResourceStatus was set from StatusEvent
if resourceInfo.resourceStatus != tc.statusEvent.PollResourceInfo {
t.Errorf("status event not processed for %s", id)
}
}
}
})
}
}
func TestResourceStateCollector_ProcessValidationEvent(t *testing.T) {
testCases := map[string]struct {
resourceGroups []event.ActionGroup
event event.ValidationEvent
expectedError error
}{
"zero objects, return error": {
event: event.ValidationEvent{
Identifiers: object.ObjMetadataSet{},
Error: errors.New("unexpected"),
},
expectedError: errors.New("invalid validation event: no identifiers: unexpected"),
},
"one object, missing namespace": {
resourceGroups: []event.ActionGroup{
{
Action: event.ApplyAction,
Identifiers: object.ObjMetadataSet{depID},
},
},
event: event.ValidationEvent{
Identifiers: object.ObjMetadataSet{depID},
Error: validation.NewError(
field.Required(field.NewPath("metadata", "namespace"), "namespace is required"),
depID,
),
},
},
"two objects, cyclic dependency": {
event: event.ValidationEvent{
Identifiers: object.ObjMetadataSet{depID, depID2},
Error: validation.NewError(
graph.CyclicDependencyError{
Edges: []graph.Edge{
{
From: depID,
To: depID2,
},
{
From: depID2,
To: depID,
},
},
},
depID,
depID2,
),
},
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
rsc := newResourceStateCollector(tc.resourceGroups)
err := rsc.processValidationEvent(tc.event)
if tc.expectedError != nil {
assert.EqualError(t, err, tc.expectedError.Error())
return
}
for _, id := range tc.event.Identifiers {
resourceInfo, found := rsc.resourceInfos[id]
if found {
assert.Equal(t, &pe.ResourceStatus{
Identifier: id,
Status: InvalidStatus,
Message: tc.event.Error.Error(),
}, resourceInfo.resourceStatus)
}
}
})
}
}
func getID(e event.StatusEvent) (object.ObjMetadata, bool) {
if e.Resource == nil {
return object.ObjMetadata{}, false
}
return e.Identifier, true
}