Repository: G-Research/fasttrackml
Pull request #763 (2023-12-20 13:14:48)

Upgrade MLFlow integration tests to 2.9.2
This PR upgrades our MLFlow integration tests to 2.9.2, enables a few additional tests (artifacts and path validation), disables the SQLite memory backend, and makes a few changes to our code for increased compatibility with MLFlow:
- the `file://` prefix gets added to the default artifact root when a local path is provided
- a distinction is made between generic SQL errors and a run not being found when trying to delete a run tag
- the error message returned for an invalid path has been changed to "Invalid path" (and the validation logic has been DRYed up)
- the error message when an inexistent run is provided has been changed to "Run '_id_' not found"
- the error message when an inexistent tag is provided to `DeleteRunTag` has been changed to "No tag with name: _key_"
- the validator for `LogBatch` is more exhaustive

Our own tests have also been updated to check for these changes.


Patch:
diff --git a/pkg/api/mlflow/config/config.go b/pkg/api/mlflow/config/config.go
index 7578fa8..31279aa 100644
--- a/pkg/api/mlflow/config/config.go
+++ b/pkg/api/mlflow/config/config.go
@@ -89,7 +89,7 @@ func (c *ServiceConfig) normalizeConfiguration() error {
 		if err != nil {
 			return eris.Wrapf(err, "error getting absolute path for 'default-artifact-root': %s", c.DefaultArtifactRoot)
 		}
-		c.DefaultArtifactRoot = absoluteArtifactRoot
+		c.DefaultArtifactRoot = "file://" + absoluteArtifactRoot
 	}
 	return nil
 }
