github.com/mattermost/mattermost-server/v5@v5.39.3/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  	"context"
     8  	"errors"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/mattermost/mattermost-server/v5/model"
    17  	"github.com/mattermost/mattermost-server/v5/store"
    18  	"github.com/mattermost/mattermost-server/v5/store/retrylayer"
    19  )
    20  
    21  func TestReactionStore(t *testing.T, ss store.Store, s SqlStore) {
    22  	t.Run("ReactionSave", func(t *testing.T) { testReactionSave(t, ss) })
    23  	t.Run("ReactionDelete", func(t *testing.T) { testReactionDelete(t, ss) })
    24  	t.Run("ReactionGetForPost", func(t *testing.T) { testReactionGetForPost(t, ss) })
    25  	t.Run("ReactionGetForPostSince", func(t *testing.T) { testReactionGetForPostSince(t, ss, s) })
    26  	t.Run("ReactionDeleteAllWithEmojiName", func(t *testing.T) { testReactionDeleteAllWithEmojiName(t, ss, s) })
    27  	t.Run("PermanentDeleteBatch", func(t *testing.T) { testReactionStorePermanentDeleteBatch(t, ss) })
    28  	t.Run("ReactionBulkGetForPosts", func(t *testing.T) { testReactionBulkGetForPosts(t, ss) })
    29  	t.Run("ReactionDeadlock", func(t *testing.T) { testReactionDeadlock(t, ss) })
    30  }
    31  
    32  func testReactionSave(t *testing.T, ss store.Store) {
    33  	post, err := ss.Post().Save(&model.Post{
    34  		ChannelId: model.NewId(),
    35  		UserId:    model.NewId(),
    36  	})
    37  	require.NoError(t, err)
    38  	firstUpdateAt := post.UpdateAt
    39  
    40  	reaction1 := &model.Reaction{
    41  		UserId:    model.NewId(),
    42  		PostId:    post.Id,
    43  		EmojiName: model.NewId(),
    44  	}
    45  
    46  	time.Sleep(time.Millisecond)
    47  	reaction, nErr := ss.Reaction().Save(reaction1)
    48  	require.NoError(t, nErr)
    49  
    50  	saved := reaction
    51  	assert.Equal(t, saved.UserId, reaction1.UserId, "should've saved reaction user_id and returned it")
    52  	assert.Equal(t, saved.PostId, reaction1.PostId, "should've saved reaction post_id and returned it")
    53  	assert.Equal(t, saved.EmojiName, reaction1.EmojiName, "should've saved reaction emoji_name and returned it")
    54  	assert.NotZero(t, saved.UpdateAt, "should've saved reaction update_at and returned it")
    55  	assert.Zero(t, saved.DeleteAt, "should've saved reaction delete_at with zero value and returned it")
    56  
    57  	var secondUpdateAt int64
    58  	postList, err := ss.Post().Get(context.Background(), reaction1.PostId, false, false, false, "")
    59  	require.NoError(t, err)
    60  
    61  	assert.True(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = true on post")
    62  	assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, firstUpdateAt, "should've marked post as updated when HasReactions changed")
    63  
    64  	if postList.Posts[post.Id].HasReactions && postList.Posts[post.Id].UpdateAt != firstUpdateAt {
    65  		secondUpdateAt = postList.Posts[post.Id].UpdateAt
    66  	}
    67  
    68  	_, nErr = ss.Reaction().Save(reaction1)
    69  	assert.NoError(t, nErr, "should've allowed saving a duplicate reaction")
    70  
    71  	// different user
    72  	reaction2 := &model.Reaction{
    73  		UserId:    model.NewId(),
    74  		PostId:    reaction1.PostId,
    75  		EmojiName: reaction1.EmojiName,
    76  	}
    77  
    78  	time.Sleep(time.Millisecond)
    79  	_, nErr = ss.Reaction().Save(reaction2)
    80  	require.NoError(t, nErr)
    81  
    82  	postList, err = ss.Post().Get(context.Background(), reaction2.PostId, false, false, false, "")
    83  	require.NoError(t, err)
    84  
    85  	assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, secondUpdateAt, "should've marked post as updated even if HasReactions doesn't change")
    86  
    87  	// different post
    88  	reaction3 := &model.Reaction{
    89  		UserId:    reaction1.UserId,
    90  		PostId:    model.NewId(),
    91  		EmojiName: reaction1.EmojiName,
    92  	}
    93  	_, nErr = ss.Reaction().Save(reaction3)
    94  	require.NoError(t, nErr)
    95  
    96  	// different emoji
    97  	reaction4 := &model.Reaction{
    98  		UserId:    reaction1.UserId,
    99  		PostId:    reaction1.PostId,
   100  		EmojiName: model.NewId(),
   101  	}
   102  	_, nErr = ss.Reaction().Save(reaction4)
   103  	require.NoError(t, nErr)
   104  
   105  	// invalid reaction
   106  	reaction5 := &model.Reaction{
   107  		UserId: reaction1.UserId,
   108  		PostId: reaction1.PostId,
   109  	}
   110  	_, nErr = ss.Reaction().Save(reaction5)
   111  	require.Error(t, nErr, "should've failed for invalid reaction")
   112  
   113  }
   114  
   115  func testReactionDelete(t *testing.T, ss store.Store) {
   116  	t.Run("Delete", func(t *testing.T) {
   117  		post, err := ss.Post().Save(&model.Post{
   118  			ChannelId: model.NewId(),
   119  			UserId:    model.NewId(),
   120  		})
   121  		require.NoError(t, err)
   122  
   123  		reaction := &model.Reaction{
   124  			UserId:    model.NewId(),
   125  			PostId:    post.Id,
   126  			EmojiName: model.NewId(),
   127  		}
   128  
   129  		_, nErr := ss.Reaction().Save(reaction)
   130  		require.NoError(t, nErr)
   131  
   132  		result, err := ss.Post().Get(context.Background(), reaction.PostId, false, false, false, "")
   133  		require.NoError(t, err)
   134  
   135  		firstUpdateAt := result.Posts[post.Id].UpdateAt
   136  
   137  		_, nErr = ss.Reaction().Delete(reaction)
   138  		require.NoError(t, nErr)
   139  
   140  		reactions, rErr := ss.Reaction().GetForPost(post.Id, false)
   141  		require.NoError(t, rErr)
   142  
   143  		assert.Empty(t, reactions, "should've deleted reaction")
   144  
   145  		postList, err := ss.Post().Get(context.Background(), post.Id, false, false, false, "")
   146  		require.NoError(t, err)
   147  
   148  		assert.False(t, postList.Posts[post.Id].HasReactions, "should've set HasReactions = false on post")
   149  		assert.NotEqual(t, postList.Posts[post.Id].UpdateAt, firstUpdateAt, "should mark post as updated after deleting reactions")
   150  	})
   151  
   152  	t.Run("Undelete", func(t *testing.T) {
   153  		post, err := ss.Post().Save(&model.Post{
   154  			ChannelId: model.NewId(),
   155  			UserId:    model.NewId(),
   156  		})
   157  		require.NoError(t, err)
   158  
   159  		reaction := &model.Reaction{
   160  			UserId:    model.NewId(),
   161  			PostId:    post.Id,
   162  			EmojiName: model.NewId(),
   163  		}
   164  
   165  		savedReaction, nErr := ss.Reaction().Save(reaction)
   166  		require.NoError(t, nErr)
   167  
   168  		updateAt := savedReaction.UpdateAt
   169  
   170  		_, nErr = ss.Reaction().Delete(savedReaction)
   171  		require.NoError(t, nErr)
   172  
   173  		// add same reaction back and ensure update_at is set
   174  		_, nErr = ss.Reaction().Save(savedReaction)
   175  		require.NoError(t, nErr)
   176  
   177  		reactions, err := ss.Reaction().GetForPost(post.Id, false)
   178  		require.NoError(t, err)
   179  
   180  		assert.Len(t, reactions, 1)
   181  		assert.GreaterOrEqual(t, reactions[0].UpdateAt, updateAt)
   182  	})
   183  }
   184  
   185  func testReactionGetForPost(t *testing.T, ss store.Store) {
   186  	postId := model.NewId()
   187  
   188  	userId := model.NewId()
   189  
   190  	reactions := []*model.Reaction{
   191  		{
   192  			UserId:    userId,
   193  			PostId:    postId,
   194  			EmojiName: "smile",
   195  		},
   196  		{
   197  			UserId:    model.NewId(),
   198  			PostId:    postId,
   199  			EmojiName: "smile",
   200  		},
   201  		{
   202  			UserId:    userId,
   203  			PostId:    postId,
   204  			EmojiName: "sad",
   205  		},
   206  		{
   207  			UserId:    userId,
   208  			PostId:    model.NewId(),
   209  			EmojiName: "angry",
   210  		},
   211  	}
   212  
   213  	for _, reaction := range reactions {
   214  		_, err := ss.Reaction().Save(reaction)
   215  		require.NoError(t, err)
   216  	}
   217  
   218  	// save and delete an additional reaction to test soft deletion
   219  	temp := &model.Reaction{
   220  		UserId:    userId,
   221  		PostId:    postId,
   222  		EmojiName: "grin",
   223  	}
   224  	savedTmp, err := ss.Reaction().Save(temp)
   225  	require.NoError(t, err)
   226  	_, err = ss.Reaction().Delete(savedTmp)
   227  	require.NoError(t, err)
   228  
   229  	returned, err := ss.Reaction().GetForPost(postId, false)
   230  	require.NoError(t, err)
   231  	require.Len(t, returned, 3, "should've returned 3 reactions")
   232  
   233  	for _, reaction := range reactions {
   234  		found := false
   235  
   236  		for _, returnedReaction := range returned {
   237  			if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
   238  				returnedReaction.EmojiName == reaction.EmojiName && returnedReaction.UpdateAt > 0 {
   239  				found = true
   240  				break
   241  			}
   242  		}
   243  
   244  		if !found {
   245  			assert.NotEqual(t, reaction.PostId, postId, "should've returned reaction for post %v", reaction)
   246  		} else if found {
   247  			assert.Equal(t, reaction.PostId, postId, "shouldn't have returned reaction for another post")
   248  		}
   249  	}
   250  
   251  	// Should return cached item
   252  	returned, err = ss.Reaction().GetForPost(postId, true)
   253  	require.NoError(t, err)
   254  	require.Len(t, returned, 3, "should've returned 3 reactions")
   255  
   256  	for _, reaction := range reactions {
   257  		found := false
   258  
   259  		for _, returnedReaction := range returned {
   260  			if returnedReaction.UserId == reaction.UserId && returnedReaction.PostId == reaction.PostId &&
   261  				returnedReaction.EmojiName == reaction.EmojiName {
   262  				found = true
   263  				break
   264  			}
   265  		}
   266  
   267  		if !found {
   268  			assert.NotEqual(t, reaction.PostId, postId, "should've returned reaction for post %v", reaction)
   269  		} else if found {
   270  			assert.Equal(t, reaction.PostId, postId, "shouldn't have returned reaction for another post")
   271  		}
   272  	}
   273  }
   274  
   275  func testReactionGetForPostSince(t *testing.T, ss store.Store, s SqlStore) {
   276  	now := model.GetMillis()
   277  	later := now + 1800000 // add 30 minutes
   278  	remoteId := model.NewId()
   279  
   280  	postId := model.NewId()
   281  	userId := model.NewId()
   282  	reactions := []*model.Reaction{
   283  		{
   284  			UserId:    userId,
   285  			PostId:    postId,
   286  			EmojiName: "smile",
   287  			UpdateAt:  later,
   288  		},
   289  		{
   290  			UserId:    model.NewId(),
   291  			PostId:    postId,
   292  			EmojiName: "smile",
   293  		},
   294  		{
   295  			UserId:    userId,
   296  			PostId:    postId,
   297  			EmojiName: "sad",
   298  			UpdateAt:  later,
   299  			RemoteId:  &remoteId,
   300  		},
   301  		{
   302  			UserId:    userId,
   303  			PostId:    model.NewId(),
   304  			EmojiName: "angry",
   305  		},
   306  		{
   307  			UserId:    userId,
   308  			PostId:    postId,
   309  			EmojiName: "angry",
   310  			DeleteAt:  now + 1,
   311  			UpdateAt:  later,
   312  		},
   313  	}
   314  
   315  	for _, reaction := range reactions {
   316  		delete := reaction.DeleteAt
   317  		update := reaction.UpdateAt
   318  
   319  		_, err := ss.Reaction().Save(reaction)
   320  		require.NoError(t, err)
   321  
   322  		if delete > 0 {
   323  			_, err = ss.Reaction().Delete(reaction)
   324  			require.NoError(t, err)
   325  		}
   326  		if update > 0 {
   327  			err = forceUpdateAt(reaction, update, s)
   328  			require.NoError(t, err)
   329  		}
   330  		err = forceNULL(reaction, s) // test COALESCE
   331  		require.NoError(t, err)
   332  	}
   333  
   334  	t.Run("reactions since", func(t *testing.T) {
   335  		// should return 2 reactions that are not deleted for post
   336  		returned, err := ss.Reaction().GetForPostSince(postId, later-1, "", false)
   337  		require.NoError(t, err)
   338  		require.Len(t, returned, 2, "should've returned 2 non-deleted reactions")
   339  		for _, r := range returned {
   340  			assert.Zero(t, r.DeleteAt, "should not have returned deleted reaction")
   341  		}
   342  
   343  	})
   344  
   345  	t.Run("reactions since, incl deleted", func(t *testing.T) {
   346  		// should return 3 reactions for post, including one deleted
   347  		returned, err := ss.Reaction().GetForPostSince(postId, later-1, "", true)
   348  		require.NoError(t, err)
   349  		require.Len(t, returned, 3, "should've returned 3 reactions")
   350  		var count int
   351  		for _, r := range returned {
   352  			if r.DeleteAt > 0 {
   353  				count++
   354  			}
   355  		}
   356  		assert.Equal(t, 1, count, "should not have returned 1 deleted reaction")
   357  
   358  	})
   359  
   360  	t.Run("reactions since, filter remoteId", func(t *testing.T) {
   361  		// should return 1 reactions that are not deleted for post and have no remoteId
   362  		returned, err := ss.Reaction().GetForPostSince(postId, later-1, remoteId, false)
   363  		require.NoError(t, err)
   364  		require.Len(t, returned, 1, "should've returned 1 filtered reactions")
   365  		for _, r := range returned {
   366  			assert.Zero(t, r.DeleteAt, "should not have returned deleted reaction")
   367  		}
   368  	})
   369  
   370  	t.Run("reactions since, invalid post", func(t *testing.T) {
   371  		// should return 0 reactions for invalid post
   372  		returned, err := ss.Reaction().GetForPostSince(model.NewId(), later-1, "", true)
   373  		require.NoError(t, err)
   374  		require.Empty(t, returned, "should've returned 0 reactions")
   375  	})
   376  
   377  	t.Run("reactions since, far future", func(t *testing.T) {
   378  		// should return 0 reactions for since far in the future
   379  		returned, err := ss.Reaction().GetForPostSince(postId, later*2, "", true)
   380  		require.NoError(t, err)
   381  		require.Empty(t, returned, "should've returned 0 reactions")
   382  	})
   383  }
   384  
   385  func forceUpdateAt(reaction *model.Reaction, updateAt int64, s SqlStore) error {
   386  	params := map[string]interface{}{
   387  		"UserId":    reaction.UserId,
   388  		"PostId":    reaction.PostId,
   389  		"EmojiName": reaction.EmojiName,
   390  		"UpdateAt":  updateAt,
   391  	}
   392  
   393  	sqlResult, err := s.GetMaster().Exec(`
   394  		UPDATE 
   395  			Reactions 
   396  		SET 
   397  			UpdateAt=:UpdateAt 
   398  		WHERE 
   399  			UserId = :UserId AND 
   400  			PostId = :PostId AND 
   401  			EmojiName = :EmojiName`, params,
   402  	)
   403  
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	rows, err := sqlResult.RowsAffected()
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	if rows != 1 {
   414  		return errors.New("expected one row affected")
   415  	}
   416  	return nil
   417  }
   418  
   419  func forceNULL(reaction *model.Reaction, s SqlStore) error {
   420  	if _, err := s.GetMaster().Exec(`UPDATE Reactions SET UpdateAt = NULL WHERE UpdateAt = 0`); err != nil {
   421  		return err
   422  	}
   423  	if _, err := s.GetMaster().Exec(`UPDATE Reactions SET DeleteAt = NULL WHERE DeleteAt = 0`); err != nil {
   424  		return err
   425  	}
   426  	return nil
   427  }
   428  
   429  func testReactionDeleteAllWithEmojiName(t *testing.T, ss store.Store, s SqlStore) {
   430  	emojiToDelete := model.NewId()
   431  
   432  	post, err1 := ss.Post().Save(&model.Post{
   433  		ChannelId: model.NewId(),
   434  		UserId:    model.NewId(),
   435  	})
   436  	require.NoError(t, err1)
   437  	post2, err2 := ss.Post().Save(&model.Post{
   438  		ChannelId: model.NewId(),
   439  		UserId:    model.NewId(),
   440  	})
   441  	require.NoError(t, err2)
   442  	post3, err3 := ss.Post().Save(&model.Post{
   443  		ChannelId: model.NewId(),
   444  		UserId:    model.NewId(),
   445  	})
   446  	require.NoError(t, err3)
   447  
   448  	userId := model.NewId()
   449  
   450  	reactions := []*model.Reaction{
   451  		{
   452  			UserId:    userId,
   453  			PostId:    post.Id,
   454  			EmojiName: emojiToDelete,
   455  		},
   456  		{
   457  			UserId:    model.NewId(),
   458  			PostId:    post.Id,
   459  			EmojiName: emojiToDelete,
   460  		},
   461  		{
   462  			UserId:    userId,
   463  			PostId:    post.Id,
   464  			EmojiName: "sad",
   465  		},
   466  		{
   467  			UserId:    userId,
   468  			PostId:    post2.Id,
   469  			EmojiName: "angry",
   470  		},
   471  		{
   472  			UserId:    userId,
   473  			PostId:    post3.Id,
   474  			EmojiName: emojiToDelete,
   475  		},
   476  	}
   477  
   478  	for _, reaction := range reactions {
   479  		_, err := ss.Reaction().Save(reaction)
   480  		require.NoError(t, err)
   481  
   482  		// make at least one Reaction record contain NULL for Update and DeleteAt to simulate post schema upgrade case.
   483  		if reaction.EmojiName == emojiToDelete {
   484  			err = forceNULL(reaction, s)
   485  			require.NoError(t, err)
   486  		}
   487  	}
   488  
   489  	err := ss.Reaction().DeleteAllWithEmojiName(emojiToDelete)
   490  	require.NoError(t, err)
   491  
   492  	// check that the reactions were deleted
   493  	returned, err := ss.Reaction().GetForPost(post.Id, false)
   494  	require.NoError(t, err)
   495  	require.Len(t, returned, 1, "should've only removed reactions with emoji name")
   496  
   497  	for _, reaction := range returned {
   498  		assert.NotEqual(t, reaction.EmojiName, "smile", "should've removed reaction with emoji name")
   499  	}
   500  
   501  	returned, err = ss.Reaction().GetForPost(post2.Id, false)
   502  	require.NoError(t, err)
   503  	assert.Len(t, returned, 1, "should've only removed reactions with emoji name")
   504  
   505  	returned, err = ss.Reaction().GetForPost(post3.Id, false)
   506  	require.NoError(t, err)
   507  	assert.Empty(t, returned, "should've only removed reactions with emoji name")
   508  
   509  	// check that the posts are updated
   510  	postList, err := ss.Post().Get(context.Background(), post.Id, false, false, false, "")
   511  	require.NoError(t, err)
   512  	assert.True(t, postList.Posts[post.Id].HasReactions, "post should still have reactions")
   513  
   514  	postList, err = ss.Post().Get(context.Background(), post2.Id, false, false, false, "")
   515  	require.NoError(t, err)
   516  	assert.True(t, postList.Posts[post2.Id].HasReactions, "post should still have reactions")
   517  
   518  	postList, err = ss.Post().Get(context.Background(), post3.Id, false, false, false, "")
   519  	require.NoError(t, err)
   520  	assert.False(t, postList.Posts[post3.Id].HasReactions, "post shouldn't have reactions any more")
   521  
   522  }
   523  
   524  func testReactionStorePermanentDeleteBatch(t *testing.T, ss store.Store) {
   525  	const limit = 1000
   526  	team, err := ss.Team().Save(&model.Team{
   527  		DisplayName: "DisplayName",
   528  		Name:        "team" + model.NewId(),
   529  		Email:       MakeEmail(),
   530  		Type:        model.TEAM_OPEN,
   531  	})
   532  	require.NoError(t, err)
   533  	channel, err := ss.Channel().Save(&model.Channel{
   534  		TeamId:      team.Id,
   535  		DisplayName: "DisplayName",
   536  		Name:        "channel" + model.NewId(),
   537  		Type:        model.CHANNEL_OPEN,
   538  	}, -1)
   539  	require.NoError(t, err)
   540  	olderPost, err := ss.Post().Save(&model.Post{
   541  		ChannelId: channel.Id,
   542  		UserId:    model.NewId(),
   543  		CreateAt:  1000,
   544  	})
   545  	require.NoError(t, err)
   546  	newerPost, err := ss.Post().Save(&model.Post{
   547  		ChannelId: channel.Id,
   548  		UserId:    model.NewId(),
   549  		CreateAt:  3000,
   550  	})
   551  	require.NoError(t, err)
   552  
   553  	// Reactions will be deleted based on the timestamp of their post. So the time at
   554  	// which a reaction was created doesn't matter.
   555  	reactions := []*model.Reaction{
   556  		{
   557  			UserId:    model.NewId(),
   558  			PostId:    olderPost.Id,
   559  			EmojiName: "sad",
   560  		},
   561  		{
   562  			UserId:    model.NewId(),
   563  			PostId:    olderPost.Id,
   564  			EmojiName: "sad",
   565  		},
   566  		{
   567  			UserId:    model.NewId(),
   568  			PostId:    newerPost.Id,
   569  			EmojiName: "smile",
   570  		},
   571  	}
   572  
   573  	for _, reaction := range reactions {
   574  		_, err = ss.Reaction().Save(reaction)
   575  		require.NoError(t, err)
   576  	}
   577  
   578  	_, _, err = ss.Post().PermanentDeleteBatchForRetentionPolicies(0, 2000, limit, model.RetentionPolicyCursor{})
   579  	require.NoError(t, err)
   580  
   581  	_, err = ss.Reaction().DeleteOrphanedRows(limit)
   582  	require.NoError(t, err)
   583  
   584  	returned, err := ss.Reaction().GetForPost(olderPost.Id, false)
   585  	require.NoError(t, err)
   586  	require.Len(t, returned, 0, "reactions for older post should have been deleted")
   587  
   588  	returned, err = ss.Reaction().GetForPost(newerPost.Id, false)
   589  	require.NoError(t, err)
   590  	require.Len(t, returned, 1, "reactions for newer post should not have been deleted")
   591  }
   592  
   593  func testReactionBulkGetForPosts(t *testing.T, ss store.Store) {
   594  	postId := model.NewId()
   595  	post2Id := model.NewId()
   596  	post3Id := model.NewId()
   597  	post4Id := model.NewId()
   598  
   599  	userId := model.NewId()
   600  
   601  	reactions := []*model.Reaction{
   602  		{
   603  			UserId:    userId,
   604  			PostId:    postId,
   605  			EmojiName: "smile",
   606  		},
   607  		{
   608  			UserId:    model.NewId(),
   609  			PostId:    post2Id,
   610  			EmojiName: "smile",
   611  		},
   612  		{
   613  			UserId:    userId,
   614  			PostId:    post3Id,
   615  			EmojiName: "sad",
   616  		},
   617  		{
   618  			UserId:    userId,
   619  			PostId:    postId,
   620  			EmojiName: "angry",
   621  		},
   622  		{
   623  			UserId:    userId,
   624  			PostId:    post2Id,
   625  			EmojiName: "angry",
   626  		},
   627  		{
   628  			UserId:    userId,
   629  			PostId:    post4Id,
   630  			EmojiName: "angry",
   631  		},
   632  	}
   633  
   634  	for _, reaction := range reactions {
   635  		_, err := ss.Reaction().Save(reaction)
   636  		require.NoError(t, err)
   637  	}
   638  
   639  	postIds := []string{postId, post2Id, post3Id}
   640  	returned, err := ss.Reaction().BulkGetForPosts(postIds)
   641  	require.NoError(t, err)
   642  	require.Len(t, returned, 5, "should've returned 5 reactions")
   643  
   644  	post4IdFound := false
   645  	for _, reaction := range returned {
   646  		if reaction.PostId == post4Id {
   647  			post4IdFound = true
   648  			break
   649  		}
   650  	}
   651  
   652  	require.False(t, post4IdFound, "Wrong reaction returned")
   653  
   654  }
   655  
   656  // testReactionDeadlock is a best-case attempt to recreate the deadlock scenario.
   657  // It at least deadlocks 2 times out of 5.
   658  func testReactionDeadlock(t *testing.T, ss store.Store) {
   659  	ss = retrylayer.New(ss)
   660  
   661  	post, err := ss.Post().Save(&model.Post{
   662  		ChannelId: model.NewId(),
   663  		UserId:    model.NewId(),
   664  	})
   665  	require.NoError(t, err)
   666  
   667  	reaction1 := &model.Reaction{
   668  		UserId:    model.NewId(),
   669  		PostId:    post.Id,
   670  		EmojiName: model.NewId(),
   671  	}
   672  	_, nErr := ss.Reaction().Save(reaction1)
   673  	require.NoError(t, nErr)
   674  
   675  	// different user
   676  	reaction2 := &model.Reaction{
   677  		UserId:    model.NewId(),
   678  		PostId:    reaction1.PostId,
   679  		EmojiName: reaction1.EmojiName,
   680  	}
   681  	_, nErr = ss.Reaction().Save(reaction2)
   682  	require.NoError(t, nErr)
   683  
   684  	// different post
   685  	reaction3 := &model.Reaction{
   686  		UserId:    reaction1.UserId,
   687  		PostId:    model.NewId(),
   688  		EmojiName: reaction1.EmojiName,
   689  	}
   690  	_, nErr = ss.Reaction().Save(reaction3)
   691  	require.NoError(t, nErr)
   692  
   693  	// different emoji
   694  	reaction4 := &model.Reaction{
   695  		UserId:    reaction1.UserId,
   696  		PostId:    reaction1.PostId,
   697  		EmojiName: model.NewId(),
   698  	}
   699  	_, nErr = ss.Reaction().Save(reaction4)
   700  	require.NoError(t, nErr)
   701  
   702  	var wg sync.WaitGroup
   703  	wg.Add(2)
   704  	// 1st tx
   705  	go func() {
   706  		defer wg.Done()
   707  		err := ss.Reaction().DeleteAllWithEmojiName(reaction1.EmojiName)
   708  		require.NoError(t, err)
   709  	}()
   710  
   711  	// 2nd tx
   712  	go func() {
   713  		defer wg.Done()
   714  		_, err := ss.Reaction().Delete(reaction2)
   715  		require.NoError(t, err)
   716  	}()
   717  	wg.Wait()
   718  }