1package v2
2
3import (
4	"encoding/json"
5	"sort"
6	"testing"
7	time "time"
8
9	"github.com/stretchr/testify/assert"
10	"github.com/stretchr/testify/require"
11)
12
13func TestFixtureEventIsValid(t *testing.T) {
14	e := FixtureEvent("entity", "check")
15	assert.NotNil(t, e)
16	assert.NotNil(t, e.Entity)
17	assert.NotNil(t, e.Check)
18}
19
20func TestEventValidate(t *testing.T) {
21	event := FixtureEvent("entity", "check")
22
23	event.Check.Name = ""
24	assert.Error(t, event.Validate())
25	event.Check.Name = "check"
26
27	event.Entity.Name = ""
28	assert.Error(t, event.Validate())
29	event.Entity.Name = "entity"
30
31	assert.NoError(t, event.Validate())
32}
33
34func TestEventValidateNoTimestamp(t *testing.T) {
35	// Events without a timestamp are valid
36	event := FixtureEvent("entity", "check")
37	event.Timestamp = 0
38	if err := event.Validate(); err != nil {
39		t.Fatal(err)
40	}
41}
42
43func TestMarshalJSON(t *testing.T) {
44	event := FixtureEvent("entity", "check")
45	_, err := json.Marshal(event)
46	require.NoError(t, err)
47}
48
49func TestEventHasMetrics(t *testing.T) {
50	testCases := []struct {
51		name     string
52		metrics  *Metrics
53		expected bool
54	}{
55		{
56			name:     "No Metrics",
57			metrics:  nil,
58			expected: false,
59		},
60		{
61			name:     "Metrics",
62			metrics:  &Metrics{},
63			expected: true,
64		},
65	}
66
67	for _, tc := range testCases {
68		t.Run(tc.name, func(t *testing.T) {
69			event := &Event{
70				Metrics: tc.metrics,
71			}
72			metrics := event.HasMetrics()
73			assert.Equal(t, tc.expected, metrics)
74		})
75	}
76}
77
78func TestEventIsIncident(t *testing.T) {
79	testCases := []struct {
80		name     string
81		status   uint32
82		expected bool
83	}{
84		{
85			name:     "OK Status",
86			status:   0,
87			expected: false,
88		},
89		{
90			name:     "Non-zero Status",
91			status:   1,
92			expected: true,
93		},
94	}
95
96	for _, tc := range testCases {
97		t.Run(tc.name, func(t *testing.T) {
98			event := &Event{
99				Check: &Check{
100					Status: tc.status,
101				},
102			}
103			incident := event.IsIncident()
104			assert.Equal(t, tc.expected, incident)
105		})
106	}
107}
108
109func TestEventIsResolution(t *testing.T) {
110	testCases := []struct {
111		name     string
112		history  []CheckHistory
113		status   uint32
114		expected bool
115	}{
116		{
117			name:     "check has no history",
118			history:  []CheckHistory{CheckHistory{}},
119			status:   0,
120			expected: false,
121		},
122		{
123			name: "check has not transitioned",
124			history: []CheckHistory{
125				CheckHistory{Status: 1},
126				CheckHistory{Status: 0},
127			},
128			status:   0,
129			expected: true,
130		},
131		{
132			name: "check has just transitioned",
133			history: []CheckHistory{
134				CheckHistory{Status: 0},
135				CheckHistory{Status: 1},
136			},
137			status:   0,
138			expected: false,
139		},
140		{
141			name: "check has transitioned but still an incident",
142			history: []CheckHistory{
143				CheckHistory{Status: 2},
144				CheckHistory{Status: 1},
145			},
146			status:   1,
147			expected: false,
148		},
149	}
150
151	for _, tc := range testCases {
152		t.Run(tc.name, func(t *testing.T) {
153			event := &Event{
154				Check: &Check{
155					History: tc.history,
156					Status:  tc.status,
157				},
158			}
159			resolution := event.IsResolution()
160			assert.Equal(t, tc.expected, resolution)
161		})
162	}
163}
164
165func TestEventIsSilenced(t *testing.T) {
166	testCases := []struct {
167		name     string
168		event    *Event
169		silenced []string
170		expected bool
171	}{
172		{
173			name:     "No silenced entries",
174			event:    FixtureEvent("entity1", "check1"),
175			silenced: []string{},
176			expected: false,
177		},
178		{
179			name:     "Silenced entry",
180			event:    FixtureEvent("entity1", "check1"),
181			silenced: []string{"entity1"},
182			expected: true,
183		},
184		{
185			name:     "Metric without a check",
186			event:    &Event{},
187			silenced: []string{"entity1"},
188			expected: false,
189		},
190	}
191
192	for _, tc := range testCases {
193		t.Run(tc.name, func(t *testing.T) {
194			if tc.event.Check != nil {
195				tc.event.Check.Silenced = tc.silenced
196			}
197			silenced := tc.event.IsSilenced()
198			assert.Equal(t, tc.expected, silenced)
199		})
200	}
201}
202
203func TestEventsBySeverity(t *testing.T) {
204	critical := FixtureEvent("entity", "check")
205	critical.Check.Status = 2 // crit
206	warn := FixtureEvent("entity", "check")
207	warn.Check.Status = 1 // warn
208	unknown := FixtureEvent("entity", "check")
209	unknown.Check.Status = 3 // unknown
210	ok := FixtureEvent("entity", "check")
211	ok.Check.Status = 0 // ok
212	ok.Check.LastOK = 42
213	okOlder := FixtureEvent("entity", "check")
214	okOlder.Check.Status = 0 // ok
215	okOlder.Check.LastOK = 7
216	okOlderDiff := FixtureEvent("entity", "check")
217	okOlderDiff.Check.Status = 0 // ok
218	okOlderDiff.Check.LastOK = 7
219	okOlderDiff.Entity.Name = "zzz"
220	noCheck := FixtureEvent("entity", "check")
221	noCheck.Check = nil
222
223	testCases := []struct {
224		name     string
225		input    []*Event
226		expected []*Event
227	}{
228		{
229			name:     "Sorts by severity",
230			input:    []*Event{ok, warn, unknown, noCheck, okOlderDiff, okOlder, critical},
231			expected: []*Event{critical, warn, unknown, ok, okOlder, okOlderDiff, noCheck},
232		},
233		{
234			name:     "Fallback to lastOK when severity is same",
235			input:    []*Event{okOlder, ok, okOlder},
236			expected: []*Event{ok, okOlder, okOlder},
237		},
238		{
239			name:     "Fallback to entity name when severity is same",
240			input:    []*Event{okOlderDiff, okOlder, ok, okOlder},
241			expected: []*Event{ok, okOlder, okOlder, okOlderDiff},
242		},
243		{
244			name:     "Events w/o a check are sorted to end",
245			input:    []*Event{critical, noCheck, ok},
246			expected: []*Event{critical, ok, noCheck},
247		},
248	}
249
250	for _, tc := range testCases {
251		t.Run(tc.name, func(t *testing.T) {
252			sort.Sort(EventsBySeverity(tc.input))
253			assert.EqualValues(t, tc.expected, tc.input)
254		})
255	}
256}
257
258func TestEventsByLastOk(t *testing.T) {
259	incident := FixtureEvent("zeta", "check")
260	incident.Check.Status = 2 // crit
261	incidentNewer := FixtureEvent("zeta", "check")
262	incidentNewer.Check.Status = 2 // crit
263	incidentNewer.Check.LastOK = 1
264	ok := FixtureEvent("zeta", "check")
265	ok.Check.Status = 0 // ok
266	okNewer := FixtureEvent("zeta", "check")
267	okNewer.Check.Status = 0 // ok
268	okNewer.Check.LastOK = 1
269	okDiffEntity := FixtureEvent("abba", "check")
270	okDiffEntity.Check.Status = 0 // ok
271	okDiffCheck := FixtureEvent("abba", "0bba")
272	okDiffCheck.Check.Status = 0 // ok
273
274	testCases := []struct {
275		name     string
276		input    []*Event
277		expected []*Event
278	}{
279		{
280			name:     "Sorts by lastOK",
281			input:    []*Event{ok, okNewer, incidentNewer, incident},
282			expected: []*Event{incidentNewer, incident, okNewer, ok},
283		},
284		{
285			name:     "incidents are sorted to the top",
286			input:    []*Event{okNewer, incidentNewer, ok, incident},
287			expected: []*Event{incidentNewer, incident, okNewer, ok},
288		},
289		{
290			name:     "Fallback to entity & check name when severity is same",
291			input:    []*Event{ok, okNewer, okDiffCheck, okDiffEntity},
292			expected: []*Event{okNewer, okDiffCheck, okDiffEntity, ok},
293		},
294	}
295
296	for _, tc := range testCases {
297		t.Run(tc.name, func(t *testing.T) {
298			sort.Sort(EventsByLastOk(tc.input))
299			assert.EqualValues(t, tc.expected, tc.input)
300		})
301	}
302}
303
304func TestEventsByTimestamp(t *testing.T) {
305	old := &Event{Timestamp: 3}
306	older := &Event{Timestamp: 2}
307	oldest := &Event{Timestamp: 1}
308	okButHow := &Event{Timestamp: 0}
309
310	testCases := []struct {
311		name     string
312		inEvents []*Event
313		inDir    bool
314		expected []*Event
315	}{
316		{
317			name:     "Sorts ascending",
318			inDir:    false,
319			inEvents: []*Event{old, okButHow, oldest, older},
320			expected: []*Event{okButHow, oldest, older, old},
321		},
322		{
323			name:     "Sorts descending",
324			inDir:    true,
325			inEvents: []*Event{old, okButHow, oldest, older},
326			expected: []*Event{old, older, oldest, okButHow},
327		},
328	}
329
330	for _, tc := range testCases {
331		t.Run(tc.name, func(t *testing.T) {
332			sort.Sort(EventsByTimestamp(tc.inEvents, tc.inDir))
333			assert.EqualValues(t, tc.expected, tc.inEvents)
334		})
335	}
336}
337
338func TestSilencedBy(t *testing.T) {
339	testCases := []struct {
340		name            string
341		event           *Event
342		entries         []*Silenced
343		expectedEntries []*Silenced
344	}{
345		{
346			name:            "no entries",
347			event:           FixtureEvent("foo", "check_cpu"),
348			entries:         []*Silenced{},
349			expectedEntries: []*Silenced{},
350		},
351		{
352			name:  "not silenced",
353			event: FixtureEvent("foo", "check_cpu"),
354			entries: []*Silenced{
355				FixtureSilenced("entity:foo:check_mem"),
356				FixtureSilenced("entity:bar:*"),
357				FixtureSilenced("foo:check_cpu"),
358				FixtureSilenced("foo:*"),
359				FixtureSilenced("*:check_mem"),
360			},
361			expectedEntries: []*Silenced{},
362		},
363		{
364			name:  "silenced by check",
365			event: FixtureEvent("foo", "check_cpu"),
366			entries: []*Silenced{
367				FixtureSilenced("*:check_cpu"),
368			},
369			expectedEntries: []*Silenced{
370				FixtureSilenced("*:check_cpu"),
371			},
372		},
373		{
374			name:  "silenced by entity subscription",
375			event: FixtureEvent("foo", "check_cpu"),
376			entries: []*Silenced{
377				FixtureSilenced("entity:foo:*"),
378			},
379			expectedEntries: []*Silenced{
380				FixtureSilenced("entity:foo:*"),
381			},
382		},
383		{
384			name:  "silenced by entity's check subscription",
385			event: FixtureEvent("foo", "check_cpu"),
386			entries: []*Silenced{
387				FixtureSilenced("entity:foo:check_cpu"),
388			},
389			expectedEntries: []*Silenced{
390				FixtureSilenced("entity:foo:check_cpu"),
391			},
392		},
393		{
394			name:  "silenced by check subscription",
395			event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
396			entries: []*Silenced{
397				FixtureSilenced("linux:*"),
398			},
399			expectedEntries: []*Silenced{
400				FixtureSilenced("linux:*"),
401			},
402		},
403		{
404			name:  "silenced by subscription with check",
405			event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
406			entries: []*Silenced{
407				FixtureSilenced("linux:check_cpu"),
408			},
409			expectedEntries: []*Silenced{
410				FixtureSilenced("linux:check_cpu"),
411			},
412		},
413		{
414			name:  "silenced by multiple entries",
415			event: FixtureEvent("foo", "check_cpu"), // has a linux subscription
416			entries: []*Silenced{
417				FixtureSilenced("entity:foo:*"),
418				FixtureSilenced("linux:check_cpu"),
419			},
420			expectedEntries: []*Silenced{
421				FixtureSilenced("entity:foo:*"),
422				FixtureSilenced("linux:check_cpu"),
423			},
424		},
425		{
426			name: "not silenced, silenced & client don't have a common subscription",
427			event: &Event{
428				Check: &Check{
429					ObjectMeta: ObjectMeta{
430						Name: "check_cpu",
431					},
432					Subscriptions: []string{"linux", "windows"},
433				},
434				Entity: &Entity{
435					ObjectMeta: ObjectMeta{
436						Name: "foo",
437					},
438					Subscriptions: []string{"linux"},
439				},
440			},
441			entries: []*Silenced{
442				FixtureSilenced("windows:check_cpu"),
443			},
444			expectedEntries: []*Silenced{},
445		},
446		{
447			name: "silenced, silenced & client do have a common subscription",
448			event: &Event{
449				Check: &Check{
450					ObjectMeta: ObjectMeta{
451						Name: "check_cpu",
452					},
453					Subscriptions: []string{"linux", "windows"},
454				},
455				Entity: &Entity{
456					ObjectMeta: ObjectMeta{
457						Name: "foo",
458					},
459					Subscriptions: []string{"linux"},
460				},
461			},
462			entries: []*Silenced{
463				FixtureSilenced("linux:check_cpu"),
464			},
465			expectedEntries: []*Silenced{
466				FixtureSilenced("linux:check_cpu"),
467			},
468		},
469	}
470
471	for _, tc := range testCases {
472		t.Run(tc.name, func(t *testing.T) {
473			result := tc.event.SilencedBy(tc.entries)
474			assert.EqualValues(t, tc.expectedEntries, result)
475		})
476	}
477}
478
479func TestIsSilencedBy(t *testing.T) {
480	testCases := []struct {
481		name           string
482		event          *Event
483		silence        *Silenced
484		expectedResult bool
485	}{
486		{
487			name:  "silence has not started",
488			event: FixtureEvent("foo", "check_cpu"),
489			silence: &Silenced{
490				ObjectMeta: ObjectMeta{
491					Name: "*:check_cpu",
492				},
493				Begin: time.Now().Add(1 * time.Hour).Unix(),
494			},
495			expectedResult: false,
496		},
497		{
498			name:           "check matches w/ wildcard subscription",
499			event:          FixtureEvent("foo", "check_cpu"),
500			silence:        FixtureSilenced("*:check_cpu"),
501			expectedResult: true,
502		},
503		{
504			name:           "entity subscription matches w/ wildcard check",
505			event:          FixtureEvent("foo", "check_cpu"),
506			silence:        FixtureSilenced("entity:foo:*"),
507			expectedResult: true,
508		},
509		{
510			name:           "entity subscription and check match",
511			event:          FixtureEvent("foo", "check_cpu"),
512			silence:        FixtureSilenced("entity:foo:check_cpu"),
513			expectedResult: true,
514		},
515		{
516			name: "subscription matches",
517			event: &Event{
518				Check: &Check{
519					ObjectMeta: ObjectMeta{
520						Name: "check_cpu",
521					},
522					Subscriptions: []string{"unix"},
523				},
524				Entity: &Entity{
525					ObjectMeta: ObjectMeta{
526						Name: "foo",
527					},
528					Subscriptions: []string{"unix"},
529				},
530			},
531			silence:        FixtureSilenced("unix:check_cpu"),
532			expectedResult: true,
533		},
534		{
535			name: "subscription does not match",
536			event: &Event{
537				Check: &Check{
538					ObjectMeta: ObjectMeta{
539						Name: "check_cpu",
540					},
541					Subscriptions: []string{"unix"},
542				},
543				Entity: &Entity{
544					ObjectMeta: ObjectMeta{
545						Name: "foo",
546					},
547					Subscriptions: []string{"unix"},
548				},
549			},
550			silence:        FixtureSilenced("windows:check_cpu"),
551			expectedResult: false,
552		},
553		{
554			name: "entity subscription doesn't match",
555			event: &Event{
556				Check: &Check{
557					ObjectMeta: ObjectMeta{
558						Name: "check_cpu",
559					},
560					Subscriptions: []string{"unix"},
561				},
562				Entity: &Entity{
563					ObjectMeta: ObjectMeta{
564						Name: "foo",
565					},
566					Subscriptions: []string{"windows"},
567				},
568			},
569			silence:        FixtureSilenced("check:check_cpu"),
570			expectedResult: false,
571		},
572		{
573			name:           "check does not match",
574			event:          FixtureEvent("foo", "check_mem"),
575			silence:        FixtureSilenced("*:check_cpu"),
576			expectedResult: false,
577		},
578	}
579
580	for _, tc := range testCases {
581		t.Run(tc.name, func(t *testing.T) {
582			result := tc.event.IsSilencedBy(tc.silence)
583			assert.EqualValues(t, tc.expectedResult, result)
584		})
585	}
586}
587