Repository: G-Research/fasttrackml
Pull request #405 (2023-09-29 12:13:17)

Fix bug preventing getting/listing artifacts with space in the name
This PR improves on the work from #368 and #367 and brings the following changes:
* **Fix bug preventing getting/listing artifacts with space in the name**
* **Propagate context to artifact storage providers**
* Tests cleanup
* Remove unnecessary error check
* Unify style
* Improve comments
* Remove unnecessary else statement


Patch:
diff --git a/pkg/api/mlflow/service/artifact/service.go b/pkg/api/mlflow/service/artifact/service.go
index aae589f..7fdb622 100644
--- a/pkg/api/mlflow/service/artifact/service.go
+++ b/pkg/api/mlflow/service/artifact/service.go
@@ -46,12 +46,12 @@ func (s Service) ListArtifacts(
 		return "", nil, api.NewInternalError("unable to get artifact URI for run '%s'", req.GetRunID())
 	}
 
-	artifactStorage, err := s.artifactStorageFactory.GetStorage(run.ArtifactURI)
+	artifactStorage, err := s.artifactStorageFactory.GetStorage(ctx, run.ArtifactURI)
 	if err != nil {
 		return "", nil, api.NewInternalError("run with id '%s' has unsupported artifact storage", run.ID)
 	}
 
-	artifacts, err := artifactStorage.List(run.ArtifactURI, req.Path)
+	artifacts, err := artifactStorage.List(ctx, run.ArtifactURI, req.Path)
 	if err != nil {
 		return "", nil, api.NewInternalError("error getting artifact list from storage")
 	}
@@ -79,24 +79,20 @@ func (s Service) GetArtifact(
 	if run == nil {
 		return nil, api.NewResourceDoesNotExistError("unable to find run '%s'", req.GetRunID())
 	}
-	artifactStorage, err := s.artifactStorageFactory.GetStorage(run.ArtifactURI)
+	artifactStorage, err := s.artifactStorageFactory.GetStorage(ctx, run.ArtifactURI)
 	if err != nil {
 		return nil, api.NewInternalError("run with id '%s' has unsupported artifact storage", run.ID)
 	}
 
 	artifactReader, err := artifactStorage.Get(
-		run.ArtifactURI, req.Path,
+		ctx, run.ArtifactURI, req.Path,
 	)
 	if err != nil {
-		msg := fmt.Sprintf(
-			"error getting artifact object for URI: %s",
-			filepath.Join(run.ArtifactURI, req.Path),
-		)
+		msg := fmt.Sprintf("error getting artifact object for URI: %s", filepath.Join(run.ArtifactURI, req.Path))
 		if errors.Is(err, fs.ErrNotExist) {
 			return nil, api.NewResourceDoesNotExistError(msg)
-		} else {
-			return nil, api.NewInternalError(msg)
 		}
+		return nil, api.NewInternalError(msg)
 	}
 	return artifactReader, nil
 }