diff --git a/pkg/api/mlflow/dao/repositories/tag.go b/pkg/api/mlflow/dao/repositories/tag.go
index 112b420..1e21280 100644
--- a/pkg/api/mlflow/dao/repositories/tag.go
+++ b/pkg/api/mlflow/dao/repositories/tag.go
@@ -67,6 +67,9 @@ func (r TagRepository) CreateRunTagWithTransaction(
 func (r TagRepository) GetByRunIDAndKey(ctx context.Context, runID, key string) (*models.Tag, error) {
 	tag := models.Tag{RunID: runID, Key: key}
 	if err := r.db.WithContext(ctx).First(&tag).Error; err != nil {
+		if eris.Is(err, gorm.ErrRecordNotFound) {
+			return nil, nil
+		}
 		return nil, eris.Wrapf(err, "error getting tag by run id: %s and tag key: %s", runID, key)
 	}
 	return &tag, nil
diff --git a/pkg/api/mlflow/service/artifact/validators.go b/pkg/api/mlflow/service/artifact/validators.go
index 12764c3..1955946 100644
--- a/pkg/api/mlflow/service/artifact/validators.go
+++ b/pkg/api/mlflow/service/artifact/validators.go
@@ -16,21 +16,7 @@ func ValidateListArtifactsRequest(req *request.ListArtifactsRequest) error {
 		return api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'")
 	}
 
-	parsedUrl, err := url.Parse(req.Path)
-	if err != nil {
-		return api.NewInvalidParameterValueError("error parsing 'path' parameter")
-	}
-	if parsedUrl.Scheme != "" || parsedUrl.Host != "" || parsedUrl.RawQuery != "" ||
-		parsedUrl.RawFragment != "" || parsedUrl.User != nil {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
-	}
-	if filepath.IsAbs(parsedUrl.Path) {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
-	}
-	if slices.Contains(strings.Split(parsedUrl.Path, "/"), "..") {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
-	}
-	return nil
+	return validatePath(req.Path)
 }
 
 // ValidateGetArtifactRequest validates `GET /artifacts/get` request.
@@ -39,19 +25,21 @@ func ValidateGetArtifactRequest(req *request.GetArtifactRequest) error {
 		return api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'")
 	}
 
-	parsedUrl, err := url.Parse(req.Path)
-	if err != nil {
-		return api.NewInvalidParameterValueError("error parsing 'path' parameter")
-	}
-	if parsedUrl.Scheme != "" || parsedUrl.Host != "" || parsedUrl.RawQuery != "" ||
-		parsedUrl.RawFragment != "" || parsedUrl.User != nil {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
-	}
-	if filepath.IsAbs(parsedUrl.Path) {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
-	}
-	if slices.Contains(strings.Split(parsedUrl.Path, "/"), "..") {
-		return api.NewInvalidParameterValueError("provided 'path' parameter is invalid")
+	return validatePath(req.Path)
+}
+
+// validatePath validates path parameter.
+func validatePath(path string) error {
+	parsedUrl, err := url.Parse(path)
+	if err != nil ||
+		parsedUrl.Scheme != "" ||
+		parsedUrl.Host != "" ||
+		parsedUrl.RawQuery != "" ||
+		parsedUrl.RawFragment != "" ||
+		parsedUrl.User != nil ||
+		filepath.IsAbs(parsedUrl.Path) ||
+		slices.Contains(strings.Split(parsedUrl.Path, "/"), "..") {
+		return api.NewInvalidParameterValueError("Invalid path")
 	}
 	return nil
 }
diff --git a/pkg/api/mlflow/service/run/service.go b/pkg/api/mlflow/service/run/service.go
index 9a90133..365b419 100644
--- a/pkg/api/mlflow/service/run/service.go
+++ b/pkg/api/mlflow/service/run/service.go
@@ -518,7 +518,7 @@ func (s Service) LogParam(
 		return api.NewInternalError("Unable to find run '%s': %s", req.RunID, err)
 	}
 	if run == nil {
-		return api.NewResourceDoesNotExistError("Unable to find active run '%s'", req.RunID)
+		return api.NewResourceDoesNotExistError("Run '%s' not found", req.RunID)
 	}
 
 	param := convertors.ConvertLogParamRequestToDBModel(run.ID, req)
@@ -548,7 +548,7 @@ func (s Service) SetRunTag(
 		return api.NewInternalError("Unable to find run '%s': %s", req.RunID, err)
 	}
 	if run == nil {
-		return api.NewResourceDoesNotExistError("Unable to find active run '%s'", req.RunID)
+		return api.NewResourceDoesNotExistError("Run '%s' not found", req.RunID)
 	}
 
 	tag := convertors.ConvertSetRunTagRequestToDBModel(run.ID, req)
@@ -574,12 +574,15 @@ func (s Service) DeleteRunTag(
 		return api.NewInternalError("Unable to find run '%s': %s", req.RunID, err)
 	}
 	if run == nil {
-		return api.NewResourceDoesNotExistError("Unable to find active run '%s'", req.RunID)
+		return api.NewResourceDoesNotExistError("Run '%s' not found", req.RunID)
 	}
 
 	tag, err := s.tagRepository.GetByRunIDAndKey(ctx, run.ID, req.Key)
 	if err != nil {
-		return api.NewResourceDoesNotExistError("Unable to find tag '%s' for run '%s': %s", req.Key, req.RunID, err)
+		return api.NewInternalError("Unable to find tag '%s' for run '%s': %s", req.Key, req.RunID, err)
+	}
+	if tag == nil {
+		return api.NewResourceDoesNotExistError("No tag with name: %s", req.Key)
 	}
 
 	if err := s.tagRepository.Delete(ctx, tag); err != nil {
@@ -605,7 +608,7 @@ func (s Service) LogBatch(
 		return api.NewInternalError("Unable to find run '%s': %s", req.RunID, err)
 	}
 	if run == nil {
-		return api.NewResourceDoesNotExistError("Unable to find active run '%s'", req.RunID)
+		return api.NewResourceDoesNotExistError("Run '%s' not found", req.RunID)
 	}
 
 	metrics, params, tags, err := convertors.ConvertLogBatchRequestToDBModel(run.ID, req)
diff --git a/pkg/api/mlflow/service/run/validators.go b/pkg/api/mlflow/service/run/validators.go
index fbc4667..33d8414 100644
--- a/pkg/api/mlflow/service/run/validators.go
+++ b/pkg/api/mlflow/service/run/validators.go
@@ -104,6 +104,22 @@ func ValidateLogBatchRequest(req *request.LogBatchRequest) error {
 	if req.RunID == "" {
 		return api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'")
 	}
+	for _, metric := range req.Metrics {
+		if metric.Key == "" || metric.Timestamp == 0 {
+			return api.NewInvalidParameterValueError("Invalid value for parameter 'metrics' supplied")
+		}
+	}
+	for _, param := range req.Params {
+		if param.Key == "" {
+			return api.NewInvalidParameterValueError("Invalid value for parameter 'params' supplied")
+		}
+	}
+	for _, tag := range req.Tags {
+		if tag.Key == "" {
+			return api.NewInvalidParameterValueError("Invalid value for parameter 'tags' supplied")
+		}
+	}
+
 	return nil
 }
 


Test patch:
diff --git a/pkg/api/mlflow/config/config_test.go b/pkg/api/mlflow/config/config_test.go
index ec0ab6d..2ff31d2 100644
--- a/pkg/api/mlflow/config/config_test.go
+++ b/pkg/api/mlflow/config/config_test.go
@@ -33,7 +33,7 @@ func TestServiceConfig_Validate_Ok(t *testing.T) {
 				DefaultArtifactRoot: (func() string {
 					path, err := filepath.Abs("path1/path2/path3")
 					require.Nil(t, err)
-					return path
+					return "file://" + path
 				})(),
 			},
 		},
@@ -43,7 +43,7 @@ func TestServiceConfig_Validate_Ok(t *testing.T) {
 				DefaultArtifactRoot: "file:///path1/path2/path3",
 			},
 			expectedConfig: &ServiceConfig{
-				DefaultArtifactRoot: "/path1/path2/path3",
+				DefaultArtifactRoot: "file:///path1/path2/path3",
 			},
 		},
 		{
@@ -52,7 +52,7 @@ func TestServiceConfig_Validate_Ok(t *testing.T) {
 				DefaultArtifactRoot: "/path1/path2/path3",
 			},
 			expectedConfig: &ServiceConfig{
-				DefaultArtifactRoot: "/path1/path2/path3",
+				DefaultArtifactRoot: "file:///path1/path2/path3",
 			},
 		},
 		{
@@ -64,7 +64,7 @@ func TestServiceConfig_Validate_Ok(t *testing.T) {
 				DefaultArtifactRoot: (func() string {
 					path, err := filepath.Abs("path1/path2/path3")
 					require.Nil(t, err)
-					return path
+					return "file://" + path
 				})(),
 			},
 		},
