1//go:build integration 2// +build integration 3 4package sqlstore 5 6import ( 7 "context" 8 "errors" 9 "regexp" 10 "testing" 11 "time" 12 13 "github.com/grafana/grafana/pkg/bus" 14 "github.com/grafana/grafana/pkg/components/simplejson" 15 "github.com/grafana/grafana/pkg/models" 16 17 "github.com/stretchr/testify/require" 18) 19 20func TestAlertNotificationSQLAccess(t *testing.T) { 21 var sqlStore *SQLStore 22 setup := func() { 23 sqlStore := InitTestDB(t) 24 25 // Set up bus handlers 26 bus.AddHandlerCtx("deleteAlertNotification", func(ctx context.Context, cmd *models.DeleteAlertNotificationCommand) error { 27 return sqlStore.DeleteAlertNotification(ctx, cmd) 28 }) 29 } 30 31 t.Run("Alert notification state", func(t *testing.T) { 32 setup() 33 var alertID int64 = 7 34 var orgID int64 = 5 35 var notifierID int64 = 10 36 oldTimeNow := timeNow 37 now := time.Date(2018, 9, 30, 0, 0, 0, 0, time.UTC) 38 timeNow = func() time.Time { return now } 39 40 defer func() { timeNow = oldTimeNow }() 41 42 t.Run("Get no existing state should create a new state", func(t *testing.T) { 43 query := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} 44 err := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query) 45 require.Nil(t, err) 46 require.NotNil(t, query.Result) 47 require.Equal(t, models.AlertNotificationStateUnknown, query.Result.State) 48 require.Equal(t, int64(0), query.Result.Version) 49 require.Equal(t, now.Unix(), query.Result.UpdatedAt) 50 51 t.Run("Get existing state should not create a new state", func(t *testing.T) { 52 query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} 53 err := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) 54 require.Nil(t, err) 55 require.NotNil(t, query2.Result) 56 require.Equal(t, query.Result.Id, query2.Result.Id) 57 require.Equal(t, now.Unix(), query2.Result.UpdatedAt) 58 }) 59 60 t.Run("Update existing state to pending with correct version should update database", func(t *testing.T) { 61 s := *query.Result 62 63 cmd := models.SetAlertNotificationStateToPendingCommand{ 64 Id: s.Id, 65 Version: s.Version, 66 AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, 67 } 68 69 err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) 70 require.Nil(t, err) 71 require.Equal(t, int64(1), cmd.ResultVersion) 72 73 query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} 74 err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) 75 require.Nil(t, err) 76 require.Equal(t, int64(1), query2.Result.Version) 77 require.Equal(t, models.AlertNotificationStatePending, query2.Result.State) 78 require.Equal(t, now.Unix(), query2.Result.UpdatedAt) 79 80 t.Run("Update existing state to completed should update database", func(t *testing.T) { 81 s := *query.Result 82 setStateCmd := models.SetAlertNotificationStateToCompleteCommand{ 83 Id: s.Id, 84 Version: cmd.ResultVersion, 85 } 86 err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd) 87 require.Nil(t, err) 88 89 query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} 90 err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) 91 require.Nil(t, err) 92 require.Equal(t, int64(2), query3.Result.Version) 93 require.Equal(t, models.AlertNotificationStateCompleted, query3.Result.State) 94 require.Equal(t, now.Unix(), query3.Result.UpdatedAt) 95 }) 96 97 t.Run("Update existing state to completed should update database. regardless of version", func(t *testing.T) { 98 s := *query.Result 99 unknownVersion := int64(1000) 100 cmd := models.SetAlertNotificationStateToCompleteCommand{ 101 Id: s.Id, 102 Version: unknownVersion, 103 } 104 err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd) 105 require.Nil(t, err) 106 107 query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} 108 err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) 109 require.Nil(t, err) 110 require.Equal(t, unknownVersion+1, query3.Result.Version) 111 require.Equal(t, models.AlertNotificationStateCompleted, query3.Result.State) 112 require.Equal(t, now.Unix(), query3.Result.UpdatedAt) 113 }) 114 }) 115 116 t.Run("Update existing state to pending with incorrect version should return version mismatch error", func(t *testing.T) { 117 s := *query.Result 118 s.Version = 1000 119 cmd := models.SetAlertNotificationStateToPendingCommand{ 120 Id: s.NotifierId, 121 Version: s.Version, 122 AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, 123 } 124 err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) 125 require.Equal(t, models.ErrAlertNotificationStateVersionConflict, err) 126 }) 127 128 t.Run("Updating existing state to pending with incorrect version since alert rule state update version is higher", func(t *testing.T) { 129 s := *query.Result 130 cmd := models.SetAlertNotificationStateToPendingCommand{ 131 Id: s.Id, 132 Version: s.Version, 133 AlertRuleStateUpdatedVersion: 1000, 134 } 135 err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) 136 require.Nil(t, err) 137 138 require.Equal(t, int64(1), cmd.ResultVersion) 139 }) 140 141 t.Run("different version and same alert state change version should return error", func(t *testing.T) { 142 s := *query.Result 143 s.Version = 1000 144 cmd := models.SetAlertNotificationStateToPendingCommand{ 145 Id: s.Id, 146 Version: s.Version, 147 AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, 148 } 149 err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) 150 require.Error(t, err) 151 }) 152 }) 153 }) 154 155 t.Run("Alert notifications should be empty", func(t *testing.T) { 156 setup() 157 cmd := &models.GetAlertNotificationsQuery{ 158 OrgId: 2, 159 Name: "email", 160 } 161 162 err := sqlStore.GetAlertNotifications(context.Background(), cmd) 163 require.Nil(t, err) 164 require.Nil(t, cmd.Result) 165 }) 166 167 t.Run("Cannot save alert notifier with send reminder = true", func(t *testing.T) { 168 setup() 169 cmd := &models.CreateAlertNotificationCommand{ 170 Name: "ops", 171 Type: "email", 172 OrgId: 1, 173 SendReminder: true, 174 Settings: simplejson.New(), 175 } 176 177 t.Run("and missing frequency", func(t *testing.T) { 178 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 179 require.Equal(t, models.ErrNotificationFrequencyNotFound, err) 180 }) 181 182 t.Run("invalid frequency", func(t *testing.T) { 183 cmd.Frequency = "invalid duration" 184 185 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 186 require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( 187 err.Error())) 188 }) 189 }) 190 191 t.Run("Cannot update alert notifier with send reminder = false", func(t *testing.T) { 192 setup() 193 cmd := &models.CreateAlertNotificationCommand{ 194 Name: "ops update", 195 Type: "email", 196 OrgId: 1, 197 SendReminder: false, 198 Settings: simplejson.New(), 199 } 200 201 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 202 require.Nil(t, err) 203 204 updateCmd := &models.UpdateAlertNotificationCommand{ 205 Id: cmd.Result.Id, 206 SendReminder: true, 207 } 208 209 t.Run("and missing frequency", func(t *testing.T) { 210 err := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) 211 require.Equal(t, models.ErrNotificationFrequencyNotFound, err) 212 }) 213 214 t.Run("invalid frequency", func(t *testing.T) { 215 updateCmd.Frequency = "invalid duration" 216 217 err := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) 218 require.Error(t, err) 219 require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( 220 err.Error())) 221 }) 222 }) 223 224 t.Run("Can save Alert Notification", func(t *testing.T) { 225 setup() 226 cmd := &models.CreateAlertNotificationCommand{ 227 Name: "ops", 228 Type: "email", 229 OrgId: 1, 230 SendReminder: true, 231 Frequency: "10s", 232 Settings: simplejson.New(), 233 } 234 235 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 236 require.Nil(t, err) 237 require.NotEqual(t, 0, cmd.Result.Id) 238 require.NotEqual(t, 0, cmd.Result.OrgId) 239 require.Equal(t, "email", cmd.Result.Type) 240 require.Equal(t, 10*time.Second, cmd.Result.Frequency) 241 require.False(t, cmd.Result.DisableResolveMessage) 242 require.NotEmpty(t, cmd.Result.Uid) 243 244 t.Run("Cannot save Alert Notification with the same name", func(t *testing.T) { 245 err = sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 246 require.Error(t, err) 247 }) 248 t.Run("Cannot save Alert Notification with the same name and another uid", func(t *testing.T) { 249 anotherUidCmd := &models.CreateAlertNotificationCommand{ 250 Name: cmd.Name, 251 Type: cmd.Type, 252 OrgId: 1, 253 SendReminder: cmd.SendReminder, 254 Frequency: cmd.Frequency, 255 Settings: cmd.Settings, 256 Uid: "notifier1", 257 } 258 err = sqlStore.CreateAlertNotificationCommand(context.Background(), anotherUidCmd) 259 require.Error(t, err) 260 }) 261 t.Run("Can save Alert Notification with another name and another uid", func(t *testing.T) { 262 anotherUidCmd := &models.CreateAlertNotificationCommand{ 263 Name: "another ops", 264 Type: cmd.Type, 265 OrgId: 1, 266 SendReminder: cmd.SendReminder, 267 Frequency: cmd.Frequency, 268 Settings: cmd.Settings, 269 Uid: "notifier2", 270 } 271 err = sqlStore.CreateAlertNotificationCommand(context.Background(), anotherUidCmd) 272 require.Nil(t, err) 273 }) 274 275 t.Run("Can update alert notification", func(t *testing.T) { 276 newCmd := &models.UpdateAlertNotificationCommand{ 277 Name: "NewName", 278 Type: "webhook", 279 OrgId: cmd.Result.OrgId, 280 SendReminder: true, 281 DisableResolveMessage: true, 282 Frequency: "60s", 283 Settings: simplejson.New(), 284 Id: cmd.Result.Id, 285 } 286 err := sqlStore.UpdateAlertNotification(context.Background(), newCmd) 287 require.Nil(t, err) 288 require.Equal(t, "NewName", newCmd.Result.Name) 289 require.Equal(t, 60*time.Second, newCmd.Result.Frequency) 290 require.True(t, newCmd.Result.DisableResolveMessage) 291 }) 292 293 t.Run("Can update alert notification to disable sending of reminders", func(t *testing.T) { 294 newCmd := &models.UpdateAlertNotificationCommand{ 295 Name: "NewName", 296 Type: "webhook", 297 OrgId: cmd.Result.OrgId, 298 SendReminder: false, 299 Settings: simplejson.New(), 300 Id: cmd.Result.Id, 301 } 302 err := sqlStore.UpdateAlertNotification(context.Background(), newCmd) 303 require.Nil(t, err) 304 require.False(t, newCmd.Result.SendReminder) 305 }) 306 }) 307 308 t.Run("Can search using an array of ids", func(t *testing.T) { 309 setup() 310 cmd1 := models.CreateAlertNotificationCommand{Name: "nagios", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} 311 cmd2 := models.CreateAlertNotificationCommand{Name: "slack", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} 312 cmd3 := models.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} 313 cmd4 := models.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} 314 315 otherOrg := models.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} 316 317 require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd1)) 318 require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd2)) 319 require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd3)) 320 require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd4)) 321 require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &otherOrg)) 322 323 t.Run("search", func(t *testing.T) { 324 query := &models.GetAlertNotificationsWithUidToSendQuery{ 325 Uids: []string{cmd1.Result.Uid, cmd2.Result.Uid, "112341231"}, 326 OrgId: 1, 327 } 328 329 err := sqlStore.GetAlertNotificationsWithUidToSend(context.Background(), query) 330 require.Nil(t, err) 331 require.Equal(t, 3, len(query.Result)) 332 }) 333 334 t.Run("all", func(t *testing.T) { 335 query := &models.GetAllAlertNotificationsQuery{ 336 OrgId: 1, 337 } 338 339 err := sqlStore.GetAllAlertNotifications(context.Background(), query) 340 require.Nil(t, err) 341 require.Equal(t, 4, len(query.Result)) 342 require.Equal(t, cmd4.Name, query.Result[0].Name) 343 require.Equal(t, cmd1.Name, query.Result[1].Name) 344 require.Equal(t, cmd3.Name, query.Result[2].Name) 345 require.Equal(t, cmd2.Name, query.Result[3].Name) 346 }) 347 }) 348 349 t.Run("Notification Uid by Id Caching", func(t *testing.T) { 350 setup() 351 ss := InitTestDB(t) 352 353 notification := &models.CreateAlertNotificationCommand{Uid: "aNotificationUid", OrgId: 1, Name: "aNotificationUid"} 354 err := sqlStore.CreateAlertNotificationCommand(context.Background(), notification) 355 require.Nil(t, err) 356 357 byUidQuery := &models.GetAlertNotificationsWithUidQuery{ 358 Uid: notification.Uid, 359 OrgId: notification.OrgId, 360 } 361 362 notificationByUidErr := sqlStore.GetAlertNotificationsWithUid(context.Background(), byUidQuery) 363 require.Nil(t, notificationByUidErr) 364 365 t.Run("Can cache notification Uid", func(t *testing.T) { 366 byIdQuery := &models.GetAlertNotificationUidQuery{ 367 Id: byUidQuery.Result.Id, 368 OrgId: byUidQuery.Result.OrgId, 369 } 370 371 cacheKey := newAlertNotificationUidCacheKey(byIdQuery.OrgId, byIdQuery.Id) 372 373 resultBeforeCaching, foundBeforeCaching := ss.CacheService.Get(cacheKey) 374 require.False(t, foundBeforeCaching) 375 require.Nil(t, resultBeforeCaching) 376 377 notificationByIdErr := ss.GetAlertNotificationUidWithId(context.Background(), byIdQuery) 378 require.Nil(t, notificationByIdErr) 379 380 resultAfterCaching, foundAfterCaching := ss.CacheService.Get(cacheKey) 381 require.True(t, foundAfterCaching) 382 require.Equal(t, notification.Uid, resultAfterCaching) 383 }) 384 385 t.Run("Retrieves from cache when exists", func(t *testing.T) { 386 query := &models.GetAlertNotificationUidQuery{ 387 Id: 999, 388 OrgId: 100, 389 } 390 cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id) 391 ss.CacheService.Set(cacheKey, "a-cached-uid", -1) 392 393 err := ss.GetAlertNotificationUidWithId(context.Background(), query) 394 require.Nil(t, err) 395 require.Equal(t, "a-cached-uid", query.Result) 396 }) 397 398 t.Run("Returns an error without populating cache when the notification doesn't exist in the database", func(t *testing.T) { 399 query := &models.GetAlertNotificationUidQuery{ 400 Id: -1, 401 OrgId: 100, 402 } 403 404 err := ss.GetAlertNotificationUidWithId(context.Background(), query) 405 require.Equal(t, "", query.Result) 406 require.Error(t, err) 407 require.True(t, errors.Is(err, models.ErrAlertNotificationFailedTranslateUniqueID)) 408 409 cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id) 410 result, found := ss.CacheService.Get(cacheKey) 411 require.False(t, found) 412 require.Nil(t, result) 413 }) 414 }) 415 416 t.Run("Cannot update non-existing Alert Notification", func(t *testing.T) { 417 setup() 418 updateCmd := &models.UpdateAlertNotificationCommand{ 419 Name: "NewName", 420 Type: "webhook", 421 OrgId: 1, 422 SendReminder: true, 423 DisableResolveMessage: true, 424 Frequency: "60s", 425 Settings: simplejson.New(), 426 Id: 1, 427 } 428 err := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) 429 require.Equal(t, models.ErrAlertNotificationNotFound, err) 430 431 t.Run("using UID", func(t *testing.T) { 432 updateWithUidCmd := &models.UpdateAlertNotificationWithUidCommand{ 433 Name: "NewName", 434 Type: "webhook", 435 OrgId: 1, 436 SendReminder: true, 437 DisableResolveMessage: true, 438 Frequency: "60s", 439 Settings: simplejson.New(), 440 Uid: "uid", 441 NewUid: "newUid", 442 } 443 err := sqlStore.UpdateAlertNotificationWithUid(context.Background(), updateWithUidCmd) 444 require.Equal(t, models.ErrAlertNotificationNotFound, err) 445 }) 446 }) 447 448 t.Run("Can delete Alert Notification", func(t *testing.T) { 449 setup() 450 cmd := &models.CreateAlertNotificationCommand{ 451 Name: "ops update", 452 Type: "email", 453 OrgId: 1, 454 SendReminder: false, 455 Settings: simplejson.New(), 456 } 457 458 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 459 require.Nil(t, err) 460 461 deleteCmd := &models.DeleteAlertNotificationCommand{ 462 Id: cmd.Result.Id, 463 OrgId: 1, 464 } 465 err = sqlStore.DeleteAlertNotification(context.Background(), deleteCmd) 466 require.Nil(t, err) 467 468 t.Run("using UID", func(t *testing.T) { 469 err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) 470 require.Nil(t, err) 471 472 deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{ 473 Uid: cmd.Result.Uid, 474 OrgId: 1, 475 } 476 477 err = sqlStore.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) 478 require.Nil(t, err) 479 require.Equal(t, cmd.Result.Id, deleteWithUidCmd.DeletedAlertNotificationId) 480 }) 481 }) 482 483 t.Run("Cannot delete non-existing Alert Notification", func(t *testing.T) { 484 setup() 485 deleteCmd := &models.DeleteAlertNotificationCommand{ 486 Id: 1, 487 OrgId: 1, 488 } 489 err := sqlStore.DeleteAlertNotification(context.Background(), deleteCmd) 490 require.Equal(t, models.ErrAlertNotificationNotFound, err) 491 492 t.Run("using UID", func(t *testing.T) { 493 deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{ 494 Uid: "uid", 495 OrgId: 1, 496 } 497 err = sqlStore.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) 498 require.Equal(t, models.ErrAlertNotificationNotFound, err) 499 }) 500 }) 501} 502