140 lines
4.9 KiB
Go
140 lines
4.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/company/ai-ops/internal/domain/model"
|
|
)
|
|
|
|
type fakeChannelRepo struct {
|
|
channels []model.NotificationChannel
|
|
}
|
|
|
|
func (r *fakeChannelRepo) List(ctx context.Context) ([]model.NotificationChannel, error) {
|
|
return r.channels, nil
|
|
}
|
|
func (r *fakeChannelRepo) GetByID(ctx context.Context, id string) (*model.NotificationChannel, error) {
|
|
return nil, nil
|
|
}
|
|
func (r *fakeChannelRepo) Create(ctx context.Context, ch *model.NotificationChannel) error {
|
|
return nil
|
|
}
|
|
func (r *fakeChannelRepo) Update(ctx context.Context, ch *model.NotificationChannel) error {
|
|
return nil
|
|
}
|
|
func (r *fakeChannelRepo) Delete(ctx context.Context, id string) error { return nil }
|
|
|
|
type fakeNotificationLogRepo struct {
|
|
created []model.NotificationLog
|
|
sent []string
|
|
failed []string
|
|
}
|
|
|
|
func (r *fakeNotificationLogRepo) CreateLog(ctx context.Context, log *model.NotificationLog) error {
|
|
if log.ID == "" {
|
|
log.ID = "log-1"
|
|
}
|
|
r.created = append(r.created, *log)
|
|
return nil
|
|
}
|
|
func (r *fakeNotificationLogRepo) MarkSent(ctx context.Context, id string) error {
|
|
r.sent = append(r.sent, id)
|
|
return nil
|
|
}
|
|
func (r *fakeNotificationLogRepo) MarkFailed(ctx context.Context, id string, retryCount int, errMessage string) error {
|
|
r.failed = append(r.failed, id)
|
|
return nil
|
|
}
|
|
|
|
func TestNotificationServiceWritesLogWhenWebhookSent(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
channelRepo := &fakeChannelRepo{channels: []model.NotificationChannel{{
|
|
ID: "11111111-1111-4111-8111-111111111111",
|
|
Name: "webhook",
|
|
ChannelType: "webhook",
|
|
Config: map[string]any{"webhook_url": server.URL},
|
|
Priority: 10,
|
|
Enabled: true,
|
|
}}}
|
|
logRepo := &fakeNotificationLogRepo{}
|
|
svc := NewNotificationService(channelRepo, logRepo)
|
|
defer svc.Stop()
|
|
|
|
svc.processTask(context.Background(), NotificationTask{
|
|
Event: &model.AlertEvent{
|
|
ID: "22222222-2222-4222-8222-222222222222",
|
|
RuleID: "33333333-3333-4333-8333-333333333333",
|
|
Level: "P1",
|
|
Status: "triggered",
|
|
ResourceID: "svc-a",
|
|
},
|
|
ChannelIDs: []string{"11111111-1111-4111-8111-111111111111"},
|
|
Priority: "P1",
|
|
})
|
|
|
|
if len(logRepo.created) != 1 {
|
|
t.Fatalf("created logs = %d, want 1", len(logRepo.created))
|
|
}
|
|
if len(logRepo.sent) != 1 || logRepo.sent[0] != "log-1" {
|
|
t.Fatalf("sent logs = %#v, want [log-1]", logRepo.sent)
|
|
}
|
|
if len(logRepo.failed) != 0 {
|
|
t.Fatalf("failed logs = %#v, want empty", logRepo.failed)
|
|
}
|
|
}
|
|
|
|
func TestNotificationServiceFailureAndFallbackBranches(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusBadGateway)
|
|
}))
|
|
defer server.Close()
|
|
|
|
channels := []model.NotificationChannel{
|
|
{ID: "c1", ChannelType: "webhook", Config: map[string]any{"webhook_url": server.URL}, Priority: 1, Enabled: true},
|
|
{ID: "c2", ChannelType: "email", Priority: 2, Enabled: true},
|
|
{ID: "c3", ChannelType: "feishu", Priority: 3, Enabled: true},
|
|
{ID: "c4", ChannelType: "wechat", Priority: 4, Enabled: true},
|
|
{ID: "c5", ChannelType: "sms", Priority: 5, Enabled: true},
|
|
{ID: "disabled", ChannelType: "webhook", Priority: 99, Enabled: false},
|
|
}
|
|
logs := &fakeNotificationLogRepo{}
|
|
svc := NewNotificationService(&fakeChannelRepo{channels: channels}, logs)
|
|
defer svc.Stop()
|
|
event := &model.AlertEvent{ID: "event-1", RuleID: "rule-1", Level: "P1", ResourceType: "svc", ResourceID: "api", CurrentValue: "10", ThresholdValue: "5"}
|
|
|
|
ordered := svc.filterAndOrderChannels(channels, []string{"c1", "c2", "missing", "disabled"})
|
|
if len(ordered) != 3 || ordered[0].ID != "disabled" || ordered[1].ID != "c2" || ordered[2].ID != "c1" {
|
|
t.Fatalf("unexpected ordered channels: %+v", ordered)
|
|
}
|
|
svc.processTask(context.Background(), NotificationTask{Event: event, ChannelIDs: []string{"c1", "c2"}})
|
|
if len(logs.failed) < 2 || len(logs.sent) != 0 {
|
|
t.Fatalf("expected multiple failures and no success: sent=%+v failed=%+v", logs.sent, logs.failed)
|
|
}
|
|
if err := svc.sendToChannel(context.Background(), event, &model.NotificationChannel{ChannelType: "unknown"}); err == nil {
|
|
t.Fatal("expected unsupported channel error")
|
|
}
|
|
if err := svc.sendWebhook(context.Background(), event, &model.NotificationChannel{Config: map[string]any{}}); err == nil {
|
|
t.Fatal("expected missing webhook url error")
|
|
}
|
|
svc.Enqueue(event, []string{"c2"})
|
|
}
|
|
|
|
func TestNotificationServiceExplicitUnsupportedPlaceholders(t *testing.T) {
|
|
svc := NewNotificationService(&fakeChannelRepo{})
|
|
defer svc.Stop()
|
|
event := &model.AlertEvent{ID: "event-placeholders", RuleID: "rule", Level: "P2"}
|
|
for _, channelType := range []string{"email", "feishu", "wechat"} {
|
|
err := svc.sendToChannel(context.Background(), event, &model.NotificationChannel{ChannelType: channelType})
|
|
if err == nil {
|
|
t.Fatalf("expected %s placeholder error", channelType)
|
|
}
|
|
}
|
|
}
|