mirror of https://github.com/fluxcd/cli-utils.git
278 lines
6.5 KiB
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
|
|
}
|