1
2
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,
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
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