diff --git a/tests/integration/schema/client_introspection/altair_graphiql_postman_2023.gql b/tests/integration/schema/client_introspection/altair_graphiql_postman_2023.gql new file mode 100644 index 0000000000..5ad5777ffe --- /dev/null +++ b/tests/integration/schema/client_introspection/altair_graphiql_postman_2023.gql @@ -0,0 +1,91 @@ +query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/integration/schema/client_introspection/one_many_test.go b/tests/integration/schema/client_introspection/one_many_test.go new file mode 100644 index 0000000000..45c8c392eb --- /dev/null +++ b/tests/integration/schema/client_introspection/one_many_test.go @@ -0,0 +1,47 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package client_introspection + +import ( + _ "embed" + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestClientIntrospectionWithOneToManySchema(t *testing.T) { + test := testUtils.TestCase{ + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Book { + name: String + author: Author + } + type Author { + name: String + published: [Book] + } + `, + }, + testUtils.ClientIntrospectionRequest{ + Request: clientIntrospectionQuery, + // TODO: this should pass without error. + // https://github.com/sourcenetwork/defradb/issues/1502 + ExpectedError: "Unknown kind of type: ", + // TODO: this should pass without error. + // https://github.com/sourcenetwork/defradb/issues/1463 + // ExpectedError: "InputFields are missing", + }, + }, + } + testUtils.ExecuteTestCase(t, []string{"Book", "Author"}, test) +} diff --git a/tests/integration/schema/client_introspection/simple_test.go b/tests/integration/schema/client_introspection/simple_test.go new file mode 100644 index 0000000000..5576cd91d0 --- /dev/null +++ b/tests/integration/schema/client_introspection/simple_test.go @@ -0,0 +1,32 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package client_introspection + +import ( + _ "embed" + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +//go:embed altair_graphiql_postman_2023.gql +var clientIntrospectionQuery string + +func TestClientIntrospectionBasic(t *testing.T) { + test := testUtils.TestCase{ + Actions: []any{ + testUtils.ClientIntrospectionRequest{ + Request: clientIntrospectionQuery, + }, + }, + } + testUtils.ExecuteTestCase(t, []string{}, test) +} diff --git a/tests/integration/test_case.go b/tests/integration/test_case.go index 1b97fc01da..02f9165297 100644 --- a/tests/integration/test_case.go +++ b/tests/integration/test_case.go @@ -251,3 +251,17 @@ type IntrospectionRequest struct { // contains this string. ExpectedError string } + +// ClientIntrospectionRequest represents a GraphQL client introspection request. +// The GraphQL clients usually use this to fetch the schema state with a default introspection +// query they provide. +type ClientIntrospectionRequest struct { + // The introspection request to use when fetching schema state. + Request string + + // Any error expected from the action. Optional. + // + // String can be a partial, and the test will pass if an error is returned that + // contains this string. + ExpectedError string +} diff --git a/tests/integration/utils2.go b/tests/integration/utils2.go index 1402667c44..941ef374df 100644 --- a/tests/integration/utils2.go +++ b/tests/integration/utils2.go @@ -384,6 +384,9 @@ func executeTestCase( case IntrospectionRequest: assertIntrospectionResults(ctx, t, testCase.Description, db, action) + case ClientIntrospectionRequest: + assertClientIntrospectionResults(ctx, t, testCase.Description, db, action) + case WaitForSync: waitForSync(t, testCase, action, syncChans) @@ -1179,6 +1182,55 @@ func assertIntrospectionResults( return false } +// Asserts that the client introspection results conform to our expectations. +func assertClientIntrospectionResults( + ctx context.Context, + t *testing.T, + description string, + db client.DB, + action ClientIntrospectionRequest, +) bool { + result := db.ExecRequest(ctx, action.Request) + + if AssertErrors(t, description, result.GQL.Errors, action.ExpectedError) { + return true + } + resultantData := result.GQL.Data.(map[string]any) + + if len(resultantData) == 0 { + return false + } + + // Iterate through all types, validating each type definition. + // Inspired from buildClientSchema.ts from graphql-js, + // which is one way that clients do validate the schema. + types := resultantData["__schema"].(map[string]any)["types"].([]any) + + for _, typeData := range types { + typeDef := typeData.(map[string]any) + kind := typeDef["kind"].(string) + + switch kind { + case "SCALAR", "INTERFACE", "UNION", "ENUM": + // No validation for these types in this test + case "OBJECT": + fields := typeDef["fields"] + if fields == nil { + t.Errorf("Fields are missing for OBJECT type %v", typeDef["name"]) + } + case "INPUT_OBJECT": + inputFields := typeDef["inputFields"] + if inputFields == nil { + t.Errorf("InputFields are missing for INPUT_OBJECT type %v", typeDef["name"]) + } + default: + // t.Errorf("Unknown type kind: %v", kind) + } + } + + return true +} + // Asserts that the `actual` contains the given `contains` value according to the logic // described on the [RequestTestCase.ContainsData] property. func assertContains(t *testing.T, contains map[string]any, actual map[string]any) {