2022-04-27 15:15:41 -05:00
package definitions
import (
2023-02-02 15:26:39 -05:00
"errors"
2022-04-27 15:15:41 -05:00
"testing"
2022-05-26 14:24:34 -05:00
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/timeinterval"
2022-04-27 15:15:41 -05:00
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
)
func TestValidateRoutes ( t * testing . T ) {
zero := model . Duration ( 0 )
type testCase struct {
desc string
route Route
expMsg string
}
t . Run ( "valid route" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "empty" ,
route : Route { } ,
} ,
{
desc : "simple" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
} ,
} ,
{
desc : "nested" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
Routes : [ ] * Route {
{
Receiver : "bar" ,
} ,
} ,
} ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
2024-03-06 20:48:32 +00:00
err := c . route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . NoError ( t , err )
} )
}
} )
t . Run ( "invalid route" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "zero group interval" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
GroupInterval : & zero ,
} ,
expMsg : "group_interval cannot be zero" ,
} ,
{
desc : "zero repeat interval" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
RepeatInterval : & zero ,
} ,
expMsg : "repeat_interval cannot be zero" ,
} ,
{
desc : "duplicated label" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string {
"abc" ,
"abc" ,
} ,
} ,
expMsg : "duplicated label" ,
} ,
{
desc : "wildcard and non-wildcard label simultaneously" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string {
"..." ,
"abc" ,
} ,
} ,
expMsg : "cannot have wildcard" ,
} ,
{
desc : "valid with nested invalid" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
Routes : [ ] * Route {
{
GroupByStr : [ ] string {
"abc" ,
"abc" ,
} ,
} ,
} ,
} ,
expMsg : "duplicated label" ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
2024-03-06 20:48:32 +00:00
err := c . route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , c . expMsg )
} )
}
} )
t . Run ( "route validator normalizes group_by" , func ( t * testing . T ) {
t . Run ( "when grouping normally" , func ( t * testing . T ) {
route := Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "abc" , "def" } ,
}
2024-03-06 20:48:32 +00:00
_ = route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . False ( t , route . GroupByAll )
require . Equal ( t , [ ] model . LabelName { "abc" , "def" } , route . GroupBy )
} )
t . Run ( "when grouping by wildcard, nil" , func ( t * testing . T ) {
route := Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
}
2024-03-06 20:48:32 +00:00
_ = route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . True ( t , route . GroupByAll )
require . Nil ( t , route . GroupBy )
} )
t . Run ( "idempotently" , func ( t * testing . T ) {
route := Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "abc" , "def" } ,
}
2024-03-06 20:48:32 +00:00
err := route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . NoError ( t , err )
2024-03-06 20:48:32 +00:00
err = route . ValidateChild ( )
2022-04-27 15:15:41 -05:00
require . NoError ( t , err )
require . False ( t , route . GroupByAll )
require . Equal ( t , [ ] model . LabelName { "abc" , "def" } , route . GroupBy )
} )
} )
t . Run ( "valid root route" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "simple" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
} ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
err := c . route . Validate ( )
require . NoError ( t , err )
} )
}
} )
t . Run ( "invalid root route" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "no receiver" ,
route : Route {
GroupByStr : [ ] string { "..." } ,
} ,
expMsg : "must specify a default receiver" ,
} ,
{
desc : "exact matchers present" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
Match : map [ string ] string {
"abc" : "def" ,
} ,
} ,
expMsg : "must not have any matchers" ,
} ,
{
desc : "regex matchers present" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
Match : map [ string ] string {
"abc" : "def" ,
} ,
} ,
expMsg : "must not have any matchers" ,
} ,
{
desc : "mute time intervals present" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "..." } ,
MuteTimeIntervals : [ ] string { "10" } ,
} ,
expMsg : "must not have any mute time intervals" ,
} ,
{
desc : "validation error that is not specific to root" ,
route : Route {
Receiver : "foo" ,
GroupByStr : [ ] string { "abc" , "abc" } ,
} ,
expMsg : "duplicated label" ,
} ,
{
desc : "nested validation error that is not specific to root" ,
route : Route {
Receiver : "foo" ,
Routes : [ ] * Route {
{
GroupByStr : [ ] string { "abc" , "abc" } ,
} ,
} ,
} ,
expMsg : "duplicated label" ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
err := c . route . Validate ( )
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , c . expMsg )
} )
}
} )
}
2022-05-26 14:24:34 -05:00
func TestValidateMuteTimeInterval ( t * testing . T ) {
type testCase struct {
desc string
mti MuteTimeInterval
expMsg string
}
t . Run ( "valid interval" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "nil intervals" ,
mti : MuteTimeInterval {
MuteTimeInterval : config . MuteTimeInterval {
Name : "interval" ,
} ,
} ,
} ,
{
desc : "empty intervals" ,
mti : MuteTimeInterval {
MuteTimeInterval : config . MuteTimeInterval {
Name : "interval" ,
TimeIntervals : [ ] timeinterval . TimeInterval { } ,
} ,
} ,
} ,
{
desc : "blank interval" ,
mti : MuteTimeInterval {
MuteTimeInterval : config . MuteTimeInterval {
Name : "interval" ,
TimeIntervals : [ ] timeinterval . TimeInterval {
{ } ,
} ,
} ,
} ,
} ,
{
desc : "simple" ,
mti : MuteTimeInterval {
MuteTimeInterval : config . MuteTimeInterval {
Name : "interval" ,
TimeIntervals : [ ] timeinterval . TimeInterval {
{
Weekdays : [ ] timeinterval . WeekdayRange {
{
InclusiveRange : timeinterval . InclusiveRange {
Begin : 1 ,
End : 2 ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
err := c . mti . Validate ( )
require . NoError ( t , err )
} )
}
} )
t . Run ( "invalid interval" , func ( t * testing . T ) {
cases := [ ] testCase {
{
desc : "empty" ,
mti : MuteTimeInterval { } ,
expMsg : "missing name" ,
} ,
{
desc : "empty" ,
mti : MuteTimeInterval {
MuteTimeInterval : config . MuteTimeInterval {
Name : "interval" ,
TimeIntervals : [ ] timeinterval . TimeInterval {
{
Weekdays : [ ] timeinterval . WeekdayRange {
{
InclusiveRange : timeinterval . InclusiveRange {
Begin : - 1 ,
End : 7 ,
} ,
} ,
} ,
} ,
} ,
} ,
} ,
expMsg : "unable to convert -1 into weekday" ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
err := c . mti . Validate ( )
require . ErrorContains ( t , err , c . expMsg )
} )
}
} )
}
2023-02-02 15:26:39 -05:00
func TestValidateNotificationTemplates ( t * testing . T ) {
tc := [ ] struct {
name string
template NotificationTemplate
expContent string
expError error
} {
{
name : "Same template name as definition" ,
template : NotificationTemplate {
Name : "Same name as definition" ,
Template : ` {{ define "Same name as definition" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
Provenance : "test" ,
} ,
expContent : ` {{ define "Same name as definition" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
expError : nil ,
} ,
{
name : "Different template name than definition" ,
template : NotificationTemplate {
Name : "Different name than definition" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
Provenance : "test" ,
} ,
expContent : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
expError : nil ,
} ,
{
name : "Fix template - missing both {{ define }} and {{ end }}" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` Firing: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} ` ,
Provenance : "test" ,
} ,
expContent : "{{ define \"Alert Instance Template\" }}\n Firing: {{ .Labels.alertname }}\\nSilence: {{ .SilenceURL }}\n{{ end }}" ,
expError : nil ,
} ,
{
name : "Multiple definitions" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} {{ define "Alert Instance Template2" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
Provenance : "test" ,
} ,
expContent : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} {{ define "Alert Instance Template2" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
expError : nil ,
} ,
{
name : "Malformed template - missing {{ define }}" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
Provenance : "test" ,
} ,
expError : errors . New ( "invalid template: template: Alert Instance Template:3: unexpected {{end}}" ) ,
} ,
{
name : "Malformed template - missing {{ end }}" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n ` ,
Provenance : "test" ,
} ,
expError : errors . New ( "invalid template: template: Alert Instance Template:1: unexpected EOF" ) ,
} ,
{
name : "Malformed template - multiple definitions duplicate name" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} \n {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} ` ,
Provenance : "test" ,
} ,
expError : errors . New ( "invalid template: template: Different name than definition:1: template: multiple definition of template \"Alert Instance Template\"" ) ,
} ,
{
// This is fine as long as the template name is different from the definition, it just ignores the extra text.
name : "Extra text outside definition block - different template name and definition" ,
template : NotificationTemplate {
Name : "Different name than definition" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} [what is this?] ` ,
Provenance : "test" ,
} ,
expContent : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} [what is this?] ` ,
expError : nil ,
} ,
{
// This is NOT fine as the template name is the same as the definition.
// GO template parser will treat it as if it's wrapped in {{ define "Alert Instance Template" }}, thus creating a duplicate definition.
name : "Extra text outside definition block - same template name and definition" ,
template : NotificationTemplate {
Name : "Alert Instance Template" ,
Template : ` {{ define "Alert Instance Template" }} \nFiring: {{ .Labels .alertname }} \nSilence: {{ .SilenceURL }} \n {{ end }} [what is this?] ` ,
Provenance : "test" ,
} ,
expError : errors . New ( "invalid template: template: Alert Instance Template:1: template: multiple definition of template \"Alert Instance Template\"" ) ,
} ,
}
for _ , tt := range tc {
t . Run ( tt . name , func ( t * testing . T ) {
err := tt . template . Validate ( )
if tt . expError == nil {
require . NoError ( t , err )
} else {
require . Error ( t , err )
return
}
require . Equal ( t , tt . expContent , tt . template . Template )
} )
}
}