github.com/weaviate/weaviate@v1.24.6/test/acceptance/multi_tenancy/gql_get_tenant_objects_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 "strings" 17 "testing" 18 19 "github.com/go-openapi/strfmt" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 "github.com/weaviate/weaviate/entities/models" 23 "github.com/weaviate/weaviate/entities/schema" 24 "github.com/weaviate/weaviate/test/helper" 25 graphqlhelper "github.com/weaviate/weaviate/test/helper/graphql" 26 ) 27 28 func TestGQLGetTenantObjects(t *testing.T) { 29 testClass := models.Class{ 30 Class: "MultiTenantClass", 31 MultiTenancyConfig: &models.MultiTenancyConfig{ 32 Enabled: true, 33 }, 34 Properties: []*models.Property{ 35 { 36 Name: "name", 37 DataType: schema.DataTypeText.PropString(), 38 }, 39 { 40 Name: "text", 41 DataType: schema.DataTypeText.PropString(), 42 }, 43 }, 44 Vectorizer: "text2vec-contextionary", 45 } 46 tenant := "Tenant1" 47 otherTenant := "otherTenant" 48 tenantObjects := []*models.Object{ 49 { 50 ID: "0927a1e0-398e-4e76-91fb-04a7a8f0405c", 51 Class: testClass.Class, 52 Properties: map[string]interface{}{ 53 "name": tenant, 54 "text": "meat", 55 }, 56 Tenant: tenant, 57 }, 58 { 59 ID: "831ae1d0-f441-44b1-bb2a-46548048e26f", 60 Class: testClass.Class, 61 Properties: map[string]interface{}{ 62 "name": tenant, 63 "text": "bananas", 64 }, 65 Tenant: tenant, 66 }, 67 { 68 ID: "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59", 69 Class: testClass.Class, 70 Properties: map[string]interface{}{ 71 "name": tenant, 72 "text": "kiwi", 73 }, 74 Tenant: tenant, 75 }, 76 { 77 ID: "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59", 78 Class: testClass.Class, 79 Properties: map[string]interface{}{ 80 "name": tenant, 81 "text": "kiwi", 82 }, 83 Tenant: otherTenant, 84 }, 85 } 86 87 // add more objects for other tenant, won't show up in search 88 89 defer func() { 90 helper.DeleteClass(t, testClass.Class) 91 }() 92 93 helper.CreateClass(t, &testClass) 94 helper.CreateTenants(t, testClass.Class, []*models.Tenant{{Name: tenant}, {Name: otherTenant}}) 95 helper.CreateObjectsBatch(t, tenantObjects) 96 97 t.Run("Test tenant objects", func(t *testing.T) { 98 for _, obj := range tenantObjects { 99 resp, err := helper.TenantObject(t, obj.Class, obj.ID, obj.Tenant) 100 require.Nil(t, err) 101 assert.Equal(t, obj.ID, resp.ID) 102 assert.Equal(t, obj.Class, resp.Class) 103 assert.Equal(t, obj.Properties, resp.Properties) 104 } 105 }) 106 107 t.Run("GQL Get tenant objects", func(t *testing.T) { 108 expectedIDs := map[strfmt.UUID]bool{} 109 for _, obj := range tenantObjects { 110 expectedIDs[obj.ID] = false 111 } 112 113 query := fmt.Sprintf(`{Get{%s(tenant:%q){_additional{id}}}}`, testClass.Class, tenant) 114 result := graphqlhelper.AssertGraphQL(t, helper.RootAuth, query) 115 for _, obj := range result.Get("Get", testClass.Class).AsSlice() { 116 id := obj.(map[string]any)["_additional"].(map[string]any)["id"].(string) 117 if _, ok := expectedIDs[strfmt.UUID(id)]; ok { 118 expectedIDs[strfmt.UUID(id)] = true 119 } else { 120 t.Fatalf("found unexpected id %q", id) 121 } 122 } 123 124 for id, found := range expectedIDs { 125 if !found { 126 t.Fatalf("expected to find id %q, but didn't", id) 127 } 128 } 129 }) 130 131 t.Run("GQL near objects", func(t *testing.T) { 132 expectedIDs := map[strfmt.UUID]bool{} 133 for _, obj := range tenantObjects { 134 expectedIDs[obj.ID] = false 135 } 136 137 query := fmt.Sprintf(`{Get{%s(nearObject:{id: %q}, tenant:%q){_additional{id}}}}`, testClass.Class, tenantObjects[0].ID, tenant) 138 result := graphqlhelper.AssertGraphQL(t, helper.RootAuth, query) 139 res := result.Get("Get", testClass.Class) 140 require.NotNil(t, res) // objects have no content, so no result 141 }) 142 143 t.Run("GQL near text", func(t *testing.T) { 144 expectedIDs := map[strfmt.UUID]bool{} 145 for _, obj := range tenantObjects { 146 expectedIDs[obj.ID] = false 147 } 148 149 query := fmt.Sprintf(`{Get{%s(nearText:{concepts: "apple", moveTo: {concepts: ["fruit"], force: 0.1}, moveAwayFrom: {objects: [{id:"0927a1e0-398e-4e76-91fb-04a7a8f0405c"}], force: 0.1}}, tenant:%q){_additional{id}}}}`, testClass.Class, tenant) 150 result := graphqlhelper.AssertGraphQL(t, helper.RootAuth, query) 151 res := result.Get("Get", testClass.Class) 152 require.NotNil(t, res) 153 require.Len(t, res.Result, 3) // don't find object from other tenants 154 }) 155 156 t.Run("GQL bm25", func(t *testing.T) { 157 expectedIDs := map[strfmt.UUID]bool{} 158 for _, obj := range tenantObjects { 159 expectedIDs[obj.ID] = false 160 } 161 162 query := fmt.Sprintf(`{Get{%s(bm25:{query: "kiwi"}, tenant:%q){_additional{id}}}}`, testClass.Class, tenant) 163 result := graphqlhelper.AssertGraphQL(t, helper.RootAuth, query) 164 res := result.Get("Get", testClass.Class) 165 require.NotNil(t, res) 166 require.Len(t, res.Result, 1) // don't find object from other tenants 167 }) 168 169 t.Run("GQL hybrid", func(t *testing.T) { 170 expectedIDs := map[strfmt.UUID]bool{} 171 for _, obj := range tenantObjects { 172 expectedIDs[obj.ID] = false 173 } 174 175 query := fmt.Sprintf(`{Get{%s(hybrid:{query: "kiwi", alpha: 0.1}, tenant:%q, autocut:1){text _additional{id}}}}`, testClass.Class, tenant) 176 result := graphqlhelper.AssertGraphQL(t, helper.RootAuth, query) 177 res := result.Get("Get", testClass.Class) 178 require.NotNil(t, res) 179 require.Len(t, res.Result, 1) // find only relevant results from tenant 180 }) 181 } 182 183 func TestGQLGetTenantObjects_MissingTenant(t *testing.T) { 184 testClass := models.Class{ 185 Class: "MultiTenantClass", 186 MultiTenancyConfig: &models.MultiTenancyConfig{ 187 Enabled: true, 188 }, 189 Properties: []*models.Property{ 190 { 191 Name: "name", 192 DataType: schema.DataTypeText.PropString(), 193 }, 194 }, 195 } 196 tenantName := "Tenant1" 197 tenantObjects := []*models.Object{ 198 { 199 ID: "0927a1e0-398e-4e76-91fb-04a7a8f0405c", 200 Class: testClass.Class, 201 Properties: map[string]interface{}{ 202 "name": tenantName, 203 }, 204 Tenant: tenantName, 205 }, 206 { 207 ID: "831ae1d0-f441-44b1-bb2a-46548048e26f", 208 Class: testClass.Class, 209 Properties: map[string]interface{}{ 210 "name": tenantName, 211 }, 212 Tenant: tenantName, 213 }, 214 { 215 ID: "6f3363e0-c0a0-4618-bf1f-b6cad9cdff59", 216 Class: testClass.Class, 217 Properties: map[string]interface{}{ 218 "name": tenantName, 219 }, 220 Tenant: tenantName, 221 }, 222 } 223 224 defer func() { 225 helper.DeleteClass(t, testClass.Class) 226 }() 227 228 helper.CreateClass(t, &testClass) 229 helper.CreateTenants(t, testClass.Class, []*models.Tenant{{Name: tenantName}}) 230 helper.CreateObjectsBatch(t, tenantObjects) 231 232 for _, obj := range tenantObjects { 233 resp, err := helper.TenantObject(t, obj.Class, obj.ID, tenantName) 234 require.Nil(t, err) 235 assert.Equal(t, obj.ID, resp.ID) 236 assert.Equal(t, obj.Class, resp.Class) 237 assert.Equal(t, obj.Properties, resp.Properties) 238 } 239 240 query := fmt.Sprintf(`{Get{%s{_additional{id}}}}`, testClass.Class) 241 result, err := graphqlhelper.QueryGraphQL(t, helper.RootAuth, "", query, nil) 242 require.Nil(t, err) 243 require.Len(t, result.Errors, 1) 244 assert.Nil(t, result.Data["Get"].(map[string]interface{})[testClass.Class]) 245 msg := fmt.Sprintf(`explorer: list class: search: object search at index %s: `, 246 strings.ToLower(testClass.Class)) + 247 fmt.Sprintf(`class %s has multi-tenancy enabled, but request was without tenant`, testClass.Class) 248 assert.Equal(t, result.Errors[0].Message, msg) 249 }