Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/api/handler/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (h *NotificationsHandler) ListSystemNotifications(ctx echo.Context) error {
seenDestinations := make(map[string]struct{})

for i := range rows {
name, ok := notification.NormalizeNotificationType(rows[i].NotificationType)
name, ok := notification.NormalizeSystemNotificationName(rows[i].NotificationType)
if !ok {
err := fmt.Errorf("unsupported notification type %q", rows[i].NotificationType)
h.sugar.Errorw("Invalid configured system notification type", "error", err, "notificationType", rows[i].NotificationType)
Expand Down Expand Up @@ -251,7 +251,7 @@ func (h *NotificationsHandler) ListSystemNotifications(ctx echo.Context) error {
// @Router /admin/notifications/{notificationName}/destinations [post]
func (h *NotificationsHandler) CreateSystemNotificationDestination(ctx echo.Context) error {
notificationName := ctx.Param("notificationName")
canonicalType, ok := notification.NormalizeNotificationType(notificationName)
canonicalType, ok := notification.NormalizeSystemNotificationName(notificationName)
if !ok {
err := fmt.Errorf("unsupported notification type %q", notificationName)
h.sugar.Warnw("Invalid system notification type", "error", err, "notificationName", notificationName)
Expand Down Expand Up @@ -351,7 +351,7 @@ func (h *NotificationsHandler) CreateSystemNotificationDestination(ctx echo.Cont
// @Router /admin/notifications/{notificationName}/destinations [delete]
func (h *NotificationsHandler) DeleteSystemNotificationDestination(ctx echo.Context) error {
notificationName := ctx.Param("notificationName")
canonicalType, ok := notification.NormalizeNotificationType(notificationName)
canonicalType, ok := notification.NormalizeSystemNotificationName(notificationName)
if !ok {
err := fmt.Errorf("unsupported notification type %q", notificationName)
h.sugar.Warnw("Invalid system notification type", "error", err, "notificationName", notificationName)
Expand Down
72 changes: 58 additions & 14 deletions internal/api/handler/notifications_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListNotificationProviderStatu

func (suite *NotificationsApiIntegrationSuite) TestListSystemNotifications() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -159,7 +159,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotifications() {
}).Error)

suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -169,7 +169,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotifications() {
}).Error)

suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand Down Expand Up @@ -205,7 +205,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotifications() {

func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsDeduplicatesEquivalentDestinations() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -215,7 +215,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsDedupl
}),
}).Error)
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -242,7 +242,7 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsDedupl

func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsIncludesConfiguredSupportedTypesOutsideDefaultBaseline() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeTaskAvailable,
NotificationType: notification.SubscriptionGateTaskAvailable,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -267,6 +267,33 @@ func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsInclud
}, response.Data[0].ConfiguredDestinations)
}

func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsIncludesWorkflowExecutionFailed() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.SystemNotificationNameWorkflowExecutionFailed,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
emailprovider.AddressKeyEmail: "alerts@example.com",
},
}),
}).Error)

rec, req := suite.authedRequest(http.MethodGet, "/api/admin/notifications")

suite.server.E().ServeHTTP(rec, req)
suite.Equal(200, rec.Code, "Expected OK response for ListSystemNotifications")

var response GenericDataListResponse[systemNotificationResponse]
err := json.Unmarshal(rec.Body.Bytes(), &response)
suite.Require().NoError(err, "Failed to unmarshal notifications response")
suite.Require().Len(response.Data, 1)

suite.Equal("WORKFLOW_EXECUTION_FAILED", response.Data[0].Name)
suite.Equal([]configuredSystemDestinationResponse{
{ProviderType: "email", DestinationTarget: "alerts@example.com"},
}, response.Data[0].ConfiguredDestinations)
}

func (suite *NotificationsApiIntegrationSuite) TestListSystemNotificationsReturnsEmptyDataWhenNoConfigurationsExist() {
rec, req := suite.authedRequest(http.MethodGet, "/api/admin/notifications")

Expand Down Expand Up @@ -299,7 +326,7 @@ func (suite *NotificationsApiIntegrationSuite) TestCreateSystemNotificationDesti
var rows []relational.SystemNotificationDestination
suite.Require().NoError(suite.DB.Find(&rows).Error)
suite.Require().Len(rows, 1)
suite.Equal(notification.NotificationTypeEvidenceDigest, rows[0].NotificationType)
suite.Equal(notification.SubscriptionGateEvidenceDigest, rows[0].NotificationType)
suite.Equal(notification.DeliveryChannelEmail, rows[0].Provider)
suite.Equal("alerts@example.com", rows[0].Target.Data().Address[emailprovider.AddressKeyEmail])
}
Expand Down Expand Up @@ -334,15 +361,32 @@ func (suite *NotificationsApiIntegrationSuite) TestCreateSystemNotificationDesti
var rows []relational.SystemNotificationDestination
suite.Require().NoError(suite.DB.Find(&rows).Error)
suite.Require().Len(rows, 1)
suite.Equal(notification.NotificationTypeTaskAvailable, rows[0].NotificationType)
suite.Equal(notification.SubscriptionGateTaskAvailable, rows[0].NotificationType)
suite.Equal(notification.DeliveryChannelSlack, rows[0].Provider)
suite.Equal("ccf-slack-int", rows[0].Target.Data().Address[slackprovider.AddressKeyChannel])
suite.Equal(slackprovider.TargetTypeChannel, rows[0].Target.Data().Address[slackprovider.AddressKeyTargetType])
}

