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 }