diff --git a/pkg/api/mlflow/service/artifact/storage/local.go b/pkg/api/mlflow/service/artifact/storage/local.go
index 250f911..54d8b45 100644
--- a/pkg/api/mlflow/service/artifact/storage/local.go
+++ b/pkg/api/mlflow/service/artifact/storage/local.go
@@ -1,10 +1,10 @@
 package storage
 
 import (
+	"context"
 	"errors"
 	"io"
 	"io/fs"
-	"net/url"
 	"os"
 	"path/filepath"
 	"strings"
@@ -29,15 +29,12 @@ func NewLocal(config *config.ServiceConfig) (*Local, error) {
 }
 
 // List implements ArtifactStorageProvider interface.
-func (s Local) List(artifactURI, path string) ([]ArtifactObject, error) {
+func (s Local) List(ctx context.Context, artifactURI, path string) ([]ArtifactObject, error) {
 	// 1. trim the `file://` prefix if it exists.
 	artifactURI = strings.TrimPrefix(artifactURI, "file://")
 
 	// 2. process search `path` parameter.
-	absPath, err := url.JoinPath(artifactURI, path)
-	if err != nil {
-		return nil, eris.Wrap(err, "error constructing full path")
-	}
+	absPath := filepath.Join(artifactURI, path)
 
 	// 3. read data from local storage.
 	objects, err := os.ReadDir(absPath)
@@ -72,22 +69,29 @@ func (s Local) List(artifactURI, path string) ([]ArtifactObject, error) {
 }
 
 // Get returns actual file content at the storage location.
-func (s Local) Get(artifactURI, itemPath string) (io.ReadCloser, error) {
+func (s Local) Get(ctx context.Context, artifactURI, path string) (io.ReadCloser, error) {
+	// 1. trim the `file://` prefix if it exists.
 	artifactURI = strings.TrimPrefix(artifactURI, "file://")
-	path, err := url.JoinPath(artifactURI, itemPath)
-	if err != nil {
-		return nil, eris.Wrap(err, "error constructing full path")
-	}
-	fileInfo, err := os.Stat(path)
+
+	// 2. process `path` parameter.
+	absPath := filepath.Join(artifactURI, path)
+
+	// 3. check that the file exists and is not a directory.
+	fileInfo, err := os.Stat(absPath)
 	if err != nil {
 		return nil, eris.Wrap(err, "path could not be opened")
 	}
 	if fileInfo.IsDir() {
 		return nil, eris.Wrap(fs.ErrNotExist, "path is a directory")
 	}
-	file, err := os.Open(path)
+
+	// 4. open the file.
+	// artifactURI and path are validated by the caller
+	// #nosec G304
+	file, err := os.Open(absPath)
 	if err != nil {
 		return nil, eris.Wrap(err, "unable to open file")
 	}
+
 	return file, nil
 }
diff --git a/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_factory_provider.go b/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_factory_provider.go
index 992ce71..98e14c7 100644
--- a/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_factory_provider.go
+++ b/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_factory_provider.go
@@ -2,32 +2,36 @@
 
 package storage
 
-import mock "github.com/stretchr/testify/mock"
+import (
+	context "context"
+
+	mock "github.com/stretchr/testify/mock"
+)
 
 // MockArtifactStorageFactoryProvider is an autogenerated mock type for the ArtifactStorageFactoryProvider type
 type MockArtifactStorageFactoryProvider struct {
 	mock.Mock
 }
 
-// GetStorage provides a mock function with given fields: runArtifactPath
-func (_m *MockArtifactStorageFactoryProvider) GetStorage(runArtifactPath string) (ArtifactStorageProvider, error) {
-	ret := _m.Called(runArtifactPath)
+// GetStorage provides a mock function with given fields: ctx, runArtifactPath
+func (_m *MockArtifactStorageFactoryProvider) GetStorage(ctx context.Context, runArtifactPath string) (ArtifactStorageProvider, error) {
+	ret := _m.Called(ctx, runArtifactPath)
 
 	var r0 ArtifactStorageProvider
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string) (ArtifactStorageProvider, error)); ok {
-		return rf(runArtifactPath)
+	if rf, ok := ret.Get(0).(func(context.Context, string) (ArtifactStorageProvider, error)); ok {
+		return rf(ctx, runArtifactPath)
 	}
-	if rf, ok := ret.Get(0).(func(string) ArtifactStorageProvider); ok {
-		r0 = rf(runArtifactPath)
+	if rf, ok := ret.Get(0).(func(context.Context, string) ArtifactStorageProvider); ok {
+		r0 = rf(ctx, runArtifactPath)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(ArtifactStorageProvider)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(string) error); ok {
-		r1 = rf(runArtifactPath)
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, runArtifactPath)
 	} else {
 		r1 = ret.Error(1)
 	}
diff --git a/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_provider.go b/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_provider.go
index 5f77f6f..d49cd86 100644
--- a/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_provider.go
+++ b/pkg/api/mlflow/service/artifact/storage/mock_artifact_storage_provider.go
@@ -3,6 +3,7 @@
 package storage
 
 import (
+	context "context"
 	io "io"
 
 	mock "github.com/stretchr/testify/mock"
@@ -13,25 +14,25 @@ type MockArtifactStorageProvider struct {
 	mock.Mock
 }
 
-// Get provides a mock function with given fields: artifactURI, path
-func (_m *MockArtifactStorageProvider) Get(artifactURI string, path string) (io.ReadCloser, error) {
-	ret := _m.Called(artifactURI, path)
+// Get provides a mock function with given fields: ctx, artifactURI, path
+func (_m *MockArtifactStorageProvider) Get(ctx context.Context, artifactURI string, path string) (io.ReadCloser, error) {
+	ret := _m.Called(ctx, artifactURI, path)
 
 	var r0 io.ReadCloser
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string, string) (io.ReadCloser, error)); ok {
-		return rf(artifactURI, path)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) (io.ReadCloser, error)); ok {
+		return rf(ctx, artifactURI, path)
 	}
-	if rf, ok := ret.Get(0).(func(string, string) io.ReadCloser); ok {
-		r0 = rf(artifactURI, path)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) io.ReadCloser); ok {
+		r0 = rf(ctx, artifactURI, path)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(io.ReadCloser)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(string, string) error); ok {
-		r1 = rf(artifactURI, path)
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, artifactURI, path)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -39,25 +40,25 @@ func (_m *MockArtifactStorageProvider) Get(artifactURI string, path string) (io.
 	return r0, r1
 }
 
-// List provides a mock function with given fields: artifactURI, path
-func (_m *MockArtifactStorageProvider) List(artifactURI string, path string) ([]ArtifactObject, error) {
-	ret := _m.Called(artifactURI, path)
+// List provides a mock function with given fields: ctx, artifactURI, path
+func (_m *MockArtifactStorageProvider) List(ctx context.Context, artifactURI string, path string) ([]ArtifactObject, error) {
+	ret := _m.Called(ctx, artifactURI, path)
 
 	var r0 []ArtifactObject
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string, string) ([]ArtifactObject, error)); ok {
-		return rf(artifactURI, path)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) ([]ArtifactObject, error)); ok {
+		return rf(ctx, artifactURI, path)
 	}
-	if rf, ok := ret.Get(0).(func(string, string) []ArtifactObject); ok {
-		r0 = rf(artifactURI, path)
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) []ArtifactObject); ok {
+		r0 = rf(ctx, artifactURI, path)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).([]ArtifactObject)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(string, string) error); ok {
-		r1 = rf(artifactURI, path)
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, artifactURI, path)
 	} else {
 		r1 = ret.Error(1)
 	}
