Files
ai-ops/internal/service/notification_service_test.go
2026-05-12 17:48:22 +08:00

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)
}
}
}