cli-utils/pkg/print/table/columndefs_test.go

240 lines
5.5 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package table
import (
"bytes"
"fmt"
"testing"
"time"
pe "github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
"github.com/fluxcd/cli-utils/pkg/object"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestColumnDefs(t *testing.T) {
testCases := map[string]struct {
columnName string
resource Resource
columnWidth int
expectedOutput string
}{
"namespace": {
columnName: "namespace",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Identifier: object.ObjMetadata{
Namespace: "Foo",
},
},
},
columnWidth: 10,
expectedOutput: "Foo",
},
"namespace trimmed": {
columnName: "namespace",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Identifier: object.ObjMetadata{
Namespace: "ICanHearTheHeartBeatingAsOne",
},
},
},
columnWidth: 10,
expectedOutput: "ICanHearTh",
},
"resource": {
columnName: "resource",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Identifier: object.ObjMetadata{
Name: "YoLaTengo",
GroupKind: schema.GroupKind{
Kind: "RoleBinding",
},
},
},
},
columnWidth: 40,
expectedOutput: "RoleBinding/YoLaTengo",
},
"resource trimmed": {
columnName: "resource",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Identifier: object.ObjMetadata{
Name: "SlantedAndEnchanted",
GroupKind: schema.GroupKind{
Kind: "Pavement",
},
},
},
},
columnWidth: 25,
expectedOutput: "Pavement/SlantedAndEnchan",
},
"status with color": {
columnName: "status",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Status: status.CurrentStatus,
},
},
columnWidth: 10,
expectedOutput: "\x1b[32mCurrent\x1b[0m",
},
"status trimmed": {
columnName: "status",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Status: status.NotFoundStatus,
},
},
columnWidth: 5,
expectedOutput: "NotFo",
},
"conditions with color": {
columnName: "conditions",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Resource: mustResourceWithConditions([]condition{
{
Type: "Ready",
Status: v1.ConditionUnknown,
},
}),
},
},
columnWidth: 10,
expectedOutput: "\x1b[33mReady\x1b[0m",
},
"conditions trimmed": {
columnName: "conditions",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Resource: mustResourceWithConditions([]condition{
{
Type: "Ready",
Status: v1.ConditionTrue,
},
{
Type: "Reconciling",
Status: v1.ConditionFalse,
},
}),
},
},
columnWidth: 10,
expectedOutput: "\x1b[32mReady\x1b[0m,\x1b[31mReco\x1b[0m",
},
"age not found": {
columnName: "age",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Resource: &unstructured.Unstructured{},
},
},
columnWidth: 10,
expectedOutput: "-",
},
"age": {
columnName: "age",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Resource: mustResourceWithCreationTimestamp(45 * time.Minute),
},
},
columnWidth: 10,
expectedOutput: "45m",
},
"message without error": {
columnName: "message",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Message: "this is a test",
},
},
columnWidth: 30,
expectedOutput: "this is a test",
},
"message from error": {
columnName: "message",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Message: "this is a test",
Error: fmt.Errorf("something went wrong somewhere"),
},
},
columnWidth: 50,
expectedOutput: "something went wrong somewhere",
},
"message trimmed": {
columnName: "message",
resource: &fakeResource{
resourceStatus: &pe.ResourceStatus{
Message: "this is a test",
},
},
columnWidth: 6,
expectedOutput: "this i",
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
columnDef := MustColumn(tc.columnName)
var buf bytes.Buffer
_, err := columnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
if err != nil {
t.Error(err)
}
if want, got := tc.expectedOutput, buf.String(); want != got {
t.Errorf("expected %q, but got %q", want, got)
}
})
}
}
type condition struct {
Type string
Status v1.ConditionStatus
}
func mustResourceWithConditions(conditions []condition) *unstructured.Unstructured {
u := &unstructured.Unstructured{
Object: make(map[string]interface{}),
}
var conditionsSlice []interface{}
for _, c := range conditions {
cond := make(map[string]interface{})
cond["type"] = c.Type
cond["status"] = string(c.Status)
conditionsSlice = append(conditionsSlice, cond)
}
err := unstructured.SetNestedSlice(u.Object, conditionsSlice,
"status", "conditions")
if err != nil {
panic(err)
}
return u
}
func mustResourceWithCreationTimestamp(age time.Duration) *unstructured.Unstructured {
u := &unstructured.Unstructured{
Object: make(map[string]interface{}),
}
creationTime := time.Now().Add(-age)
u.SetCreationTimestamp(metav1.Time{
Time: creationTime,
})
return u
}