File: schema_spec.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (384 lines) | stat: -rw-r--r-- 12,539 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# frozen_string_literal: true
require "spec_helper"

describe GraphQL::Schema do
  let(:schema) { Dummy::Schema }
  let(:admin_schema) { Dummy::AdminSchema }
  let(:relay_schema)  { StarWars::Schema }
  let(:empty_schema) { Class.new(GraphQL::Schema) }

  describe "#find" do
    it "finds a member using a string path" do
      field = schema.find("Edible.fatContent")
      assert_equal "fatContent", field.name
    end
  end

  describe "#union_memberships" do
    it "returns a list of unions that include the type" do
      assert_equal [schema.types["Animal"], schema.types["AnimalAsCow"]], schema.union_memberships(schema.types["Cow"])
    end
  end

  describe "#to_document" do
    it "returns the AST for the schema IDL" do
      expected = GraphQL::Language::DocumentFromSchemaDefinition.new(schema).document
      assert_equal expected.to_query_string, schema.to_document.to_query_string
    end
  end

  describe "#root_types" do
    it "returns a list of the schema's root types" do
      assert_equal(
        [
          Dummy::DairyAppQuery,
          Dummy::DairyAppMutation,
          Dummy::Subscription
        ],
        schema.root_types
      )
    end
  end

  describe "#references_to" do
    it "returns a list of Field and Arguments of that type" do
      cow_field = schema.get_field("Query", "cow")
      assert_equal [cow_field], schema.references_to("Cow")
    end

    it "returns an empty list when type is not referenced by any field or argument" do
      assert_equal [], schema.references_to("Goat")
    end
  end

  describe "#to_definition" do
    it "prints out the schema definition" do
      assert_equal schema.to_definition, GraphQL::Schema::Printer.print_schema(schema)
    end
  end

  describe "#resolve_type" do
    describe "when the return value is nil" do
      it "returns nil" do
        result = relay_schema.resolve_type(123, nil, GraphQL::Query::NullContext.instance)
        assert_equal([nil, nil], result)
      end
    end

    describe "when the return value is not a BaseType" do
      it "raises an error " do
        err = assert_raises(RuntimeError) {
          relay_schema.resolve_type(nil, :test_error, GraphQL::Query::NullContext.instance)
        }
        assert_includes err.message, "not_a_type (Symbol)"
      end
    end

    describe "when the hook wasn't implemented" do
      it "raises not implemented" do
        assert_raises(GraphQL::RequiredImplementationMissingError) {
          empty_schema.resolve_type(Class.new(GraphQL::Schema::Union), nil, nil)
        }
      end
    end
  end

  describe "#disable_introspection_entry_points" do
    it "enables entry points by default" do
      refute_empty empty_schema.introspection_system.entry_points
    end

    describe "when disable_introspection_entry_points is configured" do
      let(:schema) do
        Class.new(GraphQL::Schema) do
          disable_introspection_entry_points
        end
      end

      it "clears entry points" do
        assert_empty schema.introspection_system.entry_points
      end
    end
  end

  describe "object_from_id" do
    describe "when the hook wasn't implemented" do
      it "raises not implemented" do
        assert_raises(GraphQL::RequiredImplementationMissingError) {
          empty_schema.object_from_id(nil, nil)
        }
      end
    end
  end

  describe "id_from_object" do
    describe "when the hook wasn't implemented" do
      it "raises not implemented" do
        assert_raises(GraphQL::RequiredImplementationMissingError) {
          empty_schema.id_from_object(nil, nil, nil)
        }
      end
    end

    describe "when a schema is defined with a node field, but no hook" do
      it "raises not implemented" do
        query_type = Class.new(GraphQL::Schema::Object) do
          graphql_name "Query"
          include GraphQL::Types::Relay::HasNodeField
        end

        thing_type = Class.new(GraphQL::Schema::Object) do
          graphql_name "Thing"
          implements GraphQL::Types::Relay::Node
        end

        schema = Class.new(GraphQL::Schema) {
          query(query_type)
          orphan_types(thing_type)
        }

        assert_raises(GraphQL::RequiredImplementationMissingError) {
          schema.execute("{ node(id: \"1\") { id } }")
        }
      end
    end
  end

  describe "directives" do
    describe "when directives are not overwritten" do
      it "contains built-in directives" do
        schema = GraphQL::Schema

        assert_equal ['deprecated', 'include', 'oneOf', 'skip', 'specifiedBy'], schema.directives.keys.sort

        assert_equal GraphQL::Schema::Directive::Deprecated, schema.directives['deprecated']
        assert_equal GraphQL::Schema::Directive::Include, schema.directives['include']
        assert_equal GraphQL::Schema::Directive::Skip, schema.directives['skip']
        assert_equal GraphQL::Schema::Directive::OneOf, schema.directives['oneOf']
        assert_equal GraphQL::Schema::Directive::SpecifiedBy, schema.directives['specifiedBy']
      end
    end
  end

  describe ".from_definition" do
    it "uses BuildFromSchema to build a schema from a definition string" do
      schema = <<-SCHEMA
