diff --git a/alert.go b/alert.go index fd5c861..428765d 100644 --- a/alert.go +++ b/alert.go @@ -54,7 +54,7 @@ func (alert *Alert) BuildTemplates() { ) log.Printf("Template built: %v", alert.commandShellTemplate) } else { - panic("No template provided for alert %s", alert.Name) + log.Fatalf("No template provided for alert %s", alert.Name) } } diff --git a/monitor.go b/monitor.go index 975606f..d1ce3b5 100644 --- a/monitor.go +++ b/monitor.go @@ -31,7 +31,7 @@ type Monitor struct { func (monitor Monitor) IsValid() bool { atLeastOneCommand := (monitor.CommandShell != "" || monitor.Command != nil) atMostOneCommand := (monitor.CommandShell == "" || monitor.Command == nil) - return atLeastOneCommand && atMostOneCommand + return atLeastOneCommand && atMostOneCommand && monitor.AlertAfter >= 0 } // ShouldCheck returns a boolean indicating if the Monitor is ready to be @@ -116,9 +116,12 @@ func (monitor *Monitor) failure() (notice *AlertNotice) { } failureCount := (monitor.failureCount - monitor.AlertAfter) + log.Printf("Total fail %v, this fail %v", monitor.failureCount, failureCount) if monitor.AlertEvery > 0 { // Handle integer number of failures before alerting + modVal := failureCount % monitor.AlertEvery + log.Printf("Alert every > 0: Mod val: %v", modVal) if failureCount%monitor.AlertEvery == 0 { notice = monitor.createAlertNotice(false) } diff --git a/monitor_test.go b/monitor_test.go new file mode 100644 index 0000000..1ae0969 --- /dev/null +++ b/monitor_test.go @@ -0,0 +1,188 @@ +package main + +import ( + "log" + "testing" + "time" +) + +// TestMonitorIsValid tests the Monitor.IsValid() +func TestMonitorIsValid(t *testing.T) { + cases := []struct { + monitor Monitor + expected bool + name string + }{ + {Monitor{Command: []string{"echo", "test"}}, true, "Command only"}, + {Monitor{CommandShell: "echo test"}, true, "CommandShell only"}, + {Monitor{}, false, "No commands"}, + { + Monitor{Command: []string{"echo", "test"}, CommandShell: "echo test"}, + false, + "Both commands", + }, + {Monitor{AlertAfter: -1}, false, "Invalid alert threshold, -1"}, + {Monitor{AlertAfter: 0}, false, "Invalid alert threshold, 0"}, + } + + for _, c := range cases { + actual := c.monitor.IsValid() + if actual != c.expected { + t.Errorf("IsValid(%v), expected=%t actual=%t", c.name, c.expected, actual) + } + } +} + +// TestMonitorShouldCheck tests the Monitor.ShouldCheck() +func TestMonitorShouldCheck(t *testing.T) { + timeNow := time.Now() + timeTenSecAgo := time.Now().Add(time.Second * -10) + timeTwentySecAgo := time.Now().Add(time.Second * -20) + + cases := []struct { + monitor Monitor + expected bool + name string + }{ + {Monitor{}, true, "Empty"}, + {Monitor{lastCheck: timeNow, CheckInterval: 15}, false, "Just checked"}, + {Monitor{lastCheck: timeTenSecAgo, CheckInterval: 15}, false, "-10s"}, + {Monitor{lastCheck: timeTwentySecAgo, CheckInterval: 15}, true, "-20s"}, + } + + for _, c := range cases { + actual := c.monitor.ShouldCheck() + if actual != c.expected { + t.Errorf("ShouldCheck(%v), expected=%t actual=%t", c.name, c.expected, actual) + } + } +} + +// TestMonitorIsUp tests the Monitor.isUp() +func TestMonitorIsUp(t *testing.T) { + cases := []struct { + monitor Monitor + expected bool + name string + }{ + {Monitor{}, true, "Empty"}, + {Monitor{alertCount: 1}, false, "Has alert"}, + {Monitor{alertCount: -1}, false, "Negative alerts"}, + {Monitor{alertCount: 0}, true, "No alerts"}, + } + + for _, c := range cases { + actual := c.monitor.isUp() + if actual != c.expected { + t.Errorf("isUp(%v), expected=%t actual=%t", c.name, c.expected, actual) + } + } +} + +// TestMonitorSuccess tests the Monitor.success() +func TestMonitorSuccess(t *testing.T) { + cases := []struct { + monitor Monitor + expectNotice bool + name string + }{ + {Monitor{}, false, "Empty"}, + {Monitor{alertCount: 0}, false, "No alerts"}, + {Monitor{alertCount: 1}, true, "Has alert"}, + } + + for _, c := range cases { + notice := c.monitor.success() + hasNotice := (notice != nil) + if hasNotice != c.expectNotice { + t.Errorf("success(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice) + } + } +} + +// TestMonitorFailureAlertAfter tests that alerts will not trigger until +// hitting the threshold provided by AlertAfter +func TestMonitorFailureAlertAfter(t *testing.T) { + cases := []struct { + monitor Monitor + expectNotice bool + name string + }{ + {Monitor{AlertAfter: 1}, true, "Empty"}, // Defaults to true because and AlertEvery default to 0 + // TODO: Update this if meaning of AlertEvery changes + {Monitor{failureCount: 0, AlertAfter: 0, AlertEvery: 1}, true, "Alert after 0: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 0, AlertEvery: 1}, true, "Alert after 0: second failure"}, + // TODO: This one probably shouldn't trigger an alert if AlertAfter is made to behave consistently + {Monitor{failureCount: 0, AlertAfter: 1, AlertEvery: 1}, true, "Alert after 1: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 1, AlertEvery: 1}, true, "Alert after 1: second failure"}, + {Monitor{failureCount: 0, AlertAfter: 20, AlertEvery: 1}, false, "Alert after 20: first failure"}, + {Monitor{failureCount: 19, AlertAfter: 20, AlertEvery: 1}, true, "Alert after 20: 20th failure"}, + {Monitor{failureCount: 20, AlertAfter: 20, AlertEvery: 1}, true, "Alert after 20: 21st failure"}, + } + + for _, c := range cases { + log.Printf("Testing case %s", c.name) + + notice := c.monitor.failure() + hasNotice := (notice != nil) + if hasNotice != c.expectNotice { + t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice) + log.Printf("Case failed: %s", c.name) + } + log.Println("-----") + } +} + +// TestMonitorFailureAlertEvery tests that alerts will trigger +// on the expected intervals +func TestMonitorFailureAlertEvery(t *testing.T) { + cases := []struct { + monitor Monitor + expectNotice bool + name string + }{ + /* + TODO: Actually found a bug in original implementation. There is an inconsistency in the way AlertAfter is treated. + For "First alert only" (ie. AlertEvery=0), it is the number of failures to ignore before alerting, so AlertAfter=1 + will ignore the first failure and alert on the second failure + For other intervals (ie. AlertEvery=1), it is essentially indexed on one. Essentially making AlertAfter=1 trigger + on the first failure. + + For usabilty, this should be consistent. Consistent with what though? minitor-py? Or itself? Dun dun duuuunnnnn! + */ + {Monitor{}, true, "Empty"}, // Defaults to true because AlertAfter and AlertEvery default to 0 + // Alert first time only, after 0 + {Monitor{failureCount: 0, AlertAfter: 0, AlertEvery: 0}, true, "Alert first time only after 0: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 0, AlertEvery: 0}, false, "Alert first time only after 0: second failure"}, + // Alert first time only, after 1 + {Monitor{failureCount: 0, AlertAfter: 1, AlertEvery: 0}, false, "Alert first time only after 1: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 1, AlertEvery: 0}, true, "Alert first time only after 1: second failure"}, + {Monitor{failureCount: 2, AlertAfter: 1, AlertEvery: 0}, false, "Alert first time only after 1: third failure"}, + {Monitor{failureCount: 3, AlertAfter: 1, AlertEvery: 0}, false, "Alert first time only after 1: fourth failure"}, + // Alert every time, after 0 + {Monitor{failureCount: 0, AlertAfter: 0, AlertEvery: 1}, true, "Alert every time after 0: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 0, AlertEvery: 1}, true, "Alert every time after 0: second failure"}, + // Alert every time, after 1 + {Monitor{failureCount: 0, AlertAfter: 1, AlertEvery: 1}, false, "Alert every time after 1: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 1, AlertEvery: 1}, true, "Alert every time after 1: second failure"}, + {Monitor{failureCount: 1, AlertAfter: 1, AlertEvery: 1}, true, "Alert every time after 1: third failure"}, + // Alert every other time + {Monitor{failureCount: 0, AlertAfter: 0, AlertEvery: 2}, true, "Alert every other time after 0: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 0, AlertEvery: 2}, false, "Alert every other time after 0: second failure"}, + {Monitor{failureCount: 2, AlertAfter: 0, AlertEvery: 2}, true, "Alert every other time after 0: third failure2"}, + {Monitor{failureCount: 3, AlertAfter: 0, AlertEvery: 2}, false, "Alert every other time after 0: fourth failure"}, + // Alert every other time, after 1 + {Monitor{failureCount: 0, AlertAfter: 1, AlertEvery: 2}, false, "Alert every other time after 1: first failure"}, + {Monitor{failureCount: 1, AlertAfter: 1, AlertEvery: 2}, true, "Alert every other time after 1: second failure"}, + {Monitor{failureCount: 2, AlertAfter: 1, AlertEvery: 2}, false, "Alert every other time after 1: third failure"}, + {Monitor{failureCount: 3, AlertAfter: 1, AlertEvery: 2}, true, "Alert every other time after 1: fourth failure"}, + } + + for _, c := range cases { + notice := c.monitor.failure() + hasNotice := (notice != nil) + if hasNotice != c.expectNotice { + t.Errorf("failure(%v), expected=%t actual=%t", c.name, c.expectNotice, hasNotice) + } + } +}