diff --git a/pkg/api/mlflow/service/artifact/service_test.go b/pkg/api/mlflow/service/artifact/service_test.go
index ca5296a..d6f0f59 100644
--- a/pkg/api/mlflow/service/artifact/service_test.go
+++ b/pkg/api/mlflow/service/artifact/service_test.go
@@ -102,7 +102,7 @@ func TestService_ListArtifacts_Error(t *testing.T) {
 		},
 		{
 			name:  "PathIsRelativeAndContains2Dots",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "id",
 				Path:  "../",
@@ -246,7 +246,7 @@ func TestService_GetArtifact_Error(t *testing.T) {
 		},
 		{
 			name:  "PathIsRelativeAndContains2Dots",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "id",
 				Path:  "../",
diff --git a/pkg/api/mlflow/service/artifact/validators_test.go b/pkg/api/mlflow/service/artifact/validators_test.go
index 90a5ae4..da6f479 100644
--- a/pkg/api/mlflow/service/artifact/validators_test.go
+++ b/pkg/api/mlflow/service/artifact/validators_test.go
@@ -93,7 +93,7 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -101,7 +101,7 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -109,7 +109,7 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -117,7 +117,7 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -125,7 +125,7 @@ func TestValidateListArtifactsRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
@@ -245,7 +245,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -253,7 +253,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -261,7 +261,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -269,7 +269,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -277,7 +277,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
@@ -285,7 +285,7 @@ func TestValidateGetArtifactRequest_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectLeadingSlash",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: &request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "/foo.bar",
diff --git a/pkg/api/mlflow/service/run/service_test.go b/pkg/api/mlflow/service/run/service_test.go
index c7ec671..e5ff4bc 100644
--- a/pkg/api/mlflow/service/run/service_test.go
+++ b/pkg/api/mlflow/service/run/service_test.go
@@ -624,7 +624,7 @@ func TestService_DeleteRunTag_Error(t *testing.T) {
 		},
 		{
 			name:  "ActiveRunNotFound",
-			error: api.NewResourceDoesNotExistError("Unable to find active run '1'"),
+			error: api.NewResourceDoesNotExistError("Run '1' not found"),
 			request: &request.DeleteRunTagRequest{
 				RunID: "1",
 			},
@@ -648,7 +648,42 @@ func TestService_DeleteRunTag_Error(t *testing.T) {
 		},
 		{
 			name:  "NotFoundTag",
-			error: api.NewResourceDoesNotExistError("Unable to find tag 'key' for run '1': database error"),
+			error: api.NewResourceDoesNotExistError("No tag with name: key"),
+			request: &request.DeleteRunTagRequest{
+				RunID: "1",
+				Key:   "key",
+			},
+			service: func() *Service {
+				runRepository := repositories.MockRunRepositoryProvider{}
+				runRepository.On(
+					"GetByNamespaceIDRunIDAndLifecycleStage",
+					context.TODO(),
+					uint(1),
+					"1",
+					models.LifecycleStageActive,
+				).Return(&models.Run{
+					ID:             "1",
+					LifecycleStage: models.LifecycleStageActive,
+				}, nil)
+				tagRepository := repositories.MockTagRepositoryProvider{}
+				tagRepository.On(
+					"GetByRunIDAndKey",
+					context.TODO(),
+					"1",
+					"key",
+				).Return(nil, nil)
+				return NewService(
+					&tagRepository,
+					&runRepository,
+					&repositories.MockParamRepositoryProvider{},
+					&repositories.MockMetricRepositoryProvider{},
+					&repositories.MockExperimentRepositoryProvider{},
+				)
+			},
+		},
+		{
+			name:  "NotFoundTagDatabaseError",
+			error: api.NewInternalError("Unable to find tag 'key' for run '1': database error"),
 			request: &request.DeleteRunTagRequest{
 				RunID: "1",
 				Key:   "key",
@@ -1031,7 +1066,7 @@ func TestService_LogBatch_Error(t *testing.T) {
 		},
 		{
 			name:  "RunNotFoundDatabaseNotFoundError",
-			error: api.NewResourceDoesNotExistError(`Unable to find active run '1'`),
+			error: api.NewResourceDoesNotExistError("Run '1' not found"),
 			request: &request.LogBatchRequest{
 				RunID: "1",
 			},
@@ -1055,7 +1090,7 @@ func TestService_LogBatch_Error(t *testing.T) {
 		},
 		{
 			name:  "NoActiveRunFound",
-			error: api.NewResourceDoesNotExistError(`Unable to find active run '1'`),
+			error: api.NewResourceDoesNotExistError("Run '1' not found"),
 			request: &request.LogBatchRequest{
 				RunID: "1",
 			},
@@ -1079,13 +1114,14 @@ func TestService_LogBatch_Error(t *testing.T) {
 		},
 		{
 			name:  "IncorrectMetricValue",
-			error: api.NewInvalidParameterValueError(`invalid metric value 'incorrect_value'`),
+			error: api.NewInvalidParameterValueError("invalid metric value 'incorrect_value'"),
 			request: &request.LogBatchRequest{
 				RunID: "1",
 				Metrics: []request.MetricPartialRequest{
 					{
-						Key:   "key",
-						Value: "incorrect_value",
+						Key:       "key",
+						Value:     "incorrect_value",
+						Timestamp: 1234567890,
 					},
 				},
 			},
@@ -1706,7 +1742,7 @@ func TestService_LogParam_Error(t *testing.T) {
 		},
 		{
 			name:  "NoActiveRunFound",
-			error: api.NewResourceDoesNotExistError(`Unable to find active run '1'`),
+			error: api.NewResourceDoesNotExistError("Run '1' not found"),
 			request: &request.LogParamRequest{
 				RunID: "1",
 				Key:   "key",
diff --git a/pkg/api/mlflow/service/run/validators_test.go b/pkg/api/mlflow/service/run/validators_test.go
index ba2315c..c27c720 100644
--- a/pkg/api/mlflow/service/run/validators_test.go
+++ b/pkg/api/mlflow/service/run/validators_test.go
@@ -128,7 +128,7 @@ func TestValidateLogMetricRequest_Ok(t *testing.T) {
 		RunID:     "id",
 		RunUUID:   "uuid",
 		Key:       "key",
-		Timestamp: 123,
+		Timestamp: 123456789,
 	})
 	require.Nil(t, err)
 }
@@ -289,6 +289,74 @@ func TestValidateLogBatchRequest_Error(t *testing.T) {
 			error:   api.NewInvalidParameterValueError("Missing value for required parameter 'run_id'"),
 			request: &request.LogBatchRequest{},
 		},
+		{
+			name:  "EmptyMetricsKey",
+			error: api.NewInvalidParameterValueError("Invalid value for parameter 'metrics' supplied"),
+			request: &request.LogBatchRequest{
+				RunID: "id",
+				Metrics: []request.MetricPartialRequest{
+					{
+						Key:       "key1",
+						Timestamp: 123456789,
+						Value:     1.0,
+					},
+					{
+						Timestamp: 123456789,
+						Value:     1.0,
+					},
+				},
+			},
+		},
+		{
+			name:  "EmptyMetricsTimestamp",
+			error: api.NewInvalidParameterValueError("Invalid value for parameter 'metrics' supplied"),
+			request: &request.LogBatchRequest{
+				RunID: "id",
+				Metrics: []request.MetricPartialRequest{
+					{
+						Key:       "key1",
+						Timestamp: 123456789,
+						Value:     1.0,
+					},
+					{
+						Key:   "key2",
+						Value: 1.0,
+					},
+				},
+			},
+		},
+		{
+			name:  "EmptyParamsKey",
+			error: api.NewInvalidParameterValueError("Invalid value for parameter 'params' supplied"),
+			request: &request.LogBatchRequest{
+				RunID: "id",
+				Params: []request.ParamPartialRequest{
+					{
+						Key:   "key1",
+						Value: "value1",
+					},
+					{
+						Value: "value2",
+					},
+				},
+			},
+		},
+		{
+			name:  "EmptyTagsKey",
+			error: api.NewInvalidParameterValueError("Invalid value for parameter 'tags' supplied"),
+			request: &request.LogBatchRequest{
+				RunID: "id",
+				Tags: []request.TagPartialRequest{
+					{
+						Key:   "key1",
+						Value: "value1",
+					},
+					{
+						Value: "value2",
+					},
+				},
+			},
+		},
 	}
 
 	for _, tt := range testData {
diff --git a/tests/integration/golang/mlflow/artifact/get_artifact_gs_test.go b/tests/integration/golang/mlflow/artifact/get_artifact_gs_test.go
index 5126f5f..bae30a8 100644
--- a/tests/integration/golang/mlflow/artifact/get_artifact_gs_test.go
+++ b/tests/integration/golang/mlflow/artifact/get_artifact_gs_test.go
@@ -163,7 +163,7 @@ func (s *GetArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -171,7 +171,7 @@ func (s *GetArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -179,7 +179,7 @@ func (s *GetArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -187,7 +187,7 @@ func (s *GetArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -195,7 +195,7 @@ func (s *GetArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
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 a25ec5c..48398a9 100644
--- a/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go
+++ b/tests/integration/golang/mlflow/artifact/get_artifact_local_test.go
@@ -156,7 +156,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -164,7 +164,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -172,7 +172,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -180,7 +180,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -188,7 +188,7 @@ func (s *GetArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
diff --git a/tests/integration/golang/mlflow/artifact/get_artifact_s3_test.go b/tests/integration/golang/mlflow/artifact/get_artifact_s3_test.go
index d0f4286..9751113 100644
--- a/tests/integration/golang/mlflow/artifact/get_artifact_s3_test.go
+++ b/tests/integration/golang/mlflow/artifact/get_artifact_s3_test.go
@@ -170,7 +170,7 @@ func (s *GetArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -178,7 +178,7 @@ func (s *GetArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -186,7 +186,7 @@ func (s *GetArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -194,7 +194,7 @@ func (s *GetArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -202,7 +202,7 @@ func (s *GetArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.GetArtifactRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
diff --git a/tests/integration/golang/mlflow/artifact/list_gs_test.go b/tests/integration/golang/mlflow/artifact/list_gs_test.go
index 845ff8a..3b4de3a 100644
--- a/tests/integration/golang/mlflow/artifact/list_gs_test.go
+++ b/tests/integration/golang/mlflow/artifact/list_gs_test.go
@@ -184,7 +184,7 @@ func (s *ListArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -192,7 +192,7 @@ func (s *ListArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -200,7 +200,7 @@ func (s *ListArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -208,7 +208,7 @@ func (s *ListArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -216,7 +216,7 @@ func (s *ListArtifactGSTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
diff --git a/tests/integration/golang/mlflow/artifact/list_local_test.go b/tests/integration/golang/mlflow/artifact/list_local_test.go
index 3ec42ee..aef92e1 100644
--- a/tests/integration/golang/mlflow/artifact/list_local_test.go
+++ b/tests/integration/golang/mlflow/artifact/list_local_test.go
@@ -175,7 +175,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -183,7 +183,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -191,7 +191,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -199,7 +199,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -207,7 +207,7 @@ func (s *ListArtifactLocalTestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
diff --git a/tests/integration/golang/mlflow/artifact/list_s3_test.go b/tests/integration/golang/mlflow/artifact/list_s3_test.go
index 2fa55fd..8d3799c 100644
--- a/tests/integration/golang/mlflow/artifact/list_s3_test.go
+++ b/tests/integration/golang/mlflow/artifact/list_s3_test.go
@@ -176,7 +176,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase1",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "..",
@@ -184,7 +184,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase2",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./..",
@@ -192,7 +192,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase3",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "./../",
@@ -200,7 +200,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase4",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "foo/../bar",
@@ -208,7 +208,7 @@ func (s *ListArtifactS3TestSuite) Test_Error() {
 		},
 		{
 			name:  "IncorrectPathProvidedCase5",
-			error: api.NewInvalidParameterValueError("provided 'path' parameter is invalid"),
+			error: api.NewInvalidParameterValueError("Invalid path"),
 			request: request.ListArtifactsRequest{
 				RunID: "run_id",
 				Path:  "/foo/../bar",
diff --git a/tests/integration/golang/mlflow/run/delete_tag_test.go b/tests/integration/golang/mlflow/run/delete_tag_test.go
index 83e86f2..dbf23a7 100644
--- a/tests/integration/golang/mlflow/run/delete_tag_test.go
+++ b/tests/integration/golang/mlflow/run/delete_tag_test.go
@@ -134,7 +134,7 @@ func (s *DeleteRunTagTestSuite) Test_Error() {
 			request: request.DeleteRunTagRequest{
 				RunID: "id",
 			},
-			error: api.NewResourceDoesNotExistError("Unable to find active run 'id'"),
+			error: api.NewResourceDoesNotExistError("Run 'id' not found"),
 		},
 		{
 			name: "NotFoundTag",
@@ -142,11 +142,7 @@ func (s *DeleteRunTagTestSuite) Test_Error() {
 				Key:   "not_found_tag",
 				RunID: run.ID,
 			},
-			error: api.NewResourceDoesNotExistError(
-				`Unable to find tag 'not_found_tag' for run '%s': error getting tag by run id: %s and `+
-					`tag key: not_found_tag: record not found`,
-				run.ID, run.ID,
-			),
+			error: api.NewResourceDoesNotExistError("No tag with name: not_found_tag"),
 		},
 	}
 	for _, tt := range tests {
diff --git a/tests/integration/golang/mlflow/run/set_tag_test.go b/tests/integration/golang/mlflow/run/set_tag_test.go
index b0bfe0e..03684d6 100644
--- a/tests/integration/golang/mlflow/run/set_tag_test.go
+++ b/tests/integration/golang/mlflow/run/set_tag_test.go
@@ -105,7 +105,7 @@ func (s *SetRunTagTestSuite) Test_Error() {
 				Key:   "key1",
 				RunID: "id",
 			},
-			error: api.NewResourceDoesNotExistError("Unable to find active run 'id'"),
+			error: api.NewResourceDoesNotExistError("Run 'id' not found"),
 		},
 	}
 	for _, tt := range tests {
diff --git a/tests/integration/python/config.json b/tests/integration/python/config.json
index c930d4d..1613cee 100644
--- a/tests/integration/python/config.json
+++ b/tests/integration/python/config.json
@@ -27,32 +27,32 @@
         },
         "mlflow": {
             "repository": "https://github.com/mlflow/mlflow.git",
-            "tag": "v2.2.2",
+            "tag": "v2.9.2",
             "patch": "mlflow.patch",
             "requirements": [
-                "alembic==1.12.1",
-                "blinker==1.6.3",
-                "certifi==2023.7.22",
-                "charset-normalizer==3.3.1",
+                "alembic==1.13.0",
+                "blinker==1.7.0",
+                "certifi==2023.11.17",
+                "charset-normalizer==3.3.2",
                 "click==8.1.7",
                 "cloudpickle==3.0.0",
                 "databricks-cli==0.18.0",
                 "entrypoints==0.4",
                 "Flask==3.0.0",
-                "greenlet==3.0.1",
-                "idna==3.4",
-                "importlib-metadata==6.8.0",
+                "greenlet==3.0.2",
+                "idna==3.6",
+                "importlib-metadata==7.0.0",
                 "iniconfig==2.0.0",
                 "itsdangerous==2.1.2",
                 "Jinja2==3.1.2",
-                "Mako==1.2.4",
+                "Mako==1.3.0",
                 "MarkupSafe==2.1.3",
-                "numpy==1.26.1",
+                "numpy==1.26.2",
                 "oauthlib==3.2.2",
                 "packaging==23.2",
-                "pandas==2.1.2",
+                "pandas==2.1.4",
                 "pluggy==1.3.0",
-                "protobuf==4.24.4",
+                "protobuf==4.25.1",
                 "PyJWT==2.8.0",
                 "pytest==7.4.3",
                 "python-dateutil==2.8.2",
@@ -60,12 +60,12 @@
                 "PyYAML==6.0.1",
                 "requests==2.31.0",
                 "six==1.16.0",
-                "SQLAlchemy==2.0.22",
+                "SQLAlchemy==2.0.23",
                 "sqlparse==0.4.4",
                 "tabulate==0.9.0",
-                "typing_extensions==4.8.0",
+                "typing_extensions==4.9.0",
                 "tzdata==2023.3",
-                "urllib3==2.0.7",
+                "urllib3==2.1.0",
                 "Werkzeug==3.0.1",
                 "zipp==3.17.0"
             ],
diff --git a/tests/integration/python/mlflow.patch b/tests/integration/python/mlflow.patch
index c04006e..df879da 100644
--- a/tests/integration/python/mlflow.patch
+++ b/tests/integration/python/mlflow.patch
@@ -1,14 +1,30 @@
+diff --git a/pytest.ini b/pytest.ini
+index 90cb8c3b8..4506a194f 100644
+--- a/pytest.ini
++++ b/pytest.ini
+@@ -4,4 +4,3 @@ filterwarnings =
+   # Prevent deprecated numpy type aliases from being used
+   error:^`np\.[a-z]+` is a deprecated alias for.+:DeprecationWarning:mlflow
+   error:^`np\.[a-z]+` is a deprecated alias for.+:DeprecationWarning:tests
+-timeout = 1200
 diff --git a/tests/tracking/integration_test_utils.py b/tests/tracking/integration_test_utils.py
-index 41ec85f..a99de1d 100644
+index f64ea82d1..461a63a2a 100644
 --- a/tests/tracking/integration_test_utils.py
 +++ b/tests/tracking/integration_test_utils.py
-@@ -52,14 +52,16 @@ def _init_server(backend_uri, root_artifact_uri):
+@@ -42,21 +42,16 @@ def _init_server(backend_uri, root_artifact_uri, extra_env=None, app="mlflow.ser
      server_port = get_safe_port()
-     process = Popen(
+     with Popen(
          [
 -            sys.executable,
--            "-c",
--            f'from mlflow.server import app; app.run("{LOCALHOST}", {server_port})',
+-            "-m",
+-            "flask",
+-            "--app",
+-            app,
+-            "run",
+-            "--host",
+-            LOCALHOST,
+-            "--port",
+-            str(server_port),
 +            "fml",
 +            "server",
          ],
@@ -18,41 +34,39 @@ index 41ec85f..a99de1d 100644
 -            ARTIFACT_ROOT_ENV_VAR: root_artifact_uri,
 +            "FML_LISTEN_ADDRESS": f"{LOCALHOST}:{server_port}",
 +            "FML_DATABASE_URI": backend_uri,
-+            "FML_ARTIFACT_ROOT": root_artifact_uri,
++            "FML_DEFAULT_ARTIFACT_ROOT": root_artifact_uri,
 +            "FML_LOG_LEVEL": "debug",
 +            "FML_DATABASE_RESET": str(backend_uri.startswith("postgres://")),
+             **(extra_env or {}),
          },
-     )
- 
+     ) as proc:
 diff --git a/tests/tracking/test_rest_tracking.py b/tests/tracking/test_rest_tracking.py
-index 45ee41e..3afdfcb 100644
+index 5f952dda4..7679a1d7c 100644
 --- a/tests/tracking/test_rest_tracking.py
 +++ b/tests/tracking/test_rest_tracking.py
-@@ -51,7 +51,7 @@ from tests.tracking.integration_test_utils import (
+@@ -60,7 +60,7 @@ from tests.tracking.integration_test_utils import (
  _logger = logging.getLogger(__name__)
  
  
 -@pytest.fixture(params=["file", "sqlalchemy"])
-+@pytest.fixture(params=["sqlite+memory", "sqlite+file", "sqlite+key", "postgres"])
++@pytest.fixture(params=["sqlite", "sqlcipher", "postgres"])
  def mlflow_client(request, tmp_path):
      """Provides an MLflow Tracking API client pointed at the local tracking server."""
      if request.param == "file":
-@@ -61,6 +61,14 @@ def mlflow_client(request, tmp_path):
+@@ -70,6 +70,12 @@ def mlflow_client(request, tmp_path):
          backend_uri = ("sqlite://" if sys.platform == "win32" else "sqlite:////") + path[
              len("file://") :
          ]
-+    elif request.param == "sqlite+memory":
-+        backend_uri = f"sqlite://{tmp_path.joinpath('sqlalchemy.db')}?mode=memory&cache=shared"
-+    elif request.param == "sqlite+file":
-+        backend_uri = f"sqlite://{tmp_path.joinpath('sqlalchemy.db')}?_journal_mode=WAL"
-+    elif request.param == "sqlite+key":
-+        backend_uri = f"sqlite://{tmp_path.joinpath('sqlalchemy.db')}?_key=passphrase&_journal_mode=WAL"
++    elif request.param == "sqlite":
++        backend_uri = f"sqlite://{tmp_path.joinpath('mlflow.db')}"
++    elif request.param == "sqlcipher":
++        backend_uri = f"sqlite://{tmp_path.joinpath('mlflow.db')}?_key=passphrase"
 +    elif request.param == "postgres":
 +        backend_uri = "postgres://postgres:postgres@postgres/postgres"
  
-     url, process = _init_server(backend_uri, root_artifact_uri=tmp_path.as_uri())
-     yield MlflowClient(url)
-@@ -140,7 +148,7 @@ def test_create_experiment_validation(mlflow_client):
+     with _init_server(backend_uri, root_artifact_uri=tmp_path.as_uri()) as url:
+         yield MlflowClient(url)
+@@ -146,7 +152,7 @@ def test_create_experiment_validation(mlflow_client):
          {
              "name": 123,
          },
@@ -61,7 +75,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request({}, "Missing value for required parameter 'name'")
      assert_bad_request(
-@@ -149,7 +157,7 @@ def test_create_experiment_validation(mlflow_client):
+@@ -155,7 +161,7 @@ def test_create_experiment_validation(mlflow_client):
              "artifact_location": 9.0,
              "tags": [{"key": "key", "value": "value"}],
          },
@@ -70,7 +84,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -157,7 +165,7 @@ def test_create_experiment_validation(mlflow_client):
+@@ -163,7 +169,7 @@ def test_create_experiment_validation(mlflow_client):
              "artifact_location": "my_location",
              "tags": "5",
          },
@@ -79,7 +93,7 @@ index 45ee41e..3afdfcb 100644
      )
  
  
-@@ -324,7 +332,7 @@ def test_log_metric_validation(mlflow_client):
+@@ -328,7 +334,7 @@ def test_log_metric_validation(mlflow_client):
              "timestamp": 59,
              "step": 26,
          },
@@ -88,7 +102,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -334,7 +342,7 @@ def test_log_metric_validation(mlflow_client):
+@@ -338,7 +344,7 @@ def test_log_metric_validation(mlflow_client):
              "timestamp": 59,
              "step": 26,
          },
@@ -97,7 +111,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -344,7 +352,7 @@ def test_log_metric_validation(mlflow_client):
+@@ -348,7 +354,7 @@ def test_log_metric_validation(mlflow_client):
              "timestamp": 59,
              "step": "foo",
          },
@@ -106,7 +120,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -354,7 +362,7 @@ def test_log_metric_validation(mlflow_client):
+@@ -358,7 +364,7 @@ def test_log_metric_validation(mlflow_client):
              "timestamp": "foo",
              "step": 41,
          },
@@ -115,7 +129,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -408,7 +416,7 @@ def test_log_param_validation(mlflow_client):
+@@ -412,7 +418,7 @@ def test_log_param_validation(mlflow_client):
              "key": "param",
              "value": 41,
          },
@@ -124,7 +138,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -416,7 +424,7 @@ def test_log_param_validation(mlflow_client):
+@@ -420,7 +426,7 @@ def test_log_param_validation(mlflow_client):
              "key": 31,
              "value": 41,
          },
@@ -133,7 +147,7 @@ index 45ee41e..3afdfcb 100644
      )
  
  
-@@ -478,7 +486,7 @@ def test_set_tag_validation(mlflow_client):
+@@ -482,7 +488,7 @@ def test_set_tag_validation(mlflow_client):
              "key": "tag",
              "value": 41,
          },
@@ -142,7 +156,7 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -486,7 +494,7 @@ def test_set_tag_validation(mlflow_client):
+@@ -490,7 +496,7 @@ def test_set_tag_validation(mlflow_client):
              "key": "param",
              "value": 41,
          },
@@ -151,31 +165,35 @@ index 45ee41e..3afdfcb 100644
      )
      assert_bad_request(
          {
-@@ -537,6 +545,7 @@ def test_validate_path_is_safe_bad(path):
-         validate_path_is_safe(path)
+@@ -533,17 +539,11 @@ def test_path_validation(mlflow_client):
+     assert_response(response)
  
+     response = requests.get(
+-        f"{mlflow_client.tracking_uri}/get-artifact",
++        f"{mlflow_client.tracking_uri}/ajax-api/2.0/mlflow/artifacts/get",
+         params={"run_id": run_id, "path": invalid_path},
+     )
+     assert_response(response)
  
-+@pytest.mark.skip
- def test_path_validation(mlflow_client):
-     experiment_id = mlflow_client.create_experiment("tags validation")
-     created_run = mlflow_client.create_run(experiment_id)
-@@ -618,12 +627,12 @@ def test_delete_tag(mlflow_client):
-     mlflow_client.delete_tag(run_id, "taggity")
-     run = mlflow_client.get_run(run_id)
-     assert "taggity" not in run.data.tags
--    with pytest.raises(MlflowException, match=r"Run .+ not found"):
-+    with pytest.raises(MlflowException, match="Unable to find active run 'fake_run_id'"):
-         mlflow_client.delete_tag("fake_run_id", "taggity")
--    with pytest.raises(MlflowException, match="No tag with name: fakeTag"):
-+    with pytest.raises(MlflowException, match="Unable to find tag 'fakeTag'"):
+-    response = requests.get(
+-        f"{mlflow_client.tracking_uri}//model-versions/get-artifact",
+-        params={"name": "model", "version": 1, "path": invalid_path},
+-    )
+-    assert_response(response)
+-
+ 
+ def test_set_experiment_tag(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("SetExperimentTagTest")
+@@ -602,7 +602,7 @@ def test_delete_tag(mlflow_client):
+     with pytest.raises(MlflowException, match="No tag with name: fakeTag"):
          mlflow_client.delete_tag(run_id, "fakeTag")
      mlflow_client.delete_run(run_id)
 -    with pytest.raises(MlflowException, match=f"The run {run_id} must be in"):
-+    with pytest.raises(MlflowException, match=f"Unable to find active run '{run_id}'"):
++    with pytest.raises(MlflowException, match=f"Run '{run_id}' not found"):
          mlflow_client.delete_tag(run_id, "taggity")
  
  
-@@ -670,10 +679,11 @@ def test_log_batch_validation(mlflow_client):
+@@ -649,13 +649,13 @@ def test_log_batch_validation(mlflow_client):
                  "run_id": run_id,
                  request_parameter: "foo",
              },
@@ -183,20 +201,24 @@ index 45ee41e..3afdfcb 100644
 +            f"Invalid value for parameter '{request_parameter}' supplied",
          )
  
+     ## Should 400 if missing timestamp
+     assert_bad_request(
+         {"run_id": run_id, "metrics": [{"key": "mae", "value": 2.5}]},
+-        "Invalid value [{'key': 'mae', 'value': 2.5}] for parameter 'metrics' supplied",
++        "Invalid value for parameter 'metrics' supplied",
+     )
+ 
+     ## Should 200 if timestamp provided but step is not
+@@ -668,7 +668,7 @@ def test_log_batch_validation(mlflow_client):
+     assert response.status_code == 200
  
-+@pytest.mark.skip
- @pytest.mark.allow_infer_pip_requirements_fallback
+ 
+-@pytest.mark.allow_infer_pip_requirements_fallback
++@pytest.mark.skip()
  def test_log_model(mlflow_client):
      experiment_id = mlflow_client.create_experiment("Log models")
-@@ -727,6 +737,7 @@ def test_set_terminated_status(mlflow_client):
-     assert mlflow_client.get_run(run_id).info.end_time <= get_current_time_millis()
- 
- 
-+@pytest.mark.skip
- def test_artifacts(mlflow_client, tmp_path):
-     experiment_id = mlflow_client.create_experiment("Art In Fact")
-     experiment_info = mlflow_client.get_experiment(experiment_id)
-@@ -780,7 +791,7 @@ def test_search_pagination(mlflow_client):
+     with TempDir(chdr=True):
+@@ -778,7 +778,7 @@ def test_search_pagination(mlflow_client):
  def test_search_validation(mlflow_client):
      experiment_id = mlflow_client.create_experiment("search_validation")
      with pytest.raises(
@@ -205,19 +227,131 @@ index 45ee41e..3afdfcb 100644
      ):
          mlflow_client.search_runs([experiment_id], max_results=123456789)
  
-@@ -1044,6 +1055,7 @@ def test_get_metric_history_bulk_calls_optimized_impl_when_expected(monkeypatch,
-         )
+@@ -1003,6 +1003,7 @@ def test_get_metric_history_bulk_respects_max_results(mlflow_client):
+     ]
  
  
 +@pytest.mark.skip
- def test_create_model_version_with_local_source(mlflow_client):
-     name = "mode"
+ def test_get_metric_history_bulk_calls_optimized_impl_when_expected(tmp_path):
+     from mlflow.server.handlers import get_metric_history_bulk_handler
+ 
+@@ -1045,6 +1046,7 @@ def test_get_metric_history_bulk_calls_optimized_impl_when_expected(tmp_path):
+         )
+ 
+ 
++@pytest.mark.skip()
+ def test_search_dataset_handler_rejects_invalid_requests(mlflow_client):
+     def assert_response(resp, message_part):
+         assert resp.status_code == 400
+@@ -1080,6 +1082,7 @@ def test_search_dataset_handler_rejects_invalid_requests(mlflow_client):
+     )
+ 
+ 
++@pytest.mark.skip()
+ def test_search_dataset_handler_returns_expected_results(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("log inputs test")
+     created_run = mlflow_client.create_run(experiment_id)
+@@ -1113,6 +1116,7 @@ def test_search_dataset_handler_returns_expected_results(mlflow_client):
+     assert response.json().get("dataset_summaries") == [expected]
+ 
+ 
++@pytest.mark.skip()
+ def test_create_model_version_with_path_source(mlflow_client):
+     name = "model"
      mlflow_client.create_registered_model(name)
-@@ -1124,6 +1136,7 @@ def test_create_model_version_with_local_source(mlflow_client):
-     assert "Invalid source" in resp["message"]
+@@ -1153,6 +1157,7 @@ def test_create_model_version_with_path_source(mlflow_client):
+     assert "To use a local path as a model version" in response.json()["message"]
  
  
-+@pytest.mark.skip
++@pytest.mark.skip()
+ def test_create_model_version_with_non_local_source(mlflow_client):
+     name = "model"
+     mlflow_client.create_registered_model(name)
+@@ -1313,6 +1318,7 @@ def test_create_model_version_with_non_local_source(mlflow_client):
+     assert "If supplying a source as an http, https," in response.json()["message"]
+ 
+ 
++@pytest.mark.skip()
+ def test_create_model_version_with_file_uri(mlflow_client):
+     name = "test"
+     mlflow_client.create_registered_model(name)
+@@ -1393,6 +1399,7 @@ def test_create_model_version_with_file_uri(mlflow_client):
+     assert "MLflow tracking server doesn't allow" in response.json()["message"]
+ 
+ 
++@pytest.mark.skip()
+ def test_create_model_version_with_file_uri_env_var(tmp_path):
+     backend_uri = tmp_path.joinpath("file").as_uri()
+     with _init_server(
+@@ -1417,6 +1424,7 @@ def test_create_model_version_with_file_uri_env_var(tmp_path):
+         assert response.status_code == 200
+ 
+ 
++@pytest.mark.skip()
  def test_logging_model_with_local_artifact_uri(mlflow_client):
      from sklearn.linear_model import LogisticRegression
  
+@@ -1427,6 +1435,7 @@ def test_logging_model_with_local_artifact_uri(mlflow_client):
+         mlflow.pyfunc.load_model("models:/rmn/1")
+ 
+ 
++@pytest.mark.skip()
+ def test_log_input(mlflow_client, tmp_path):
+     df = pd.DataFrame([[1, 2, 3], [1, 2, 3]], columns=["a", "b", "c"])
+     path = tmp_path / "temp.csv"
+@@ -1461,6 +1470,7 @@ def test_log_input(mlflow_client, tmp_path):
+     assert dataset_inputs[0].tags[1].value == "train"
+ 
+ 
++@pytest.mark.skip()
+ def test_log_inputs(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("log inputs test")
+     created_run = mlflow_client.create_run(experiment_id)
+@@ -1490,6 +1500,7 @@ def test_log_inputs(mlflow_client):
+     assert run.inputs.dataset_inputs[0].tags[0].value == "value1"
+ 
+ 
++@pytest.mark.skip()
+ def test_log_inputs_validation(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("log inputs validation")
+     created_run = mlflow_client.create_run(experiment_id)
+@@ -1537,6 +1548,7 @@ def test_update_run_name_without_changing_status(mlflow_client):
+     assert updated_run_info.status == "FINISHED"
+ 
+ 
++@pytest.mark.skip()
+ def test_create_promptlab_run_handler_rejects_invalid_requests(mlflow_client):
+     def assert_response(resp, message_part):
+         assert resp.status_code == 400
+@@ -1626,6 +1638,7 @@ def test_create_promptlab_run_handler_rejects_invalid_requests(mlflow_client):
+     )
+ 
+ 
++@pytest.mark.skip()
+ def test_create_promptlab_run_handler_returns_expected_results(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("log inputs test")
+ 
+@@ -1669,6 +1682,7 @@ def test_create_promptlab_run_handler_returns_expected_results(mlflow_client):
+     ]["tags"]
+ 
+ 
++@pytest.mark.skip()
+ def test_gateway_proxy_handler_rejects_invalid_requests(mlflow_client):
+     def assert_response(resp, message_part):
+         assert resp.status_code == 400
+@@ -1693,6 +1707,7 @@ def test_gateway_proxy_handler_rejects_invalid_requests(mlflow_client):
+         )
+ 
+ 
++@pytest.mark.skip()
+ def test_upload_artifact_handler_rejects_invalid_requests(mlflow_client):
+     def assert_response(resp, message_part):
+         assert resp.status_code == 400
+@@ -1738,6 +1753,7 @@ def test_upload_artifact_handler_rejects_invalid_requests(mlflow_client):
+     assert_response(response, "Request must specify data.")
+ 
+ 
++@pytest.mark.skip()
+ def test_upload_artifact_handler(mlflow_client):
+     experiment_id = mlflow_client.create_experiment("upload_artifacts_test")
+     created_run = mlflow_client.create_run(experiment_id)