type Query {
  str: String
}
      SCHEMA

      built_schema = GraphQL::Schema.from_definition(schema)
      assert_equal schema, GraphQL::Schema::Printer.print_schema(built_schema)
    end

    it "builds from a file" do
      schema = GraphQL::Schema.from_definition("spec/support/magic_cards/schema.graphql")
      assert_instance_of Class, schema
      expected_types =  ["Card", "Color", "Expansion", "Printing"]
      assert_equal expected_types, (expected_types & schema.types.keys)
    end
  end

  describe ".from_introspection" do
    let(:schema) {
      # This type would easily be mistaken for a connection... but it's not one.
      db_connection = Class.new(GraphQL::Schema::Object) do
        graphql_name "DatabaseConnection"
        field :name, String, null: false
      end

      query_root = Class.new(GraphQL::Schema::Object) do
        graphql_name 'Query'
        field :str, String
        field :db, db_connection, null: false, connection: false
      end

      Class.new(GraphQL::Schema) do
        query query_root
      end
    }
    let(:schema_json) {
      schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
    }
    it "uses Schema::Loader to build a schema from an introspection result" do
      built_schema = GraphQL::Schema.from_introspection(schema_json)
      assert_equal GraphQL::Schema::Printer.print_schema(schema), GraphQL::Schema::Printer.print_schema(built_schema)
    end
  end

  describe "#instrument" do
    module VariableCountTrace
      def execute_query(query:)
        query.context[:counter] << query.variables.length
        super
      ensure
        query.context[:counter] << :end
      end
    end

    # Use this to assert instrumenters are called as a stack
    module StackCheckTrace
      def execute_query(query:)
        query.context[:counter] << :in
        super
      ensure
        query.context[:counter] << :out
      end
    end

    let(:variable_counts) {
      []
    }

    let(:schema) {
      Class.new(GraphQL::Schema) do
        query_type = Class.new(GraphQL::Schema::Object) do
          graphql_name "Query"
          field :int, Integer do
            argument :value, Integer, required: false
          end

          def int(value:)
            value == 13 ? raise("13 is unlucky") : value
          end
        end

        query(query_type)
        trace_with VariableCountTrace
        trace_with StackCheckTrace
      end
    }

    it "can wrap query execution" do
      schema.execute("query getInt($val: Int = 5){ int(value: $val) } ", context: { counter: variable_counts })
      schema.execute("query getInt($val: Int = 5, $val2: Int = 3){ int(value: $val) int2: int(value: $val2) } ", context: { counter: variable_counts })
      assert_equal [:in, 1, :end, :out, :in, 2, :end, :out], variable_counts
    end

    it "runs even when a runtime error occurs" do
      schema.execute("query getInt($val: Int = 5){ int(value: $val) } ", context: { counter: variable_counts })
      assert_raises(RuntimeError) {
        schema.execute("query getInt($val: Int = 13){ int(value: $val) } ", context: { counter: variable_counts })
      }
      assert_equal [:in, 1, :end, :out, :in, 1, :end, :out], variable_counts
    end
  end

  describe "#lazy? / #lazy_method_name" do
    class LazyObj; end
    class LazyObjChild < LazyObj; end

    let(:schema) {
      query_type = Class.new(GraphQL::Schema::Object) do
        graphql_name "Query"
      end

      Class.new(GraphQL::Schema) do
        query(query_type)
        lazy_resolve(Integer, :itself)
        lazy_resolve(LazyObj, :dup)
      end
    }

    it "returns registered lazy method names by class/superclass, or returns nil" do
      assert_equal :itself, schema.lazy_method_name(68)
      assert_equal true, schema.lazy?(77)
      assert_equal :dup, schema.lazy_method_name(LazyObj.new)
      assert_equal true, schema.lazy?(LazyObj.new)
      assert_equal :dup, schema.lazy_method_name(LazyObjChild.new)
      assert_equal true, schema.lazy?(LazyObjChild.new)
      assert_nil schema.lazy_method_name({})
      assert_equal false, schema.lazy?({})
    end
  end


  describe "#validate" do
    it "returns errors on the query string" do
      errors = schema.validate("{ cheese(id: 1) { flavor flavor: id } }")
      assert_equal 1, errors.length
      assert_equal "Field 'flavor' has a field conflict: flavor or id?", errors.first.message

      errors = schema.validate("{ cheese(id: 1) { flavor id } }")
      assert_equal [], errors
    end

    it "accepts a list of custom rules" do
      custom_rules = GraphQL::StaticValidation::ALL_RULES - [GraphQL::StaticValidation::FragmentsAreNamed]
      errors = schema.validate("fragment on Cheese { id }", rules: custom_rules)
      assert_equal([], errors)
    end

    it "accepts a context hash" do
      context = { admin: false }
      # AdminSchema is a barebones dummy schema, where fields are visible only with context[:admin] == true
      errors = admin_schema.validate('query { adminOnlyMessage }', context: context)
      assert_equal 1, errors.length
      assert_equal("Field 'adminOnlyMessage' doesn't exist on type 'AdminDairyAppQuery'", errors.first.message)

      context = { admin: true }
      errors = admin_schema.validate('query { adminOnlyMessage }', context: context)
      assert_equal([], errors)
    end

    describe "with error limiting" do
      describe("disabled") do
        it "does not limit errors when not enabled" do
          disabled_schema = Class.new(schema) { validate_max_errors(nil) }
          errors = disabled_schema.validate("{ cheese(id: 1) { flavor flavor: id, cow } }")
          messages = errors.map { |e| e.message }
          assert_equal([
            "Field 'flavor' has a field conflict: flavor or id?",
            "Field 'cow' doesn't exist on type 'Cheese'"
          ], messages)
        end
      end
      describe("enabled") do
        it "does limit errors when enabled" do
          enabled_schema = Class.new(schema) { validate_max_errors(1) }
          errors = enabled_schema.validate("{ cheese(id: 1) { flavor flavor: id, cow } }")
          messages = errors.map { |e| e.message }
          assert_equal([
            "Field 'flavor' has a field conflict: flavor or id?",
          ], messages)
        end
      end
    end
  end

  describe "#as_json / #to_json" do
    it "returns the instrospection result" do
      result = schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
      assert_equal result, schema.as_json
      assert_equal result, JSON.parse(schema.to_json)
    end
  end

  describe "#as_json" do
    it "returns a hash" do
      result = schema.execute(GraphQL::Introspection::INTROSPECTION_QUERY)
      assert_equal result.as_json.class, Hash
    end
  end

  describe "#get_field" do
    it "returns fields by type or type name" do
      field = schema.get_field("Cheese", "id")
      assert_instance_of Dummy::BaseField, field
      field_2 = schema.get_field(Dummy::Cheese, "id")
      assert_equal field, field_2
    end
  end

  describe "class-based schemas" do
    it "implements methods" do
      # Not delegated:
      assert_equal Jazz::Query, Jazz::Schema.query
      assert Jazz::Schema.respond_to?(:query)
      # Delegated
      assert_equal [], Jazz::Schema.tracers
      assert Jazz::Schema.respond_to?(:tracers)
    end
  end
end