diff --git a/backend/internal/services/notification_service.go b/backend/internal/services/notification_service.go index abfe680e..6466856a 100644 --- a/backend/internal/services/notification_service.go +++ b/backend/internal/services/notification_service.go @@ -33,14 +33,29 @@ type NotificationService struct { validateSlackURL func(string) error } -func NewNotificationService(db *gorm.DB, mailService MailServiceInterface) *NotificationService { - return &NotificationService{ +// NotificationServiceOption configures a NotificationService at construction time. +type NotificationServiceOption func(*NotificationService) + +// WithSlackURLValidator overrides the Slack webhook URL validator. Intended for use +// in tests that need to bypass real URL validation without mutating shared state. +func WithSlackURLValidator(fn func(string) error) NotificationServiceOption { + return func(s *NotificationService) { + s.validateSlackURL = fn + } +} + +func NewNotificationService(db *gorm.DB, mailService MailServiceInterface, opts ...NotificationServiceOption) *NotificationService { + s := &NotificationService{ DB: db, httpWrapper: notifications.NewNotifyHTTPWrapper(), mailService: mailService, telegramAPIBaseURL: "https://api.telegram.org", validateSlackURL: validateSlackWebhookURL, } + for _, opt := range opts { + opt(s) + } + return s } var discordWebhookRegex = regexp.MustCompile(`^https://discord(?:app)?\.com/api/webhooks/(\d+)/([a-zA-Z0-9_-]+)`) diff --git a/backend/internal/services/notification_service_json_test.go b/backend/internal/services/notification_service_json_test.go index c69f21fd..7c84a3e3 100644 --- a/backend/internal/services/notification_service_json_test.go +++ b/backend/internal/services/notification_service_json_test.go @@ -193,8 +193,7 @@ func TestSendJSONPayload_Slack(t *testing.T) { db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{}) require.NoError(t, err) - svc := NewNotificationService(db, nil) - svc.validateSlackURL = func(rawURL string) error { return nil } + svc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", diff --git a/backend/internal/services/notification_service_test.go b/backend/internal/services/notification_service_test.go index d41a2e6e..20a645bd 100644 --- a/backend/internal/services/notification_service_test.go +++ b/backend/internal/services/notification_service_test.go @@ -1453,8 +1453,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { }) t.Run("slack_requires_text_or_blocks", func(t *testing.T) { - defer func() { svc.validateSlackURL = validateSlackWebhookURL }() - svc.validateSlackURL = func(rawURL string) error { return nil } + subSvc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", @@ -1470,7 +1469,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { "EventType": "test", } - err := svc.sendJSONPayload(context.Background(), provider, data) + err := subSvc.sendJSONPayload(context.Background(), provider, data) require.Error(t, err) assert.Contains(t, err.Error(), "slack payload requires 'text' or 'blocks' field") }) @@ -1480,9 +1479,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer server.Close() - - defer func() { svc.validateSlackURL = validateSlackWebhookURL }() - svc.validateSlackURL = func(rawURL string) error { return nil } + subSvc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", @@ -1498,7 +1495,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { "EventType": "test", } - err := svc.sendJSONPayload(context.Background(), provider, data) + err := subSvc.sendJSONPayload(context.Background(), provider, data) require.NoError(t, err) }) @@ -1507,9 +1504,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { w.WriteHeader(http.StatusOK) })) defer server.Close() - - defer func() { svc.validateSlackURL = validateSlackWebhookURL }() - svc.validateSlackURL = func(rawURL string) error { return nil } + subSvc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", @@ -1525,7 +1520,7 @@ func TestSendJSONPayload_ServiceSpecificValidation(t *testing.T) { "EventType": "test", } - err := svc.sendJSONPayload(context.Background(), provider, data) + err := subSvc.sendJSONPayload(context.Background(), provider, data) require.NoError(t, err) }) @@ -3306,8 +3301,7 @@ func TestNotificationService_TestProvider_Slack(t *testing.T) { })) defer server.Close() - svc := NewNotificationService(db, nil) - svc.validateSlackURL = func(rawURL string) error { return nil } + svc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", @@ -3337,8 +3331,7 @@ func TestNotificationService_SendExternal_Slack(t *testing.T) { })) defer server.Close() - svc := NewNotificationService(db, nil) - svc.validateSlackURL = func(rawURL string) error { return nil } + svc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Name: "Slack E2E", @@ -3374,8 +3367,7 @@ func TestNotificationService_Slack_PayloadNormalizesMessageToText(t *testing.T) })) defer server.Close() - svc := NewNotificationService(db, nil) - svc.validateSlackURL = func(rawURL string) error { return nil } + svc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack", @@ -3402,8 +3394,7 @@ func TestNotificationService_Slack_PayloadNormalizesMessageToText(t *testing.T) func TestNotificationService_Slack_PayloadRequiresTextOrBlocks(t *testing.T) { db := setupNotificationTestDB(t) - svc := NewNotificationService(db, nil) - svc.validateSlackURL = func(rawURL string) error { return nil } + svc := NewNotificationService(db, nil, WithSlackURLValidator(func(string) error { return nil })) provider := models.NotificationProvider{ Type: "slack",