File: errors_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 (332 lines) | stat: -rw-r--r-- 9,719 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
# frozen_string_literal: true
require "spec_helper"

describe "GraphQL::Execution::Errors" do
  class ParentErrorsTestSchema < GraphQL::Schema
    class ErrorD < RuntimeError; end

    rescue_from(ErrorD) do |err, obj, args, ctx, field|
      raise GraphQL::ExecutionError, "ErrorD on #{obj.inspect} at #{field ? "#{field.path}(#{args})" : "boot"}"
    end
  end

  class ErrorsTestSchema < ParentErrorsTestSchema
    ErrorD = ParentErrorsTestSchema::ErrorD
    class ErrorA < RuntimeError; end
    class ErrorB < RuntimeError; end

    class ErrorC < RuntimeError
      attr_reader :value
      def initialize(value:)
        @value = value
        super
      end
    end

    class ErrorASubclass < ErrorA; end
    class ErrorBChildClass < ErrorB; end
    class ErrorBGrandchildClass < ErrorBChildClass; end

    rescue_from(ErrorA) do |err, obj, args, ctx, field|
      ctx[:errors] << "#{err.message} (#{field.owner.name}.#{field.graphql_name}, #{obj.inspect}, #{args.inspect})"
      nil
    end

    rescue_from(ErrorBChildClass) do |*|
      "Handled ErrorBChildClass"
    end

    # Trying to assert that _specificity_ takes priority
    # over sequence, but the stability of that assertion
    # depends on the underlying implementation.
    rescue_from(ErrorBGrandchildClass) do |*|
      "Handled ErrorBGrandchildClass"
    end

    rescue_from(ErrorB) do |*|
      raise GraphQL::ExecutionError, "boom!"
    end

    rescue_from(ErrorC) do |err, *|
      err.value
    end

    class ErrorList < Array
      def each
        raise ErrorB
      end
    end

    class Thing < GraphQL::Schema::Object
      def self.authorized?(obj, ctx)
        if ctx[:authorized] == false
          raise ErrorD
        end
        true
      end

      field :string, String, null: false
      def string
        "a string"
      end
    end

    class ValuesInput < GraphQL::Schema::InputObject
      argument :value, Int, loads: Thing

      def self.object_from_id(type, value, ctx)
        if value == 1
          :thing
        else
          raise ErrorD
        end
      end
    end

    class PickyString < GraphQL::Schema::Scalar
      def self.coerce_input(value, ctx)
        if value == "picky"
          value
        else
          raise ErrorB, "The string wasn't \"picky\""
        end
      end
    end

    class Query < GraphQL::Schema::Object
      field :f1, Int do
        argument :a1, Int, required: false
      end

      def f1(a1: nil)
        raise ErrorA, "f1 broke"
      end

      field :f2, Int
      def f2
        -> { raise ErrorA, "f2 broke" }
      end

      field :f3, Int

      def f3
        raise ErrorB
      end

      field :f4, Int, null: false
      def f4
        raise ErrorC.new(value: 20)
      end

      field :f5, Int
      def f5
        raise ErrorASubclass, "raised subclass"
      end

      field :f6, Int
      def f6
        -> { raise ErrorB }
      end

      field :f7, String
      def f7
        raise ErrorBGrandchildClass
      end

      field :f8, String do
        argument :input, PickyString
      end

      def f8(input:)
        input
      end

      field :f9, String do
        argument :thing_id, ID, loads: Thing
      end

      def f9(thing:)
        thing[:id]
      end

      field :thing, Thing
      def thing
        :thing
      end

      field :input_field, Int do
        argument :values, ValuesInput
      end

      field :non_nullable_array, [String], null: false
      def non_nullable_array
        [nil]
      end

      field :error_in_each, [Int]

      def error_in_each
        ErrorList.new
      end
    end

    query(Query)
    lazy_resolve(Proc, :call)

    def self.object_from_id(id, ctx)
      if id == "boom"
        raise ErrorB
      end

      { thing: true, id: id }
    end

    def self.resolve_type(type, obj, ctx)
      Thing
    end
  end

  class ErrorsTestSchemaWithoutInterpreter < GraphQL::Schema
    class Query < GraphQL::Schema::Object
      field :non_nullable_array, [String], null: false
      def non_nullable_array
        [nil]
      end
    end

    query(Query)
  end

  describe "rescue_from handling" do
    it "can replace values with `nil`" do
      ctx = { errors: [] }
      res = ErrorsTestSchema.execute "{ f1(a1: 1) }", context: ctx, root_value: :abc
      assert_equal({ "data" => { "f1" => nil } }, res)
      assert_equal ["f1 broke (ErrorsTestSchema::Query.f1, :abc, {:a1=>1})"], ctx[:errors]
    end

    it "rescues errors from lazy code" do
      ctx = { errors: [] }
      res = ErrorsTestSchema.execute("{ f2 }", context: ctx)
      assert_equal({ "data" => { "f2" => nil } }, res)
      assert_equal ["f2 broke (ErrorsTestSchema::Query.f2, nil, {})"], ctx[:errors]
    end

    it "picks the most specific handler and uses the return value from it" do
      res = ErrorsTestSchema.execute("{ f7 }")
      assert_equal({ "data" => { "f7" => "Handled ErrorBGrandchildClass" } }, res)
    end

    it "rescues errors from lazy code with handlers that re-raise" do
      res = ErrorsTestSchema.execute("{ f6 }")
      expected_error = {
        "message"=>"boom!",
        "locations"=>[{"line"=>1, "column"=>3}],
        "path"=>["f6"]
      }
      assert_equal({ "data" => { "f6" => nil }, "errors" => [expected_error] }, res)
    end

    it "can raise new errors" do
      res = ErrorsTestSchema.execute("{ f3 }")
      expected_error = {
        "message"=>"boom!",
        "locations"=>[{"line"=>1, "column"=>3}],
        "path"=>["f3"]
      }
      assert_equal({ "data" => { "f3" => nil }, "errors" => [expected_error] }, res)
    end

    it "can replace values with non-nil" do
      res = ErrorsTestSchema.execute("{ f4 }")
      assert_equal({ "data" => { "f4" => 20 } }, res)
    end

    it "rescues subclasses" do
      context = { errors: [] }
      res = ErrorsTestSchema.execute("{ f5 }", context: context)
      assert_equal({ "data" => { "f5" => nil } }, res)
      assert_equal ["raised subclass (ErrorsTestSchema::Query.f5, nil, {})"], context[:errors]
    end

    describe "errors raised when coercing inputs" do
      it "rescues them" do
        res1 = ErrorsTestSchema.execute("{ f8(input: \"picky\") }")
        assert_equal "picky", res1["data"]["f8"]
        res2 = ErrorsTestSchema.execute("{ f8(input: \"blah\") }")
        assert_equal ["errors"], res2.keys
        assert_equal ["boom!"], res2["errors"].map { |e| e["message"] }

        res3 = ErrorsTestSchema.execute("query($v: PickyString!) { f8(input: $v) }", variables: { v: "blah" })
        assert_equal ["errors"], res3.keys
        assert_equal ["Variable $v of type PickyString! was provided invalid value"], res3["errors"].map { |e| e["message"] }
        assert_equal [["boom!"]], res3["errors"].map { |e| e["extensions"]["problems"].map { |pr| pr["explanation"] } }
      end
    end

    describe "errors raised when loading objects from ID" do
      it "rescues them" do
        res1 = ErrorsTestSchema.execute("{ f9(thingId: \"abc\") }")
        assert_equal "abc", res1["data"]["f9"]

        res2 = ErrorsTestSchema.execute("{ f9(thingId: \"boom\") }")
        assert_equal ["boom!"], res2["errors"].map { |e| e["message"] }
      end
    end

    describe "errors raised in authorized hooks" do
      it "rescues them" do
        context = { authorized: false }
        res = ErrorsTestSchema.execute(" { thing { string } } ", context: context)
        assert_equal ["ErrorD on nil at Query.thing({})"], res["errors"].map { |e| e["message"] }
      end
    end

    describe "errors raised in input_object loads" do
      it "rescues them from literal values" do
        context = { authorized: false }
        res = ErrorsTestSchema.execute(" { inputField(values: { value: 2 }) } ", root_value: :root, context: context)
        # It would be better to have the arguments here, but since this error was raised during _creation_ of keywords,
        # so the runtime arguments aren't available now.
        assert_equal ["ErrorD on :root at Query.inputField()"], res["errors"].map { |e| e["message"] }
      end

      it "rescues them from variable values" do
        context = { authorized: false }
        res = ErrorsTestSchema.execute(
          "query($values: ValuesInput!) { inputField(values: $values) } ",
          variables: { values: { "value" => 2 } },
          context: context,
        )

        assert_equal ["ErrorD on nil at Query.inputField()"], res["errors"].map { |e| e["message"] }
      end
    end

    describe "errors raised in non_nullable_array loads" do
      it "outputs the appropriate error message when using non-interpreter schema" do
        res = ErrorsTestSchemaWithoutInterpreter.execute("{ nonNullableArray }")
        expected_error = {
          "message" => "Cannot return null for non-nullable field Query.nonNullableArray"
        }
        assert_equal({ "data" => nil, "errors" => [expected_error] }, res)
      end

      it "outputs the appropriate error message when using interpreter schema" do
        res = ErrorsTestSchema.execute("{ nonNullableArray }")
        expected_error = {
          "message" => "Cannot return null for non-nullable field Query.nonNullableArray"
        }
        assert_equal({ "data" => nil, "errors" => [expected_error] }, res)
      end
    end

    describe "when .each on a list type raises an error" do
      it "rescues it properly" do
        res = ErrorsTestSchema.execute("{ __typename errorInEach }")
        expected_error = { "message" => "boom!", "locations"=>[{"line"=>1, "column"=>14}], "path"=>["errorInEach"] }
        assert_equal({ "data" => { "__typename" => "Query", "errorInEach" => nil }, "errors" => [expected_error] }, res)
      end
    end
  end
end