Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package local | |
import ( | |
"fmt" | |
"runtime/debug" | |
"testing" | |
logrus "github.com/sirupsen/logrus/hooks/test" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
"github.com/tailor-inc/graphql" | |
"github.com/weaviate/weaviate/entities/models" | |
"github.com/weaviate/weaviate/entities/schema" | |
"github.com/weaviate/weaviate/usecases/config" | |
"github.com/weaviate/weaviate/usecases/modules" | |
) | |
// These tests are component tests for the local package including all its | |
// subpackages, such as get, getmeta, etc.. However, they only assert that the | |
// graphql tree can be built under certain circumstances. This helps us to | |
// catch errors on edge cases like empty schemas, classes with empty | |
// properties, empty peer lists, peers with empty schemas, etc. However, we | |
// don't get any guarantee of whether the individual queries resolve | |
// correctly. For those cases we have unit tests in die individual subpackages | |
// (i.e. get, getmeta, aggregate, etc.). Additionally we have (a few) e2e | |
// tests. | |
func TestBuild_GraphQLNetwork(t *testing.T) { | |
tests := testCases{ | |
// This tests asserts that an action-only schema doesn't lead to errors. | |
testCase{ | |
name: "with only objects locally", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "BestLocalAction", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Name: "myStringProp", | |
Tokenization: models.PropertyTokenizationWhitespace, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
// This tests asserts that a things-only schema doesn't lead to errors. | |
testCase{ | |
name: "with only objects locally", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "BestLocalThing", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Name: "myStringProp", | |
Tokenization: models.PropertyTokenizationWhitespace, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
// This tests asserts that a class without any properties doesn't lead to | |
// errors. | |
testCase{ | |
name: "with things without properties locally", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "BestLocalThing", | |
Properties: []*models.Property{}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
testCase{ | |
name: "without any peers", | |
localSchema: validSchema(), | |
}, | |
} | |
tests.AssertNoError(t) | |
} | |
func TestBuild_RefProps(t *testing.T) { | |
t.Run("expected error logs", func(t *testing.T) { | |
tests := testCases{ | |
{ | |
name: "build class with nonexistent ref prop", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "ThisClassExists", | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{"ThisClassDoesNotExist"}, | |
Name: "ofNonexistentClass", | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
} | |
expectedLogMsg := "ignoring ref prop \"ofNonexistentClass\" on class \"ThisClassExists\", " + | |
"because it contains reference to nonexistent class [\"ThisClassDoesNotExist\"]" | |
tests.AssertErrorLogs(t, expectedLogMsg) | |
}) | |
t.Run("expected success", func(t *testing.T) { | |
tests := testCases{ | |
{ | |
name: "build class with existing non-circular ref prop", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "ThisClassExists", | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{"ThisClassAlsoExists"}, | |
Name: "ofExistingClass", | |
}, | |
}, | |
}, | |
{ | |
Class: "ThisClassAlsoExists", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Name: "stringProp", | |
Tokenization: models.PropertyTokenizationWhitespace, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
{ | |
name: "build class with existing circular ref prop", | |
localSchema: schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "ThisClassExists", | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{"ThisClassAlsoExists"}, | |
Name: "ofExistingClass", | |
}, | |
}, | |
}, | |
{ | |
Class: "ThisClassAlsoExists", | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{"ThisClassExists"}, | |
Name: "ofExistingClass", | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
}, | |
} | |
tests.AssertNoError(t) | |
}) | |
} | |
type testCase struct { | |
name string | |
localSchema schema.Schema | |
} | |
type testCases []testCase | |
func (tests testCases) AssertNoError(t *testing.T) { | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
modules := modules.NewProvider() | |
localSchema, err := Build(&test.localSchema, nil, config.Config{}, modules) | |
require.Nil(t, err, test.name) | |
schemaObject := graphql.ObjectConfig{ | |
Name: "WeaviateObj", | |
Description: "Location of the root query", | |
Fields: localSchema, | |
} | |
func() { | |
defer func() { | |
if r := recover(); r != nil { | |
err = fmt.Errorf("%v at %s", r, debug.Stack()) | |
} | |
}() | |
_, err = graphql.NewSchema(graphql.SchemaConfig{ | |
Query: graphql.NewObject(schemaObject), | |
}) | |
}() | |
assert.Nil(t, err, test.name) | |
}) | |
} | |
} | |
// AssertErrorLogs still expects the test to pass without errors, | |
// but does expect the Build logger to contain errors messages | |
// from the GQL schema rebuilding thunk | |
func (tests testCases) AssertErrorLogs(t *testing.T, expectedMsg string) { | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
modules := modules.NewProvider() | |
logger, logsHook := logrus.NewNullLogger() | |
localSchema, err := Build(&test.localSchema, logger, config.Config{}, modules) | |
require.Nil(t, err, test.name) | |
schemaObject := graphql.ObjectConfig{ | |
Name: "WeaviateObj", | |
Description: "Location of the root query", | |
Fields: localSchema, | |
} | |
func() { | |
defer func() { | |
if r := recover(); r != nil { | |
err = fmt.Errorf("%v at %s", r, debug.Stack()) | |
} | |
}() | |
_, err = graphql.NewSchema(graphql.SchemaConfig{ | |
Query: graphql.NewObject(schemaObject), | |
}) | |
}() | |
last := logsHook.LastEntry() | |
assert.Contains(t, last.Message, expectedMsg) | |
assert.Nil(t, err) | |
}) | |
} | |
} | |
func validSchema() schema.Schema { | |
return schema.Schema{ | |
Objects: &models.Schema{ | |
Classes: []*models.Class{ | |
{ | |
Class: "BestLocalThing", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Name: "myStringProp", | |
Tokenization: models.PropertyTokenizationWhitespace, | |
}, | |
}, | |
}, | |
}, | |
}, | |
} | |
} | |