github.com/weaviate/weaviate@v1.24.6/usecases/modules/module_config_init_and_validate_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package modules
    13  
    14  import (
    15  	"context"
    16  	"testing"
    17  
    18  	"github.com/pkg/errors"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"github.com/weaviate/weaviate/entities/models"
    22  	"github.com/weaviate/weaviate/entities/modulecapabilities"
    23  	"github.com/weaviate/weaviate/entities/moduletools"
    24  	"github.com/weaviate/weaviate/entities/schema"
    25  )
    26  
    27  func TestSetClassDefaults(t *testing.T) {
    28  	t.Run("no modules", func(t *testing.T) {
    29  		class := &models.Class{
    30  			Class:      "Foo",
    31  			Vectorizer: "none",
    32  		}
    33  
    34  		p := NewProvider()
    35  		p.SetClassDefaults(class)
    36  
    37  		assert.Equal(t, &models.Class{Class: "Foo", Vectorizer: "none"}, class,
    38  			"the class is not changed")
    39  	})
    40  
    41  	t.Run("module is set, but does not have config capability", func(t *testing.T) {
    42  		class := &models.Class{
    43  			Class:      "Foo",
    44  			Vectorizer: "my-module",
    45  		}
    46  
    47  		p := NewProvider()
    48  		p.Register(&dummyText2VecModuleNoCapabilities{name: "my-module"})
    49  		p.SetClassDefaults(class)
    50  
    51  		assert.Equal(t, &models.Class{Class: "Foo", Vectorizer: "my-module"}, class,
    52  			"the class is not changed")
    53  	})
    54  
    55  	t.Run("without user-provided values", func(t *testing.T) {
    56  		class := &models.Class{
    57  			Class: "Foo",
    58  			Properties: []*models.Property{{
    59  				Name:         "Foo",
    60  				DataType:     schema.DataTypeText.PropString(),
    61  				Tokenization: models.PropertyTokenizationWhitespace,
    62  			}},
    63  			Vectorizer: "my-module",
    64  		}
    65  		expected := &models.Class{
    66  			Class: "Foo",
    67  			ModuleConfig: map[string]interface{}{
    68  				"my-module": map[string]interface{}{
    69  					"per-class-prop-1": "some default value",
    70  					"per-class-prop-2": "some default value",
    71  				},
    72  			},
    73  			Properties: []*models.Property{{
    74  				Name:         "Foo",
    75  				DataType:     schema.DataTypeText.PropString(),
    76  				Tokenization: models.PropertyTokenizationWhitespace,
    77  				ModuleConfig: map[string]interface{}{
    78  					"my-module": map[string]interface{}{
    79  						"per-prop-1": "prop default value",
    80  						"per-prop-2": "prop default value",
    81  					},
    82  				},
    83  			}},
    84  			Vectorizer: "my-module",
    85  		}
    86  
    87  		p := NewProvider()
    88  		p.Register(&dummyModuleClassConfigurator{
    89  			dummyText2VecModuleNoCapabilities: dummyText2VecModuleNoCapabilities{
    90  				name: "my-module",
    91  			},
    92  		})
    93  		p.SetClassDefaults(class)
    94  
    95  		assert.Equal(t, expected, class,
    96  			"the defaults were set from config")
    97  	})
    98  
    99  	t.Run("with some user-provided values", func(t *testing.T) {
   100  		class := &models.Class{
   101  			Class: "Foo",
   102  			ModuleConfig: map[string]interface{}{
   103  				"my-module": map[string]interface{}{
   104  					"per-class-prop-1": "overwritten by user",
   105  				},
   106  			},
   107  			Properties: []*models.Property{{
   108  				Name:         "Foo",
   109  				DataType:     schema.DataTypeText.PropString(),
   110  				Tokenization: models.PropertyTokenizationWhitespace,
   111  				ModuleConfig: map[string]interface{}{
   112  					"my-module": map[string]interface{}{
   113  						"per-prop-1": "prop overwritten by user",
   114  					},
   115  				},
   116  			}},
   117  			Vectorizer: "my-module",
   118  		}
   119  		expected := &models.Class{
   120  			Class: "Foo",
   121  			ModuleConfig: map[string]interface{}{
   122  				"my-module": map[string]interface{}{
   123  					"per-class-prop-1": "overwritten by user",
   124  					"per-class-prop-2": "some default value",
   125  				},
   126  			},
   127  			Properties: []*models.Property{{
   128  				Name:         "Foo",
   129  				DataType:     schema.DataTypeText.PropString(),
   130  				Tokenization: models.PropertyTokenizationWhitespace,
   131  				ModuleConfig: map[string]interface{}{
   132  					"my-module": map[string]interface{}{
   133  						"per-prop-1": "prop overwritten by user",
   134  						"per-prop-2": "prop default value",
   135  					},
   136  				},
   137  			}},
   138  			Vectorizer: "my-module",
   139  		}
   140  
   141  		p := NewProvider()
   142  		p.Register(&dummyModuleClassConfigurator{
   143  			dummyText2VecModuleNoCapabilities: dummyText2VecModuleNoCapabilities{
   144  				name: "my-module",
   145  			},
   146  		})
   147  		p.SetClassDefaults(class)
   148  
   149  		assert.Equal(t, expected, class,
   150  			"the defaults were set from config")
   151  	})
   152  }
   153  
   154  func TestValidateClass(t *testing.T) {
   155  	ctx := context.Background()
   156  	t.Run("when class has no vectorizer set, it does not check", func(t *testing.T) {
   157  		class := &models.Class{
   158  			Class: "Foo",
   159  			Properties: []*models.Property{{
   160  				Name:         "Foo",
   161  				DataType:     schema.DataTypeText.PropString(),
   162  				Tokenization: models.PropertyTokenizationWhitespace,
   163  			}},
   164  			Vectorizer: "none",
   165  		}
   166  
   167  		p := NewProvider()
   168  		p.Register(&dummyModuleClassConfigurator{
   169  			validateError: errors.Errorf("if I was used, you'd fail"),
   170  			dummyText2VecModuleNoCapabilities: dummyText2VecModuleNoCapabilities{
   171  				name: "my-module",
   172  			},
   173  		})
   174  		p.SetClassDefaults(class)
   175  
   176  		assert.Nil(t, p.ValidateClass(ctx, class))
   177  	})
   178  
   179  	t.Run("when vectorizer does not have capability, it skips validation",
   180  		func(t *testing.T) {
   181  			class := &models.Class{
   182  				Class: "Foo",
   183  				Properties: []*models.Property{{
   184  					Name:         "Foo",
   185  					DataType:     schema.DataTypeText.PropString(),
   186  					Tokenization: models.PropertyTokenizationWhitespace,
   187  				}},
   188  				Vectorizer: "my-module",
   189  			}
   190  
   191  			p := NewProvider()
   192  			p.Register(&dummyText2VecModuleNoCapabilities{
   193  				name: "my-module",
   194  			})
   195  			p.SetClassDefaults(class)
   196  
   197  			assert.Nil(t, p.ValidateClass(ctx, class))
   198  		})
   199  
   200  	t.Run("the module validates if capable and configured", func(t *testing.T) {
   201  		class := &models.Class{
   202  			Class: "Foo",
   203  			Properties: []*models.Property{{
   204  				Name:         "Foo",
   205  				DataType:     schema.DataTypeText.PropString(),
   206  				Tokenization: models.PropertyTokenizationWhitespace,
   207  			}},
   208  			Vectorizer: "my-module",
   209  		}
   210  
   211  		p := NewProvider()
   212  		p.Register(&dummyModuleClassConfigurator{
   213  			validateError: errors.Errorf("no can do!"),
   214  			dummyText2VecModuleNoCapabilities: dummyText2VecModuleNoCapabilities{
   215  				name: "my-module",
   216  			},
   217  		})
   218  		p.SetClassDefaults(class)
   219  
   220  		err := p.ValidateClass(ctx, class)
   221  		require.NotNil(t, err)
   222  		assert.Equal(t, "module 'my-module': no can do!", err.Error())
   223  	})
   224  }
   225  
   226  func TestSetSinglePropertyDefaults(t *testing.T) {
   227  	class := &models.Class{
   228  		Class: "Foo",
   229  		ModuleConfig: map[string]interface{}{
   230  			"my-module": map[string]interface{}{
   231  				"per-class-prop-1": "overwritten by user",
   232  			},
   233  		},
   234  		Properties: []*models.Property{{
   235  			Name:         "Foo",
   236  			DataType:     schema.DataTypeText.PropString(),
   237  			Tokenization: models.PropertyTokenizationWhitespace,
   238  			ModuleConfig: map[string]interface{}{
   239  				"my-module": map[string]interface{}{
   240  					"per-prop-1": "prop overwritten by user",
   241  				},
   242  			},
   243  		}},
   244  		Vectorizer: "my-module",
   245  	}
   246  	prop := &models.Property{
   247  		DataType: []string{"boolean"},
   248  		ModuleConfig: map[string]interface{}{
   249  			"my-module": map[string]interface{}{
   250  				"per-prop-1": "overwritten by user",
   251  			},
   252  		},
   253  		Name: "newProp",
   254  	}
   255  	expected := &models.Property{
   256  		DataType: []string{"boolean"},
   257  		ModuleConfig: map[string]interface{}{
   258  			"my-module": map[string]interface{}{
   259  				"per-prop-1": "overwritten by user",
   260  				"per-prop-2": "prop default value",
   261  			},
   262  		},
   263  		Name: "newProp",
   264  	}
   265  
   266  	p := NewProvider()
   267  	p.Register(&dummyModuleClassConfigurator{
   268  		dummyText2VecModuleNoCapabilities: dummyText2VecModuleNoCapabilities{
   269  			name: "my-module",
   270  		},
   271  	})
   272  	p.SetSinglePropertyDefaults(class, prop)
   273  
   274  	assert.Equal(t, expected, prop,
   275  		"user specified module config is used, for rest the default value is used")
   276  }
   277  
   278  type dummyModuleClassConfigurator struct {
   279  	dummyText2VecModuleNoCapabilities
   280  	validateError error
   281  }
   282  
   283  func (d *dummyModuleClassConfigurator) ClassConfigDefaults() map[string]interface{} {
   284  	return map[string]interface{}{
   285  		"per-class-prop-1": "some default value",
   286  		"per-class-prop-2": "some default value",
   287  	}
   288  }
   289  
   290  func (d *dummyModuleClassConfigurator) PropertyConfigDefaults(
   291  	dt *schema.DataType,
   292  ) map[string]interface{} {
   293  	return map[string]interface{}{
   294  		"per-prop-1": "prop default value",
   295  		"per-prop-2": "prop default value",
   296  	}
   297  }
   298  
   299  func (d *dummyModuleClassConfigurator) ValidateClass(ctx context.Context,
   300  	class *models.Class, cfg moduletools.ClassConfig,
   301  ) error {
   302  	return d.validateError
   303  }
   304  
   305  var _ = modulecapabilities.ClassConfigurator(
   306  	&dummyModuleClassConfigurator{})