1
2
3
4
5
6
7
8
9
10
11
12
13
14 package template
15
16 import (
17 "net/url"
18 "testing"
19 "time"
20
21 "github.com/prometheus/common/model"
22 "github.com/stretchr/testify/require"
23
24 "github.com/prometheus/alertmanager/types"
25 )
26
27 func TestPairNames(t *testing.T) {
28 pairs := Pairs{
29 {"name1", "value1"},
30 {"name2", "value2"},
31 {"name3", "value3"},
32 }
33
34 expected := []string{"name1", "name2", "name3"}
35 require.EqualValues(t, expected, pairs.Names())
36 }
37
38 func TestPairValues(t *testing.T) {
39 pairs := Pairs{
40 {"name1", "value1"},
41 {"name2", "value2"},
42 {"name3", "value3"},
43 }
44
45 expected := []string{"value1", "value2", "value3"}
46 require.EqualValues(t, expected, pairs.Values())
47 }
48
49 func TestKVSortedPairs(t *testing.T) {
50 kv := KV{"d": "dVal", "b": "bVal", "c": "cVal"}
51
52 expectedPairs := Pairs{
53 {"b", "bVal"},
54 {"c", "cVal"},
55 {"d", "dVal"},
56 }
57
58 for i, p := range kv.SortedPairs() {
59 require.EqualValues(t, p.Name, expectedPairs[i].Name)
60 require.EqualValues(t, p.Value, expectedPairs[i].Value)
61 }
62
63
64 kv = KV{"d": "dVal", "b": "bVal", "c": "cVal", "alertname": "alert", "a": "aVal"}
65
66 expectedPairs = Pairs{
67 {"alertname", "alert"},
68 {"a", "aVal"},
69 {"b", "bVal"},
70 {"c", "cVal"},
71 {"d", "dVal"},
72 }
73
74 for i, p := range kv.SortedPairs() {
75 require.EqualValues(t, p.Name, expectedPairs[i].Name)
76 require.EqualValues(t, p.Value, expectedPairs[i].Value)
77 }
78 }
79
80 func TestKVRemove(t *testing.T) {
81 kv := KV{
82 "key1": "val1",
83 "key2": "val2",
84 "key3": "val3",
85 "key4": "val4",
86 }
87
88 kv = kv.Remove([]string{"key2", "key4"})
89
90 expected := []string{"key1", "key3"}
91 require.EqualValues(t, expected, kv.Names())
92 }
93
94 func TestAlertsFiring(t *testing.T) {
95 alerts := Alerts{
96 {Status: string(model.AlertFiring)},
97 {Status: string(model.AlertResolved)},
98 {Status: string(model.AlertFiring)},
99 {Status: string(model.AlertResolved)},
100 {Status: string(model.AlertResolved)},
101 }
102
103 for _, alert := range alerts.Firing() {
104 if alert.Status != string(model.AlertFiring) {
105 t.Errorf("unexpected status %q", alert.Status)
106 }
107 }
108 }
109
110 func TestAlertsResolved(t *testing.T) {
111 alerts := Alerts{
112 {Status: string(model.AlertFiring)},
113 {Status: string(model.AlertResolved)},
114 {Status: string(model.AlertFiring)},
115 {Status: string(model.AlertResolved)},
116 {Status: string(model.AlertResolved)},
117 }
118
119 for _, alert := range alerts.Resolved() {
120 if alert.Status != string(model.AlertResolved) {
121 t.Errorf("unexpected status %q", alert.Status)
122 }
123 }
124 }
125
126 func TestData(t *testing.T) {
127 u, err := url.Parse("http://example.com/")
128 require.NoError(t, err)
129 tmpl := &Template{ExternalURL: u}
130 startTime := time.Time{}.Add(1 * time.Second)
131 endTime := time.Time{}.Add(2 * time.Second)
132
133 for _, tc := range []struct {
134 receiver string
135 groupLabels model.LabelSet
136 alerts []*types.Alert
137
138 exp *Data
139 }{
140 {
141 receiver: "webhook",
142 exp: &Data{
143 Receiver: "webhook",
144 Status: "resolved",
145 Alerts: Alerts{},
146 GroupLabels: KV{},
147 CommonLabels: KV{},
148 CommonAnnotations: KV{},
149 ExternalURL: u.String(),
150 },
151 },
152 {
153 receiver: "webhook",
154 groupLabels: model.LabelSet{
155 model.LabelName("job"): model.LabelValue("foo"),
156 },
157 alerts: []*types.Alert{
158 {
159 Alert: model.Alert{
160 StartsAt: startTime,
161 Labels: model.LabelSet{
162 model.LabelName("severity"): model.LabelValue("warning"),
163 model.LabelName("job"): model.LabelValue("foo"),
164 },
165 Annotations: model.LabelSet{
166 model.LabelName("description"): model.LabelValue("something happened"),
167 model.LabelName("runbook"): model.LabelValue("foo"),
168 },
169 },
170 },
171 {
172 Alert: model.Alert{
173 StartsAt: startTime,
174 EndsAt: endTime,
175 Labels: model.LabelSet{
176 model.LabelName("severity"): model.LabelValue("critical"),
177 model.LabelName("job"): model.LabelValue("foo"),
178 },
179 Annotations: model.LabelSet{
180 model.LabelName("description"): model.LabelValue("something else happened"),
181 model.LabelName("runbook"): model.LabelValue("foo"),
182 },
183 },
184 },
185 },
186 exp: &Data{
187 Receiver: "webhook",
188 Status: "firing",
189 Alerts: Alerts{
190 {
191 Status: "firing",
192 Labels: KV{"severity": "warning", "job": "foo"},
193 Annotations: KV{"description": "something happened", "runbook": "foo"},
194 StartsAt: startTime,
195 Fingerprint: "9266ef3da838ad95",
196 },
197 {
198 Status: "resolved",
199 Labels: KV{"severity": "critical", "job": "foo"},
200 Annotations: KV{"description": "something else happened", "runbook": "foo"},
201 StartsAt: startTime,
202 EndsAt: endTime,
203 Fingerprint: "3b15fd163d36582e",
204 },
205 },
206 GroupLabels: KV{"job": "foo"},
207 CommonLabels: KV{"job": "foo"},
208 CommonAnnotations: KV{"runbook": "foo"},
209 ExternalURL: u.String(),
210 },
211 },
212 {
213 receiver: "webhook",
214 groupLabels: model.LabelSet{},
215 alerts: []*types.Alert{
216 {
217 Alert: model.Alert{
218 StartsAt: startTime,
219 Labels: model.LabelSet{
220 model.LabelName("severity"): model.LabelValue("warning"),
221 model.LabelName("job"): model.LabelValue("foo"),
222 },
223 Annotations: model.LabelSet{
224 model.LabelName("description"): model.LabelValue("something happened"),
225 model.LabelName("runbook"): model.LabelValue("foo"),
226 },
227 },
228 },
229 {
230 Alert: model.Alert{
231 StartsAt: startTime,
232 EndsAt: endTime,
233 Labels: model.LabelSet{
234 model.LabelName("severity"): model.LabelValue("critical"),
235 model.LabelName("job"): model.LabelValue("bar"),
236 },
237 Annotations: model.LabelSet{
238 model.LabelName("description"): model.LabelValue("something else happened"),
239 model.LabelName("runbook"): model.LabelValue("bar"),
240 },
241 },
242 },
243 },
244 exp: &Data{
245 Receiver: "webhook",
246 Status: "firing",
247 Alerts: Alerts{
248 {
249 Status: "firing",
250 Labels: KV{"severity": "warning", "job": "foo"},
251 Annotations: KV{"description": "something happened", "runbook": "foo"},
252 StartsAt: startTime,
253 Fingerprint: "9266ef3da838ad95",
254 },
255 {
256 Status: "resolved",
257 Labels: KV{"severity": "critical", "job": "bar"},
258 Annotations: KV{"description": "something else happened", "runbook": "bar"},
259 StartsAt: startTime,
260 EndsAt: endTime,
261 Fingerprint: "c7e68cb08e3e67f9",
262 },
263 },
264 GroupLabels: KV{},
265 CommonLabels: KV{},
266 CommonAnnotations: KV{},
267 ExternalURL: u.String(),
268 },
269 },
270 } {
271 tc := tc
272 t.Run("", func(t *testing.T) {
273 got := tmpl.Data(tc.receiver, tc.groupLabels, tc.alerts...)
274 require.Equal(t, tc.exp, got)
275 })
276 }
277 }
278
279 func TestTemplateExpansion(t *testing.T) {
280 tmpl, err := FromGlobs()
281 require.NoError(t, err)
282
283 for _, tc := range []struct {
284 title string
285 in string
286 data interface{}
287 html bool
288
289 exp string
290 fail bool
291 }{
292 {
293 title: "Template without action",
294 in: `abc`,
295 exp: "abc",
296 },
297 {
298 title: "Template with simple action",
299 in: `{{ "abc" }}`,
300 exp: "abc",
301 },
302 {
303 title: "Template with invalid syntax",
304 in: `{{ `,
305 fail: true,
306 },
307 {
308 title: "Template using toUpper",
309 in: `{{ "abc" | toUpper }}`,
310 exp: "ABC",
311 },
312 {
313 title: "Template using toLower",
314 in: `{{ "ABC" | toLower }}`,
315 exp: "abc",
316 },
317 {
318 title: "Template using title",
319 in: `{{ "abc" | title }}`,
320 exp: "Abc",
321 },
322 {
323 title: "Template using positive match",
324 in: `{{ if match "^a" "abc"}}abc{{ end }}`,
325 exp: "abc",
326 },
327 {
328 title: "Template using negative match",
329 in: `{{ if match "abcd" "abc" }}abc{{ end }}`,
330 exp: "",
331 },
332 {
333 title: "Template using join",
334 in: `{{ . | join "," }}`,
335 data: []string{"a", "b", "c"},
336 exp: "a,b,c",
337 },
338 {
339 title: "Text template without HTML escaping",
340 in: `{{ "<b>" }}`,
341 exp: "<b>",
342 },
343 {
344 title: "HTML template with escaping",
345 in: `{{ "<b>" }}`,
346 html: true,
347 exp: "<b>",
348 },
349 {
350 title: "HTML template using safeHTML",
351 in: `{{ "<b>" | safeHtml }}`,
352 html: true,
353 exp: "<b>",
354 },
355 {
356 title: "Template using reReplaceAll",
357 in: `{{ reReplaceAll "ab" "AB" "abcdabcda"}}`,
358 exp: "ABcdABcda",
359 },
360 {
361 title: "Template using stringSlice",
362 in: `{{ with .GroupLabels }}{{ with .Remove (stringSlice "key1" "key3") }}{{ .SortedPairs.Values }}{{ end }}{{ end }}`,
363 data: Data{
364 GroupLabels: KV{
365 "key1": "key1",
366 "key2": "key2",
367 "key3": "key3",
368 "key4": "key4",
369 },
370 },
371 exp: "[key2 key4]",
372 },
373 } {
374 tc := tc
375 t.Run(tc.title, func(t *testing.T) {
376 f := tmpl.ExecuteTextString
377 if tc.html {
378 f = tmpl.ExecuteHTMLString
379 }
380 got, err := f(tc.in, tc.data)
381 if tc.fail {
382 require.NotNil(t, err)
383 return
384 }
385 require.NoError(t, err)
386 require.Equal(t, tc.exp, got)
387 })
388 }
389 }
390
View as plain text