github.com/minio/console@v1.4.1/api/user_objects_test.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "crypto/tls" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "net/http" 27 "path/filepath" 28 "reflect" 29 "testing" 30 "time" 31 32 "github.com/go-openapi/runtime/middleware" 33 "github.com/minio/console/api/operations/object" 34 35 "github.com/go-openapi/swag" 36 "github.com/minio/console/models" 37 mc "github.com/minio/mc/cmd" 38 "github.com/minio/mc/pkg/probe" 39 "github.com/minio/minio-go/v7" 40 "github.com/minio/minio-go/v7/pkg/tags" 41 "github.com/stretchr/testify/assert" 42 ) 43 44 var ( 45 minioListObjectsMock func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo 46 minioGetObjectLegalHoldMock func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) 47 minioGetObjectRetentionMock func(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) 48 minioPutObjectMock func(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) 49 minioPutObjectLegalHoldMock func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error 50 minioPutObjectRetentionMock func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error 51 minioGetObjectTaggingMock func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error) 52 minioPutObjectTaggingMock func(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error 53 minioStatObjectMock func(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error) 54 ) 55 56 var ( 57 mcListMock func(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent 58 mcRemoveMock func(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult 59 mcGetMock func(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error) 60 mcShareDownloadMock func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) 61 ) 62 63 // mock functions for minioClientMock 64 func (ac minioClientMock) listObjects(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo { 65 return minioListObjectsMock(ctx, bucket, opts) 66 } 67 68 func (ac minioClientMock) getObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 69 return minioGetObjectLegalHoldMock(ctx, bucketName, objectName, opts) 70 } 71 72 func (ac minioClientMock) getObjectRetention(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 73 return minioGetObjectRetentionMock(ctx, bucketName, objectName, versionID) 74 } 75 76 func (ac minioClientMock) putObject(ctx context.Context, bucketName, objectName string, reader io.Reader, objectSize int64, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) { 77 return minioPutObjectMock(ctx, bucketName, objectName, reader, objectSize, opts) 78 } 79 80 func (ac minioClientMock) putObjectLegalHold(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error { 81 return minioPutObjectLegalHoldMock(ctx, bucketName, objectName, opts) 82 } 83 84 func (ac minioClientMock) putObjectRetention(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error { 85 return minioPutObjectRetentionMock(ctx, bucketName, objectName, opts) 86 } 87 88 func (ac minioClientMock) getObjectTagging(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error) { 89 return minioGetObjectTaggingMock(ctx, bucketName, objectName, opts) 90 } 91 92 func (ac minioClientMock) putObjectTagging(ctx context.Context, bucketName, objectName string, otags *tags.Tags, opts minio.PutObjectTaggingOptions) error { 93 return minioPutObjectTaggingMock(ctx, bucketName, objectName, otags, opts) 94 } 95 96 func (ac minioClientMock) statObject(ctx context.Context, bucketName, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error) { 97 return minioStatObjectMock(ctx, bucketName, prefix, opts) 98 } 99 100 // mock functions for s3ClientMock 101 func (c s3ClientMock) list(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent { 102 return mcListMock(ctx, opts) 103 } 104 105 func (c s3ClientMock) remove(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult { 106 return mcRemoveMock(ctx, isIncomplete, isRemoveBucket, isBypass, forceDelete, contentCh) 107 } 108 109 func (c s3ClientMock) get(ctx context.Context, opts mc.GetOptions) (io.ReadCloser, *probe.Error) { 110 return mcGetMock(ctx, opts) 111 } 112 113 func (c s3ClientMock) shareDownload(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) { 114 return mcShareDownloadMock(ctx, versionID, expires) 115 } 116 117 func Test_listObjects(t *testing.T) { 118 ctx, cancel := context.WithCancel(context.Background()) 119 defer cancel() 120 t1 := time.Now() 121 tretention := time.Now() 122 minClient := minioClientMock{} 123 type args struct { 124 bucketName string 125 prefix string 126 recursive bool 127 withVersions bool 128 withMetadata bool 129 limit *int32 130 listFunc func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo 131 objectLegalHoldFunc func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) 132 objectRetentionFunc func(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) 133 objectGetTaggingFunc func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectTaggingOptions) (*tags.Tags, error) 134 } 135 tests := []struct { 136 test string 137 args args 138 expectedResp []*models.BucketObject 139 wantError error 140 }{ 141 { 142 test: "Return objects", 143 args: args{ 144 bucketName: "bucket1", 145 prefix: "prefix", 146 recursive: true, 147 withVersions: false, 148 withMetadata: false, 149 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 150 objectStatCh := make(chan minio.ObjectInfo, 1) 151 go func(objectStatCh chan<- minio.ObjectInfo) { 152 defer close(objectStatCh) 153 for _, bucket := range []minio.ObjectInfo{ 154 { 155 Key: "obj1", 156 LastModified: t1, 157 Size: int64(1024), 158 ContentType: "content", 159 }, 160 { 161 Key: "obj2", 162 LastModified: t1, 163 Size: int64(512), 164 ContentType: "content", 165 }, 166 } { 167 objectStatCh <- bucket 168 } 169 }(objectStatCh) 170 return objectStatCh 171 }, 172 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 173 s := minio.LegalHoldEnabled 174 return &s, nil 175 }, 176 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 177 m := minio.Governance 178 return &m, &tretention, nil 179 }, 180 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 181 tagMap := map[string]string{ 182 "tag1": "value1", 183 } 184 otags, err := tags.MapToObjectTags(tagMap) 185 if err != nil { 186 return nil, err 187 } 188 return otags, nil 189 }, 190 }, 191 expectedResp: []*models.BucketObject{ 192 { 193 Name: "obj1", 194 LastModified: t1.Format(time.RFC3339), 195 Size: int64(1024), 196 ContentType: "content", 197 LegalHoldStatus: string(minio.LegalHoldEnabled), 198 RetentionMode: string(minio.Governance), 199 RetentionUntilDate: tretention.Format(time.RFC3339), 200 Tags: map[string]string{ 201 "tag1": "value1", 202 }, 203 }, { 204 Name: "obj2", 205 LastModified: t1.Format(time.RFC3339), 206 Size: int64(512), 207 ContentType: "content", 208 LegalHoldStatus: string(minio.LegalHoldEnabled), 209 RetentionMode: string(minio.Governance), 210 RetentionUntilDate: tretention.Format(time.RFC3339), 211 Tags: map[string]string{ 212 "tag1": "value1", 213 }, 214 }, 215 }, 216 wantError: nil, 217 }, 218 { 219 test: "Return zero objects", 220 args: args{ 221 bucketName: "bucket1", 222 prefix: "prefix", 223 recursive: true, 224 withVersions: false, 225 withMetadata: false, 226 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 227 objectStatCh := make(chan minio.ObjectInfo, 1) 228 defer close(objectStatCh) 229 return objectStatCh 230 }, 231 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 232 s := minio.LegalHoldEnabled 233 return &s, nil 234 }, 235 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 236 m := minio.Governance 237 return &m, &tretention, nil 238 }, 239 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 240 tagMap := map[string]string{ 241 "tag1": "value1", 242 } 243 otags, err := tags.MapToObjectTags(tagMap) 244 if err != nil { 245 return nil, err 246 } 247 return otags, nil 248 }, 249 }, 250 expectedResp: nil, 251 wantError: nil, 252 }, 253 { 254 test: "Handle error if present on object", 255 args: args{ 256 bucketName: "bucket1", 257 prefix: "prefix", 258 recursive: true, 259 withVersions: false, 260 withMetadata: false, 261 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 262 objectStatCh := make(chan minio.ObjectInfo, 1) 263 go func(objectStatCh chan<- minio.ObjectInfo) { 264 defer close(objectStatCh) 265 for _, bucket := range []minio.ObjectInfo{ 266 { 267 Key: "obj2", 268 LastModified: t1, 269 Size: int64(512), 270 ContentType: "content", 271 }, 272 { 273 Err: errors.New("error here"), 274 }, 275 } { 276 objectStatCh <- bucket 277 } 278 }(objectStatCh) 279 return objectStatCh 280 }, 281 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 282 s := minio.LegalHoldEnabled 283 return &s, nil 284 }, 285 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 286 m := minio.Governance 287 return &m, &tretention, nil 288 }, 289 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 290 tagMap := map[string]string{ 291 "tag1": "value1", 292 } 293 otags, err := tags.MapToObjectTags(tagMap) 294 if err != nil { 295 return nil, err 296 } 297 return otags, nil 298 }, 299 }, 300 expectedResp: nil, 301 wantError: errors.New("error here"), 302 }, 303 { 304 // Description: deleted objects with IsDeleteMarker 305 // should not call legsalhold, tag or retention funcs 306 test: "Return deleted objects", 307 args: args{ 308 bucketName: "bucket1", 309 prefix: "prefix", 310 recursive: true, 311 withVersions: false, 312 withMetadata: false, 313 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 314 objectStatCh := make(chan minio.ObjectInfo, 1) 315 go func(objectStatCh chan<- minio.ObjectInfo) { 316 defer close(objectStatCh) 317 for _, bucket := range []minio.ObjectInfo{ 318 { 319 Key: "obj1", 320 LastModified: t1, 321 Size: int64(1024), 322 ContentType: "content", 323 IsDeleteMarker: true, 324 }, 325 { 326 Key: "obj2", 327 LastModified: t1, 328 Size: int64(512), 329 ContentType: "content", 330 }, 331 } { 332 objectStatCh <- bucket 333 } 334 }(objectStatCh) 335 return objectStatCh 336 }, 337 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 338 s := minio.LegalHoldEnabled 339 return &s, nil 340 }, 341 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 342 m := minio.Governance 343 return &m, &tretention, nil 344 }, 345 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 346 tagMap := map[string]string{ 347 "tag1": "value1", 348 } 349 otags, err := tags.MapToObjectTags(tagMap) 350 if err != nil { 351 return nil, err 352 } 353 return otags, nil 354 }, 355 }, 356 expectedResp: []*models.BucketObject{ 357 { 358 Name: "obj1", 359 LastModified: t1.Format(time.RFC3339), 360 Size: int64(1024), 361 ContentType: "content", 362 IsDeleteMarker: true, 363 }, { 364 Name: "obj2", 365 LastModified: t1.Format(time.RFC3339), 366 Size: int64(512), 367 ContentType: "content", 368 LegalHoldStatus: string(minio.LegalHoldEnabled), 369 RetentionMode: string(minio.Governance), 370 RetentionUntilDate: tretention.Format(time.RFC3339), 371 Tags: map[string]string{ 372 "tag1": "value1", 373 }, 374 }, 375 }, 376 wantError: nil, 377 }, 378 { 379 // Description: deleted objects with 380 // error on legalhold, tags or retention funcs 381 // should only log errors 382 test: "Return deleted objects, error on legalhold and retention", 383 args: args{ 384 bucketName: "bucket1", 385 prefix: "prefix", 386 recursive: true, 387 withVersions: false, 388 withMetadata: false, 389 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 390 objectStatCh := make(chan minio.ObjectInfo, 1) 391 go func(objectStatCh chan<- minio.ObjectInfo) { 392 defer close(objectStatCh) 393 for _, bucket := range []minio.ObjectInfo{ 394 { 395 Key: "obj1", 396 LastModified: t1, 397 Size: int64(1024), 398 ContentType: "content", 399 }, 400 } { 401 objectStatCh <- bucket 402 } 403 }(objectStatCh) 404 return objectStatCh 405 }, 406 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 407 return nil, errors.New("error legal") 408 }, 409 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 410 return nil, nil, errors.New("error retention") 411 }, 412 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 413 return nil, errors.New("error get tags") 414 }, 415 }, 416 expectedResp: []*models.BucketObject{ 417 { 418 Name: "obj1", 419 LastModified: t1.Format(time.RFC3339), 420 Size: int64(1024), 421 ContentType: "content", 422 }, 423 }, 424 wantError: nil, 425 }, 426 { 427 // Description: if the prefix end with a `/` meaning it is a folder, 428 // it should not fetch retention, legalhold nor tags for each object 429 // it should only fetch it for single objects with or without versionID 430 test: "Don't get object retention/legalhold/tags for folders", 431 args: args{ 432 bucketName: "bucket1", 433 prefix: "prefix/folder/", 434 recursive: true, 435 withVersions: false, 436 withMetadata: false, 437 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 438 objectStatCh := make(chan minio.ObjectInfo, 1) 439 go func(objectStatCh chan<- minio.ObjectInfo) { 440 defer close(objectStatCh) 441 for _, bucket := range []minio.ObjectInfo{ 442 { 443 Key: "obj1", 444 LastModified: t1, 445 Size: int64(1024), 446 ContentType: "content", 447 }, 448 { 449 Key: "obj2", 450 LastModified: t1, 451 Size: int64(512), 452 ContentType: "content", 453 }, 454 } { 455 objectStatCh <- bucket 456 } 457 }(objectStatCh) 458 return objectStatCh 459 }, 460 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 461 s := minio.LegalHoldEnabled 462 return &s, nil 463 }, 464 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 465 m := minio.Governance 466 return &m, &tretention, nil 467 }, 468 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 469 tagMap := map[string]string{ 470 "tag1": "value1", 471 } 472 otags, err := tags.MapToObjectTags(tagMap) 473 if err != nil { 474 return nil, err 475 } 476 return otags, nil 477 }, 478 }, 479 expectedResp: []*models.BucketObject{ 480 { 481 Name: "obj1", 482 LastModified: t1.Format(time.RFC3339), 483 Size: int64(1024), 484 ContentType: "content", 485 }, { 486 Name: "obj2", 487 LastModified: t1.Format(time.RFC3339), 488 Size: int64(512), 489 ContentType: "content", 490 }, 491 }, 492 wantError: nil, 493 }, 494 { 495 // Description: if the prefix is "" meaning it is all contents within a bucket, 496 // it should not fetch retention, legalhold nor tags for each object 497 // it should only fetch it for single objects with or without versionID 498 test: "Don't get object retention/legalhold/tags for empty prefix", 499 args: args{ 500 bucketName: "bucket1", 501 prefix: "", 502 recursive: true, 503 withVersions: false, 504 withMetadata: false, 505 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 506 objectStatCh := make(chan minio.ObjectInfo, 1) 507 go func(objectStatCh chan<- minio.ObjectInfo) { 508 defer close(objectStatCh) 509 for _, bucket := range []minio.ObjectInfo{ 510 { 511 Key: "obj1", 512 LastModified: t1, 513 Size: int64(1024), 514 ContentType: "content", 515 }, 516 { 517 Key: "obj2", 518 LastModified: t1, 519 Size: int64(512), 520 ContentType: "content", 521 }, 522 } { 523 objectStatCh <- bucket 524 } 525 }(objectStatCh) 526 return objectStatCh 527 }, 528 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 529 s := minio.LegalHoldEnabled 530 return &s, nil 531 }, 532 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 533 m := minio.Governance 534 return &m, &tretention, nil 535 }, 536 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 537 tagMap := map[string]string{ 538 "tag1": "value1", 539 } 540 otags, err := tags.MapToObjectTags(tagMap) 541 if err != nil { 542 return nil, err 543 } 544 return otags, nil 545 }, 546 }, 547 expectedResp: []*models.BucketObject{ 548 { 549 Name: "obj1", 550 LastModified: t1.Format(time.RFC3339), 551 Size: int64(1024), 552 ContentType: "content", 553 }, { 554 Name: "obj2", 555 LastModified: t1.Format(time.RFC3339), 556 Size: int64(512), 557 ContentType: "content", 558 }, 559 }, 560 wantError: nil, 561 }, 562 { 563 test: "Return objects", 564 args: args{ 565 bucketName: "bucket1", 566 prefix: "prefix", 567 recursive: true, 568 withVersions: false, 569 withMetadata: false, 570 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 571 objectStatCh := make(chan minio.ObjectInfo, 1) 572 go func(objectStatCh chan<- minio.ObjectInfo) { 573 defer close(objectStatCh) 574 for _, bucket := range []minio.ObjectInfo{ 575 { 576 Key: "obj1", 577 LastModified: t1, 578 Size: int64(1024), 579 ContentType: "content", 580 }, 581 { 582 Key: "obj2", 583 LastModified: t1, 584 Size: int64(512), 585 ContentType: "content", 586 }, 587 } { 588 objectStatCh <- bucket 589 } 590 }(objectStatCh) 591 return objectStatCh 592 }, 593 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 594 s := minio.LegalHoldEnabled 595 return &s, nil 596 }, 597 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 598 m := minio.Governance 599 return &m, &tretention, nil 600 }, 601 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 602 tagMap := map[string]string{ 603 "tag1": "value1", 604 } 605 otags, err := tags.MapToObjectTags(tagMap) 606 if err != nil { 607 return nil, err 608 } 609 return otags, nil 610 }, 611 }, 612 expectedResp: []*models.BucketObject{ 613 { 614 Name: "obj1", 615 LastModified: t1.Format(time.RFC3339), 616 Size: int64(1024), 617 ContentType: "content", 618 LegalHoldStatus: string(minio.LegalHoldEnabled), 619 RetentionMode: string(minio.Governance), 620 RetentionUntilDate: tretention.Format(time.RFC3339), 621 Tags: map[string]string{ 622 "tag1": "value1", 623 }, 624 }, { 625 Name: "obj2", 626 LastModified: t1.Format(time.RFC3339), 627 Size: int64(512), 628 ContentType: "content", 629 LegalHoldStatus: string(minio.LegalHoldEnabled), 630 RetentionMode: string(minio.Governance), 631 RetentionUntilDate: tretention.Format(time.RFC3339), 632 Tags: map[string]string{ 633 "tag1": "value1", 634 }, 635 }, 636 }, 637 wantError: nil, 638 }, 639 { 640 test: "Limit 1", 641 args: args{ 642 bucketName: "bucket1", 643 prefix: "prefix", 644 recursive: true, 645 withVersions: false, 646 withMetadata: false, 647 limit: swag.Int32(1), 648 listFunc: func(_ context.Context, _ string, _ minio.ListObjectsOptions) <-chan minio.ObjectInfo { 649 objectStatCh := make(chan minio.ObjectInfo, 1) 650 go func(objectStatCh chan<- minio.ObjectInfo) { 651 defer close(objectStatCh) 652 for _, bucket := range []minio.ObjectInfo{ 653 { 654 Key: "obj1", 655 LastModified: t1, 656 Size: int64(1024), 657 ContentType: "content", 658 }, 659 { 660 Key: "obj2", 661 LastModified: t1, 662 Size: int64(512), 663 ContentType: "content", 664 }, 665 } { 666 objectStatCh <- bucket 667 } 668 }(objectStatCh) 669 return objectStatCh 670 }, 671 objectLegalHoldFunc: func(_ context.Context, _, _ string, _ minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error) { 672 s := minio.LegalHoldEnabled 673 return &s, nil 674 }, 675 objectRetentionFunc: func(_ context.Context, _, _, _ string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error) { 676 m := minio.Governance 677 return &m, &tretention, nil 678 }, 679 objectGetTaggingFunc: func(_ context.Context, _, _ string, _ minio.GetObjectTaggingOptions) (*tags.Tags, error) { 680 tagMap := map[string]string{ 681 "tag1": "value1", 682 } 683 otags, err := tags.MapToObjectTags(tagMap) 684 if err != nil { 685 return nil, err 686 } 687 return otags, nil 688 }, 689 }, 690 expectedResp: []*models.BucketObject{ 691 { 692 Name: "obj1", 693 LastModified: t1.Format(time.RFC3339), 694 Size: int64(1024), 695 ContentType: "content", 696 LegalHoldStatus: string(minio.LegalHoldEnabled), 697 RetentionMode: string(minio.Governance), 698 RetentionUntilDate: tretention.Format(time.RFC3339), 699 Tags: map[string]string{ 700 "tag1": "value1", 701 }, 702 }, 703 }, 704 wantError: nil, 705 }, 706 } 707 708 t.Parallel() 709 for _, tt := range tests { 710 tt := tt 711 t.Run(tt.test, func(_ *testing.T) { 712 minioListObjectsMock = tt.args.listFunc 713 minioGetObjectLegalHoldMock = tt.args.objectLegalHoldFunc 714 minioGetObjectRetentionMock = tt.args.objectRetentionFunc 715 minioGetObjectTaggingMock = tt.args.objectGetTaggingFunc 716 resp, err := listBucketObjects(ListObjectsOpts{ 717 ctx: ctx, 718 client: minClient, 719 bucketName: tt.args.bucketName, 720 prefix: tt.args.prefix, 721 recursive: tt.args.recursive, 722 withVersions: tt.args.withVersions, 723 withMetadata: tt.args.withMetadata, 724 limit: tt.args.limit, 725 }) 726 switch { 727 case err == nil && tt.wantError != nil: 728 t.Errorf("listBucketObjects() error: %v, wantErr: %v", err, tt.wantError) 729 case err != nil && tt.wantError == nil: 730 t.Errorf("listBucketObjects() error: %v, wantErr: %v", err, tt.wantError) 731 case err != nil && tt.wantError != nil: 732 if err.Error() != tt.wantError.Error() { 733 t.Errorf("listBucketObjects() error: %v, wantErr: %v", err, tt.wantError) 734 } 735 } 736 if err == nil { 737 if !reflect.DeepEqual(resp, tt.expectedResp) { 738 ji, _ := json.Marshal(resp) 739 vi, _ := json.Marshal(tt.expectedResp) 740 t.Errorf("\ngot: %s \nwant: %s", ji, vi) 741 } 742 } 743 }) 744 } 745 } 746 747 func Test_deleteObjects(t *testing.T) { 748 ctx, cancel := context.WithCancel(context.Background()) 749 defer cancel() 750 s3Client1 := s3ClientMock{} 751 type args struct { 752 bucket string 753 path string 754 versionID string 755 recursive bool 756 nonCurrent bool 757 listFunc func(ctx context.Context, opts mc.ListOptions) <-chan *mc.ClientContent 758 removeFunc func(ctx context.Context, isIncomplete, isRemoveBucket, isBypass, forceDelete bool, contentCh <-chan *mc.ClientContent) <-chan mc.RemoveResult 759 } 760 tests := []struct { 761 test string 762 args args 763 wantError error 764 }{ 765 { 766 test: "Remove single object", 767 args: args{ 768 path: "obj.txt", 769 versionID: "", 770 recursive: false, 771 nonCurrent: false, 772 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 773 resultCh := make(chan mc.RemoveResult, 1) 774 resultCh <- mc.RemoveResult{Err: nil} 775 close(resultCh) 776 return resultCh 777 }, 778 }, 779 wantError: nil, 780 }, 781 { 782 test: "Error on Remove single object", 783 args: args{ 784 path: "obj.txt", 785 versionID: "", 786 recursive: false, 787 nonCurrent: false, 788 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 789 resultCh := make(chan mc.RemoveResult, 1) 790 resultCh <- mc.RemoveResult{Err: probe.NewError(errors.New("probe error"))} 791 close(resultCh) 792 return resultCh 793 }, 794 }, 795 wantError: errors.New("probe error"), 796 }, 797 { 798 test: "Remove multiple objects", 799 args: args{ 800 path: "path/", 801 versionID: "", 802 recursive: true, 803 nonCurrent: false, 804 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 805 resultCh := make(chan mc.RemoveResult, 1) 806 resultCh <- mc.RemoveResult{Err: nil} 807 close(resultCh) 808 return resultCh 809 }, 810 listFunc: func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent { 811 ch := make(chan *mc.ClientContent, 1) 812 ch <- &mc.ClientContent{} 813 close(ch) 814 return ch 815 }, 816 }, 817 wantError: nil, 818 }, 819 { 820 // Description handle error when error happens on remove function 821 // while deleting multiple objects 822 test: "Error on Remove multiple objects", 823 args: args{ 824 path: "path/", 825 versionID: "", 826 recursive: true, 827 nonCurrent: false, 828 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 829 resultCh := make(chan mc.RemoveResult, 1) 830 resultCh <- mc.RemoveResult{Err: probe.NewError(errors.New("probe error"))} 831 close(resultCh) 832 return resultCh 833 }, 834 listFunc: func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent { 835 ch := make(chan *mc.ClientContent, 1) 836 ch <- &mc.ClientContent{} 837 close(ch) 838 return ch 839 }, 840 }, 841 wantError: errors.New("probe error"), 842 }, 843 { 844 test: "Remove non current objects - no error", 845 args: args{ 846 path: "path/", 847 versionID: "", 848 recursive: true, 849 nonCurrent: true, 850 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 851 resultCh := make(chan mc.RemoveResult, 1) 852 resultCh <- mc.RemoveResult{Err: nil} 853 close(resultCh) 854 return resultCh 855 }, 856 listFunc: func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent { 857 ch := make(chan *mc.ClientContent, 1) 858 ch <- &mc.ClientContent{} 859 close(ch) 860 return ch 861 }, 862 }, 863 wantError: nil, 864 }, 865 { 866 // Description handle error when error happens on remove function 867 // while deleting multiple objects 868 test: "Error deleting non current objects", 869 args: args{ 870 path: "path/", 871 versionID: "", 872 recursive: true, 873 nonCurrent: true, 874 removeFunc: func(_ context.Context, _, _, _, _ bool, _ <-chan *mc.ClientContent) <-chan mc.RemoveResult { 875 resultCh := make(chan mc.RemoveResult, 1) 876 resultCh <- mc.RemoveResult{Err: probe.NewError(errors.New("probe error"))} 877 close(resultCh) 878 return resultCh 879 }, 880 listFunc: func(_ context.Context, _ mc.ListOptions) <-chan *mc.ClientContent { 881 ch := make(chan *mc.ClientContent, 1) 882 ch <- &mc.ClientContent{} 883 close(ch) 884 return ch 885 }, 886 }, 887 wantError: errors.New("probe error"), 888 }, 889 } 890 891 t.Parallel() 892 for _, tt := range tests { 893 tt := tt 894 t.Run(tt.test, func(_ *testing.T) { 895 mcListMock = tt.args.listFunc 896 mcRemoveMock = tt.args.removeFunc 897 err := deleteObjects(ctx, s3Client1, tt.args.bucket, tt.args.path, tt.args.versionID, tt.args.recursive, false, tt.args.nonCurrent, false) 898 switch { 899 case err == nil && tt.wantError != nil: 900 t.Errorf("deleteObjects() error: %v, wantErr: %v", err, tt.wantError) 901 case err != nil && tt.wantError == nil: 902 t.Errorf("deleteObjects() error: %v, wantErr: %v", err, tt.wantError) 903 case err != nil && tt.wantError != nil: 904 if err.Error() != tt.wantError.Error() { 905 t.Errorf("deleteObjects() error: %v, wantErr: %v", err, tt.wantError) 906 } 907 } 908 }) 909 } 910 } 911 912 func Test_shareObject(t *testing.T) { 913 tAssert := assert.New(t) 914 ctx, cancel := context.WithCancel(context.Background()) 915 defer cancel() 916 client := s3ClientMock{} 917 type args struct { 918 r *http.Request 919 versionID string 920 expires string 921 shareFunc func(ctx context.Context, versionID string, expires time.Duration) (string, *probe.Error) 922 } 923 tests := []struct { 924 test string 925 args args 926 setEnvVars func() 927 wantError error 928 expected string 929 }{ 930 { 931 test: "return sharefunc url base64 encoded with host name", 932 args: args{ 933 r: &http.Request{ 934 TLS: nil, 935 Host: "localhost:9090", 936 }, 937 versionID: "2121434", 938 expires: "30s", 939 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 940 return "http://someurl", nil 941 }, 942 }, 943 944 wantError: nil, 945 expected: "http://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=", 946 }, 947 { 948 test: "return https scheme if url uses TLS", 949 args: args{ 950 r: &http.Request{ 951 TLS: &tls.ConnectionState{}, 952 Host: "localhost:9090", 953 }, 954 versionID: "2121434", 955 expires: "30s", 956 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 957 return "http://someurl", nil 958 }, 959 }, 960 961 wantError: nil, 962 expected: "https://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=", 963 }, 964 { 965 test: "returns invalid expire duration if expiration is invalid", 966 args: args{ 967 r: &http.Request{ 968 TLS: nil, 969 Host: "localhost:9090", 970 }, 971 versionID: "2121434", 972 expires: "invalid", 973 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 974 return "http://someurl", nil 975 }, 976 }, 977 wantError: errors.New("time: invalid duration \"invalid\""), 978 }, 979 { 980 test: "add default expiration if expiration is empty", 981 args: args{ 982 r: &http.Request{ 983 TLS: nil, 984 Host: "localhost:9090", 985 }, 986 versionID: "2121434", 987 expires: "", 988 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 989 return "http://someurl", nil 990 }, 991 }, 992 wantError: nil, 993 expected: "http://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=", 994 }, 995 { 996 test: "return error if sharefunc returns error", 997 args: args{ 998 r: &http.Request{ 999 TLS: nil, 1000 Host: "localhost:9090", 1001 }, 1002 versionID: "2121434", 1003 expires: "3h", 1004 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 1005 return "", probe.NewError(errors.New("probe error")) 1006 }, 1007 }, 1008 wantError: errors.New("probe error"), 1009 }, 1010 { 1011 test: "return shareFunc url base64 encoded url-safe", 1012 args: args{ 1013 r: &http.Request{ 1014 TLS: nil, 1015 Host: "localhost:9090", 1016 }, 1017 versionID: "2121434", 1018 expires: "3h", 1019 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 1020 // https://127.0.0.1:9000/cestest/Audio%20icon.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256 using StdEncoding adds an extra `/` making it not url safe 1021 return "https://127.0.0.1:9000/cestest/Audio%20icon.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256", nil 1022 }, 1023 }, 1024 wantError: nil, 1025 expected: "http://localhost:9090/api/v1/download-shared-object/aHR0cHM6Ly8xMjcuMC4wLjE6OTAwMC9jZXN0ZXN0L0F1ZGlvJTIwaWNvbi5zdmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTY=", 1026 }, 1027 { 1028 test: "returns redirect url with share link if redirect url env variable set", 1029 setEnvVars: func() { 1030 t.Setenv(ConsoleBrowserRedirectURL, "http://proxy-url.com:9012/console/subpath") 1031 }, 1032 args: args{ 1033 r: &http.Request{ 1034 TLS: nil, 1035 Host: "localhost:9090", 1036 }, 1037 versionID: "2121434", 1038 expires: "30s", 1039 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 1040 return "http://someurl", nil 1041 }, 1042 }, 1043 wantError: nil, 1044 expected: "http://proxy-url.com:9012/console/subpath/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=", 1045 }, 1046 { 1047 test: "returns redirect url with share link if redirect url env variable set with trailing slash", 1048 setEnvVars: func() { 1049 t.Setenv(ConsoleBrowserRedirectURL, "http://proxy-url.com:9012/console/subpath/") 1050 }, 1051 args: args{ 1052 r: &http.Request{ 1053 TLS: nil, 1054 Host: "localhost:9090", 1055 }, 1056 versionID: "2121434", 1057 expires: "30s", 1058 shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) { 1059 return "http://someurl", nil 1060 }, 1061 }, 1062 wantError: nil, 1063 expected: "http://proxy-url.com:9012/console/subpath/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=", 1064 }, 1065 } 1066 1067 for _, tt := range tests { 1068 t.Run(tt.test, func(_ *testing.T) { 1069 mcShareDownloadMock = tt.args.shareFunc 1070 if tt.setEnvVars != nil { 1071 tt.setEnvVars() 1072 } 1073 url, err := getShareObjectURL(ctx, client, tt.args.r, tt.args.versionID, tt.args.expires) 1074 if tt.wantError != nil { 1075 if !reflect.DeepEqual(err, tt.wantError) { 1076 t.Errorf("getShareObjectURL() error: `%s`, wantErr: `%s`", err, tt.wantError) 1077 return 1078 } 1079 } else { 1080 tAssert.Equal(*url, tt.expected) 1081 } 1082 }) 1083 } 1084 } 1085 1086 func Test_putObjectLegalHold(t *testing.T) { 1087 ctx, cancel := context.WithCancel(context.Background()) 1088 defer cancel() 1089 client := minioClientMock{} 1090 type args struct { 1091 bucket string 1092 prefix string 1093 versionID string 1094 status models.ObjectLegalHoldStatus 1095 legalHoldFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error 1096 } 1097 tests := []struct { 1098 test string 1099 args args 1100 wantError error 1101 }{ 1102 { 1103 test: "Put Object Legal hold enabled status", 1104 args: args{ 1105 bucket: "buck1", 1106 versionID: "someversion", 1107 prefix: "folder/file.txt", 1108 status: models.ObjectLegalHoldStatusEnabled, 1109 legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error { 1110 return nil 1111 }, 1112 }, 1113 wantError: nil, 1114 }, 1115 { 1116 test: "Put Object Legal hold disabled status", 1117 args: args{ 1118 bucket: "buck1", 1119 versionID: "someversion", 1120 prefix: "folder/file.txt", 1121 status: models.ObjectLegalHoldStatusDisabled, 1122 legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error { 1123 return nil 1124 }, 1125 }, 1126 wantError: nil, 1127 }, 1128 { 1129 test: "Handle error on legalhold func", 1130 args: args{ 1131 bucket: "buck1", 1132 versionID: "someversion", 1133 prefix: "folder/file.txt", 1134 status: models.ObjectLegalHoldStatusDisabled, 1135 legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error { 1136 return errors.New("new error") 1137 }, 1138 }, 1139 wantError: errors.New("new error"), 1140 }, 1141 } 1142 1143 for _, tt := range tests { 1144 t.Run(tt.test, func(_ *testing.T) { 1145 minioPutObjectLegalHoldMock = tt.args.legalHoldFunc 1146 err := setObjectLegalHold(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID, tt.args.status) 1147 if !reflect.DeepEqual(err, tt.wantError) { 1148 t.Errorf("setObjectLegalHold() error: %v, wantErr: %v", err, tt.wantError) 1149 return 1150 } 1151 }) 1152 } 1153 } 1154 1155 func Test_putObjectRetention(t *testing.T) { 1156 tAssert := assert.New(t) 1157 ctx, cancel := context.WithCancel(context.Background()) 1158 defer cancel() 1159 client := minioClientMock{} 1160 type args struct { 1161 bucket string 1162 prefix string 1163 versionID string 1164 opts *models.PutObjectRetentionRequest 1165 retentionFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error 1166 } 1167 tests := []struct { 1168 test string 1169 args args 1170 wantError error 1171 }{ 1172 { 1173 test: "Put Object retention governance", 1174 args: args{ 1175 bucket: "buck1", 1176 versionID: "someversion", 1177 prefix: "folder/file.txt", 1178 opts: &models.PutObjectRetentionRequest{ 1179 Expires: swag.String("2006-01-02T15:04:05Z"), 1180 GovernanceBypass: false, 1181 Mode: models.NewObjectRetentionMode(models.ObjectRetentionModeGovernance), 1182 }, 1183 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1184 return nil 1185 }, 1186 }, 1187 wantError: nil, 1188 }, 1189 { 1190 test: "Put Object retention compliance", 1191 args: args{ 1192 bucket: "buck1", 1193 versionID: "someversion", 1194 prefix: "folder/file.txt", 1195 opts: &models.PutObjectRetentionRequest{ 1196 Expires: swag.String("2006-01-02T15:04:05Z"), 1197 GovernanceBypass: false, 1198 Mode: models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance), 1199 }, 1200 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1201 return nil 1202 }, 1203 }, 1204 wantError: nil, 1205 }, 1206 { 1207 test: "Empty opts should return error", 1208 args: args{ 1209 bucket: "buck1", 1210 versionID: "someversion", 1211 prefix: "folder/file.txt", 1212 opts: nil, 1213 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1214 return nil 1215 }, 1216 }, 1217 wantError: errors.New("object retention options can't be nil"), 1218 }, 1219 { 1220 test: "Empty expire on opts should return error", 1221 args: args{ 1222 bucket: "buck1", 1223 versionID: "someversion", 1224 prefix: "folder/file.txt", 1225 opts: &models.PutObjectRetentionRequest{ 1226 Expires: nil, 1227 GovernanceBypass: false, 1228 Mode: models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance), 1229 }, 1230 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1231 return nil 1232 }, 1233 }, 1234 wantError: errors.New("object retention expires can't be nil"), 1235 }, 1236 { 1237 test: "Handle invalid expire time", 1238 args: args{ 1239 bucket: "buck1", 1240 versionID: "someversion", 1241 prefix: "folder/file.txt", 1242 opts: &models.PutObjectRetentionRequest{ 1243 Expires: swag.String("invalidtime"), 1244 GovernanceBypass: false, 1245 Mode: models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance), 1246 }, 1247 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1248 return nil 1249 }, 1250 }, 1251 wantError: errors.New("parsing time \"invalidtime\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"invalidtime\" as \"2006\""), 1252 }, 1253 { 1254 test: "Handle error on retention func", 1255 args: args{ 1256 bucket: "buck1", 1257 versionID: "someversion", 1258 prefix: "folder/file.txt", 1259 opts: &models.PutObjectRetentionRequest{ 1260 Expires: swag.String("2006-01-02T15:04:05Z"), 1261 GovernanceBypass: false, 1262 Mode: models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance), 1263 }, 1264 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1265 return errors.New("new Error") 1266 }, 1267 }, 1268 wantError: errors.New("new Error"), 1269 }, 1270 } 1271 1272 for _, tt := range tests { 1273 t.Run(tt.test, func(_ *testing.T) { 1274 minioPutObjectRetentionMock = tt.args.retentionFunc 1275 err := setObjectRetention(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID, tt.args.opts) 1276 if tt.wantError != nil { 1277 fmt.Println(t.Name()) 1278 tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("setObjectRetention() error: `%s`, wantErr: `%s`", err, tt.wantError)) 1279 } else { 1280 tAssert.Nil(err, fmt.Sprintf("setObjectRetention() error: %v, wantErr: %v", err, tt.wantError)) 1281 } 1282 }) 1283 } 1284 } 1285 1286 func Test_deleteObjectRetention(t *testing.T) { 1287 tAssert := assert.New(t) 1288 ctx, cancel := context.WithCancel(context.Background()) 1289 defer cancel() 1290 client := minioClientMock{} 1291 type args struct { 1292 bucket string 1293 prefix string 1294 versionID string 1295 retentionFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error 1296 } 1297 tests := []struct { 1298 test string 1299 args args 1300 wantError error 1301 }{ 1302 { 1303 test: "Delete Object retention governance", 1304 args: args{ 1305 bucket: "buck1", 1306 versionID: "someversion", 1307 prefix: "folder/file.txt", 1308 retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error { 1309 return nil 1310 }, 1311 }, 1312 wantError: nil, 1313 }, 1314 } 1315 for _, tt := range tests { 1316 t.Run(tt.test, func(_ *testing.T) { 1317 minioPutObjectRetentionMock = tt.args.retentionFunc 1318 err := deleteObjectRetention(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID) 1319 if tt.wantError != nil { 1320 fmt.Println(t.Name()) 1321 tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("deleteObjectRetention() error: `%s`, wantErr: `%s`", err, tt.wantError)) 1322 } else { 1323 tAssert.Nil(err, fmt.Sprintf("deleteObjectRetention() error: %v, wantErr: %v", err, tt.wantError)) 1324 } 1325 }) 1326 } 1327 } 1328 1329 func Test_getObjectInfo(t *testing.T) { 1330 tAssert := assert.New(t) 1331 ctx, cancel := context.WithCancel(context.Background()) 1332 defer cancel() 1333 client := minioClientMock{} 1334 1335 type args struct { 1336 bucketName string 1337 prefix string 1338 versionID string 1339 statFunc func(ctx context.Context, bucketName string, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error) 1340 } 1341 tests := []struct { 1342 test string 1343 args args 1344 wantError error 1345 }{ 1346 { 1347 test: "Test function not returns an error", 1348 args: args{ 1349 bucketName: "bucket1", 1350 prefix: "someprefix", 1351 versionID: "version123", 1352 statFunc: func(_ context.Context, _ string, _ string, _ minio.GetObjectOptions) (minio.ObjectInfo, error) { 1353 return minio.ObjectInfo{}, nil 1354 }, 1355 }, 1356 wantError: nil, 1357 }, 1358 { 1359 test: "Test function returns an error", 1360 args: args{ 1361 bucketName: "bucket2", 1362 prefix: "someprefi2", 1363 versionID: "version456", 1364 statFunc: func(_ context.Context, _ string, _ string, _ minio.GetObjectOptions) (minio.ObjectInfo, error) { 1365 return minio.ObjectInfo{}, errors.New("new Error") 1366 }, 1367 }, 1368 wantError: errors.New("new Error"), 1369 }, 1370 } 1371 for _, tt := range tests { 1372 t.Run(tt.test, func(_ *testing.T) { 1373 minioStatObjectMock = tt.args.statFunc 1374 _, err := getObjectInfo(ctx, client, tt.args.bucketName, tt.args.prefix, tt.args.versionID) 1375 if tt.wantError != nil { 1376 fmt.Println(t.Name()) 1377 tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("getObjectInfo() error: `%s`, wantErr: `%s`", err, tt.wantError)) 1378 } else { 1379 tAssert.Nil(err, fmt.Sprintf("getObjectInfo() error: %v, wantErr: %v", err, tt.wantError)) 1380 } 1381 }) 1382 } 1383 } 1384 1385 func Test_getScheme(t *testing.T) { 1386 type args struct { 1387 rawurl string 1388 } 1389 tests := []struct { 1390 name string 1391 args args 1392 wantScheme string 1393 wantPath string 1394 }{ 1395 { 1396 name: "expected", 1397 args: args{ 1398 rawurl: "http://domain.com", 1399 }, 1400 wantScheme: "http", 1401 wantPath: "//domain.com", 1402 }, 1403 { 1404 name: "no scheme", 1405 args: args{ 1406 rawurl: "domain.com", 1407 }, 1408 wantScheme: "", 1409 wantPath: "domain.com", 1410 }, 1411 } 1412 for _, tt := range tests { 1413 t.Run(tt.name, func(_ *testing.T) { 1414 gotScheme, gotPath := getScheme(tt.args.rawurl) 1415 assert.Equalf(t, tt.wantScheme, gotScheme, "getScheme(%v)", tt.args.rawurl) 1416 assert.Equalf(t, tt.wantPath, gotPath, "getScheme(%v)", tt.args.rawurl) 1417 }) 1418 } 1419 } 1420 1421 func Test_splitSpecial(t *testing.T) { 1422 type args struct { 1423 s string 1424 delimiter string 1425 cutdelimiter bool 1426 } 1427 tests := []struct { 1428 name string 1429 args args 1430 want string 1431 want1 string 1432 }{ 1433 { 1434 name: "Expected", 1435 args: args{ 1436 s: "[s , s]", 1437 delimiter: ",", 1438 cutdelimiter: false, 1439 }, 1440 want: "[s ", 1441 want1: ", s]", 1442 }, 1443 { 1444 name: "no delimited", 1445 args: args{ 1446 s: "[s s]", 1447 delimiter: "", 1448 cutdelimiter: false, 1449 }, 1450 want: "", 1451 want1: "[s s]", 1452 }, 1453 { 1454 name: "Expected not delim", 1455 args: args{ 1456 s: "[s , s]", 1457 delimiter: ",", 1458 cutdelimiter: true, 1459 }, 1460 want: "[s ", 1461 want1: " s]", 1462 }, 1463 } 1464 for _, tt := range tests { 1465 t.Run(tt.name, func(_ *testing.T) { 1466 got, got1 := splitSpecial(tt.args.s, tt.args.delimiter, tt.args.cutdelimiter) 1467 assert.Equalf(t, tt.want, got, "splitSpecial(%v, %v, %v)", tt.args.s, tt.args.delimiter, tt.args.cutdelimiter) 1468 assert.Equalf(t, tt.want1, got1, "splitSpecial(%v, %v, %v)", tt.args.s, tt.args.delimiter, tt.args.cutdelimiter) 1469 }) 1470 } 1471 } 1472 1473 func Test_getHost(t *testing.T) { 1474 type args struct { 1475 authority string 1476 } 1477 tests := []struct { 1478 name string 1479 args args 1480 wantHost string 1481 }{ 1482 { 1483 name: "Expected", 1484 args: args{ 1485 authority: "username@domain.com", 1486 }, 1487 wantHost: "", 1488 }, 1489 { 1490 name: "Expected 2", 1491 args: args{ 1492 authority: "domain.com", 1493 }, 1494 wantHost: "domain.com", 1495 }, 1496 } 1497 for _, tt := range tests { 1498 t.Run(tt.name, func(_ *testing.T) { 1499 assert.Equalf(t, tt.wantHost, getHost(tt.args.authority), "getHost(%v)", tt.args.authority) 1500 }) 1501 } 1502 } 1503 1504 func Test_newClientURL(t *testing.T) { 1505 type args struct { 1506 urlStr string 1507 } 1508 tests := []struct { 1509 name string 1510 args args 1511 want mc.ClientURL 1512 }{ 1513 { 1514 name: "Expected", 1515 args: args{ 1516 urlStr: "http://domain.com", 1517 }, 1518 want: mc.ClientURL{ 1519 Type: 0, 1520 Scheme: "http", 1521 Host: "domain.com", 1522 Path: "/", 1523 SchemeSeparator: "://", 1524 Separator: 47, 1525 }, 1526 }, 1527 { 1528 name: "Expected file", 1529 args: args{ 1530 urlStr: "file.jpeg", 1531 }, 1532 want: mc.ClientURL{ 1533 Type: fileSystem, 1534 Path: "file.jpeg", 1535 Separator: filepath.Separator, 1536 }, 1537 }, 1538 } 1539 for _, tt := range tests { 1540 t.Run(tt.name, func(_ *testing.T) { 1541 assert.Equalf(t, tt.want, *newClientURL(tt.args.urlStr), "newClientURL(%v)", tt.args.urlStr) 1542 }) 1543 } 1544 } 1545 1546 func Test_getMultipleFilesDownloadResponse(t *testing.T) { 1547 type args struct { 1548 session *models.Principal 1549 params object.DownloadMultipleObjectsParams 1550 } 1551 1552 tests := []struct { 1553 name string 1554 args args 1555 want middleware.Responder 1556 want1 *CodedAPIError 1557 }{ 1558 { 1559 name: "test no objects sent for download", 1560 args: args{ 1561 session: nil, 1562 params: object.DownloadMultipleObjectsParams{ 1563 HTTPRequest: &http.Request{}, 1564 BucketName: "test-bucket", 1565 ObjectList: nil, 1566 }, 1567 }, 1568 want: nil, 1569 want1: nil, 1570 }, 1571 { 1572 name: "few objects sent for download", 1573 args: args{ 1574 session: nil, 1575 params: object.DownloadMultipleObjectsParams{ 1576 HTTPRequest: &http.Request{}, 1577 BucketName: "test-bucket", 1578 ObjectList: []string{"test.txt", ",y-obj.doc", "z-obj.png"}, 1579 }, 1580 }, 1581 want: nil, 1582 want1: nil, 1583 }, 1584 { 1585 name: "few prefixes and a file sent for download", 1586 args: args{ 1587 session: nil, 1588 params: object.DownloadMultipleObjectsParams{ 1589 HTTPRequest: &http.Request{}, 1590 BucketName: "test-bucket", 1591 ObjectList: []string{"my-folder/", "my-folder/test-nested", "z-obj.png"}, 1592 }, 1593 }, 1594 want: nil, 1595 want1: nil, 1596 }, 1597 } 1598 for _, tt := range tests { 1599 t.Run(tt.name, func(_ *testing.T) { 1600 got, got1 := getMultipleFilesDownloadResponse(tt.args.session, tt.args.params) 1601 assert.Equal(t, tt.want1, got1) 1602 assert.NotNil(t, got) 1603 }) 1604 } 1605 }