2023-04-17 10:21:21 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
2024-05-14 10:30:48 +00:00
|
|
|
"encoding/json"
|
|
|
|
"reflect"
|
2023-04-17 10:21:21 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-09-04 09:55:01 +00:00
|
|
|
"github.com/databricks/cli/bundle/config/paths"
|
2023-05-16 16:35:39 +00:00
|
|
|
"github.com/databricks/cli/bundle/config/resources"
|
2023-04-17 10:21:21 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestVerifyUniqueResourceIdentifiers(t *testing.T) {
|
|
|
|
r := Resources{
|
|
|
|
Jobs: map[string]*resources.Job{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Models: map[string]*resources.MlflowModel{
|
|
|
|
"bar": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "bar.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Experiments: map[string]*resources.MlflowExperiment{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo2.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
_, err := r.VerifyUniqueResourceIdentifiers()
|
|
|
|
assert.ErrorContains(t, err, "multiple resources named foo (job at foo.yml, mlflow_experiment at foo2.yml)")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVerifySafeMerge(t *testing.T) {
|
|
|
|
r := Resources{
|
|
|
|
Jobs: map[string]*resources.Job{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Models: map[string]*resources.MlflowModel{
|
|
|
|
"bar": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "bar.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
other := Resources{
|
|
|
|
Pipelines: map[string]*resources.Pipeline{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo2.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := r.VerifySafeMerge(&other)
|
|
|
|
assert.ErrorContains(t, err, "multiple resources named foo (job at foo.yml, pipeline at foo2.yml)")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVerifySafeMergeForSameResourceType(t *testing.T) {
|
|
|
|
r := Resources{
|
|
|
|
Jobs: map[string]*resources.Job{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Models: map[string]*resources.MlflowModel{
|
|
|
|
"bar": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "bar.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
other := Resources{
|
|
|
|
Jobs: map[string]*resources.Job{
|
|
|
|
"foo": {
|
2023-09-04 09:55:01 +00:00
|
|
|
Paths: paths.Paths{
|
2023-04-17 10:21:21 +00:00
|
|
|
ConfigFilePath: "foo2.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := r.VerifySafeMerge(&other)
|
|
|
|
assert.ErrorContains(t, err, "multiple resources named foo (job at foo.yml, job at foo2.yml)")
|
|
|
|
}
|
2023-10-16 15:32:49 +00:00
|
|
|
|
|
|
|
func TestVerifySafeMergeForRegisteredModels(t *testing.T) {
|
|
|
|
r := Resources{
|
|
|
|
Jobs: map[string]*resources.Job{
|
|
|
|
"foo": {
|
|
|
|
Paths: paths.Paths{
|
|
|
|
ConfigFilePath: "foo.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
RegisteredModels: map[string]*resources.RegisteredModel{
|
|
|
|
"bar": {
|
|
|
|
Paths: paths.Paths{
|
|
|
|
ConfigFilePath: "bar.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
other := Resources{
|
|
|
|
RegisteredModels: map[string]*resources.RegisteredModel{
|
|
|
|
"bar": {
|
|
|
|
Paths: paths.Paths{
|
|
|
|
ConfigFilePath: "bar2.yml",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := r.VerifySafeMerge(&other)
|
|
|
|
assert.ErrorContains(t, err, "multiple resources named bar (registered_model at bar.yml, registered_model at bar2.yml)")
|
|
|
|
}
|
2024-05-14 10:30:48 +00:00
|
|
|
|
|
|
|
// This test ensures that all resources have a custom marshaller and unmarshaller.
|
|
|
|
// This is required because DABs resources map to Databricks APIs, and they do so
|
|
|
|
// by embedding the corresponding Go SDK structs.
|
|
|
|
//
|
|
|
|
// Go SDK structs often implement custom marshalling and unmarshalling methods (based on the API specifics).
|
|
|
|
// If the Go SDK struct implements custom marshalling and unmarshalling and we do not
|
|
|
|
// for the resources at the top level, marshalling and unmarshalling operations will panic.
|
|
|
|
// Thus we will be overly cautious and ensure that all resources need a custom marshaller and unmarshaller.
|
|
|
|
//
|
|
|
|
// Why do we not assert this using an interface to assert MarshalJSON and UnmarshalJSON
|
|
|
|
// are implemented at the top level?
|
|
|
|
// If a method is implemented for an embedded struct, the top level struct will
|
|
|
|
// also have that method and satisfy the interface. This is why we cannot assert
|
|
|
|
// that the methods are implemented at the top level using an interface.
|
|
|
|
//
|
|
|
|
// Why don't we use reflection to assert that the methods are implemented at the
|
|
|
|
// top level?
|
|
|
|
// Same problem as above, the golang reflection package does not seem to provide
|
|
|
|
// a way to directly assert that MarshalJSON and UnmarshalJSON are implemented
|
|
|
|
// at the top level.
|
|
|
|
func TestCustomMarshallerIsImplemented(t *testing.T) {
|
|
|
|
r := Resources{}
|
|
|
|
rt := reflect.TypeOf(r)
|
|
|
|
|
|
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
|
|
field := rt.Field(i)
|
|
|
|
|
|
|
|
// Fields in Resources are expected be of the form map[string]*resourceStruct
|
|
|
|
assert.Equal(t, field.Type.Kind(), reflect.Map, "Resource %s is not a map", field.Name)
|
|
|
|
kt := field.Type.Key()
|
|
|
|
assert.Equal(t, kt.Kind(), reflect.String, "Resource %s is not a map with string keys", field.Name)
|
|
|
|
vt := field.Type.Elem()
|
|
|
|
assert.Equal(t, vt.Kind(), reflect.Ptr, "Resource %s is not a map with pointer values", field.Name)
|
|
|
|
|
|
|
|
// Marshalling a resourceStruct will panic if resourceStruct does not have a custom marshaller
|
|
|
|
// This is because resourceStruct embeds a Go SDK struct that implements
|
|
|
|
// a custom marshaller.
|
|
|
|
// Eg: resource.Job implements MarshalJSON
|
|
|
|
v := reflect.Zero(vt.Elem()).Interface()
|
|
|
|
assert.NotPanics(t, func() {
|
|
|
|
json.Marshal(v)
|
|
|
|
}, "Resource %s does not have a custom marshaller", field.Name)
|
|
|
|
|
|
|
|
// Unmarshalling a *resourceStruct will panic if the resource does not have a custom unmarshaller
|
|
|
|
// This is because resourceStruct embeds a Go SDK struct that implements
|
|
|
|
// a custom unmarshaller.
|
|
|
|
// Eg: *resource.Job implements UnmarshalJSON
|
|
|
|
v = reflect.New(vt.Elem()).Interface()
|
|
|
|
assert.NotPanics(t, func() {
|
|
|
|
json.Unmarshal([]byte("{}"), v)
|
|
|
|
}, "Resource %s does not have a custom unmarshaller", field.Name)
|
|
|
|
}
|
|
|
|
}
|