github.com/minio/console@v1.3.0/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  		wantError error
   927  		expected  string
   928  	}{
   929  		{
   930  			test: "return sharefunc url base64 encoded with host name",
   931  			args: args{
   932  				r: &http.Request{
   933  					TLS:  nil,
   934  					Host: "localhost:9090",
   935  				},
   936  				versionID: "2121434",
   937  				expires:   "30s",
   938  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
   939  					return "http://someurl", nil
   940  				},
   941  			},
   942  
   943  			wantError: nil,
   944  			expected:  "http://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=",
   945  		},
   946  		{
   947  			test: "return https scheme if url uses TLS",
   948  			args: args{
   949  				r: &http.Request{
   950  					TLS:  &tls.ConnectionState{},
   951  					Host: "localhost:9090",
   952  				},
   953  				versionID: "2121434",
   954  				expires:   "30s",
   955  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
   956  					return "http://someurl", nil
   957  				},
   958  			},
   959  
   960  			wantError: nil,
   961  			expected:  "https://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=",
   962  		},
   963  		{
   964  			test: "returns invalid expire duration if expiration is invalid",
   965  			args: args{
   966  				r: &http.Request{
   967  					TLS:  nil,
   968  					Host: "localhost:9090",
   969  				},
   970  				versionID: "2121434",
   971  				expires:   "invalid",
   972  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
   973  					return "http://someurl", nil
   974  				},
   975  			},
   976  			wantError: errors.New("time: invalid duration \"invalid\""),
   977  		},
   978  		{
   979  			test: "add default expiration if expiration is empty",
   980  			args: args{
   981  				r: &http.Request{
   982  					TLS:  nil,
   983  					Host: "localhost:9090",
   984  				},
   985  				versionID: "2121434",
   986  				expires:   "",
   987  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
   988  					return "http://someurl", nil
   989  				},
   990  			},
   991  			wantError: nil,
   992  			expected:  "http://localhost:9090/api/v1/download-shared-object/aHR0cDovL3NvbWV1cmw=",
   993  		},
   994  		{
   995  			test: "return error if sharefunc returns error",
   996  			args: args{
   997  				r: &http.Request{
   998  					TLS:  nil,
   999  					Host: "localhost:9090",
  1000  				},
  1001  				versionID: "2121434",
  1002  				expires:   "3h",
  1003  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
  1004  					return "", probe.NewError(errors.New("probe error"))
  1005  				},
  1006  			},
  1007  			wantError: errors.New("probe error"),
  1008  		},
  1009  		{
  1010  			test: "return shareFunc url base64 encoded url-safe",
  1011  			args: args{
  1012  				r: &http.Request{
  1013  					TLS:  nil,
  1014  					Host: "localhost:9090",
  1015  				},
  1016  				versionID: "2121434",
  1017  				expires:   "3h",
  1018  				shareFunc: func(_ context.Context, _ string, _ time.Duration) (string, *probe.Error) {
  1019  					// 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
  1020  					return "https://127.0.0.1:9000/cestest/Audio%20icon.svg?X-Amz-Algorithm=AWS4-HMAC-SHA256", nil
  1021  				},
  1022  			},
  1023  			wantError: nil,
  1024  			expected:  "http://localhost:9090/api/v1/download-shared-object/aHR0cHM6Ly8xMjcuMC4wLjE6OTAwMC9jZXN0ZXN0L0F1ZGlvJTIwaWNvbi5zdmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTY=",
  1025  		},
  1026  	}
  1027  
  1028  	for _, tt := range tests {
  1029  		t.Run(tt.test, func(_ *testing.T) {
  1030  			mcShareDownloadMock = tt.args.shareFunc
  1031  			url, err := getShareObjectURL(ctx, client, tt.args.r, tt.args.versionID, tt.args.expires)
  1032  			if tt.wantError != nil {
  1033  				if !reflect.DeepEqual(err, tt.wantError) {
  1034  					t.Errorf("getShareObjectURL() error: `%s`, wantErr: `%s`", err, tt.wantError)
  1035  					return
  1036  				}
  1037  			} else {
  1038  				tAssert.Equal(*url, tt.expected)
  1039  			}
  1040  		})
  1041  	}
  1042  }
  1043  
  1044  func Test_putObjectLegalHold(t *testing.T) {
  1045  	ctx, cancel := context.WithCancel(context.Background())
  1046  	defer cancel()
  1047  	client := minioClientMock{}
  1048  	type args struct {
  1049  		bucket        string
  1050  		prefix        string
  1051  		versionID     string
  1052  		status        models.ObjectLegalHoldStatus
  1053  		legalHoldFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectLegalHoldOptions) error
  1054  	}
  1055  	tests := []struct {
  1056  		test      string
  1057  		args      args
  1058  		wantError error
  1059  	}{
  1060  		{
  1061  			test: "Put Object Legal hold enabled status",
  1062  			args: args{
  1063  				bucket:    "buck1",
  1064  				versionID: "someversion",
  1065  				prefix:    "folder/file.txt",
  1066  				status:    models.ObjectLegalHoldStatusEnabled,
  1067  				legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error {
  1068  					return nil
  1069  				},
  1070  			},
  1071  			wantError: nil,
  1072  		},
  1073  		{
  1074  			test: "Put Object Legal hold disabled status",
  1075  			args: args{
  1076  				bucket:    "buck1",
  1077  				versionID: "someversion",
  1078  				prefix:    "folder/file.txt",
  1079  				status:    models.ObjectLegalHoldStatusDisabled,
  1080  				legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error {
  1081  					return nil
  1082  				},
  1083  			},
  1084  			wantError: nil,
  1085  		},
  1086  		{
  1087  			test: "Handle error on legalhold func",
  1088  			args: args{
  1089  				bucket:    "buck1",
  1090  				versionID: "someversion",
  1091  				prefix:    "folder/file.txt",
  1092  				status:    models.ObjectLegalHoldStatusDisabled,
  1093  				legalHoldFunc: func(_ context.Context, _, _ string, _ minio.PutObjectLegalHoldOptions) error {
  1094  					return errors.New("new error")
  1095  				},
  1096  			},
  1097  			wantError: errors.New("new error"),
  1098  		},
  1099  	}
  1100  
  1101  	for _, tt := range tests {
  1102  		t.Run(tt.test, func(_ *testing.T) {
  1103  			minioPutObjectLegalHoldMock = tt.args.legalHoldFunc
  1104  			err := setObjectLegalHold(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID, tt.args.status)
  1105  			if !reflect.DeepEqual(err, tt.wantError) {
  1106  				t.Errorf("setObjectLegalHold() error: %v, wantErr: %v", err, tt.wantError)
  1107  				return
  1108  			}
  1109  		})
  1110  	}
  1111  }
  1112  
  1113  func Test_putObjectRetention(t *testing.T) {
  1114  	tAssert := assert.New(t)
  1115  	ctx, cancel := context.WithCancel(context.Background())
  1116  	defer cancel()
  1117  	client := minioClientMock{}
  1118  	type args struct {
  1119  		bucket        string
  1120  		prefix        string
  1121  		versionID     string
  1122  		opts          *models.PutObjectRetentionRequest
  1123  		retentionFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error
  1124  	}
  1125  	tests := []struct {
  1126  		test      string
  1127  		args      args
  1128  		wantError error
  1129  	}{
  1130  		{
  1131  			test: "Put Object retention governance",
  1132  			args: args{
  1133  				bucket:    "buck1",
  1134  				versionID: "someversion",
  1135  				prefix:    "folder/file.txt",
  1136  				opts: &models.PutObjectRetentionRequest{
  1137  					Expires:          swag.String("2006-01-02T15:04:05Z"),
  1138  					GovernanceBypass: false,
  1139  					Mode:             models.NewObjectRetentionMode(models.ObjectRetentionModeGovernance),
  1140  				},
  1141  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1142  					return nil
  1143  				},
  1144  			},
  1145  			wantError: nil,
  1146  		},
  1147  		{
  1148  			test: "Put Object retention compliance",
  1149  			args: args{
  1150  				bucket:    "buck1",
  1151  				versionID: "someversion",
  1152  				prefix:    "folder/file.txt",
  1153  				opts: &models.PutObjectRetentionRequest{
  1154  					Expires:          swag.String("2006-01-02T15:04:05Z"),
  1155  					GovernanceBypass: false,
  1156  					Mode:             models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance),
  1157  				},
  1158  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1159  					return nil
  1160  				},
  1161  			},
  1162  			wantError: nil,
  1163  		},
  1164  		{
  1165  			test: "Empty opts should return error",
  1166  			args: args{
  1167  				bucket:    "buck1",
  1168  				versionID: "someversion",
  1169  				prefix:    "folder/file.txt",
  1170  				opts:      nil,
  1171  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1172  					return nil
  1173  				},
  1174  			},
  1175  			wantError: errors.New("object retention options can't be nil"),
  1176  		},
  1177  		{
  1178  			test: "Empty expire on opts should return error",
  1179  			args: args{
  1180  				bucket:    "buck1",
  1181  				versionID: "someversion",
  1182  				prefix:    "folder/file.txt",
  1183  				opts: &models.PutObjectRetentionRequest{
  1184  					Expires:          nil,
  1185  					GovernanceBypass: false,
  1186  					Mode:             models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance),
  1187  				},
  1188  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1189  					return nil
  1190  				},
  1191  			},
  1192  			wantError: errors.New("object retention expires can't be nil"),
  1193  		},
  1194  		{
  1195  			test: "Handle invalid expire time",
  1196  			args: args{
  1197  				bucket:    "buck1",
  1198  				versionID: "someversion",
  1199  				prefix:    "folder/file.txt",
  1200  				opts: &models.PutObjectRetentionRequest{
  1201  					Expires:          swag.String("invalidtime"),
  1202  					GovernanceBypass: false,
  1203  					Mode:             models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance),
  1204  				},
  1205  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1206  					return nil
  1207  				},
  1208  			},
  1209  			wantError: errors.New("parsing time \"invalidtime\" as \"2006-01-02T15:04:05Z07:00\": cannot parse \"invalidtime\" as \"2006\""),
  1210  		},
  1211  		{
  1212  			test: "Handle error on retention func",
  1213  			args: args{
  1214  				bucket:    "buck1",
  1215  				versionID: "someversion",
  1216  				prefix:    "folder/file.txt",
  1217  				opts: &models.PutObjectRetentionRequest{
  1218  					Expires:          swag.String("2006-01-02T15:04:05Z"),
  1219  					GovernanceBypass: false,
  1220  					Mode:             models.NewObjectRetentionMode(models.ObjectRetentionModeCompliance),
  1221  				},
  1222  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1223  					return errors.New("new Error")
  1224  				},
  1225  			},
  1226  			wantError: errors.New("new Error"),
  1227  		},
  1228  	}
  1229  
  1230  	for _, tt := range tests {
  1231  		t.Run(tt.test, func(_ *testing.T) {
  1232  			minioPutObjectRetentionMock = tt.args.retentionFunc
  1233  			err := setObjectRetention(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID, tt.args.opts)
  1234  			if tt.wantError != nil {
  1235  				fmt.Println(t.Name())
  1236  				tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("setObjectRetention() error: `%s`, wantErr: `%s`", err, tt.wantError))
  1237  			} else {
  1238  				tAssert.Nil(err, fmt.Sprintf("setObjectRetention() error: %v, wantErr: %v", err, tt.wantError))
  1239  			}
  1240  		})
  1241  	}
  1242  }
  1243  
  1244  func Test_deleteObjectRetention(t *testing.T) {
  1245  	tAssert := assert.New(t)
  1246  	ctx, cancel := context.WithCancel(context.Background())
  1247  	defer cancel()
  1248  	client := minioClientMock{}
  1249  	type args struct {
  1250  		bucket        string
  1251  		prefix        string
  1252  		versionID     string
  1253  		retentionFunc func(ctx context.Context, bucketName, objectName string, opts minio.PutObjectRetentionOptions) error
  1254  	}
  1255  	tests := []struct {
  1256  		test      string
  1257  		args      args
  1258  		wantError error
  1259  	}{
  1260  		{
  1261  			test: "Delete Object retention governance",
  1262  			args: args{
  1263  				bucket:    "buck1",
  1264  				versionID: "someversion",
  1265  				prefix:    "folder/file.txt",
  1266  				retentionFunc: func(_ context.Context, _, _ string, _ minio.PutObjectRetentionOptions) error {
  1267  					return nil
  1268  				},
  1269  			},
  1270  			wantError: nil,
  1271  		},
  1272  	}
  1273  	for _, tt := range tests {
  1274  		t.Run(tt.test, func(_ *testing.T) {
  1275  			minioPutObjectRetentionMock = tt.args.retentionFunc
  1276  			err := deleteObjectRetention(ctx, client, tt.args.bucket, tt.args.prefix, tt.args.versionID)
  1277  			if tt.wantError != nil {
  1278  				fmt.Println(t.Name())
  1279  				tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("deleteObjectRetention() error: `%s`, wantErr: `%s`", err, tt.wantError))
  1280  			} else {
  1281  				tAssert.Nil(err, fmt.Sprintf("deleteObjectRetention() error: %v, wantErr: %v", err, tt.wantError))
  1282  			}
  1283  		})
  1284  	}
  1285  }
  1286  
  1287  func Test_getObjectInfo(t *testing.T) {
  1288  	tAssert := assert.New(t)
  1289  	ctx, cancel := context.WithCancel(context.Background())
  1290  	defer cancel()
  1291  	client := minioClientMock{}
  1292  
  1293  	type args struct {
  1294  		bucketName string
  1295  		prefix     string
  1296  		statFunc   func(ctx context.Context, bucketName string, prefix string, opts minio.GetObjectOptions) (objectInfo minio.ObjectInfo, err error)
  1297  	}
  1298  	tests := []struct {
  1299  		test      string
  1300  		args      args
  1301  		wantError error
  1302  	}{
  1303  		{
  1304  			test: "Test function not returns an error",
  1305  			args: args{
  1306  				bucketName: "bucket1",
  1307  				prefix:     "someprefix",
  1308  				statFunc: func(_ context.Context, _ string, _ string, _ minio.GetObjectOptions) (minio.ObjectInfo, error) {
  1309  					return minio.ObjectInfo{}, nil
  1310  				},
  1311  			},
  1312  			wantError: nil,
  1313  		},
  1314  		{
  1315  			test: "Test function returns an error",
  1316  			args: args{
  1317  				bucketName: "bucket2",
  1318  				prefix:     "someprefi2",
  1319  				statFunc: func(_ context.Context, _ string, _ string, _ minio.GetObjectOptions) (minio.ObjectInfo, error) {
  1320  					return minio.ObjectInfo{}, errors.New("new Error")
  1321  				},
  1322  			},
  1323  			wantError: errors.New("new Error"),
  1324  		},
  1325  	}
  1326  	for _, tt := range tests {
  1327  		t.Run(tt.test, func(_ *testing.T) {
  1328  			minioStatObjectMock = tt.args.statFunc
  1329  			_, err := getObjectInfo(ctx, client, tt.args.bucketName, tt.args.prefix)
  1330  			if tt.wantError != nil {
  1331  				fmt.Println(t.Name())
  1332  				tAssert.Equal(tt.wantError.Error(), err.Error(), fmt.Sprintf("getObjectInfo() error: `%s`, wantErr: `%s`", err, tt.wantError))
  1333  			} else {
  1334  				tAssert.Nil(err, fmt.Sprintf("getObjectInfo() error: %v, wantErr: %v", err, tt.wantError))
  1335  			}
  1336  		})
  1337  	}
  1338  }
  1339  
  1340  func Test_getScheme(t *testing.T) {
  1341  	type args struct {
  1342  		rawurl string
  1343  	}
  1344  	tests := []struct {
  1345  		name       string
  1346  		args       args
  1347  		wantScheme string
  1348  		wantPath   string
  1349  	}{
  1350  		{
  1351  			name: "expected",
  1352  			args: args{
  1353  				rawurl: "http://domain.com",
  1354  			},
  1355  			wantScheme: "http",
  1356  			wantPath:   "//domain.com",
  1357  		},
  1358  		{
  1359  			name: "no scheme",
  1360  			args: args{
  1361  				rawurl: "domain.com",
  1362  			},
  1363  			wantScheme: "",
  1364  			wantPath:   "domain.com",
  1365  		},
  1366  	}
  1367  	for _, tt := range tests {
  1368  		t.Run(tt.name, func(_ *testing.T) {
  1369  			gotScheme, gotPath := getScheme(tt.args.rawurl)
  1370  			assert.Equalf(t, tt.wantScheme, gotScheme, "getScheme(%v)", tt.args.rawurl)
  1371  			assert.Equalf(t, tt.wantPath, gotPath, "getScheme(%v)", tt.args.rawurl)
  1372  		})
  1373  	}
  1374  }
  1375  
  1376  func Test_splitSpecial(t *testing.T) {
  1377  	type args struct {
  1378  		s            string
  1379  		delimiter    string
  1380  		cutdelimiter bool
  1381  	}
  1382  	tests := []struct {
  1383  		name  string
  1384  		args  args
  1385  		want  string
  1386  		want1 string
  1387  	}{
  1388  		{
  1389  			name: "Expected",
  1390  			args: args{
  1391  				s:            "[s , s]",
  1392  				delimiter:    ",",
  1393  				cutdelimiter: false,
  1394  			},
  1395  			want:  "[s ",
  1396  			want1: ", s]",
  1397  		},
  1398  		{
  1399  			name: "no delimited",
  1400  			args: args{
  1401  				s:            "[s s]",
  1402  				delimiter:    "",
  1403  				cutdelimiter: false,
  1404  			},
  1405  			want:  "",
  1406  			want1: "[s s]",
  1407  		},
  1408  		{
  1409  			name: "Expected not delim",
  1410  			args: args{
  1411  				s:            "[s , s]",
  1412  				delimiter:    ",",
  1413  				cutdelimiter: true,
  1414  			},
  1415  			want:  "[s ",
  1416  			want1: " s]",
  1417  		},
  1418  	}
  1419  	for _, tt := range tests {
  1420  		t.Run(tt.name, func(_ *testing.T) {
  1421  			got, got1 := splitSpecial(tt.args.s, tt.args.delimiter, tt.args.cutdelimiter)
  1422  			assert.Equalf(t, tt.want, got, "splitSpecial(%v, %v, %v)", tt.args.s, tt.args.delimiter, tt.args.cutdelimiter)
  1423  			assert.Equalf(t, tt.want1, got1, "splitSpecial(%v, %v, %v)", tt.args.s, tt.args.delimiter, tt.args.cutdelimiter)
  1424  		})
  1425  	}
  1426  }
  1427  
  1428  func Test_getHost(t *testing.T) {
  1429  	type args struct {
  1430  		authority string
  1431  	}
  1432  	tests := []struct {
  1433  		name     string
  1434  		args     args
  1435  		wantHost string
  1436  	}{
  1437  		{
  1438  			name: "Expected",
  1439  			args: args{
  1440  				authority: "username@domain.com",
  1441  			},
  1442  			wantHost: "",
  1443  		},
  1444  		{
  1445  			name: "Expected 2",
  1446  			args: args{
  1447  				authority: "domain.com",
  1448  			},
  1449  			wantHost: "domain.com",
  1450  		},
  1451  	}
  1452  	for _, tt := range tests {
  1453  		t.Run(tt.name, func(_ *testing.T) {
  1454  			assert.Equalf(t, tt.wantHost, getHost(tt.args.authority), "getHost(%v)", tt.args.authority)
  1455  		})
  1456  	}
  1457  }
  1458  
  1459  func Test_newClientURL(t *testing.T) {
  1460  	type args struct {
  1461  		urlStr string
  1462  	}
  1463  	tests := []struct {
  1464  		name string
  1465  		args args
  1466  		want mc.ClientURL
  1467  	}{
  1468  		{
  1469  			name: "Expected",
  1470  			args: args{
  1471  				urlStr: "http://domain.com",
  1472  			},
  1473  			want: mc.ClientURL{
  1474  				Type:            0,
  1475  				Scheme:          "http",
  1476  				Host:            "domain.com",
  1477  				Path:            "/",
  1478  				SchemeSeparator: "://",
  1479  				Separator:       47,
  1480  			},
  1481  		},
  1482  		{
  1483  			name: "Expected file",
  1484  			args: args{
  1485  				urlStr: "file.jpeg",
  1486  			},
  1487  			want: mc.ClientURL{
  1488  				Type:      fileSystem,
  1489  				Path:      "file.jpeg",
  1490  				Separator: filepath.Separator,
  1491  			},
  1492  		},
  1493  	}
  1494  	for _, tt := range tests {
  1495  		t.Run(tt.name, func(_ *testing.T) {
  1496  			assert.Equalf(t, tt.want, *newClientURL(tt.args.urlStr), "newClientURL(%v)", tt.args.urlStr)
  1497  		})
  1498  	}
  1499  }
  1500  
  1501  func Test_getMultipleFilesDownloadResponse(t *testing.T) {
  1502  	type args struct {
  1503  		session *models.Principal
  1504  		params  object.DownloadMultipleObjectsParams
  1505  	}
  1506  
  1507  	tests := []struct {
  1508  		name  string
  1509  		args  args
  1510  		want  middleware.Responder
  1511  		want1 *CodedAPIError
  1512  	}{
  1513  		{
  1514  			name: "test no objects sent for download",
  1515  			args: args{
  1516  				session: nil,
  1517  				params: object.DownloadMultipleObjectsParams{
  1518  					HTTPRequest: &http.Request{},
  1519  					BucketName:  "test-bucket",
  1520  					ObjectList:  nil,
  1521  				},
  1522  			},
  1523  			want:  nil,
  1524  			want1: nil,
  1525  		},
  1526  		{
  1527  			name: "few objects sent for download",
  1528  			args: args{
  1529  				session: nil,
  1530  				params: object.DownloadMultipleObjectsParams{
  1531  					HTTPRequest: &http.Request{},
  1532  					BucketName:  "test-bucket",
  1533  					ObjectList:  []string{"test.txt", ",y-obj.doc", "z-obj.png"},
  1534  				},
  1535  			},
  1536  			want:  nil,
  1537  			want1: nil,
  1538  		},
  1539  		{
  1540  			name: "few prefixes and a file sent for download",
  1541  			args: args{
  1542  				session: nil,
  1543  				params: object.DownloadMultipleObjectsParams{
  1544  					HTTPRequest: &http.Request{},
  1545  					BucketName:  "test-bucket",
  1546  					ObjectList:  []string{"my-folder/", "my-folder/test-nested", "z-obj.png"},
  1547  				},
  1548  			},
  1549  			want:  nil,
  1550  			want1: nil,
  1551  		},
  1552  	}
  1553  	for _, tt := range tests {
  1554  		t.Run(tt.name, func(_ *testing.T) {
  1555  			got, got1 := getMultipleFilesDownloadResponse(tt.args.session, tt.args.params)
  1556  			assert.Equal(t, tt.want1, got1)
  1557  			assert.NotNil(t, got)
  1558  		})
  1559  	}
  1560  }