github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/store/storetest/reaction_store.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package storetest
     5  
     6  import (
     7  	"sync"
     8  	"testing"
     9  
    10  	"github.com/mattermost/mattermost-server/v5/model"
    11  	"github.com/mattermost/mattermost-server/v5/store"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestReactionStore(t *testing.T, ss store.Store) {
    18  	t.Run("ReactionSave", func(t *testing.T) { testReactionSave(t, ss) })
    19  	t.Run("ReactionDelete", func(t *testing.T) { testReactionDelete(t, ss) })
    20  	t.Run("ReactionGetForPost", func(t *testing.T) { testReactionGetForPost(t, ss) })
    21  	t.Run("ReactionDeleteAllWithEmojiName", func(t *testing.T) { testReactionDeleteAllWithEmojiName(t, ss) })
    22  	t.Run("PermanentDeleteBatch", func(t *testing.T) { testReactionStorePermanentDeleteBatch(t, ss) })
    23  	t.Run("ReactionBulkGetForPosts", func(t *testing.T) { testReactionBulkGetForPosts(t, ss) })
    24  	t.Run("ReactionDeadlock", func(t *testing.T) { testReactionDeadlock(t, ss) })
    25  }
    26  
    27  func testReactionSave(t *testing.T, ss store.Store) {
    28  	post, err := ss.Post().Save(&model.Post{
    29  		ChannelId: model.NewId(),
    30  		UserId:    model.NewId(),
    31  	})
    32  	require.Nil(t, err)
    33  	firstUpdateAt := post.UpdateAt
    34  
    35  	reaction1 := &model.Reaction{
    36  		UserId:    model.NewId(),
    37  		PostId:    post.Id,
    38  		EmojiName: model.NewId(),
    39  	}
    40  	reaction, nErr := ss.Reaction().Save(reaction1)
    41  	require.Nil(t, nErr)
    42  
    43  	saved := reaction
    44  	assert.Equal(t, saved.UserId, reaction1.UserId, "should've saved reaction user_id and returned it")
    45  	assert.Equal(t, saved.PostId, reaction1.PostId, "should've saved reaction post_id and returned it")
    46  	assert.Equal(t, saved.EmojiName, reaction1.EmojiName, "should've saved reaction emoji_name and returned it")
    47  
    48  	var secondUpdateAt int64
    49  	postList, err := ss.Post().Get(reaction1.PostId, false)
    50  	require.Nil(t, err)
    51  
    52  	assert.True(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = true on post")
    53  	assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, firstUpdateAt, "should've marked post as updated when HasReactions changed")
    54  
    55  	if postList.Posts[post.Id].HasReactions && postList.Posts[post.Id].UpdateAt != firstUpdateAt {
    56  		secondUpdateAt = postList.Posts[post.Id].UpdateAt
    57  	}
    58  
    59  	_, nErr = ss.Reaction().Save(reaction1)
    60  	assert.Nil(t, nErr, "should've allowed saving a duplicate reaction")
    61  
    62  	// different user
    63  	reaction2 := &model.Reaction{
    64  		UserId:    model.NewId(),
    65  		PostId:    reaction1.PostId,
    66  		EmojiName: reaction1.EmojiName,
    67  	}
    68  	_, nErr = ss.Reaction().Save(reaction2)
    69  	require.Nil(t, nErr)
    70  
    71  	postList, err = ss.Post().Get(reaction2.PostId, false)
    72  	require.Nil(t, err)
    73  
    74  	assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, secondUpdateAt, "should've marked post as updated even if HasReactions doesn't change")
    75  
    76  	// different post
    77  	reaction3 := &model.Reaction{
    78  		UserId:    reaction1.UserId,
    79  		PostId:    model.NewId(),
    80  		EmojiName: reaction1.EmojiName,
    81  	}
    82  	_, nErr = ss.Reaction().Save(reaction3)
    83  	require.Nil(t, nErr)
    84  
    85  	// different emoji
    86  	reaction4 := &model.Reaction{
    87  		UserId:    reaction1.UserId,
    88  		PostId:    reaction1.PostId,
    89  		EmojiName: model.NewId(),
    90  	}
    91  	_, nErr = ss.Reaction().Save(reaction4)
    92  	require.Nil(t, nErr)
    93  
    94  	// invalid reaction
    95  	reaction5 := &model.Reaction{
    96  		UserId: reaction1.UserId,
    97  		PostId: reaction1.PostId,
    98  	}
    99  	_, nErr = ss.Reaction().Save(reaction5)
   100  	require.NotNil(t, nErr, "should've failed for invalid reaction")
   101  
   102  }
   103  
   104  func testReactionDelete(t *testing.T, ss store.Store) {
   105  	post, err := ss.Post().Save(&model.Post{
   106  		ChannelId: model.NewId(),
   107  		UserId:    model.NewId(),
   108  	})
   109  	require.Nil(t, err)
   110  
   111  	reaction := &model.Reaction{
   112  		UserId:    model.NewId(),
   113  		PostId:    post.Id,
   114  		EmojiName: model.NewId(),
   115  	}
   116  
   117  	_, nErr := ss.Reaction().Save(reaction)
   118  	require.Nil(t, nErr)
   119  
   120  	result, err := ss.Post().Get(reaction.PostId, false)
   121  	require.Nil(t, err)
   122  
   123  	firstUpdateAt := result.Posts[post.Id].UpdateAt
   124  
   125  	_, nErr = ss.Reaction().Delete(reaction)
   126  	require.Nil(t, nErr)
   127  
   128  	reactions, rErr := ss.Reaction().GetForPost(post.Id, false)
   129  	require.Nil(t, rErr)
   130  
   131  	assert.Empty(t, reactions, "should've deleted reaction")
   132  
   133  	postList, err := ss.Post().Get(post.Id, false)
   134  	require.Nil(t, err)
   135  
   136  	assert.False(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = false on post")
   137  	assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, firstUpdateAt, "should mark post as updated after deleting reactions")
   138  }
   139  
   140  func testReactionGetForPost(t *testing.T, ss store.Store) {
   141  	postId := model.NewId()
   142  
   143  	userId := model.NewId()
   144  
   145  	reactions := []*model.Reaction{
   146  		{
   147  			UserId:    userId,
   148  			PostId:    postId,
   149  			EmojiName: "smile",
   150  		},
   151  		{
   152  			UserId:    model.NewId(),
   153  			PostId:    postId,
   154  			EmojiName: "smile",
   155  		},
   156  		{
   157  			UserId:    userId,
   158  			PostId:    postId,
   159  			EmojiName: "sad",
   160  		},
   161  		{
   162  			UserId:    userId,
   163  			PostId:    model.NewId(),
   164  			EmojiName: "angry",
   165  		},
   166  	}
   167  
   168  	for _, reaction := range reactions {
   169  		_, err := ss.Reaction().Save(reaction)
   170  		require.Nil(t, err)
   171  	}
   172  
   173  	returned, err := ss.Reaction().GetForPost(postId, false)
   174  	require.Nil(t, err)
   175  	require.Len(t, returned, 3, "should've returned 3 reactions")
   176  
   177  	for _, reaction := range reactions {
   178  		found := false
   179  
   180  		for _, returnedReaction := range returned {
   181  			if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
   182  				returnedReaction.EmojiName == reaction.EmojiName {
   183  				found = true
   184  				break
   185  			}
   186  		}
   187  
   188  		if !found {
   189  			assert.NotEqual(t, reaction.PostId, postId, "should've returned reaction for post %v", reaction)
   190  		} else if found {
   191  			assert.Equal(t, reaction.PostId, postId, "shouldn't have returned reaction for another post")
   192  		}
   193  	}
   194  
   195  	// Should return cached item
   196  	returned, err = ss.Reaction().GetForPost(postId, true)
   197  	require.Nil(t, err)
   198  	require.Len(t, returned, 3, "should've returned 3 reactions")
   199  
   200  	for _, reaction := range reactions {
   201  		found := false
   202  
   203  		for _, returnedReaction := range returned {
   204  			if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
   205  				returnedReaction.EmojiName == reaction.EmojiName {
   206  				found = true
   207  				break
   208  			}
   209  		}
   210  
   211  		if !found {
   212  			assert.NotEqual(t, reaction.PostId, postId, "should've returned reaction for post %v", reaction)
   213  		} else if found {
   214  			assert.Equal(t, reaction.PostId, postId, "shouldn't have returned reaction for another post")
   215  		}
   216  	}
   217  }
   218  
   219  func testReactionDeleteAllWithEmojiName(t *testing.T, ss store.Store) {
   220  	emojiToDelete := model.NewId()
   221  
   222  	post, err1 := ss.Post().Save(&model.Post{
   223  		ChannelId: model.NewId(),
   224  		UserId:    model.NewId(),
   225  	})
   226  	require.Nil(t, err1)
   227  	post2, err2 := ss.Post().Save(&model.Post{
   228  		ChannelId: model.NewId(),
   229  		UserId:    model.NewId(),
   230  	})
   231  	require.Nil(t, err2)
   232  	post3, err3 := ss.Post().Save(&model.Post{
   233  		ChannelId: model.NewId(),
   234  		UserId:    model.NewId(),
   235  	})
   236  	require.Nil(t, err3)
   237  
   238  	userId := model.NewId()
   239  
   240  	reactions := []*model.Reaction{
   241  		{
   242  			UserId:    userId,
   243  			PostId:    post.Id,
   244  			EmojiName: emojiToDelete,
   245  		},
   246  		{
   247  			UserId:    model.NewId(),
   248  			PostId:    post.Id,
   249  			EmojiName: emojiToDelete,
   250  		},
   251  		{
   252  			UserId:    userId,
   253  			PostId:    post.Id,
   254  			EmojiName: "sad",
   255  		},
   256  		{
   257  			UserId:    userId,
   258  			PostId:    post2.Id,
   259  			EmojiName: "angry",
   260  		},
   261  		{
   262  			UserId:    userId,
   263  			PostId:    post3.Id,
   264  			EmojiName: emojiToDelete,
   265  		},
   266  	}
   267  
   268  	for _, reaction := range reactions {
   269  		_, err := ss.Reaction().Save(reaction)
   270  		require.Nil(t, err)
   271  	}
   272  
   273  	err := ss.Reaction().DeleteAllWithEmojiName(emojiToDelete)
   274  	require.Nil(t, err)
   275  
   276  	// check that the reactions were deleted
   277  	returned, err := ss.Reaction().GetForPost(post.Id, false)
   278  	require.Nil(t, err)
   279  	require.Len(t, returned, 1, "should've only removed reactions with emoji name")
   280  
   281  	for _, reaction := range returned {
   282  		assert.NotEqual(t, reaction.EmojiName, "smile", "should've removed reaction with emoji name")
   283  	}
   284  
   285  	returned, err = ss.Reaction().GetForPost(post2.Id, false)
   286  	require.Nil(t, err)
   287  	assert.Len(t, returned, 1, "should've only removed reactions with emoji name")
   288  
   289  	returned, err = ss.Reaction().GetForPost(post3.Id, false)
   290  	require.Nil(t, err)
   291  	assert.Empty(t, returned, "should've only removed reactions with emoji name")
   292  
   293  	// check that the posts are updated
   294  	postList, err := ss.Post().Get(post.Id, false)
   295  	require.Nil(t, err)
   296  	assert.True(t, postList.Posts[post.Id].HasReactions, "post should still have reactions")
   297  
   298  	postList, err = ss.Post().Get(post2.Id, false)
   299  	require.Nil(t, err)
   300  	assert.True(t, postList.Posts[post2.Id].HasReactions, "post should still have reactions")
   301  
   302  	postList, err = ss.Post().Get(post3.Id, false)
   303  	require.Nil(t, err)
   304  	assert.False(t, postList.Posts[post3.Id].HasReactions, "post shouldn't have reactions any more")
   305  
   306  }
   307  
   308  func testReactionStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
   309  	post, err1 := ss.Post().Save(&model.Post{
   310  		ChannelId: model.NewId(),
   311  		UserId:    model.NewId(),
   312  	})
   313  	require.Nil(t, err1)
   314  
   315  	reactions := []*model.Reaction{
   316  		{
   317  			UserId:    model.NewId(),
   318  			PostId:    post.Id,
   319  			EmojiName: "sad",
   320  			CreateAt:  1000,
   321  		},
   322  		{
   323  			UserId:    model.NewId(),
   324  			PostId:    post.Id,
   325  			EmojiName: "sad",
   326  			CreateAt:  1500,
   327  		},
   328  		{
   329  			UserId:    model.NewId(),
   330  			PostId:    post.Id,
   331  			EmojiName: "sad",
   332  			CreateAt:  2000,
   333  		},
   334  		{
   335  			UserId:    model.NewId(),
   336  			PostId:    post.Id,
   337  			EmojiName: "sad",
   338  			CreateAt:  2000,
   339  		},
   340  	}
   341  
   342  	// Need to hang on to a reaction to delete later in order to clear the cache, as "allowFromCache" isn't honoured any more.
   343  	var lastReaction *model.Reaction
   344  	for _, reaction := range reactions {
   345  		var nErr error
   346  		lastReaction, nErr = ss.Reaction().Save(reaction)
   347  		require.Nil(t, nErr)
   348  	}
   349  
   350  	returned, err := ss.Reaction().GetForPost(post.Id, false)
   351  	require.Nil(t, err)
   352  	require.Len(t, returned, 4, "expected 4 reactions")
   353  
   354  	_, err = ss.Reaction().PermanentDeleteBatch(1800, 1000)
   355  	require.Nil(t, err)
   356  
   357  	// This is to force a clear of the cache.
   358  	_, err = ss.Reaction().Delete(lastReaction)
   359  	require.Nil(t, err)
   360  
   361  	returned, err = ss.Reaction().GetForPost(post.Id, false)
   362  	require.Nil(t, err)
   363  	require.Len(t, returned, 1, "expected 1 reaction. Got: %v", len(returned))
   364  }
   365  
   366  func testReactionBulkGetForPosts(t *testing.T, ss store.Store) {
   367  	postId := model.NewId()
   368  	post2Id := model.NewId()
   369  	post3Id := model.NewId()
   370  	post4Id := model.NewId()
   371  
   372  	userId := model.NewId()
   373  
   374  	reactions := []*model.Reaction{
   375  		{
   376  			UserId:    userId,
   377  			PostId:    postId,
   378  			EmojiName: "smile",
   379  		},
   380  		{
   381  			UserId:    model.NewId(),
   382  			PostId:    post2Id,
   383  			EmojiName: "smile",
   384  		},
   385  		{
   386  			UserId:    userId,
   387  			PostId:    post3Id,
   388  			EmojiName: "sad",
   389  		},
   390  		{
   391  			UserId:    userId,
   392  			PostId:    postId,
   393  			EmojiName: "angry",
   394  		},
   395  		{
   396  			UserId:    userId,
   397  			PostId:    post2Id,
   398  			EmojiName: "angry",
   399  		},
   400  		{
   401  			UserId:    userId,
   402  			PostId:    post4Id,
   403  			EmojiName: "angry",
   404  		},
   405  	}
   406  
   407  	for _, reaction := range reactions {
   408  		_, err := ss.Reaction().Save(reaction)
   409  		require.Nil(t, err)
   410  	}
   411  
   412  	postIds := []string{postId, post2Id, post3Id}
   413  	returned, err := ss.Reaction().BulkGetForPosts(postIds)
   414  	require.Nil(t, err)
   415  	require.Len(t, returned, 5, "should've returned 5 reactions")
   416  
   417  	post4IdFound := false
   418  	for _, reaction := range returned {
   419  		if reaction.PostId == post4Id {
   420  			post4IdFound = true
   421  			break
   422  		}
   423  	}
   424  
   425  	require.False(t, post4IdFound, "Wrong reaction returned")
   426  
   427  }
   428  
   429  // testReactionDeadlock is a best-case attempt to recreate the deadlock scenario.
   430  // It at least deadlocks 2 times out of 5.
   431  func testReactionDeadlock(t *testing.T, ss store.Store) {
   432  	post, err := ss.Post().Save(&model.Post{
   433  		ChannelId: model.NewId(),
   434  		UserId:    model.NewId(),
   435  	})
   436  	require.Nil(t, err)
   437  
   438  	reaction1 := &model.Reaction{
   439  		UserId:    model.NewId(),
   440  		PostId:    post.Id,
   441  		EmojiName: model.NewId(),
   442  	}
   443  	_, nErr := ss.Reaction().Save(reaction1)
   444  	require.Nil(t, nErr)
   445  
   446  	// different user
   447  	reaction2 := &model.Reaction{
   448  		UserId:    model.NewId(),
   449  		PostId:    reaction1.PostId,
   450  		EmojiName: reaction1.EmojiName,
   451  	}
   452  	_, nErr = ss.Reaction().Save(reaction2)
   453  	require.Nil(t, nErr)
   454  
   455  	// different post
   456  	reaction3 := &model.Reaction{
   457  		UserId:    reaction1.UserId,
   458  		PostId:    model.NewId(),
   459  		EmojiName: reaction1.EmojiName,
   460  	}
   461  	_, nErr = ss.Reaction().Save(reaction3)
   462  	require.Nil(t, nErr)
   463  
   464  	// different emoji
   465  	reaction4 := &model.Reaction{
   466  		UserId:    reaction1.UserId,
   467  		PostId:    reaction1.PostId,
   468  		EmojiName: model.NewId(),
   469  	}
   470  	_, nErr = ss.Reaction().Save(reaction4)
   471  	require.Nil(t, nErr)
   472  
   473  	var wg sync.WaitGroup
   474  	wg.Add(2)
   475  	// 1st tx
   476  	go func() {
   477  		defer wg.Done()
   478  		err := ss.Reaction().DeleteAllWithEmojiName(reaction1.EmojiName)
   479  		require.Nil(t, err)
   480  	}()
   481  
   482  	// 2nd tx
   483  	go func() {
   484  		defer wg.Done()
   485  		_, err := ss.Reaction().Delete(reaction2)
   486  		require.Nil(t, err)
   487  	}()
   488  	wg.Wait()
   489  }