1
2
3
4 package table
5
6 import (
7 "bytes"
8 "fmt"
9 "testing"
10 "time"
11
12 v1 "k8s.io/api/core/v1"
13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15 "k8s.io/apimachinery/pkg/runtime/schema"
16 pe "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
17 "sigs.k8s.io/cli-utils/pkg/kstatus/status"
18 "sigs.k8s.io/cli-utils/pkg/object"
19 )
20
21 func TestColumnDefs(t *testing.T) {
22 testCases := map[string]struct {
23 columnName string
24 resource Resource
25 columnWidth int
26 expectedOutput string
27 }{
28 "namespace": {
29 columnName: "namespace",
30 resource: &fakeResource{
31 resourceStatus: &pe.ResourceStatus{
32 Identifier: object.ObjMetadata{
33 Namespace: "Foo",
34 },
35 },
36 },
37 columnWidth: 10,
38 expectedOutput: "Foo",
39 },
40 "namespace trimmed": {
41 columnName: "namespace",
42 resource: &fakeResource{
43 resourceStatus: &pe.ResourceStatus{
44 Identifier: object.ObjMetadata{
45 Namespace: "ICanHearTheHeartBeatingAsOne",
46 },
47 },
48 },
49 columnWidth: 10,
50 expectedOutput: "ICanHearTh",
51 },
52 "resource": {
53 columnName: "resource",
54 resource: &fakeResource{
55 resourceStatus: &pe.ResourceStatus{
56 Identifier: object.ObjMetadata{
57 Name: "YoLaTengo",
58 GroupKind: schema.GroupKind{
59 Kind: "RoleBinding",
60 },
61 },
62 },
63 },
64 columnWidth: 40,
65 expectedOutput: "RoleBinding/YoLaTengo",
66 },
67 "resource trimmed": {
68 columnName: "resource",
69 resource: &fakeResource{
70 resourceStatus: &pe.ResourceStatus{
71 Identifier: object.ObjMetadata{
72 Name: "SlantedAndEnchanted",
73 GroupKind: schema.GroupKind{
74 Kind: "Pavement",
75 },
76 },
77 },
78 },
79 columnWidth: 25,
80 expectedOutput: "Pavement/SlantedAndEnchan",
81 },
82 "status with color": {
83 columnName: "status",
84 resource: &fakeResource{
85 resourceStatus: &pe.ResourceStatus{
86 Status: status.CurrentStatus,
87 },
88 },
89 columnWidth: 10,
90 expectedOutput: "\x1b[32mCurrent\x1b[0m",
91 },
92 "status trimmed": {
93 columnName: "status",
94 resource: &fakeResource{
95 resourceStatus: &pe.ResourceStatus{
96 Status: status.NotFoundStatus,
97 },
98 },
99 columnWidth: 5,
100 expectedOutput: "NotFo",
101 },
102 "conditions with color": {
103 columnName: "conditions",
104 resource: &fakeResource{
105 resourceStatus: &pe.ResourceStatus{
106 Resource: mustResourceWithConditions([]condition{
107 {
108 Type: "Ready",
109 Status: v1.ConditionUnknown,
110 },
111 }),
112 },
113 },
114 columnWidth: 10,
115 expectedOutput: "\x1b[33mReady\x1b[0m",
116 },
117 "conditions trimmed": {
118 columnName: "conditions",
119 resource: &fakeResource{
120 resourceStatus: &pe.ResourceStatus{
121 Resource: mustResourceWithConditions([]condition{
122 {
123 Type: "Ready",
124 Status: v1.ConditionTrue,
125 },
126 {
127 Type: "Reconciling",
128 Status: v1.ConditionFalse,
129 },
130 }),
131 },
132 },
133 columnWidth: 10,
134 expectedOutput: "\x1b[32mReady\x1b[0m,\x1b[31mReco\x1b[0m",
135 },
136 "age not found": {
137 columnName: "age",
138 resource: &fakeResource{
139 resourceStatus: &pe.ResourceStatus{
140 Resource: &unstructured.Unstructured{},
141 },
142 },
143 columnWidth: 10,
144 expectedOutput: "-",
145 },
146 "age": {
147 columnName: "age",
148 resource: &fakeResource{
149 resourceStatus: &pe.ResourceStatus{
150 Resource: mustResourceWithCreationTimestamp(45 * time.Minute),
151 },
152 },
153 columnWidth: 10,
154 expectedOutput: "45m",
155 },
156 "message without error": {
157 columnName: "message",
158 resource: &fakeResource{
159 resourceStatus: &pe.ResourceStatus{
160 Message: "this is a test",
161 },
162 },
163 columnWidth: 30,
164 expectedOutput: "this is a test",
165 },
166 "message from error": {
167 columnName: "message",
168 resource: &fakeResource{
169 resourceStatus: &pe.ResourceStatus{
170 Message: "this is a test",
171 Error: fmt.Errorf("something went wrong somewhere"),
172 },
173 },
174 columnWidth: 50,
175 expectedOutput: "something went wrong somewhere",
176 },
177 "message trimmed": {
178 columnName: "message",
179 resource: &fakeResource{
180 resourceStatus: &pe.ResourceStatus{
181 Message: "this is a test",
182 },
183 },
184 columnWidth: 6,
185 expectedOutput: "this i",
186 },
187 }
188
189 for tn, tc := range testCases {
190 t.Run(tn, func(t *testing.T) {
191 columnDef := MustColumn(tc.columnName)
192
193 var buf bytes.Buffer
194 _, err := columnDef.PrintResource(&buf, tc.columnWidth, tc.resource)
195 if err != nil {
196 t.Error(err)
197 }
198
199 if want, got := tc.expectedOutput, buf.String(); want != got {
200 t.Errorf("expected %q, but got %q", want, got)
201 }
202 })
203 }
204 }
205
206 type condition struct {
207 Type string
208 Status v1.ConditionStatus
209 }
210
211 func mustResourceWithConditions(conditions []condition) *unstructured.Unstructured {
212 u := &unstructured.Unstructured{
213 Object: make(map[string]interface{}),
214 }
215 var conditionsSlice []interface{}
216 for _, c := range conditions {
217 cond := make(map[string]interface{})
218 cond["type"] = c.Type
219 cond["status"] = string(c.Status)
220 conditionsSlice = append(conditionsSlice, cond)
221 }
222 err := unstructured.SetNestedSlice(u.Object, conditionsSlice,
223 "status", "conditions")
224 if err != nil {
225 panic(err)
226 }
227 return u
228 }
229
230 func mustResourceWithCreationTimestamp(age time.Duration) *unstructured.Unstructured {
231 u := &unstructured.Unstructured{
232 Object: make(map[string]interface{}),
233 }
234 creationTime := time.Now().Add(-age)
235 u.SetCreationTimestamp(metav1.Time{
236 Time: creationTime,
237 })
238 return u
239 }
240
View as plain text