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  }