github.com/weaviate/weaviate@v1.24.6/test/acceptance/multi_tenancy/batch_add_tenant_references_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 test
    13  
    14  import (
    15  	"fmt"
    16  	"testing"
    17  
    18  	"github.com/google/uuid"
    19  
    20  	"github.com/go-openapi/strfmt"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  	"github.com/weaviate/weaviate/entities/models"
    24  	"github.com/weaviate/weaviate/entities/schema"
    25  	"github.com/weaviate/weaviate/entities/schema/crossref"
    26  	"github.com/weaviate/weaviate/test/helper"
    27  )
    28  
    29  func TestBatchAddTenantReferences(t *testing.T) {
    30  	className1 := "MultiTenantClass1"
    31  	className2 := "MultiTenantClass2"
    32  	className3 := "SingleTenantClass1"
    33  	className4 := "SingleTenantClass2"
    34  	tenantName1 := "Tenant1"
    35  	tenantName2 := "Tenant2"
    36  	mtRefProp1 := "relatedToMT1"
    37  	mtRefProp2 := "relatedToMT2"
    38  	stRefProp := "relatedToST"
    39  	mtClass1 := models.Class{
    40  		Class:              className1,
    41  		MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true},
    42  		Properties: []*models.Property{
    43  			{
    44  				Name:     "name",
    45  				DataType: schema.DataTypeText.PropString(),
    46  			},
    47  			{
    48  				Name:     mtRefProp1,
    49  				DataType: []string{className1},
    50  			},
    51  			{
    52  				Name:     mtRefProp2,
    53  				DataType: []string{className2},
    54  			},
    55  			{
    56  				Name:     stRefProp,
    57  				DataType: []string{className3},
    58  			},
    59  		},
    60  	}
    61  	mtClass2 := models.Class{
    62  		Class: className2,
    63  		MultiTenancyConfig: &models.MultiTenancyConfig{
    64  			Enabled: true,
    65  		},
    66  		Properties: []*models.Property{
    67  			{
    68  				Name:     "name",
    69  				DataType: schema.DataTypeText.PropString(),
    70  			},
    71  		},
    72  	}
    73  	stClass1 := models.Class{
    74  		Class: className3,
    75  		Properties: []*models.Property{
    76  			{
    77  				Name:     "stringProp",
    78  				DataType: schema.DataTypeText.PropString(),
    79  			},
    80  		},
    81  	}
    82  	stClass2 := models.Class{
    83  		Class: className4,
    84  		Properties: []*models.Property{
    85  			{
    86  				Name:     mtRefProp1,
    87  				DataType: []string{className1},
    88  			},
    89  		},
    90  	}
    91  	mtObject1 := &models.Object{
    92  		ID:    "0927a1e0-398e-4e76-91fb-04a7a8f0405c",
    93  		Class: className1,
    94  		Properties: map[string]interface{}{
    95  			"name": tenantName1,
    96  		},
    97  		Tenant: tenantName1,
    98  	}
    99  	mtObject2DiffTenant := &models.Object{
   100  		ID:    "af90a7e3-53b3-4eb0-b395-10a04d217263",
   101  		Class: className2,
   102  		Properties: map[string]interface{}{
   103  			"name": tenantName2,
   104  		},
   105  		Tenant: tenantName2,
   106  	}
   107  	mtObject2SameTenant := &models.Object{
   108  		ID:    "4076df6b-0767-43a9-a0a4-2ec153bf262e",
   109  		Class: className2,
   110  		Properties: map[string]interface{}{
   111  			"name": tenantName1,
   112  		},
   113  		Tenant: tenantName1,
   114  	}
   115  	stObject1 := &models.Object{
   116  		ID:    "bea841c7-d689-4526-8af3-56c44b44274a",
   117  		Class: className3,
   118  		Properties: map[string]interface{}{
   119  			"stringProp": "123",
   120  		},
   121  	}
   122  	stObject2 := &models.Object{
   123  		ID:    "744f869a-7dcb-4fb5-8b0a-73075da1e116",
   124  		Class: className4,
   125  	}
   126  
   127  	defer func() {
   128  		helper.DeleteClass(t, className1)
   129  		helper.DeleteClass(t, className2)
   130  		helper.DeleteClass(t, className3)
   131  		helper.DeleteClass(t, className4)
   132  	}()
   133  
   134  	t.Run("create classes", func(t *testing.T) {
   135  		helper.CreateClass(t, &stClass1)
   136  		helper.CreateClass(t, &mtClass2)
   137  		helper.CreateClass(t, &mtClass1)
   138  		helper.CreateClass(t, &stClass2)
   139  	})
   140  
   141  	t.Run("create tenants", func(t *testing.T) {
   142  		helper.CreateTenants(t, className1, []*models.Tenant{{Name: tenantName1}})
   143  		helper.CreateTenants(t, className2, []*models.Tenant{{Name: tenantName1}})
   144  		helper.CreateTenants(t, className2, []*models.Tenant{{Name: tenantName2}})
   145  	})
   146  
   147  	t.Run("add tenant objects", func(t *testing.T) {
   148  		helper.CreateObject(t, mtObject1)
   149  		helper.CreateObject(t, mtObject2DiffTenant)
   150  		helper.CreateObject(t, mtObject2SameTenant)
   151  		helper.CreateObject(t, stObject1)
   152  		helper.CreateObject(t, stObject2)
   153  
   154  		t.Run("verify objects creation", func(t *testing.T) {
   155  			resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1)
   156  			require.Nil(t, err)
   157  			require.Equal(t, mtObject1.Class, resp.Class)
   158  			require.Equal(t, mtObject1.Properties, resp.Properties)
   159  
   160  			resp, err = helper.TenantObject(t, mtObject2DiffTenant.Class, mtObject2DiffTenant.ID, tenantName2)
   161  			require.Nil(t, err)
   162  			require.Equal(t, mtObject2DiffTenant.Class, resp.Class)
   163  			require.Equal(t, mtObject2DiffTenant.Properties, resp.Properties)
   164  
   165  			resp, err = helper.TenantObject(t, mtObject2SameTenant.Class, mtObject2SameTenant.ID, tenantName1)
   166  			require.Nil(t, err)
   167  			require.Equal(t, mtObject2SameTenant.Class, resp.Class)
   168  			require.Equal(t, mtObject2SameTenant.Properties, resp.Properties)
   169  
   170  			resp, err = helper.GetObject(t, stObject1.Class, stObject1.ID)
   171  			require.Nil(t, err)
   172  			require.Equal(t, stObject1.Class, resp.Class)
   173  
   174  			resp, err = helper.GetObject(t, stObject2.Class, stObject2.ID)
   175  			require.Nil(t, err)
   176  			require.Equal(t, stObject2.Class, resp.Class)
   177  		})
   178  	})
   179  
   180  	t.Run("add tenant reference - same class and tenant", func(t *testing.T) {
   181  		refs := []*models.BatchReference{
   182  			{
   183  				From: strfmt.URI(crossref.NewSource(schema.ClassName(className1),
   184  					schema.PropertyName(mtRefProp1), mtObject1.ID).String()),
   185  				To:     strfmt.URI(crossref.NewLocalhost(className1, mtObject1.ID).String()),
   186  				Tenant: tenantName1,
   187  			},
   188  		}
   189  		resp, err := helper.AddReferences(t, refs)
   190  		helper.CheckReferencesBatchResponse(t, resp, err)
   191  
   192  		t.Run("verify object references", func(t *testing.T) {
   193  			resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1)
   194  			require.Nil(t, err)
   195  			require.Equal(t, mtObject1.Class, resp.Class)
   196  			require.Equal(t, mtObject1.ID, resp.ID)
   197  			relatedTo := resp.Properties.(map[string]interface{})[mtRefProp1].([]interface{})
   198  			require.Len(t, relatedTo, 1)
   199  			beacon := relatedTo[0].(map[string]interface{})["beacon"].(string)
   200  			assert.Equal(t, helper.NewBeacon(className1, mtObject1.ID), strfmt.URI(beacon))
   201  		})
   202  	})
   203  
   204  	t.Run("add tenant reference - different MT class same tenant", func(t *testing.T) {
   205  		refs := []*models.BatchReference{
   206  			{
   207  				From: strfmt.URI(crossref.NewSource(schema.ClassName(className1),
   208  					schema.PropertyName(mtRefProp2), mtObject1.ID).String()),
   209  				To:     strfmt.URI(crossref.NewLocalhost(className2, mtObject2SameTenant.ID).String()),
   210  				Tenant: tenantName1,
   211  			},
   212  		}
   213  		resp, err := helper.AddReferences(t, refs)
   214  		helper.CheckReferencesBatchResponse(t, resp, err)
   215  
   216  		t.Run("verify object references", func(t *testing.T) {
   217  			resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1)
   218  			require.Nil(t, err)
   219  			require.Equal(t, mtObject1.Class, resp.Class)
   220  			require.Equal(t, mtObject1.ID, resp.ID)
   221  			relatedTo := resp.Properties.(map[string]interface{})[mtRefProp2].([]interface{})
   222  			require.Len(t, relatedTo, 1)
   223  			beacon := relatedTo[0].(map[string]interface{})["beacon"].(string)
   224  			assert.Equal(t, helper.NewBeacon(className2, mtObject2SameTenant.ID), strfmt.URI(beacon))
   225  		})
   226  	})
   227  
   228  	t.Run("no references between different tenants", func(t *testing.T) {
   229  		refs := []*models.BatchReference{
   230  			{
   231  				From: strfmt.URI(crossref.NewSource(schema.ClassName(className1),
   232  					schema.PropertyName(mtRefProp2), mtObject1.ID).String()),
   233  				To:     strfmt.URI(crossref.NewLocalhost(className2, mtObject2DiffTenant.ID).String()),
   234  				Tenant: tenantName1,
   235  			},
   236  		}
   237  
   238  		resp, err := helper.AddReferences(t, refs)
   239  		require.Nil(t, err)
   240  		require.NotNil(t, resp)
   241  		require.Len(t, resp, 1)
   242  		require.Empty(t, resp[0].To)
   243  		require.Empty(t, resp[0].From)
   244  		require.NotNil(t, resp[0].Result)
   245  		require.NotNil(t, resp[0].Result.Errors)
   246  		require.Len(t, resp[0].Result.Errors.Error, 1)
   247  		require.NotNil(t, resp[0].Result.Errors.Error[0])
   248  		expectedMsg := fmt.Sprintf(`target: object %s/%s not found for tenant %q`, className2, mtObject2DiffTenant.ID, tenantName1)
   249  		assert.Equal(t, expectedMsg, resp[0].Result.Errors.Error[0].Message)
   250  	})
   251  
   252  	t.Run("add tenant reference - from MT class to single tenant class", func(t *testing.T) {
   253  		refs := []*models.BatchReference{
   254  			{
   255  				From: strfmt.URI(crossref.NewSource(schema.ClassName(className1),
   256  					schema.PropertyName(stRefProp), mtObject1.ID).String()),
   257  				To:     strfmt.URI(crossref.NewLocalhost(className3, stObject1.ID).String()),
   258  				Tenant: tenantName1,
   259  			},
   260  		}
   261  		resp, err := helper.AddReferences(t, refs)
   262  		helper.CheckReferencesBatchResponse(t, resp, err)
   263  
   264  		t.Run("verify object references", func(t *testing.T) {
   265  			resp, err := helper.TenantObject(t, mtObject1.Class, mtObject1.ID, tenantName1)
   266  			require.Nil(t, err)
   267  			require.Equal(t, mtObject1.Class, resp.Class)
   268  			require.Equal(t, mtObject1.ID, resp.ID)
   269  			relatedTo := resp.Properties.(map[string]interface{})[stRefProp].([]interface{})
   270  			require.Len(t, relatedTo, 1)
   271  			beacon := relatedTo[0].(map[string]interface{})["beacon"].(string)
   272  			assert.Equal(t, helper.NewBeacon(className3, stObject1.ID), strfmt.URI(beacon))
   273  		})
   274  	})
   275  
   276  	t.Run("no references from single tenant class to MT class", func(t *testing.T) {
   277  		refs := []*models.BatchReference{
   278  			{
   279  				From: strfmt.URI(crossref.NewSource(schema.ClassName(className4),
   280  					schema.PropertyName(mtRefProp1), stObject2.ID).String()),
   281  				To: strfmt.URI(crossref.NewLocalhost(className1, mtObject1.ID).String()),
   282  			},
   283  		}
   284  
   285  		resp, err := helper.AddReferences(t, refs)
   286  		require.Nil(t, err)
   287  		require.NotNil(t, resp)
   288  		require.Len(t, resp, 1)
   289  		require.Empty(t, resp[0].To)
   290  		require.Empty(t, resp[0].From)
   291  		require.NotNil(t, resp[0].Result)
   292  		require.NotNil(t, resp[0].Result.Errors)
   293  		require.Len(t, resp[0].Result.Errors.Error, 1)
   294  		require.NotNil(t, resp[0].Result.Errors.Error[0])
   295  		expectedMsg := "invalid reference: cannot reference a multi-tenant enabled class from a non multi-tenant enabled class"
   296  		assert.Equal(t, expectedMsg, resp[0].Result.Errors.Error[0].Message)
   297  	})
   298  }
   299  
   300  func TestAddMultipleTenantsForBatch(t *testing.T) {
   301  	tenants := []string{"tenant1", "tenant2"}
   302  	classNames := []string{"MultiTenantRefs1", "MultiTenantRefs2", "MultiTenantRefs3"}
   303  	refProps := []string{"refPropST", "refPropOtherMT", "refPropSelf"}
   304  	classes := []models.Class{
   305  		{Class: classNames[0]},
   306  		{
   307  			Class:              classNames[1],
   308  			MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true},
   309  		},
   310  		{
   311  			Class:              classNames[2],
   312  			MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true},
   313  			Properties: []*models.Property{
   314  				{
   315  					Name:     refProps[0],
   316  					DataType: []string{classNames[0]},
   317  				},
   318  				{
   319  					Name:     refProps[1],
   320  					DataType: []string{classNames[1]},
   321  				},
   322  				{
   323  					Name:     refProps[2],
   324  					DataType: []string{classNames[2]},
   325  				},
   326  			},
   327  		},
   328  	}
   329  	defer func() {
   330  		for i := range classes {
   331  			helper.DeleteClass(t, classes[i].Class)
   332  		}
   333  	}()
   334  	for i := range classes {
   335  		helper.CreateClass(t, &classes[i])
   336  	}
   337  
   338  	for _, class := range classes[1:] {
   339  		for k := range tenants {
   340  			helper.CreateTenants(t, class.Class, []*models.Tenant{{Name: tenants[k]}})
   341  		}
   342  	}
   343  
   344  	var tenantObjects []*models.Object
   345  	objMap := make(map[string][]int)
   346  
   347  	for i := 0; i < 9; i++ {
   348  		obj := &models.Object{
   349  			ID:    strfmt.UUID(uuid.New().String()),
   350  			Class: classes[i%len(classes)].Class,
   351  		}
   352  		if i%len(classes) > 0 { // only for MMT class
   353  			obj.Tenant = tenants[i%len(tenants)]
   354  		}
   355  		tenantObjects = append(tenantObjects, obj)
   356  		objMap[obj.Class] = append(objMap[obj.Class], i)
   357  	}
   358  	helper.CreateObjectsBatch(t, tenantObjects)
   359  
   360  	t.Run("refs between same class", func(t *testing.T) {
   361  		var refs []*models.BatchReference
   362  		for _, objectIndex := range objMap[classNames[2]] {
   363  			obj := tenantObjects[objectIndex]
   364  			refs = append(refs, &models.BatchReference{
   365  				From: strfmt.URI(crossref.NewSource(schema.ClassName(obj.Class),
   366  					schema.PropertyName(refProps[2]), obj.ID).String()),
   367  				To:     strfmt.URI(crossref.NewLocalhost(classNames[2], obj.ID).String()),
   368  				Tenant: obj.Tenant,
   369  			},
   370  			)
   371  		}
   372  		resp, err := helper.AddReferences(t, refs)
   373  		helper.CheckReferencesBatchResponse(t, resp, err)
   374  
   375  		// verify refs
   376  		for _, objectIndex := range objMap[classNames[2]] {
   377  			obj := tenantObjects[objectIndex]
   378  
   379  			resp, err := helper.TenantObject(t, classNames[2], obj.ID, obj.Tenant)
   380  			require.Nil(t, err)
   381  			require.Equal(t, obj.Class, resp.Class)
   382  			require.Equal(t, fmt.Sprintf("weaviate://localhost/%s/%v", obj.Class, obj.ID), resp.Properties.(map[string]interface{})[refProps[2]].([]interface{})[0].(map[string]interface{})["beacon"])
   383  			require.Equal(t, obj.Tenant, resp.Tenant)
   384  		}
   385  	})
   386  
   387  	t.Run("refs between multiple classes class", func(t *testing.T) {
   388  		var refs []*models.BatchReference
   389  		for i, objectIndexClass2 := range objMap[classNames[2]] {
   390  			objClass2 := tenantObjects[objectIndexClass2]
   391  			// refs between two MMT classes
   392  			if len(objMap[classNames[1]]) > i {
   393  				objClass1 := tenantObjects[objMap[classNames[1]][i]]
   394  				if objClass2.Tenant == objClass1.Tenant {
   395  					refs = append(refs, &models.BatchReference{
   396  						From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]),
   397  							schema.PropertyName(refProps[1]), objClass2.ID).String()),
   398  						To:     strfmt.URI(crossref.NewLocalhost(classNames[1], objClass1.ID).String()),
   399  						Tenant: objClass2.Tenant,
   400  					})
   401  				}
   402  			}
   403  
   404  			// refs between MMT and non MMT class
   405  			if len(objMap[classNames[0]]) > i {
   406  				objClass0 := tenantObjects[objMap[classNames[0]][i]]
   407  				refs = append(refs, &models.BatchReference{
   408  					From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]),
   409  						schema.PropertyName(refProps[0]), objClass2.ID).String()),
   410  					To:     strfmt.URI(crossref.NewLocalhost(classNames[0], objClass0.ID).String()),
   411  					Tenant: objClass2.Tenant,
   412  				})
   413  			}
   414  		}
   415  		resp, err := helper.AddReferences(t, refs)
   416  		helper.CheckReferencesBatchResponse(t, resp, err)
   417  
   418  		// verify refs
   419  		for i, objectIndexClass2 := range objMap[classNames[2]] {
   420  			objClass2 := tenantObjects[objectIndexClass2]
   421  			// refs between two MMT classes
   422  			if len(objMap[classNames[1]]) > i {
   423  				objClass1 := tenantObjects[objMap[classNames[1]][i]]
   424  				if objClass2.Tenant != objClass1.Tenant {
   425  					continue
   426  				}
   427  
   428  				resp, err := helper.TenantObject(t, classNames[2], objClass2.ID, objClass2.Tenant)
   429  				require.Nil(t, err)
   430  				require.Equal(t, objClass2.Class, resp.Class)
   431  				require.Equal(t, fmt.Sprintf("weaviate://localhost/%s/%v", objClass1.Class, objClass1.ID), resp.Properties.(map[string]interface{})[refProps[1]].([]interface{})[0].(map[string]interface{})["beacon"])
   432  				require.Equal(t, objClass2.Tenant, resp.Tenant)
   433  
   434  			}
   435  
   436  			// refs between MMT and non MMT class
   437  			if len(objMap[classNames[0]]) > i {
   438  				objClass0 := tenantObjects[objMap[classNames[0]][i]]
   439  				refs = append(refs, &models.BatchReference{
   440  					From: strfmt.URI(crossref.NewSource(schema.ClassName(classNames[2]),
   441  						schema.PropertyName(refProps[0]), objClass2.ID).String()),
   442  					To:     strfmt.URI(crossref.NewLocalhost(classNames[0], objClass0.ID).String()),
   443  					Tenant: objClass2.Tenant,
   444  				})
   445  			}
   446  		}
   447  	})
   448  }