github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/store/storetest/file_info_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 "fmt" 8 "sort" 9 "testing" 10 "time" 11 12 "github.com/mattermost/mattermost-server/v5/model" 13 "github.com/mattermost/mattermost-server/v5/store" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestFileInfoStore(t *testing.T, ss store.Store) { 20 t.Run("FileInfoSaveGet", func(t *testing.T) { testFileInfoSaveGet(t, ss) }) 21 t.Run("FileInfoSaveGetByPath", func(t *testing.T) { testFileInfoSaveGetByPath(t, ss) }) 22 t.Run("FileInfoGetForPost", func(t *testing.T) { testFileInfoGetForPost(t, ss) }) 23 t.Run("FileInfoGetForUser", func(t *testing.T) { testFileInfoGetForUser(t, ss) }) 24 t.Run("FileInfoGetWithOptions", func(t *testing.T) { testFileInfoGetWithOptions(t, ss) }) 25 t.Run("FileInfoAttachToPost", func(t *testing.T) { testFileInfoAttachToPost(t, ss) }) 26 t.Run("FileInfoDeleteForPost", func(t *testing.T) { testFileInfoDeleteForPost(t, ss) }) 27 t.Run("FileInfoPermanentDelete", func(t *testing.T) { testFileInfoPermanentDelete(t, ss) }) 28 t.Run("FileInfoPermanentDeleteBatch", func(t *testing.T) { testFileInfoPermanentDeleteBatch(t, ss) }) 29 t.Run("FileInfoPermanentDeleteByUser", func(t *testing.T) { testFileInfoPermanentDeleteByUser(t, ss) }) 30 } 31 32 func testFileInfoSaveGet(t *testing.T, ss store.Store) { 33 info := &model.FileInfo{ 34 CreatorId: model.NewId(), 35 Path: "file.txt", 36 } 37 38 info, err := ss.FileInfo().Save(info) 39 require.NoError(t, err) 40 require.NotEqual(t, len(info.Id), 0) 41 42 defer func() { 43 ss.FileInfo().PermanentDelete(info.Id) 44 }() 45 46 rinfo, err := ss.FileInfo().Get(info.Id) 47 require.NoError(t, err) 48 require.Equal(t, info.Id, rinfo.Id) 49 50 info2, err := ss.FileInfo().Save(&model.FileInfo{ 51 CreatorId: model.NewId(), 52 Path: "file.txt", 53 DeleteAt: 123, 54 }) 55 require.NoError(t, err) 56 57 _, err = ss.FileInfo().Get(info2.Id) 58 assert.Error(t, err) 59 60 defer func() { 61 ss.FileInfo().PermanentDelete(info2.Id) 62 }() 63 } 64 65 func testFileInfoSaveGetByPath(t *testing.T, ss store.Store) { 66 info := &model.FileInfo{ 67 CreatorId: model.NewId(), 68 Path: fmt.Sprintf("%v/file.txt", model.NewId()), 69 } 70 71 info, err := ss.FileInfo().Save(info) 72 require.NoError(t, err) 73 assert.NotEqual(t, len(info.Id), 0) 74 defer func() { 75 ss.FileInfo().PermanentDelete(info.Id) 76 }() 77 78 rinfo, err := ss.FileInfo().GetByPath(info.Path) 79 require.NoError(t, err) 80 assert.Equal(t, info.Id, rinfo.Id) 81 82 info2, err := ss.FileInfo().Save(&model.FileInfo{ 83 CreatorId: model.NewId(), 84 Path: "file.txt", 85 DeleteAt: 123, 86 }) 87 require.NoError(t, err) 88 89 _, err = ss.FileInfo().GetByPath(info2.Id) 90 assert.Error(t, err) 91 92 defer func() { 93 ss.FileInfo().PermanentDelete(info2.Id) 94 }() 95 } 96 97 func testFileInfoGetForPost(t *testing.T, ss store.Store) { 98 userId := model.NewId() 99 postId := model.NewId() 100 101 infos := []*model.FileInfo{ 102 { 103 PostId: postId, 104 CreatorId: userId, 105 Path: "file.txt", 106 }, 107 { 108 PostId: postId, 109 CreatorId: userId, 110 Path: "file.txt", 111 }, 112 { 113 PostId: postId, 114 CreatorId: userId, 115 Path: "file.txt", 116 DeleteAt: 123, 117 }, 118 { 119 PostId: model.NewId(), 120 CreatorId: userId, 121 Path: "file.txt", 122 }, 123 } 124 125 for i, info := range infos { 126 newInfo, err := ss.FileInfo().Save(info) 127 require.NoError(t, err) 128 infos[i] = newInfo 129 defer func(id string) { 130 ss.FileInfo().PermanentDelete(id) 131 }(newInfo.Id) 132 } 133 134 testCases := []struct { 135 Name string 136 PostId string 137 ReadFromMaster bool 138 IncludeDeleted bool 139 AllowFromCache bool 140 ExpectedPosts int 141 }{ 142 { 143 Name: "Fetch from master, without deleted and without cache", 144 PostId: postId, 145 ReadFromMaster: true, 146 IncludeDeleted: false, 147 AllowFromCache: false, 148 ExpectedPosts: 2, 149 }, 150 { 151 Name: "Fetch from master, with deleted and without cache", 152 PostId: postId, 153 ReadFromMaster: true, 154 IncludeDeleted: true, 155 AllowFromCache: false, 156 ExpectedPosts: 3, 157 }, 158 { 159 Name: "Fetch from master, with deleted and with cache", 160 PostId: postId, 161 ReadFromMaster: true, 162 IncludeDeleted: true, 163 AllowFromCache: true, 164 ExpectedPosts: 3, 165 }, 166 { 167 Name: "Fetch from replica, without deleted and without cache", 168 PostId: postId, 169 ReadFromMaster: false, 170 IncludeDeleted: false, 171 AllowFromCache: false, 172 ExpectedPosts: 2, 173 }, 174 { 175 Name: "Fetch from replica, with deleted and without cache", 176 PostId: postId, 177 ReadFromMaster: false, 178 IncludeDeleted: true, 179 AllowFromCache: false, 180 ExpectedPosts: 3, 181 }, 182 { 183 Name: "Fetch from replica, with deleted and without cache", 184 PostId: postId, 185 ReadFromMaster: false, 186 IncludeDeleted: true, 187 AllowFromCache: true, 188 ExpectedPosts: 3, 189 }, 190 { 191 Name: "Fetch from replica, without deleted and with cache", 192 PostId: postId, 193 ReadFromMaster: true, 194 IncludeDeleted: false, 195 AllowFromCache: true, 196 ExpectedPosts: 2, 197 }, 198 } 199 200 for _, tc := range testCases { 201 t.Run(tc.Name, func(t *testing.T) { 202 postInfos, err := ss.FileInfo().GetForPost( 203 tc.PostId, 204 tc.ReadFromMaster, 205 tc.IncludeDeleted, 206 tc.AllowFromCache, 207 ) 208 require.NoError(t, err) 209 assert.Len(t, postInfos, tc.ExpectedPosts) 210 211 }) 212 } 213 } 214 215 func testFileInfoGetForUser(t *testing.T, ss store.Store) { 216 userId := model.NewId() 217 userId2 := model.NewId() 218 postId := model.NewId() 219 220 infos := []*model.FileInfo{ 221 { 222 PostId: postId, 223 CreatorId: userId, 224 Path: "file.txt", 225 }, 226 { 227 PostId: postId, 228 CreatorId: userId, 229 Path: "file.txt", 230 }, 231 { 232 PostId: postId, 233 CreatorId: userId, 234 Path: "file.txt", 235 }, 236 { 237 PostId: model.NewId(), 238 CreatorId: userId2, 239 Path: "file.txt", 240 }, 241 } 242 243 for i, info := range infos { 244 newInfo, err := ss.FileInfo().Save(info) 245 require.NoError(t, err) 246 infos[i] = newInfo 247 defer func(id string) { 248 ss.FileInfo().PermanentDelete(id) 249 }(newInfo.Id) 250 } 251 252 userPosts, err := ss.FileInfo().GetForUser(userId) 253 require.NoError(t, err) 254 assert.Len(t, userPosts, 3) 255 256 userPosts, err = ss.FileInfo().GetForUser(userId2) 257 require.NoError(t, err) 258 assert.Len(t, userPosts, 1) 259 } 260 261 func testFileInfoGetWithOptions(t *testing.T, ss store.Store) { 262 makePost := func(chId string, user string) *model.Post { 263 post := model.Post{} 264 post.ChannelId = chId 265 post.UserId = user 266 _, err := ss.Post().Save(&post) 267 require.NoError(t, err) 268 return &post 269 } 270 271 makeFile := func(post *model.Post, user string, createAt int64, idPrefix string) model.FileInfo { 272 id := model.NewId() 273 id = idPrefix + id[1:] // hacky way to get sortable Ids to confirm secondary Id sort works 274 fileInfo := model.FileInfo{ 275 Id: id, 276 CreatorId: user, 277 Path: "file.txt", 278 CreateAt: createAt, 279 } 280 if post.Id != "" { 281 fileInfo.PostId = post.Id 282 } 283 _, err := ss.FileInfo().Save(&fileInfo) 284 require.NoError(t, err) 285 return fileInfo 286 } 287 288 userId1 := model.NewId() 289 userId2 := model.NewId() 290 291 channelId1 := model.NewId() 292 channelId2 := model.NewId() 293 channelId3 := model.NewId() 294 295 post1_1 := makePost(channelId1, userId1) // post 1 by user 1 296 post1_2 := makePost(channelId3, userId1) // post 2 by user 1 297 post2_1 := makePost(channelId2, userId2) 298 post2_2 := makePost(channelId3, userId2) 299 300 epoch := time.Date(2020, 1, 1, 1, 1, 1, 1, time.UTC) 301 file1_1 := makeFile(post1_1, userId1, epoch.AddDate(0, 0, 1).Unix(), "a") // file 1 by user 1 302 file1_2 := makeFile(post1_2, userId1, epoch.AddDate(0, 0, 2).Unix(), "b") // file 2 by user 1 303 file1_3 := makeFile(&model.Post{}, userId1, epoch.AddDate(0, 0, 3).Unix(), "c") // file that is not attached to a post 304 file2_1 := makeFile(post2_1, userId2, epoch.AddDate(0, 0, 4).Unix(), "d") // file 2 by user 1 305 file2_2 := makeFile(post2_2, userId2, epoch.AddDate(0, 0, 5).Unix(), "e") 306 307 // delete a file 308 _, err := ss.FileInfo().DeleteForPost(file2_2.PostId) 309 require.NoError(t, err) 310 311 testCases := []struct { 312 Name string 313 Page, PerPage int 314 Opt *model.GetFileInfosOptions 315 ExpectedFileIds []string 316 }{ 317 { 318 Name: "Get files with nil option", 319 Page: 0, 320 PerPage: 10, 321 Opt: nil, 322 ExpectedFileIds: []string{file1_1.Id, file1_2.Id, file1_3.Id, file2_1.Id}, 323 }, 324 { 325 Name: "Get files including deleted", 326 Page: 0, 327 PerPage: 10, 328 Opt: &model.GetFileInfosOptions{IncludeDeleted: true}, 329 ExpectedFileIds: []string{file1_1.Id, file1_2.Id, file1_3.Id, file2_1.Id, file2_2.Id}, 330 }, 331 { 332 Name: "Get files including deleted filtered by channel", 333 Page: 0, 334 PerPage: 10, 335 Opt: &model.GetFileInfosOptions{ 336 IncludeDeleted: true, 337 ChannelIds: []string{channelId3}, 338 }, 339 ExpectedFileIds: []string{file1_2.Id, file2_2.Id}, 340 }, 341 { 342 Name: "Get files including deleted filtered by channel and user", 343 Page: 0, 344 PerPage: 10, 345 Opt: &model.GetFileInfosOptions{ 346 IncludeDeleted: true, 347 UserIds: []string{userId1}, 348 ChannelIds: []string{channelId3}, 349 }, 350 ExpectedFileIds: []string{file1_2.Id}, 351 }, 352 { 353 Name: "Get files including deleted sorted by created at", 354 Page: 0, 355 PerPage: 10, 356 Opt: &model.GetFileInfosOptions{ 357 IncludeDeleted: true, 358 SortBy: model.FILEINFO_SORT_BY_CREATED, 359 }, 360 ExpectedFileIds: []string{file1_1.Id, file1_2.Id, file1_3.Id, file2_1.Id, file2_2.Id}, 361 }, 362 { 363 Name: "Get files filtered by user ordered by created at descending", 364 Page: 0, 365 PerPage: 10, 366 Opt: &model.GetFileInfosOptions{ 367 UserIds: []string{userId1}, 368 SortBy: model.FILEINFO_SORT_BY_CREATED, 369 SortDescending: true, 370 }, 371 ExpectedFileIds: []string{file1_3.Id, file1_2.Id, file1_1.Id}, 372 }, 373 { 374 Name: "Get all files including deleted ordered by created descending 2nd page of 3 per page ", 375 Page: 1, 376 PerPage: 3, 377 Opt: &model.GetFileInfosOptions{ 378 IncludeDeleted: true, 379 SortBy: model.FILEINFO_SORT_BY_CREATED, 380 SortDescending: true, 381 }, 382 ExpectedFileIds: []string{file1_2.Id, file1_1.Id}, 383 }, 384 } 385 386 for _, tc := range testCases { 387 t.Run(tc.Name, func(t *testing.T) { 388 fileInfos, err := ss.FileInfo().GetWithOptions(tc.Page, tc.PerPage, tc.Opt) 389 require.NoError(t, err) 390 require.Len(t, fileInfos, len(tc.ExpectedFileIds)) 391 for i := range tc.ExpectedFileIds { 392 assert.Equal(t, tc.ExpectedFileIds[i], fileInfos[i].Id) 393 } 394 }) 395 } 396 } 397 398 type byFileInfoId []*model.FileInfo 399 400 func (a byFileInfoId) Len() int { return len(a) } 401 func (a byFileInfoId) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 402 func (a byFileInfoId) Less(i, j int) bool { return a[i].Id < a[j].Id } 403 404 func testFileInfoAttachToPost(t *testing.T, ss store.Store) { 405 t.Run("should attach files", func(t *testing.T) { 406 userId := model.NewId() 407 postId := model.NewId() 408 409 info1, err := ss.FileInfo().Save(&model.FileInfo{ 410 CreatorId: userId, 411 Path: "file.txt", 412 }) 413 require.NoError(t, err) 414 info2, err := ss.FileInfo().Save(&model.FileInfo{ 415 CreatorId: userId, 416 Path: "file2.txt", 417 }) 418 require.NoError(t, err) 419 420 require.Equal(t, "", info1.PostId) 421 require.Equal(t, "", info2.PostId) 422 423 err = ss.FileInfo().AttachToPost(info1.Id, postId, userId) 424 assert.NoError(t, err) 425 info1.PostId = postId 426 427 err = ss.FileInfo().AttachToPost(info2.Id, postId, userId) 428 assert.NoError(t, err) 429 info2.PostId = postId 430 431 data, err := ss.FileInfo().GetForPost(postId, true, false, false) 432 require.NoError(t, err) 433 434 expected := []*model.FileInfo{info1, info2} 435 sort.Sort(byFileInfoId(expected)) 436 sort.Sort(byFileInfoId(data)) 437 assert.EqualValues(t, expected, data) 438 }) 439 440 t.Run("should not attach files to multiple posts", func(t *testing.T) { 441 userId := model.NewId() 442 postId := model.NewId() 443 444 info, err := ss.FileInfo().Save(&model.FileInfo{ 445 CreatorId: userId, 446 Path: "file.txt", 447 }) 448 require.NoError(t, err) 449 450 require.Equal(t, "", info.PostId) 451 452 err = ss.FileInfo().AttachToPost(info.Id, model.NewId(), userId) 453 require.NoError(t, err) 454 455 err = ss.FileInfo().AttachToPost(info.Id, postId, userId) 456 require.Error(t, err) 457 }) 458 459 t.Run("should not attach files owned from a different user", func(t *testing.T) { 460 userId := model.NewId() 461 postId := model.NewId() 462 463 info, err := ss.FileInfo().Save(&model.FileInfo{ 464 CreatorId: model.NewId(), 465 Path: "file.txt", 466 }) 467 require.NoError(t, err) 468 469 require.Equal(t, "", info.PostId) 470 471 err = ss.FileInfo().AttachToPost(info.Id, postId, userId) 472 assert.Error(t, err) 473 }) 474 475 t.Run("should attach files uploaded by nouser", func(t *testing.T) { 476 postId := model.NewId() 477 478 info, err := ss.FileInfo().Save(&model.FileInfo{ 479 CreatorId: "nouser", 480 Path: "file.txt", 481 }) 482 require.NoError(t, err) 483 assert.Equal(t, "", info.PostId) 484 485 err = ss.FileInfo().AttachToPost(info.Id, postId, model.NewId()) 486 require.NoError(t, err) 487 488 data, err := ss.FileInfo().GetForPost(postId, true, false, false) 489 require.NoError(t, err) 490 info.PostId = postId 491 assert.EqualValues(t, []*model.FileInfo{info}, data) 492 }) 493 } 494 495 func testFileInfoDeleteForPost(t *testing.T, ss store.Store) { 496 userId := model.NewId() 497 postId := model.NewId() 498 499 infos := []*model.FileInfo{ 500 { 501 PostId: postId, 502 CreatorId: userId, 503 Path: "file.txt", 504 }, 505 { 506 PostId: postId, 507 CreatorId: userId, 508 Path: "file.txt", 509 }, 510 { 511 PostId: postId, 512 CreatorId: userId, 513 Path: "file.txt", 514 DeleteAt: 123, 515 }, 516 { 517 PostId: model.NewId(), 518 CreatorId: userId, 519 Path: "file.txt", 520 }, 521 } 522 523 for i, info := range infos { 524 newInfo, err := ss.FileInfo().Save(info) 525 require.NoError(t, err) 526 infos[i] = newInfo 527 defer func(id string) { 528 ss.FileInfo().PermanentDelete(id) 529 }(newInfo.Id) 530 } 531 532 _, err := ss.FileInfo().DeleteForPost(postId) 533 require.NoError(t, err) 534 535 infos, err = ss.FileInfo().GetForPost(postId, true, false, false) 536 require.NoError(t, err) 537 assert.Empty(t, infos) 538 } 539 540 func testFileInfoPermanentDelete(t *testing.T, ss store.Store) { 541 info, err := ss.FileInfo().Save(&model.FileInfo{ 542 PostId: model.NewId(), 543 CreatorId: model.NewId(), 544 Path: "file.txt", 545 }) 546 require.NoError(t, err) 547 548 err = ss.FileInfo().PermanentDelete(info.Id) 549 require.NoError(t, err) 550 } 551 552 func testFileInfoPermanentDeleteBatch(t *testing.T, ss store.Store) { 553 postId := model.NewId() 554 555 _, err := ss.FileInfo().Save(&model.FileInfo{ 556 PostId: postId, 557 CreatorId: model.NewId(), 558 Path: "file.txt", 559 CreateAt: 1000, 560 }) 561 require.NoError(t, err) 562 563 _, err = ss.FileInfo().Save(&model.FileInfo{ 564 PostId: postId, 565 CreatorId: model.NewId(), 566 Path: "file.txt", 567 CreateAt: 1200, 568 }) 569 require.NoError(t, err) 570 571 _, err = ss.FileInfo().Save(&model.FileInfo{ 572 PostId: postId, 573 CreatorId: model.NewId(), 574 Path: "file.txt", 575 CreateAt: 2000, 576 }) 577 require.NoError(t, err) 578 579 postFiles, err := ss.FileInfo().GetForPost(postId, true, false, false) 580 require.NoError(t, err) 581 assert.Len(t, postFiles, 3) 582 583 _, err = ss.FileInfo().PermanentDeleteBatch(1500, 1000) 584 require.NoError(t, err) 585 586 postFiles, err = ss.FileInfo().GetForPost(postId, true, false, false) 587 require.NoError(t, err) 588 assert.Len(t, postFiles, 1) 589 } 590 591 func testFileInfoPermanentDeleteByUser(t *testing.T, ss store.Store) { 592 userId := model.NewId() 593 postId := model.NewId() 594 595 _, err := ss.FileInfo().Save(&model.FileInfo{ 596 PostId: postId, 597 CreatorId: userId, 598 Path: "file.txt", 599 }) 600 require.NoError(t, err) 601 602 _, err = ss.FileInfo().PermanentDeleteByUser(userId) 603 require.NoError(t, err) 604 }