// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package storetests import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mattermost/focalboard/server/model" "github.com/mattermost/focalboard/server/services/store" "github.com/mattermost/focalboard/server/utils" ) func StoreTestNotificationHintsStore(t *testing.T, setup func(t *testing.T) (store.Store, func())) { t.Run("UpsertNotificationHint", func(t *testing.T) { store, tearDown := setup(t) defer tearDown() testUpsertNotificationHint(t, store) }) t.Run("DeleteNotificationHint", func(t *testing.T) { store, tearDown := setup(t) defer tearDown() testDeleteNotificationHint(t, store) }) t.Run("GetNotificationHint", func(t *testing.T) { store, tearDown := setup(t) defer tearDown() testGetNotificationHint(t, store) }) t.Run("GetNextNotificationHint", func(t *testing.T) { store, tearDown := setup(t) defer tearDown() testGetNextNotificationHint(t, store) }) } func testUpsertNotificationHint(t *testing.T, store store.Store) { t.Run("create notification hint", func(t *testing.T) { hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: utils.NewID(utils.IDTypeUser), } hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "upsert notification hint should not error") assert.Equal(t, hint.BlockID, hintNew.BlockID) assert.NoError(t, hintNew.IsValid()) }) t.Run("duplicate notification hint", func(t *testing.T) { hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: utils.NewID(utils.IDTypeUser), } hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "upsert notification hint should not error") // sleep a short time so the notify_at timestamps won't collide time.Sleep(time.Millisecond * 20) hint = &model.NotificationHint{ BlockType: model.TypeCard, BlockID: hintNew.BlockID, ModifiedByID: hintNew.ModifiedByID, } hintDup, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "upsert notification hint should not error") // notify_at should be updated assert.Greater(t, hintDup.NotifyAt, hintNew.NotifyAt) }) t.Run("invalid notification hint", func(t *testing.T) { hint := &model.NotificationHint{} _, err := store.UpsertNotificationHint(hint, time.Second*15) assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") hint.BlockType = "board" _, err = store.UpsertNotificationHint(hint, time.Second*15) assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") _, err = store.UpsertNotificationHint(hint, time.Second*15) assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") hint.ModifiedByID = utils.NewID(utils.IDTypeUser) _, err = store.UpsertNotificationHint(hint, time.Second*15) assert.ErrorAs(t, err, &model.ErrInvalidNotificationHint{}, "invalid notification hint should error") hint.BlockID = utils.NewID(utils.IDTypeBlock) hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) assert.NoError(t, err, "valid notification hint should not error") assert.NoError(t, hintNew.IsValid(), "created notification hint should be valid") }) } func testDeleteNotificationHint(t *testing.T, store store.Store) { t.Run("delete notification hint", func(t *testing.T) { hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: utils.NewID(utils.IDTypeUser), } hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "create notification hint should not error") // check the notification hint exists hint, err = store.GetNotificationHint(hintNew.BlockID) require.NoError(t, err, "get notification hint should not error") assert.Equal(t, hintNew.BlockID, hint.BlockID) assert.Equal(t, hintNew.CreateAt, hint.CreateAt) err = store.DeleteNotificationHint(hintNew.BlockID) require.NoError(t, err, "delete notification hint should not error") // check the notification hint was deleted hint, err = store.GetNotificationHint(hintNew.BlockID) require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") assert.Nil(t, hint) }) t.Run("delete non-existent notification hint", func(t *testing.T) { err := store.DeleteNotificationHint("bogus") require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") }) } func testGetNotificationHint(t *testing.T, store store.Store) { t.Run("get notification hint", func(t *testing.T) { hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: utils.NewID(utils.IDTypeUser), } hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "create notification hint should not error") // make sure notification hint can be fetched hint, err = store.GetNotificationHint(hintNew.BlockID) require.NoError(t, err, "get notification hint should not error") assert.Equal(t, hintNew, hint) }) t.Run("get non-existent notification hint", func(t *testing.T) { hint, err := store.GetNotificationHint("bogus") require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") assert.Nil(t, hint, "hint should be nil") }) } func testGetNextNotificationHint(t *testing.T, store store.Store) { t.Run("get next notification hint", func(t *testing.T) { const loops = 5 ids := [5]string{} modifiedBy := utils.NewID(utils.IDTypeUser) // create some hints with unique notifyAt for i := 0; i < loops; i++ { hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: modifiedBy, } hintNew, err := store.UpsertNotificationHint(hint, time.Second*15) require.NoError(t, err, "create notification hint should not error") ids[i] = hintNew.BlockID time.Sleep(time.Millisecond * 20) // ensure next timestamp is unique } // check the hints come back in the right order notifyAt := utils.GetMillisForTime(time.Now().Add(time.Millisecond * 50)) for i := 0; i < loops; i++ { hint, err := store.GetNextNotificationHint(false) require.NoError(t, err, "get next notification hint should not error") require.NotNil(t, hint, "get next notification hint should not return nil") assert.Equal(t, ids[i], hint.BlockID) assert.Less(t, notifyAt, hint.NotifyAt) notifyAt = hint.NotifyAt err = store.DeleteNotificationHint(hint.BlockID) require.NoError(t, err, "delete notification hint should not error") } }) t.Run("get next notification hint from empty table", func(t *testing.T) { // empty the table err := emptyNotificationHintTable(store) require.NoError(t, err, "emptying notification hint table should not error") for { hint, err2 := store.GetNextNotificationHint(false) if model.IsErrNotFound(err2) { break } require.NoError(t, err2, "get next notification hint should not error") err2 = store.DeleteNotificationHint(hint.BlockID) require.NoError(t, err2, "delete notification hint should not error") } _, err = store.GetNextNotificationHint(false) require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") }) t.Run("get next notification hint and remove", func(t *testing.T) { // empty the table err := emptyNotificationHintTable(store) require.NoError(t, err, "emptying notification hint table should not error") hint := &model.NotificationHint{ BlockType: model.TypeCard, BlockID: utils.NewID(utils.IDTypeBlock), ModifiedByID: utils.NewID(utils.IDTypeUser), } hintNew, err := store.UpsertNotificationHint(hint, time.Second*1) require.NoError(t, err, "create notification hint should not error") hintDeleted, err := store.GetNextNotificationHint(true) require.NoError(t, err, "get next notification hint should not error") require.NotNil(t, hintDeleted, "get next notification hint should not return nil") assert.Equal(t, hintNew.BlockID, hintDeleted.BlockID) // should be no hint left _, err = store.GetNextNotificationHint(false) require.True(t, model.IsErrNotFound(err), "error should be of type store.ErrNotFound") }) } func emptyNotificationHintTable(store store.Store) error { for { hint, err := store.GetNextNotificationHint(false) if model.IsErrNotFound(err) { break } if err != nil { return err } err = store.DeleteNotificationHint(hint.BlockID) if err != nil { return err } } return nil }