func (suite *NotificationsApiIntegrationSuite) TestCreateSystemNotificationDestinationWorkflowExecutionFailed() {
rec, req := suite.authedJSONRequest(http.MethodPost, "/api/admin/notifications/WORKFLOW_EXECUTION_FAILED/destinations", map[string]string{
"providerType": "email",
"destinationTarget": "alerts@example.com",
})

suite.server.E().ServeHTTP(rec, req)
suite.Equal(http.StatusCreated, rec.Code, "Expected Created response for workflow-execution-failed destination")

var rows []relational.SystemNotificationDestination
suite.Require().NoError(suite.DB.Find(&rows).Error)
suite.Require().Len(rows, 1)
suite.Equal(notification.SystemNotificationNameWorkflowExecutionFailed, rows[0].NotificationType)
suite.Equal(notification.DeliveryChannelEmail, rows[0].Provider)
suite.Equal("alerts@example.com", rows[0].Target.Data().Address[emailprovider.AddressKeyEmail])
}

func (suite *NotificationsApiIntegrationSuite) TestCreateSystemNotificationDestinationRejectsDuplicateDestination() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand Down Expand Up @@ -398,7 +442,7 @@ func (suite *NotificationsApiIntegrationSuite) TestCreateSystemNotificationDesti

func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDestination() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -422,7 +466,7 @@ func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDesti

func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDestinationAcceptsKebabCasePayload() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelEmail,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -442,7 +486,7 @@ func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDesti

func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDestinationRemovesDuplicateRows() {
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -452,7 +496,7 @@ func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDesti
}),
}).Error)
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand All @@ -462,7 +506,7 @@ func (suite *NotificationsApiIntegrationSuite) TestDeleteSystemNotificationDesti
}),
}).Error)
suite.Require().NoError(suite.DB.Create(&relational.SystemNotificationDestination{
NotificationType: notification.NotificationTypeEvidenceDigest,
NotificationType: notification.SubscriptionGateEvidenceDigest,
Provider: notification.DeliveryChannelSlack,
Target: datatypes.NewJSONType(relational.SystemNotificationTarget{
Address: map[string]string{
Expand Down
4 changes: 2 additions & 2 deletions internal/api/handler/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,7 +693,7 @@ func (h *UserHandler) loadUserNotificationSubscriptions(ctx context.Context, use
for i := range rows {
channels := make([]string, len(rows[i].Channels))
copy(channels, rows[i].Channels)
wireType, ok := notification.WireNotificationType(rows[i].NotificationType)
wireType, ok := notification.WireSubscriptionGate(rows[i].NotificationType)
if !ok {
wireType = rows[i].NotificationType
}
Expand All @@ -710,7 +710,7 @@ func normalizeNotificationSubscriptions(notifications map[string][]string) (map[

channelSets := make(map[string]map[string]struct{}, len(notifications))
for notificationType, channels := range notifications {
normalizedType, ok := notification.NormalizeNotificationType(notificationType)
normalizedType, ok := notification.NormalizeSubscriptionGate(notificationType)
if !ok {
invalidType := strings.ToLower(strings.TrimSpace(notificationType))
return nil, fmt.Errorf("%w: %q", errInvalidNotificationTypes, invalidType)
Expand Down
34 changes: 17 additions & 17 deletions internal/api/handler/users_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,10 +649,10 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
// Test subscribing to digest notifications.
payload := map[string]interface{}{
"notifications": map[string][]string{
notification.NotificationTypeTaskAvailableWire: {"email", "slack", "email"},
notification.NotificationTypeEvidenceDigestWire: {"email", "email"},
notification.NotificationTypeTaskDailyDigestWire: {"email", "email"},
notification.NotificationTypeRiskNotifications: {"email", "slack", "email"},
notification.SubscriptionGateTaskAvailableWire: {"email", "slack", "email"},
notification.SubscriptionGateEvidenceDigestWire: {"email", "email"},
notification.SubscriptionGateTaskDailyDigestWire: {"email", "email"},
notification.SubscriptionGateRiskNotifications: {"email", "slack", "email"},
},
}
payloadJSON, err := json.Marshal(payload)
Expand All @@ -674,16 +674,16 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
err = json.Unmarshal(rec.Body.Bytes(), &response)
suite.Require().NoError(err, "Failed to unmarshal UpdateSubscriptions response")

suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.NotificationTypeTaskAvailableWire], "Expected notifications to be normalized and persisted")
suite.Equal([]string{"email"}, response.Data.Notifications[notification.NotificationTypeEvidenceDigestWire], "Expected evidence digest notifications to be normalized and persisted")
suite.Equal([]string{"email"}, response.Data.Notifications[notification.NotificationTypeTaskDailyDigestWire], "Expected task daily digest notifications to be normalized and persisted")
suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.NotificationTypeRiskNotificationsWire], "Expected risk notifications to be normalized and persisted")
suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.SubscriptionGateTaskAvailableWire], "Expected notifications to be normalized and persisted")
suite.Equal([]string{"email"}, response.Data.Notifications[notification.SubscriptionGateEvidenceDigestWire], "Expected evidence digest notifications to be normalized and persisted")
suite.Equal([]string{"email"}, response.Data.Notifications[notification.SubscriptionGateTaskDailyDigestWire], "Expected task daily digest notifications to be normalized and persisted")
suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.SubscriptionGateRiskNotificationsWire], "Expected risk notifications to be normalized and persisted")
suite.Equal(int64(4), countStoredSubscriptions(), "Expected exactly one stored row per active notification type")

// Test unsubscribing by omitting previously-configured notification types from the map.
payload = map[string]interface{}{
"notifications": map[string][]string{
notification.NotificationTypeTaskAvailableWire: {"email", "slack"},
notification.SubscriptionGateTaskAvailableWire: {"email", "slack"},
},
}
payloadJSON, err = json.Marshal(payload)
Expand All @@ -705,12 +705,12 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
err = json.Unmarshal(rec.Body.Bytes(), &response)
suite.Require().NoError(err, "Failed to unmarshal unsubscribe response")

suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.NotificationTypeTaskAvailableWire], "Expected task-available notifications to remain configured")
_, hasDigestSubscription := response.Data.Notifications[notification.NotificationTypeEvidenceDigestWire]
suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.SubscriptionGateTaskAvailableWire], "Expected task-available notifications to remain configured")
_, hasDigestSubscription := response.Data.Notifications[notification.SubscriptionGateEvidenceDigestWire]
suite.False(hasDigestSubscription, "Expected digest subscription to be removed when evidence_digest is omitted")
_, hasTaskDailyDigestSubscription := response.Data.Notifications[notification.NotificationTypeTaskDailyDigestWire]
_, hasTaskDailyDigestSubscription := response.Data.Notifications[notification.SubscriptionGateTaskDailyDigestWire]
suite.False(hasTaskDailyDigestSubscription, "Expected task daily digest subscription to be removed when taskDailyDigest is omitted")
_, hasRiskNotificationSubscription := response.Data.Notifications[notification.NotificationTypeRiskNotificationsWire]
_, hasRiskNotificationSubscription := response.Data.Notifications[notification.SubscriptionGateRiskNotificationsWire]
suite.False(hasRiskNotificationSubscription, "Expected risk notification subscription to be removed when risk_notifications is omitted")
suite.Equal(int64(1), countStoredSubscriptions(), "Expected old notification rows to be replaced rather than soft-deleted")

Expand All @@ -737,8 +737,8 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
err = json.Unmarshal(rec.Body.Bytes(), &response)
suite.Require().NoError(err, "Failed to unmarshal response for request without notifications")

suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.NotificationTypeTaskAvailableWire], "Expected notifications to remain unchanged when omitted")
_, hasDigestSubscription = response.Data.Notifications[notification.NotificationTypeEvidenceDigestWire]
suite.Equal([]string{"email", "slack"}, response.Data.Notifications[notification.SubscriptionGateTaskAvailableWire], "Expected notifications to remain unchanged when omitted")
_, hasDigestSubscription = response.Data.Notifications[notification.SubscriptionGateEvidenceDigestWire]
suite.False(hasDigestSubscription, "Expected digest notification subscription to remain unchanged when notifications are omitted")
suite.Equal(int64(1), countStoredSubscriptions(), "Expected notification row count to remain stable when notifications are omitted")
})
Expand All @@ -747,7 +747,7 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
// Test with malformed notifications payload
payload := map[string]interface{}{
"notifications": map[string]interface{}{
notification.NotificationTypeRiskNotifications: "invalid",
notification.SubscriptionGateRiskNotifications: "invalid",
},
}
payloadJSON, err := json.Marshal(payload)
Expand All @@ -764,7 +764,7 @@ func (suite *UserApiIntegrationSuite) TestSubscriptions() {
// Test with unsupported notification channel
payload2 := map[string]interface{}{
"notifications": map[string][]string{
notification.NotificationTypeTaskAvailable: {"email", "pagerduty"},
notification.SubscriptionGateTaskAvailable: {"email", "pagerduty"},
},
}
payloadJSON2, err := json.Marshal(payload2)
Expand Down
Loading
Loading