Pulse/internal/notifications/queue_observability_test.go
2026-03-18 16:06:30 +00:00

119 lines
2.9 KiB
Go

package notifications
import (
"bytes"
"strings"
"testing"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func TestGetDLQLogsStructuredContextOnScanFailure(t *testing.T) {
nq, err := NewNotificationQueue(t.TempDir())
if err != nil {
t.Fatalf("NewNotificationQueue failed: %v", err)
}
t.Cleanup(func() {
_ = nq.Stop()
})
_, err = nq.db.Exec(
`INSERT INTO notification_queue (id, type, method, status, alerts, config, attempts, max_attempts, created_at, completed_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"bad-dlq-json",
"webhook",
"slack",
QueueStatusDLQ,
"{invalid-json",
"{}",
1,
3,
time.Now().Unix(),
time.Now().Unix(),
)
if err != nil {
t.Fatalf("failed to insert malformed dlq row: %v", err)
}
logOutput := captureNotificationQueueLogs(t)
notifications, err := nq.GetDLQ(5)
if err != nil {
t.Fatalf("GetDLQ returned error: %v", err)
}
if len(notifications) != 0 {
t.Fatalf("expected malformed row to be skipped, got %d notifications", len(notifications))
}
for _, expected := range []string{
`"component":"notification_queue"`,
`"action":"scan_dlq_row"`,
`"queueStatus":"dlq"`,
`"batchLimit":5`,
`"dbPath":"` + nq.dbPath + `"`,
`"message":"Failed to scan DLQ notification"`,
`"error":"failed to unmarshal alerts`,
} {
if !strings.Contains(logOutput.String(), expected) {
t.Fatalf("expected log output to include %s, got %q", expected, logOutput.String())
}
}
}
func TestCancelByAlertIdentifiersLogsStructuredContextOnAlertUnmarshalFailure(t *testing.T) {
nq, err := NewNotificationQueue(t.TempDir())
if err != nil {
t.Fatalf("NewNotificationQueue failed: %v", err)
}
t.Cleanup(func() {
_ = nq.Stop()
})
_, err = nq.db.Exec(
`INSERT INTO notification_queue (id, type, method, status, alerts, config, attempts, max_attempts, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
"bad-cancel-json",
"email",
"smtp",
QueueStatusSending,
`{"broken":`,
"{}",
1,
3,
time.Now().Unix(),
)
if err != nil {
t.Fatalf("failed to insert malformed sending row: %v", err)
}
logOutput := captureNotificationQueueLogs(t)
if err := nq.CancelByAlertIdentifiers([]string{"alert-1"}); err != nil {
t.Fatalf("CancelByAlertIdentifiers returned error: %v", err)
}
for _, expected := range []string{
`"component":"notification_queue"`,
`"action":"cancel_unmarshal_alerts"`,
`"notifID":"bad-cancel-json"`,
`"message":"Failed to unmarshal alerts for cancellation check"`,
`"error":"unexpected end of JSON input"`,
} {
if !strings.Contains(logOutput.String(), expected) {
t.Fatalf("expected log output to include %s, got %q", expected, logOutput.String())
}
}
}
func captureNotificationQueueLogs(t *testing.T) *bytes.Buffer {
t.Helper()
var buf bytes.Buffer
origLogger := log.Logger
log.Logger = zerolog.New(&buf).Level(zerolog.DebugLevel).With().Timestamp().Logger()
t.Cleanup(func() {
log.Logger = origLogger
})
return &buf
}