File: helpers_spec.rb

package info (click to toggle)
ruby-graphql 2.5.19-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 13,868 kB
  • sloc: ruby: 80,420; ansic: 1,808; yacc: 845; javascript: 480; makefile: 6
file content (258 lines) | stat: -rw-r--r-- 9,469 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
# frozen_string_literal: true
require "spec_helper"

describe GraphQL::Testing::Helpers do
  class AssertionsSchema < GraphQL::Schema
    use GraphQL::Schema::Warden if ADD_WARDEN
    class BillSource < GraphQL::Dataloader::Source
      def fetch(students)
        students.map { |s| { amount: 1_000_001 } }
      end
    end

    class TuitionBill < GraphQL::Schema::Object
      def self.visible?(ctx)
        ctx[:current_user]&.admin?
      end

      field :amount_in_cents, Int, hash_key: :amount
    end

    class Transcript < GraphQL::Schema::Object
      def self.authorized?(object, context)
        (current_user = context[:current_user]) &&
            (admin_for = current_user[:admin_for]) &&
            (admin_for.include?(object && object[:name]))
      end

      field :gpa, Float
    end

    class Student < GraphQL::Schema::Object
      def self.authorized?(object, context)
        context.errors.empty?
      end

      field :name, String, extras: [:ast_node] do
        argument :full_name, Boolean, required: false
        argument :prefix, String, required: false, default_value: "Mc", prepare: ->(val, ctx) { -> { val.capitalize } }
      end

      def name(full_name: nil, prefix: nil, ast_node:)
        name = object[:name]
        if full_name
          "#{name} #{ast_node.alias ? "\"#{ast_node.alias}\" " : ""}#{prefix}#{name}"
        else
          name
        end
      end

      field :latest_bill, TuitionBill

      def latest_bill
        dataloader.with(BillSource).load(object)
      end

      field :is_admin_for, Boolean
      def is_admin_for
        (list = context[:admin_for]) && list.include?(object[:name])
      end

      field :transcript, Transcript, resolver_method: :object

      class Upcase < GraphQL::Schema::FieldExtension
        def after_resolve(value:, **rest)
          value.upcase
        end
      end

      field :upcased_name, String, extensions: [Upcase], hash_key: :name

      field :ssn, String do
        def authorized?(obj, args, ctx)
          ctx[:current_user]&.admin?
        end
      end

      field :current_field, String

      def current_field
        context[:current_field].path
      end
    end

    class AuthorizedObject < GraphQL::Schema::Object
      def self.authorized?(object, context)
        context.dataloader.with(BillSource).load(object)[:amount] > 5
      end

      field :id, ID
    end
    class Query < GraphQL::Schema::Object
      field :students, [Student]

      field :student, Student do
        argument :student_id, ID, loads: Student
      end

      def student(student:)
        student
      end

      field :lookahead_selections, String, extras: [:lookahead]

      def lookahead_selections(lookahead:)
        lookahead.selections.to_s
      end

      field :authorized_object, AuthorizedObject
    end

    query(Query)
    use GraphQL::Dataloader
    lazy_resolve Proc, :call
    use GraphQL::Schema::Visibility, profiles: {
      public: { public: true }
    }, dynamic: true

    def self.unauthorized_object(err)
      raise err
    end

    def self.unauthorized_field(err)
      raise err
    end

    def self.object_from_id(id, ctx)
      if id == "s1"
        -> do { name: "Student1", type: Student } end
      else
        raise ArgumentError, "No data for id: #{id.inspect}"
      end
    end

    def self.resolve_type(abs_t, obj, ctx)
      obj.fetch(:type)
    end
  end

  include GraphQL::Testing::Helpers

  let(:admin_context) { { current_user: OpenStruct.new(admin?: true) } }

  describe "top-level helpers" do
    describe "run_graphql_field" do
      it "resolves fields" do
        assert_equal "Blah", run_graphql_field(AssertionsSchema, "Student.name", { name: "Blah" })
        assert_equal "Blah McBlah", run_graphql_field(AssertionsSchema, "Student.name", { name: "Blah" }, arguments: { "fullName" => true })
        assert_equal "Blah McBlah", run_graphql_field(AssertionsSchema, "Student.name", { name: "Blah" }, arguments: { full_name: true })
        assert_equal({ amount: 1_000_001 }, run_graphql_field(AssertionsSchema, "Student.latestBill", :student, context: admin_context))
      end

      it "loads arguments with lazy_resolve" do
        student = run_graphql_field(AssertionsSchema, "Query.student", nil, arguments: { "studentId" => "s1" })
        expected_student = { name: "Student1", type: AssertionsSchema::Student }
        assert_equal(expected_student, student)

        student2 = run_graphql_field(AssertionsSchema, "Query.student", nil, arguments: { student: "s1" })
        assert_equal(expected_student, student2)
      end

      it "works with resolution context" do
        with_resolution_context(AssertionsSchema, object: { name: "Foo" }, type: "Student", context: { admin_for: ["Foo"] }) do |rc|
          rc.run_graphql_field("name")
          rc.run_graphql_field("isAdminFor")
          assert_equal "Student.currentField", rc.run_graphql_field("currentField")
        end
      end

      it "works with a visibility_profile" do
        student2 = run_graphql_field(AssertionsSchema, "Query.student", nil, visibility_profile: :public, arguments: { student: "s1" })
        expected_student = { name: "Student1", type: AssertionsSchema::Student }
        assert_equal(expected_student, student2)

        with_resolution_context(AssertionsSchema, object: {}, type: "Query", context: {}, visibility_profile: :public) do |rc|
          assert_equal :public, rc.visibility_profile
        end
      end

      it "raises an error when the type is hidden" do
        assert_equal 1_000_000, run_graphql_field(AssertionsSchema, "TuitionBill.amountInCents", { amount: 1_000_000 }, context: admin_context)

        err = assert_raises(GraphQL::Testing::Helpers::TypeNotVisibleError) do
          run_graphql_field(AssertionsSchema, "TuitionBill.amountInCents", { amount: 1_000_000 })
        end
        expected_message = "`TuitionBill` should be `visible?` this field resolution and `context`, but it was not"
        assert_equal expected_message, err.message
      end

      it "raises an error when the type isn't authorized" do
        err = assert_raises GraphQL::UnauthorizedError do
          run_graphql_field(AssertionsSchema, "Student.transcript.gpa", { gpa: 3.1 })
        end
        assert_equal "An instance of Hash failed Transcript's authorization check", err.message

        assert_equal 3.1, run_graphql_field(AssertionsSchema, "Student.transcript.gpa", { gpa: 3.1, name: "Jim" }, context: { current_user: OpenStruct.new(admin_for: ["Jim"])})
      end

      it "works with field extensions" do
        assert_equal "BILL", run_graphql_field(AssertionsSchema, "Student.upcasedName", { name: "Bill" })
      end

      it "works with extras: [:ast_node]" do
        assert_equal "Billy \"theKid\" McBilly", run_graphql_field(AssertionsSchema, "Student.name", { name: "Billy" }, arguments: { full_name: true }, ast_node: GraphQL::Language::Nodes::Field.new(name: "name", field_alias: "theKid"))
      end

      it "works with extras: [:lookahead]" do
        assert_equal "[]", run_graphql_field(AssertionsSchema, "Query.lookaheadSelections", :something)
        dummy_lookahead = OpenStruct.new(selections: ["one", "two"])
        assert_equal "[\"one\", \"two\"]", run_graphql_field(AssertionsSchema, "Query.lookaheadSelections", :something, lookahead: dummy_lookahead)
      end

      it "prepares arguments" do
        assert_equal "Blah De Blah", run_graphql_field(AssertionsSchema, "Student.name", { name: "Blah" }, arguments: { full_name: true, prefix: "de " })
      end

      it "handles unauthorized field errors" do
        assert_equal "123-45-6789", run_graphql_field(AssertionsSchema, "Student.ssn", { ssn: "123-45-6789"}, context: admin_context)
        err = assert_raises GraphQL::UnauthorizedFieldError do
          run_graphql_field(AssertionsSchema, "Student.ssn", {})
        end
        assert_equal "An instance of Hash failed AssertionsSchema::Student's authorization check on field ssn", err.message
      end

      it "works when .authorized? calls dataloader" do
        assert_equal "100", run_graphql_field(AssertionsSchema, "AuthorizedObject.id", { id: "100" })
      end

      it "raises when the type doesn't exist" do
        err = assert_raises GraphQL::Testing::Helpers::TypeNotDefinedError do
          run_graphql_field(AssertionsSchema, "Nothing.nothing", :nothing)
        end
        assert_equal "No type named `Nothing` is defined; choose another type name or define this type.", err.message
      end

      it "raises when the field doesn't exist" do
        err = assert_raises GraphQL::Testing::Helpers::FieldNotDefinedError do
          run_graphql_field(AssertionsSchema, "Student.nonsense", :student)
        end
        assert_equal "`Student` has no field named `nonsense`; pick another name or define this field.", err.message
      end
    end
  end

  describe "schema-level helpers" do
    include GraphQL::Testing::Helpers.for(AssertionsSchema)

    it "resolves fields" do
      assert_equal 5, run_graphql_field("TuitionBill.amountInCents", { amount: 5 }, context: admin_context)
    end

    it "works with resolution context" do
      with_resolution_context(object: { name: "Foo" }, type: "Student", context: { admin_for: ["Bar"] }) do |rc|
        assert_equal "Foo", rc.run_graphql_field("name")
        assert_equal false, rc.run_graphql_field("isAdminFor")
      end
    end
  end
end