diff --git a/elasticgraph-graphql/spec/spec_helper.rb b/elasticgraph-graphql/spec/spec_helper.rb index 222428f7..d4342a43 100644 --- a/elasticgraph-graphql/spec/spec_helper.rb +++ b/elasticgraph-graphql/spec/spec_helper.rb @@ -28,8 +28,9 @@ def build_datastore_core(**options, &block) meta[:builds_graphql] = true end - config.when_first_matching_example_defined(:resolver) { require_relative "support/resolver" } + config.when_first_matching_example_defined(:ensure_no_orphaned_types) { require_relative "support/ensure_no_orphaned_types" } config.when_first_matching_example_defined(:query_adapter) { require_relative "support/query_adapter" } + config.when_first_matching_example_defined(:resolver) { require_relative "support/resolver" } config.prepend ElasticGraph::GraphQLSpecHelpers, absolute_file_path: %r{/elasticgraph-graphql/} end diff --git a/elasticgraph-graphql/spec/support/ensure_no_orphaned_types.rb b/elasticgraph-graphql/spec/support/ensure_no_orphaned_types.rb new file mode 100644 index 00000000..4d9e1110 --- /dev/null +++ b/elasticgraph-graphql/spec/support/ensure_no_orphaned_types.rb @@ -0,0 +1,51 @@ +# Copyright 2024 Block, Inc. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. +# +# frozen_string_literal: true + +module ElasticGraph + class GraphQL + # Before v2.4 of the GraphQL gem, `GraphQL::Schema#types` returned _all_ types defined by the SDL string. + # Beginning in v2.4, orphaned types (that is, types not reachable from the root `Query` type) are no longer + # included. We have a number of unit tests that define orphaned types since we don't want or need a full + # schema for such a test. + # + # To avoid issues as part of upgrading to v2.4, we need to ensure that our tests don't depend on orphaned + # types that are unavailable in v2.4 and later. This mixin provides a simple solution: it adds on an indexed + # type (`IndexedTypeToEnsureNoOrphans`) with a field for each defined type, ensuring that no defined types + # are orphans. + # + # Apply it to an example or example group using the `:ensure_no_orphaned_types` tag. + module EnsureNoOrphanedTypes + def build_graphql(schema_definition: nil, **options, &block) + schema_def = lambda do |schema| + original_types = schema.state.types_by_name.keys + schema_definition.call(schema) + + # If a test is taking are of defining its own indexed types, we don't need to do anything further. + return if schema.state.object_types_by_name.values.any?(&:indexed?) + + added_types = schema.state.types_by_name.keys - original_types + + schema.object_type "IndexedTypeToEnsureNoOrphans" do |t| + added_types.each do |type_name| + t.field type_name, type_name + end + + t.field "id", "ID" + t.index "indexed_types" + end + end + + super(schema_definition: schema_def, **options, &block) + end + end + + ::RSpec.configure do |c| + c.include EnsureNoOrphanedTypes, :ensure_no_orphaned_types + end + end +end diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb index 8c60d771..221a59d7 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/query_executor_spec.rb @@ -42,6 +42,7 @@ class GraphQL } type Query { + float: Float # so the Float type exists colors(args: ColorArgs): [Color!]! colors2(args: ColorArgs): [Color2!]! } diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/get_record_field_value_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/get_record_field_value_spec.rb index fbfbd76b..d41c3a3a 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/get_record_field_value_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/get_record_field_value_spec.rb @@ -27,6 +27,7 @@ module Resolvers end schema.object_type "Person" do |t| + t.field "id", "ID" t.field "name", "String" t.field "identifiers", "PersonIdentifiers" t.field "ssn", "String", name_in_index: "identifiers.ssn", graphql_only: true @@ -36,6 +37,7 @@ module Resolvers t.field "nicknames", "[String!]" t.field "alt_nicknames", "[String!]", name_in_index: "nicknames", graphql_only: true t.field "doc_count", "Int" + t.index "people" end end end diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/relay_connection/array_adapter_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/relay_connection/array_adapter_spec.rb index 7f01af8e..4dbd78e2 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/relay_connection/array_adapter_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/relay_connection/array_adapter_spec.rb @@ -161,7 +161,9 @@ def build_graphql(**options) def generate_schema_artifacts(**options) super(**options) do |schema| schema.object_type "Widget" do |t| + t.field "id", "ID" t.paginated_collection_field "natural_numbers", "Int" + t.index "widgets" end end end diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/resolvable_value_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/resolvable_value_spec.rb index d16f7075..11e1e84f 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/resolvable_value_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/resolvers/resolvable_value_spec.rb @@ -35,6 +35,10 @@ module Resolvers favorite_quote(truncate_to: Int, foo_bar_bazz: Int): String favorite_quote2(trunc_to: Int): String } + + type Query { + person: Person + } EOS end end diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/enum_value_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/enum_value_spec.rb index 4a560dca..d97c0508 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/enum_value_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/enum_value_spec.rb @@ -12,7 +12,7 @@ module ElasticGraph class GraphQL class Schema - RSpec.describe EnumValue do + RSpec.describe EnumValue, :ensure_no_orphaned_types do it "inspects well" do enum_value = define_schema do |s| s.enum_type "ColorSpace" do |t| diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/field_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/field_spec.rb index 26d2808e..aaaeec54 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/field_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/field_spec.rb @@ -12,7 +12,7 @@ module ElasticGraph class GraphQL class Schema - RSpec.describe Field do + RSpec.describe Field, :ensure_no_orphaned_types do it "exposes the name as a lowercase symbol" do field = define_schema do |schema| schema.object_type "Color" do |t| diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/type_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/type_spec.rb index 95e72c27..991cd419 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/type_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema/type_spec.rb @@ -12,7 +12,7 @@ module ElasticGraph class GraphQL class Schema - RSpec.describe Type do + RSpec.describe Type, :ensure_no_orphaned_types do it "exposes the name as a capitalized symbol" do type = define_schema do |schema| schema.object_type "Color" @@ -102,15 +102,6 @@ class Schema t.field "node", "Color!" end - # This must be raw SDL because our schema definition API provides no way to define custom `input` types--it - # generates them based on our indexed types. Using `raw_sdl` lets us define what the test expects. We may want - # to update what the test expects to use generated filter types in the future so we don't have to use `raw_sdl` - # here. - %w[Some PersonEdge PersonConnection].each do |type| - schema.raw_sdl "input #{type}FilterInput { foo: Int }" - schema.raw_sdl "input #{type}ListFilterInput { foo: Int }" - end - schema.object_type "WrappedTypes" do |t| t.field "int", "Int" t.field "non_null_int", "Int!" @@ -147,6 +138,9 @@ class Schema t.field "non_null_list_of_indexed_aggregation", "[PersonAggregation]!", filterable: false, groupable: false do |f| f.mapping type: "object" end + + t.field "id", "ID" + t.index "wrapped_types" end end end @@ -395,9 +389,9 @@ class Schema end it "can model an input type" do - type = schema.type_named(:SomeFilterInput) + type = schema.type_named(:IntFilterInput) - expect(type.name).to eq :SomeFilterInput + expect(type.name).to eq :IntFilterInput expect(type).to only_satisfy_predicates(:nullable?, :object?) expect(type.unwrap_fully).to be type expect(type.unwrap_non_null).to be type diff --git a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema_spec.rb b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema_spec.rb index af71ac60..c15fe71d 100644 --- a/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema_spec.rb +++ b/elasticgraph-graphql/spec/unit/elastic_graph/graphql/schema_spec.rb @@ -11,7 +11,7 @@ module ElasticGraph class GraphQL - RSpec.describe Schema do + RSpec.describe Schema, :ensure_no_orphaned_types do it "can be instantiated with directives that have custom scalar arguments" do define_schema do |schema| schema.scalar_type "_FieldSet" do |t| @@ -79,17 +79,9 @@ class GraphQL s.object_type "Color" end - expect(schema.defined_types).to include( - schema.type_named(:Options), - schema.type_named(:Color), - schema.type_named(:Query) - ).and exclude( - schema.type_named(:Int), - schema.type_named(:Float), - schema.type_named(:Boolean), - schema.type_named(:String), - schema.type_named(:ID) - ) + expect(schema.defined_types).to all be_a Schema::Type + expect(schema.defined_types.map(&:name)).to include(:Options, :Color, :Query) + .and exclude(:Int, :Float, :Boolean, :String, :ID) end end