diff --git a/pkg/api/mlflow/service/artifact/storage/s3.go b/pkg/api/mlflow/service/artifact/storage/s3.go
index b412b3e..2cb6dcf 100644
--- a/pkg/api/mlflow/service/artifact/storage/s3.go
+++ b/pkg/api/mlflow/service/artifact/storage/s3.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"io"
 	"io/fs"
-	"net/url"
 	"path/filepath"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
@@ -29,7 +28,7 @@ type S3 struct {
 }
 
 // NewS3 creates new S3 instance.
-func NewS3(config *config.ServiceConfig) (*S3, error) {
+func NewS3(ctx context.Context, config *config.ServiceConfig) (*S3, error) {
 	var clientOptions []func(o *s3.Options)
 	var configOptions []func(*awsConfig.LoadOptions) error
 	if config.S3EndpointURI != "" {
@@ -51,7 +50,7 @@ func NewS3(config *config.ServiceConfig) (*S3, error) {
 		))
 	}
 
-	cfg, err := awsConfig.LoadDefaultConfig(context.TODO(), configOptions...)
+	cfg, err := awsConfig.LoadDefaultConfig(ctx, configOptions...)
 	if err != nil {
 		return nil, eris.Wrap(err, "error loading configuration for S3 client")
 	}
@@ -62,7 +61,7 @@ func NewS3(config *config.ServiceConfig) (*S3, error) {
 }
 
 // List implements ArtifactStorageProvider interface.
-func (s S3) List(artifactURI, path string) ([]ArtifactObject, error) {
+func (s S3) List(ctx context.Context, artifactURI, path string) ([]ArtifactObject, error) {
 	// 1. create s3 request input.
 	bucket, rootPrefix, err := ExtractS3BucketAndPrefix(artifactURI)
 	if err != nil {
@@ -74,24 +73,17 @@ func (s S3) List(artifactURI, path string) ([]ArtifactObject, error) {
 	}
 
 	// 2. process search `path` parameter.
-	prefix, err := url.JoinPath(rootPrefix, path)
-	if err != nil {
-		return nil, eris.Wrap(err, "error constructing s3 prefix")
-	}
+	prefix := filepath.Join(rootPrefix, path)
 	if prefix != "" {
 		prefix = prefix + "/"
 	}
 	input.Prefix = aws.String(prefix)
 
 	// 3. read data from s3 storage.
-	paginator := s3.NewListObjectsV2Paginator(s.client, &input)
-	if err != nil {
-		return nil, eris.Wrap(err, "error creating s3 paginated request")
-	}
-
 	var artifactList []ArtifactObject
+	paginator := s3.NewListObjectsV2Paginator(s.client, &input)
 	for paginator.HasMorePages() {
-		page, err := paginator.NextPage(context.TODO())
+		page, err := paginator.NextPage(ctx)
 		if err != nil {
 			return nil, eris.Wrap(err, "error getting s3 page objects")
 		}
@@ -127,7 +119,8 @@ func (s S3) List(artifactURI, path string) ([]ArtifactObject, error) {
 }
 
 // Get returns file content at the storage location.
-func (s S3) Get(artifactURI, itemPath string) (io.ReadCloser, error) {
+func (s S3) Get(ctx context.Context, artifactURI, path string) (io.ReadCloser, error) {
+	// 1. create s3 request input.
 	bucketName, prefix, err := ExtractS3BucketAndPrefix(artifactURI)
 	if err != nil {
 		return nil, eris.Wrap(err, "error extracting bucket and prefix from provided uri")
@@ -135,11 +128,13 @@ func (s S3) Get(artifactURI, itemPath string) (io.ReadCloser, error) {
 
 	input := &s3.GetObjectInput{
 		Bucket: aws.String(bucketName),
-		Key:    aws.String(filepath.Join(prefix, itemPath)),
+		Key:    aws.String(filepath.Join(prefix, path)),
 	}
 
-	resp, err := s.client.GetObject(context.TODO(), input)
+	// 2. get object from s3 storage.
+	resp, err := s.client.GetObject(ctx, input)
 	if err != nil {
+		// errors.Is is not working for s3 errors, so we need to use errors.As instead.
 		var s3NoSuchKey *types.NoSuchKey
 		if errors.As(err, &s3NoSuchKey) {
 			return nil, eris.Wrap(fs.ErrNotExist, "object does not exist")
diff --git a/pkg/api/mlflow/service/artifact/storage/storage.go b/pkg/api/mlflow/service/artifact/storage/storage.go
index 3b00823..781ade3 100644
--- a/pkg/api/mlflow/service/artifact/storage/storage.go
+++ b/pkg/api/mlflow/service/artifact/storage/storage.go
@@ -1,6 +1,7 @@
 package storage
 
 import (
+	"context"
 	"io"
 	"net/url"
 	"sync"
@@ -35,15 +36,15 @@ func (o ArtifactObject) IsDirectory() bool {
 // ArtifactStorageProvider provides an interface to work with artifact storage.
 type ArtifactStorageProvider interface {
 	// Get returns an io.ReadCloser for specific artifact.
-	Get(artifactURI, path string) (io.ReadCloser, error)
+	Get(ctx context.Context, artifactURI, path string) (io.ReadCloser, error)
 	// List lists all artifact object under provided path.
-	List(artifactURI, path string) ([]ArtifactObject, error)
+	List(ctx context.Context, artifactURI, path string) ([]ArtifactObject, error)
 }
 
 // ArtifactStorageFactoryProvider provides an interface provider to work with Artifact Storage.
 type ArtifactStorageFactoryProvider interface {
 	// GetStorage returns Artifact storage based on provided runArtifactPath.
-	GetStorage(runArtifactPath string) (ArtifactStorageProvider, error)
+	GetStorage(ctx context.Context, runArtifactPath string) (ArtifactStorageProvider, error)
 }
 
 // ArtifactStorageFactory represents Artifact Storage .
@@ -61,7 +62,10 @@ func NewArtifactStorageFactory(config *config.ServiceConfig) (*ArtifactStorageFa
 }
 
 // GetStorage returns Artifact storage based on provided runArtifactPath.
-func (s *ArtifactStorageFactory) GetStorage(runArtifactPath string) (ArtifactStorageProvider, error) {
+func (s *ArtifactStorageFactory) GetStorage(
+	ctx context.Context,
+	runArtifactPath string,
+) (ArtifactStorageProvider, error) {
 	u, err := url.Parse(runArtifactPath)
 	if err != nil {
 		return nil, eris.Wrap(err, "error parsing artifact root")
@@ -75,7 +79,7 @@ func (s *ArtifactStorageFactory) GetStorage(runArtifactPath string) (ArtifactSto
 			}
 			return nil, eris.New("storage is not s3 artifact storage")
 		}
-		storage, err := NewS3(s.config)
+		storage, err := NewS3(ctx, s.config)
 		if err != nil {
 			return nil, eris.Wrap(err, "error initializing s3 artifact storage")
 		}


Test patch:
diff --git a/pkg/api/mlflow/service/artifact/service_test.go b/pkg/api/mlflow/service/artifact/service_test.go
index 98dcd50..962034b 100644
--- a/pkg/api/mlflow/service/artifact/service_test.go
+++ b/pkg/api/mlflow/service/artifact/service_test.go
@@ -17,7 +17,7 @@ import (
 func TestService_ListArtifacts_Ok(t *testing.T) {
 	artifactStorage := storage.MockArtifactStorageProvider{}
 	artifactStorage.On(
-		"List", "/artifact/uri", "",
+		"List", context.TODO(), "/artifact/uri", "",
 	).Return(
 		[]storage.ArtifactObject{
 			{
@@ -35,7 +35,7 @@ func TestService_ListArtifacts_Ok(t *testing.T) {
 
 	artifactStorageFactory := storage.MockArtifactStorageFactoryProvider{}
 	artifactStorageFactory.On(
-		"GetStorage", "/artifact/uri",
+		"GetStorage", context.TODO(), "/artifact/uri",
 	).Return(&artifactStorage, nil)
 
 	// init repository mocks.
@@ -134,14 +134,14 @@ func TestService_ListArtifacts_Error(t *testing.T) {
 			service: func() *Service {
 				artifactStorage := storage.MockArtifactStorageProvider{}
 				artifactStorage.On(
-					"List", "/artifact/uri", "",
+					"List", context.TODO(), "/artifact/uri", "",
 				).Return(
 					nil, errors.New("storage error"),
 				)
 
 				artifactStorageFactory := storage.MockArtifactStorageFactoryProvider{}
 				artifactStorageFactory.On(
-					"GetStorage", "/artifact/uri",
+					"GetStorage", context.TODO(), "/artifact/uri",
 				).Return(&artifactStorage, nil)
 
 				runRepository := repositories.MockRunRepositoryProvider{}
diff --git a/pkg/api/mlflow/service/artifact/storage/local_test.go b/pkg/api/mlflow/service/artifact/storage/local_test.go
index 271c042..b1e9ecd 100644
--- a/pkg/api/mlflow/service/artifact/storage/local_test.go
+++ b/pkg/api/mlflow/service/artifact/storage/local_test.go
@@ -1,6 +1,7 @@
 package storage
 
 import (
+	"context"
 	"io/fs"
 	"os"
 	"path/filepath"
@@ -15,6 +16,7 @@ func TestGetArtifact_Ok(t *testing.T) {
 	fileName := "file.txt"
 	fileContent := "artifact content"
 
+	// #nosec G304
 	f, err := os.Create(filepath.Join(runArtifactRoot, fileName))
 	assert.Nil(t, err)
 	_, err = f.Write([]byte(fileContent))
@@ -24,7 +26,7 @@ func TestGetArtifact_Ok(t *testing.T) {
 	storage, err := NewLocal(nil)
 	assert.Nil(t, err)
 
-	file, err := storage.Get(runArtifactRoot, fileName)
+	file, err := storage.Get(context.Background(), runArtifactRoot, fileName)
 	assert.Nil(t, err)
 	defer file.Close()
 
@@ -48,7 +50,7 @@ func TestGetArtifact_Error(t *testing.T) {
 	storage, err := NewLocal(nil)
 	assert.Nil(t, err)
 
-	file, err := storage.Get(runArtifactRoot, "non-existent-file")
+	file, err := storage.Get(context.Background(), runArtifactRoot, "non-existent-file")
 	assert.NotNil(t, err)
 	assert.Nil(t, file)
 
@@ -57,13 +59,13 @@ func TestGetArtifact_Error(t *testing.T) {
 	assert.NotNil(t, err)
 
 	// test subdir
-	subdirFile, err := storage.Get(runArtifactRoot, subdir)
+	subdirFile, err := storage.Get(context.Background(), runArtifactRoot, subdir)
 	assert.Nil(t, subdirFile)
 	assert.NotNil(t, err)
 }
 
 func TestLocal_ListArtifacts_Ok(t *testing.T) {
-	testData := []struct {
+	tests := []struct {
 		name   string
 		prefix string
 	}{
@@ -77,7 +79,7 @@ func TestLocal_ListArtifacts_Ok(t *testing.T) {
 		},
 	}
 
-	for _, tt := range testData {
+	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			runArtifactDir := t.TempDir()
 			runArtifactURI := tt.prefix + runArtifactDir
@@ -95,7 +97,7 @@ func TestLocal_ListArtifacts_Ok(t *testing.T) {
 			assert.Nil(t, err)
 
 			// 3. list artifacts for root dir.
-			rootDirResp, err := storage.List(runArtifactURI, "")
+			rootDirResp, err := storage.List(context.Background(), runArtifactURI, "")
 			assert.Equal(t, 2, len(rootDirResp))
 			assert.Equal(t, []ArtifactObject{
 				{
@@ -112,7 +114,7 @@ func TestLocal_ListArtifacts_Ok(t *testing.T) {
 			assert.Nil(t, err)
 
 			// 4. list artifacts for sub dir.
-			subDirResp, err := storage.List(runArtifactURI, "artifact.dir")
+			subDirResp, err := storage.List(context.Background(), runArtifactURI, "artifact.dir")
 			assert.Equal(t, 1, len(subDirResp))
 			assert.Equal(t, ArtifactObject{
 				Path:  "artifact.dir/artifact.file2",
@@ -122,7 +124,7 @@ func TestLocal_ListArtifacts_Ok(t *testing.T) {
 			assert.Nil(t, err)
 
 			// 5. list artifacts for non-existing dir.
-			nonExistingDirResp, err := storage.List(runArtifactURI, "non-existing-dir")
+			nonExistingDirResp, err := storage.List(context.Background(), runArtifactURI, "non-existing-dir")
 			assert.Equal(t, 0, len(nonExistingDirResp))
 			assert.Nil(t, err)
 		})
diff --git a/pkg/api/mlflow/service/artifact/validators_test.go b/pkg/api/mlflow/service/artifact/validators_test.go
index 032e28b..e9598db 100644
--- a/pkg/api/mlflow/service/artifact/validators_test.go
+++ b/pkg/api/mlflow/service/artifact/validators_test.go
@@ -12,60 +12,60 @@ import (
 func TestValidateListArtifactsRequest_Ok(t *testing.T) {
 	tests := []struct {
 		name    string
-		request request.ListArtifactsRequest
+		request *request.ListArtifactsRequest
 	}{
 		{
 			name: "NotEmptyPathCase1",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/..asd../",
 			},
 		},
 		{
 			name: "NotEmptyPathCase2",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./foo",
 			},
 		},
 		{
 			name: "NotEmptyPathCase3",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./foo/",
 			},
 		},
 		{
 			name: "NotEmptyPathCase4",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  ".foo",
 			},
 		},
 		{
 			name: "NotEmptyPathCase5",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo.bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase6",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo..bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase7",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo../bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase8",
-			request: request.ListArtifactsRequest{
+			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/..bar",
 			},
@@ -74,7 +74,7 @@ func TestValidateListArtifactsRequest_Ok(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			assert.Nil(t, ValidateListArtifactsRequest(&tt.request))
+			assert.Nil(t, ValidateListArtifactsRequest(tt.request))
 		})
 	}
 }
@@ -143,81 +143,81 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 func TestValidateGetArtifactRequest_Ok(t *testing.T) {
 	tests := []struct {
 		name    string
-		request request.GetArtifactRequest
+		request *request.GetArtifactRequest
 	}{
 		{
 			name: "NotEmptyPathCase1",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/..asd../",
 			},
 		},
 		{
 			name: "NotEmptyPathCase2",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./foo",
 			},
 		},
 		{
 			name: "NotEmptyPathCase3",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./foo/",
 			},
 		},
 		{
 			name: "NotEmptyPathCase4",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  ".foo",
 			},
 		},
 		{
 			name: "NotEmptyPathCase5",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo.bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase6",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo..bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase7",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo../bar",
 			},
 		},
 		{
 			name: "NotEmptyPathCase8",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/..bar",
 			},
 		},
 		{
 			name: "RunUUIDAndNotEmptyPath",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunUUID: "6766d949-07e8-4fff-b3d2-b5f820007cbe",
 				Path:    "foo/..bar",
 			},
 		},
 		{
 			name: "FilenameNoSlash",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo.txt",
 			},
 		},
 		{
 			name: "Dirname/Filename",
-			request: request.GetArtifactRequest{
+			request: &request.GetArtifactRequest{
 				RunID: "directory/run_id",
 				Path:  "foo.txt",
 			},
@@ -226,7 +226,7 @@ func TestValidateGetArtifactRequest_Ok(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			assert.Nil(t, ValidateGetArtifactRequest(&tt.request))
+			assert.Nil(t, ValidateGetArtifactRequest(tt.request))
 		})
 	}
 }
diff --git a/tests/integration/golang/helpers/s3.go b/tests/integration/golang/helpers/s3.go
index 5cc5f85..ca9d09e 100644
--- a/tests/integration/golang/helpers/s3.go
+++ b/tests/integration/golang/helpers/s3.go
@@ -11,7 +11,7 @@ import (
 
 // NewS3Client creates new instance of S3 client.
 func NewS3Client(endpoint string) (*s3.Client, error) {
-	cfg, err := awsConfig.LoadDefaultConfig(context.TODO(), awsConfig.WithEndpointResolverWithOptions(
+	cfg, err := awsConfig.LoadDefaultConfig(context.Background(), awsConfig.WithEndpointResolverWithOptions(
 		aws.EndpointResolverWithOptionsFunc(
 			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
 				if service == s3.ServiceID {
diff --git a/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go b/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go
index 0b9d8ab..d5c7a29 100644
--- a/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go
+++ b/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go
@@ -167,7 +167,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 	}{
 		{
 			name:    "EmptyOrIncorrectRunIDOrRunUUID",
-			error:   api.NewInvalidParameterValueError(`Missing value for required parameter 'run_id'`),
+			error:   api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'"),
 			request: &request.GetArtifactRequest{},
 		},
 		{
diff --git a/tests/integration/golang/mlflow/artifact/list_local_test.go b/tests/integration/golang/mlflow/artifact/list_local_test.go
index a8d1854..1aef8ee 100644
--- a/tests/integration/golang/mlflow/artifact/list_local_test.go
+++ b/tests/integration/golang/mlflow/artifact/list_local_test.go
@@ -55,7 +55,7 @@ func (s *ListArtifactLocalTestSuite) Test_Ok() {
 		assert.Nil(s.T(), s.experimentFixtures.UnloadFixtures())
 	}()
 
-	testData := []struct {
+	tests := []struct {
 		name   string
 		prefix string
 	}{
@@ -69,7 +69,7 @@ func (s *ListArtifactLocalTestSuite) Test_Ok() {
 		},
 	}
 
-	for _, tt := range testData {
+	for _, tt := range tests {
 		s.T().Run(tt.name, func(t *testing.T) {
 			// 1. create test experiment.
 			experimentArtifactDir := t.TempDir()
@@ -191,14 +191,14 @@ func (s *ListArtifactLocalTestSuite) Test_Ok() {
 }
 
 func (s *ListArtifactLocalTestSuite) Test_Error() {
-	testData := []struct {
+	tests := []struct {
 		name    string
 		error   *api.ErrorResponse
 		request *request.ListArtifactsRequest
 	}{
 		{
 			name:    "EmptyOrIncorrectRunIDOrRunUUID",
-			error:   api.NewInvalidParameterValueError(`Missing value for required parameter 'run_id'`),
+			error:   api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'"),
 			request: &request.ListArtifactsRequest{},
 		},
 		{
@@ -243,7 +243,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 	}
 
-	for _, tt := range testData {
+	for _, tt := range tests {
 		s.T().Run(tt.name, func(t *testing.T) {
 			query, err := urlquery.Marshal(tt.request)
 			assert.Nil(s.T(), err)
diff --git a/tests/integration/golang/mlflow/artifact/list_s3_test.go b/tests/integration/golang/mlflow/artifact/list_s3_test.go
index 8f3391b..44496ef 100644
--- a/tests/integration/golang/mlflow/artifact/list_s3_test.go
+++ b/tests/integration/golang/mlflow/artifact/list_s3_test.go
@@ -59,7 +59,7 @@ func (s *ListArtifactS3TestSuite) Test_Ok() {
 		assert.Nil(s.T(), s.experimentFixtures.UnloadFixtures())
 	}()
 
-	testData := []struct {
+	tests := []struct {
 		name   string
 		bucket string
 	}{
@@ -73,7 +73,7 @@ func (s *ListArtifactS3TestSuite) Test_Ok() {
 		},
 	}
 
-	for _, tt := range testData {
+	for _, tt := range tests {
 		s.T().Run(tt.name, func(t *testing.T) {
 			// 1. create test experiment.
 			experiment, err := s.experimentFixtures.CreateExperiment(context.Background(), &models.Experiment{
@@ -112,13 +112,13 @@ func (s *ListArtifactS3TestSuite) Test_Ok() {
 			// 3. upload artifact objects to S3.
 			_, err = s.s3Client.PutObject(context.Background(), &s3.PutObjectInput{
 				Key:    aws.String(fmt.Sprintf("1/%s/artifacts/artifact.file1", runID)),
-				Body:   strings.NewReader(`contentX`),
+				Body:   strings.NewReader("contentX"),
 				Bucket: aws.String(tt.bucket),
 			})
 			assert.Nil(s.T(), err)
 			_, err = s.s3Client.PutObject(context.Background(), &s3.PutObjectInput{
 				Key:    aws.String(fmt.Sprintf("1/%s/artifacts/artifact.dir/artifact.file2", runID)),
-				Body:   strings.NewReader(`contentXX`),
+				Body:   strings.NewReader("contentXX"),
 				Bucket: aws.String(tt.bucket),
 			})
 			assert.Nil(s.T(), err)
@@ -197,14 +197,14 @@ func (s *ListArtifactS3TestSuite) Test_Ok() {
 }
 
 func (s *ListArtifactS3TestSuite) Test_Error() {
-	testData := []struct {
+	tests := []struct {
 		name    string
 		error   *api.ErrorResponse
 		request *request.ListArtifactsRequest
 	}{
 		{
 			name:    "EmptyOrIncorrectRunIDOrRunUUID",
-			error:   api.NewInvalidParameterValueError(`Missing value for required parameter 'run_id'`),
+			error:   api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'"),
 			request: &request.ListArtifactsRequest{},
 		},
 		{
@@ -249,7 +249,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 	}
 
-	for _, tt := range testData {
+	for _, tt := range tests {
 		s.T().Run(tt.name, func(t *testing.T) {
 			query, err := urlquery.Marshal(tt.request)
 			assert.Nil(s.T(), err)