summaryrefslogtreecommitdiff
path: root/internal/notify_state_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/notify_state_test.go')
-rw-r--r--internal/notify_state_test.go225
1 files changed, 225 insertions, 0 deletions
diff --git a/internal/notify_state_test.go b/internal/notify_state_test.go
new file mode 100644
index 0000000..4f49471
--- /dev/null
+++ b/internal/notify_state_test.go
@@ -0,0 +1,225 @@
+package internal
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+func TestNewNotifyState_FirstRun(t *testing.T) {
+ // First run with no existing state file should return empty state
+ tmpDir := t.TempDir()
+ ns, err := newNotifyState(tmpDir)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ if ns.LastNotifyEpoch != 0 {
+ t.Errorf("expected LastNotifyEpoch=0, got %d", ns.LastNotifyEpoch)
+ }
+ if len(ns.CheckStates) != 0 {
+ t.Errorf("expected empty CheckStates, got %d entries", len(ns.CheckStates))
+ }
+}
+
+func TestNotifyState_Persistence(t *testing.T) {
+ // Test round-trip: save and load notification state
+ tmpDir := t.TempDir()
+ ns, err := newNotifyState(tmpDir)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ // Create a mock state and record notification
+ mockState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ "Check B": {Status: nagiosCritical},
+ }}
+
+ if err := ns.recordNotification(mockState); err != nil {
+ t.Fatalf("failed to record notification: %v", err)
+ }
+
+ // Load the state back
+ ns2, err := newNotifyState(tmpDir)
+ if err != nil {
+ t.Fatalf("failed to reload state: %v", err)
+ }
+
+ if ns2.LastNotifyEpoch == 0 {
+ t.Error("expected non-zero LastNotifyEpoch after reload")
+ }
+ if len(ns2.CheckStates) != 2 {
+ t.Errorf("expected 2 check states, got %d", len(ns2.CheckStates))
+ }
+ if ns2.CheckStates["Check A"] != int(nagiosOk) {
+ t.Errorf("expected Check A status=%d, got %d", nagiosOk, ns2.CheckStates["Check A"])
+ }
+ if ns2.CheckStates["Check B"] != int(nagiosCritical) {
+ t.Errorf("expected Check B status=%d, got %d", nagiosCritical, ns2.CheckStates["Check B"])
+ }
+}
+
+func TestIntervalElapsed_FirstRun(t *testing.T) {
+ // First run (LastNotifyEpoch=0) should always return true
+ ns := notifyState{LastNotifyEpoch: 0}
+ if !ns.intervalElapsed(3600) {
+ t.Error("expected intervalElapsed=true on first run")
+ }
+}
+
+func TestIntervalElapsed_NotYet(t *testing.T) {
+ // Notification sent 30 seconds ago, interval is 60 seconds
+ ns := notifyState{LastNotifyEpoch: time.Now().Unix() - 30}
+ if ns.intervalElapsed(60) {
+ t.Error("expected intervalElapsed=false when only 30s of 60s elapsed")
+ }
+}
+
+func TestIntervalElapsed_Elapsed(t *testing.T) {
+ // Notification sent 120 seconds ago, interval is 60 seconds
+ ns := notifyState{LastNotifyEpoch: time.Now().Unix() - 120}
+ if !ns.intervalElapsed(60) {
+ t.Error("expected intervalElapsed=true when 120s of 60s elapsed")
+ }
+}
+
+func TestIntervalElapsed_ZeroInterval(t *testing.T) {
+ // Interval of 0 should always return true (immediate notification mode)
+ ns := notifyState{LastNotifyEpoch: time.Now().Unix()}
+ if !ns.intervalElapsed(0) {
+ t.Error("expected intervalElapsed=true when interval is 0")
+ }
+}
+
+func TestHasChanges_NoChanges(t *testing.T) {
+ ns := notifyState{
+ CheckStates: map[string]int{
+ "Check A": int(nagiosOk),
+ "Check B": int(nagiosCritical),
+ },
+ }
+
+ currentState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ "Check B": {Status: nagiosCritical},
+ }}
+
+ if ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=false when states are identical")
+ }
+}
+
+func TestHasChanges_StatusChanged(t *testing.T) {
+ ns := notifyState{
+ CheckStates: map[string]int{
+ "Check A": int(nagiosOk),
+ "Check B": int(nagiosOk),
+ },
+ }
+
+ currentState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ "Check B": {Status: nagiosCritical}, // Changed from OK to CRITICAL
+ }}
+
+ if !ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=true when check status changed")
+ }
+}
+
+func TestHasChanges_NewCheck(t *testing.T) {
+ ns := notifyState{
+ CheckStates: map[string]int{
+ "Check A": int(nagiosOk),
+ },
+ }
+
+ currentState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ "Check B": {Status: nagiosCritical}, // New check
+ }}
+
+ if !ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=true when new check added")
+ }
+}
+
+func TestHasChanges_RemovedCheck(t *testing.T) {
+ ns := notifyState{
+ CheckStates: map[string]int{
+ "Check A": int(nagiosOk),
+ "Check B": int(nagiosCritical),
+ },
+ }
+
+ currentState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ // Check B removed
+ }}
+
+ if !ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=true when check removed")
+ }
+}
+
+func TestHasChanges_EmptyPrevious(t *testing.T) {
+ // First notification - no previous state
+ ns := notifyState{
+ CheckStates: map[string]int{},
+ }
+
+ currentState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ }}
+
+ if !ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=true on first run with checks")
+ }
+}
+
+func TestHasChanges_BothEmpty(t *testing.T) {
+ ns := notifyState{
+ CheckStates: map[string]int{},
+ }
+
+ currentState := state{checks: map[string]checkState{}}
+
+ if ns.hasChanges(currentState) {
+ t.Error("expected hasChanges=false when both states are empty")
+ }
+}
+
+func TestRecordNotification(t *testing.T) {
+ tmpDir := t.TempDir()
+ ns, _ := newNotifyState(tmpDir)
+
+ mockState := state{checks: map[string]checkState{
+ "Check A": {Status: nagiosOk},
+ "Check B": {Status: nagiosWarning},
+ "Check C": {Status: nagiosCritical},
+ }}
+
+ beforeRecord := time.Now().Unix()
+ if err := ns.recordNotification(mockState); err != nil {
+ t.Fatalf("failed to record notification: %v", err)
+ }
+ afterRecord := time.Now().Unix()
+
+ // Verify timestamp is within expected range
+ if ns.LastNotifyEpoch < beforeRecord || ns.LastNotifyEpoch > afterRecord {
+ t.Errorf("LastNotifyEpoch=%d not in range [%d, %d]", ns.LastNotifyEpoch, beforeRecord, afterRecord)
+ }
+
+ // Verify all check states were captured
+ if len(ns.CheckStates) != 3 {
+ t.Errorf("expected 3 check states, got %d", len(ns.CheckStates))
+ }
+
+ // Verify state file was created
+ stateFile := filepath.Join(tmpDir, "notify_state.json")
+ if _, err := os.Stat(stateFile); os.IsNotExist(err) {
+ t.Error("expected state file to be created")
+ }
+}