graphql 1.4.5 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -138,6 +138,26 @@ describe GraphQL::Query::Arguments do
138
138
  1
139
139
  }
140
140
  end
141
+
142
+ field :noArgTest, types.Int do
143
+ resolve ->(obj, args, ctx) {
144
+ arg_values_array << args
145
+ 1
146
+ }
147
+ end
148
+
149
+ field :noDefaultsTest, types.Int do
150
+ argument :a, types.Int
151
+ argument :b, types.Int
152
+ resolve ->(obj, args, ctx) {
153
+ arg_values_array << args
154
+ 1
155
+ }
156
+ resolve ->(obj, args, ctx) {
157
+ arg_values_array << args
158
+ 1
159
+ }
160
+ end
141
161
  end
142
162
 
143
163
  GraphQL::Schema.define(query: query)
@@ -170,6 +190,14 @@ describe GraphQL::Query::Arguments do
170
190
  assert_equal({"a" => 1, "b" => 2}, last_args.to_h)
171
191
  end
172
192
 
193
+ it "uses Field#default_arguments when no args are provided" do
194
+ schema.execute("{ argTest noArgTest noDefaultsTest }")
195
+
196
+ assert schema.query.get_field("argTest").default_arguments.eql?(arg_values[0])
197
+ assert GraphQL::Query::Arguments::NO_ARGS.eql?(arg_values[1])
198
+ assert GraphQL::Query::Arguments::NO_ARGS.eql?(arg_values[2])
199
+ end
200
+
173
201
  it "works from variables" do
174
202
  variables = { "arg" => { "a" => 1, "d" => nil } }
175
203
  schema.execute("query ArgTest($arg: TestInput){ argTest(d: $arg) }", variables: variables)
@@ -33,6 +33,12 @@ describe GraphQL::Query::Variables do
33
33
  end
34
34
 
35
35
  describe "nullable variables" do
36
+ module ObjectWithThingsCount
37
+ def self.thingsCount(args, ctx) # rubocop:disable Style/MethodName
38
+ 1
39
+ end
40
+ end
41
+
36
42
  let(:schema) { GraphQL::Schema.from_definition(%|
37
43
  type Query {
38
44
  thingsCount(ids: [ID!]): Int!
@@ -45,7 +51,7 @@ describe GraphQL::Query::Variables do
45
51
  }
46
52
  |}
47
53
  let(:result) {
48
- schema.execute(query_string, variables: provided_variables, root_value: OpenStruct.new(thingsCount: 1))
54
+ schema.execute(query_string, variables: provided_variables, root_value: ObjectWithThingsCount)
49
55
  }
50
56
 
51
57
  describe "when they are present, but null" do
@@ -420,4 +420,34 @@ describe GraphQL::Query do
420
420
  assert_equal({"cheeseId" => 2}, query.provided_variables)
421
421
  end
422
422
  end
423
+
424
+ describe "parse errors" do
425
+ let(:invalid_query_string) {
426
+ <<-GRAPHQL
427
+ {
428
+ getStuff
429
+ nonsense
430
+ This is broken 1
431
+ }
432
+ GRAPHQL
433
+ }
434
+ it "adds an entry to the errors key" do
435
+ res = schema.execute(" { ")
436
+ assert_equal 1, res["errors"].length
437
+ assert_equal "Unexpected end of document", res["errors"][0]["message"]
438
+ assert_equal [], res["errors"][0]["locations"]
439
+
440
+ res = schema.execute(invalid_query_string)
441
+ assert_equal 1, res["errors"].length
442
+ assert_equal %|Parse error on "1" (INT) at [4, 26]|, res["errors"][0]["message"]
443
+ assert_equal({"line" => 4, "column" => 26}, res["errors"][0]["locations"][0])
444
+ end
445
+
446
+ it "can be configured to raise" do
447
+ raise_schema = schema.redefine(parse_error: ->(err, ctx) { raise err })
448
+ assert_raises(GraphQL::ParseError) {
449
+ raise_schema.execute(invalid_query_string)
450
+ }
451
+ end
452
+ end
423
453
  end
@@ -2,6 +2,15 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe GraphQL::Relay::BaseConnection do
5
+ module Encoder
6
+ module_function
7
+ def encode(str, nonce: false); str; end
8
+ def decode(str, nonce: false); str; end
9
+ end
10
+
11
+ let(:schema) { OpenStruct.new(cursor_encoder: Encoder) }
12
+ let(:context) { OpenStruct.new(schema: schema) }
13
+
5
14
  describe ".connection_for_nodes" do
6
15
  it "resolves most specific connection type" do
7
16
  class SpecialArray < Array; end
@@ -15,16 +24,25 @@ describe GraphQL::Relay::BaseConnection do
15
24
  end
16
25
  end
17
26
 
18
- describe "#context" do
19
- module Encoder
20
- module_function
21
- def encode(str, nonce: false); str; end
22
- def decode(str, nonce: false); str; end
23
- end
27
+ describe "arguments" do
28
+ it "limits pagination args to positive numbers" do
29
+ args = {
30
+ first: 1,
31
+ last: -1,
32
+ }
33
+ conn = GraphQL::Relay::BaseConnection.new([], args, context: context)
34
+ assert_equal 1, conn.first
35
+ assert_equal 0, conn.last
24
36
 
25
- let(:schema) { OpenStruct.new(cursor_encoder: Encoder) }
26
- let(:context) { OpenStruct.new(schema: schema) }
37
+ args = {
38
+ first: nil,
39
+ }
40
+ conn = GraphQL::Relay::BaseConnection.new([], args, context: context)
41
+ assert_equal nil, conn.first
42
+ end
43
+ end
27
44
 
45
+ describe "#context" do
28
46
  it "Has public access to the field context" do
29
47
  conn = GraphQL::Relay::BaseConnection.new([], {}, context: context)
30
48
  assert_equal context, conn.context
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Relay::ConnectionResolve do
5
+ describe "when an execution error is returned" do
6
+ let(:query_string) { <<-GRAPHQL
7
+ query getError($error: String!){
8
+ rebels {
9
+ ships(nameIncludes: $error) {
10
+ edges {
11
+ node {
12
+ name
13
+ }
14
+ }
15
+ }
16
+ }
17
+ }
18
+ GRAPHQL
19
+ }
20
+
21
+ it "adds an error" do
22
+ result = star_wars_query(query_string, { "error" => "error"})
23
+ assert_equal 1, result["errors"].length
24
+ assert_equal "error from within connection", result["errors"][0]["message"]
25
+ end
26
+
27
+ it "adds an error for a lazy error" do
28
+ result = star_wars_query(query_string, { "error" => "lazyError"})
29
+ assert_equal 1, result["errors"].length
30
+ assert_equal "lazy error from within connection", result["errors"][0]["message"]
31
+ end
32
+
33
+ it "adds an error for a lazy raised error" do
34
+ result = star_wars_query(query_string, { "error" => "lazyRaisedError"})
35
+ assert_equal 1, result["errors"].length
36
+ assert_equal "lazy raised error from within connection", result["errors"][0]["message"]
37
+ end
38
+
39
+ it "adds an error for a raised error" do
40
+ result = star_wars_query(query_string, { "error" => "raisedError"})
41
+ assert_equal 1, result["errors"].length
42
+ assert_equal "error raised from within connection", result["errors"][0]["message"]
43
+ end
44
+ end
45
+ end
@@ -56,5 +56,26 @@ describe GraphQL::Relay::ConnectionType do
56
56
  assert_equal ["Yavin", "Echo Base", "Secret Hideout"] , bases["nodes"].map { |e| e["name"] }
57
57
  end
58
58
  end
59
+
60
+
61
+ describe "when an execution error is raised" do
62
+ let(:query_string) {%|
63
+ {
64
+ basesWithNullName {
65
+ edges {
66
+ node {
67
+ name
68
+ }
69
+ }
70
+ }
71
+ }
72
+ |}
73
+
74
+ it "nullifies the parent and adds an error" do
75
+ result = star_wars_query(query_string)
76
+ assert_equal nil, result["data"]["basesWithNullName"]["edges"][0]["node"]
77
+ assert_equal "Boom!", result["errors"][0]["message"]
78
+ end
79
+ end
59
80
  end
60
81
  end
@@ -9,7 +9,21 @@ describe GraphQL::Relay::Node do
9
9
  end
10
10
 
11
11
  describe ".field" do
12
- describe "with custom resolver" do
12
+ describe "with custom definition" do
13
+ it 'creates a field with the custom definition' do
14
+ faction = StarWars::DATA['Faction'][0]
15
+
16
+ node_field = GraphQL::Relay::Node.field do
17
+ name "nod3"
18
+ description "The Relay Node Field"
19
+ resolve ->(_, _ , _) { faction }
20
+ end
21
+
22
+ assert_equal "nod3", node_field.name
23
+ assert_equal "The Relay Node Field", node_field.description
24
+ assert_equal faction, node_field.resolve(nil, { 'id' => '1' }, nil)
25
+ end
26
+
13
27
  it "executes the custom resolve instead of relay default" do
14
28
  id = "resolver_is_hardcoded_so_this_does_not_matter"
15
29
 
@@ -131,7 +145,21 @@ describe GraphQL::Relay::Node do
131
145
  end
132
146
 
133
147
  describe ".plural_identifying_field" do
134
- describe "with custom resolver" do
148
+ describe "with custom definition" do
149
+ it 'creates a field with the custom definition' do
150
+ factions = StarWars::DATA['Faction']
151
+
152
+ node_field = GraphQL::Relay::Node.plural_field do
153
+ name "nodez"
154
+ description "The Relay Nodes Field"
155
+ resolve ->(_, _ , _) { factions }
156
+ end
157
+
158
+ assert_equal "nodez", node_field.name
159
+ assert_equal "The Relay Nodes Field", node_field.description
160
+ assert_equal factions, node_field.resolve_proc.call(nil, { 'ids' => ['1', '2'] }, nil)
161
+ end
162
+
135
163
  it "executes the custom resolve instead of relay default" do
136
164
  id = ["resolver_is_hardcoded_so_this_does_not_matter", "another_id"]
137
165
 
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Relay::RangeAdd do
5
+ # Make sure that the encoder is found through `ctx.schema`:
6
+ module PassThroughEncoder
7
+ def self.encode(plaintext, nonce: false)
8
+ "__#{plaintext}"
9
+ end
10
+
11
+ def self.decode(ciphertext, nonce: false)
12
+ ciphertext[2..-1]
13
+ end
14
+ end
15
+
16
+ let(:schema) {
17
+ menus = [
18
+ OpenStruct.new(
19
+ name: "Los Primos",
20
+ items: [
21
+ OpenStruct.new(name: "California Burrito", price: 699),
22
+ OpenStruct.new(name: "Fish Taco", price: 399),
23
+ ]
24
+ )
25
+ ]
26
+
27
+ item = GraphQL::ObjectType.define do
28
+ name "Item"
29
+ field :price, !types.Int
30
+ field :name, !types.String
31
+ end
32
+ menu = GraphQL::ObjectType.define do
33
+ name "Menu"
34
+ field :name, !types.String
35
+ field :items, !item.connection_type
36
+ end
37
+ query = GraphQL::ObjectType.define do
38
+ name "Query"
39
+ field :menus, types[menu], resolve: Proc.new { menus }
40
+ end
41
+ add_item = GraphQL::Relay::Mutation.define do
42
+ name "AddItem"
43
+ input_field :name, !types.String
44
+ input_field :price, !types.Int
45
+ input_field :menu_idx, !types.Int
46
+
47
+ return_field :item_edge, item.edge_type
48
+ return_field :items, item.connection_type
49
+ return_field :menu, menu
50
+ resolve ->(obj, input, ctx) {
51
+ this_menu = menus[input[:menu_idx]]
52
+ new_item = OpenStruct.new(name: input[:name], price: input[:price])
53
+ this_menu.items << new_item
54
+ range_add = GraphQL::Relay::RangeAdd.new(
55
+ parent: this_menu,
56
+ item: new_item,
57
+ collection: this_menu.items,
58
+ context: ctx,
59
+ )
60
+
61
+ {
62
+ menu: range_add.parent,
63
+ items: range_add.connection,
64
+ item_edge: range_add.edge,
65
+ }
66
+ }
67
+ end
68
+ mutation = GraphQL::ObjectType.define do
69
+ name "Mutation"
70
+ field :add_item, add_item.field
71
+ end
72
+
73
+ GraphQL::Schema.define(query: query, mutation: mutation, cursor_encoder: PassThroughEncoder)
74
+ }
75
+
76
+
77
+ describe "returning Relay objects" do
78
+ let(:query_str) { <<-GRAPHQL
79
+ mutation {
80
+ add_item(input: {name: "Chilaquiles", price: 699, menu_idx: 0}) {
81
+ menu {
82
+ name
83
+ }
84
+ item_edge {
85
+ node {
86
+ name
87
+ price
88
+ }
89
+ }
90
+ items {
91
+ edges {
92
+ node {
93
+ name
94
+ }
95
+ cursor
96
+ }
97
+ }
98
+ }
99
+ }
100
+ GRAPHQL
101
+ }
102
+
103
+ it "returns a connection and an edge" do
104
+ res = schema.execute(query_str)
105
+
106
+ mutation_res = res["data"]["add_item"]
107
+ assert_equal("Los Primos", mutation_res["menu"]["name"])
108
+ assert_equal({"name"=>"Chilaquiles", "price"=>699}, mutation_res["item_edge"]["node"])
109
+ assert_equal(["California Burrito", "Fish Taco", "Chilaquiles"], mutation_res["items"]["edges"].map { |e| e["node"]["name"] })
110
+ assert_equal(["__1", "__2", "__3"], mutation_res["items"]["edges"].map { |e| e["cursor"] })
111
+ end
112
+ end
113
+ end
@@ -700,4 +700,118 @@ SCHEMA
700
700
  assert_equal 'Specified query type "Foo" not found in document.', err.message
701
701
  end
702
702
  end
703
+
704
+ describe "executable schemas from string" do
705
+ let(:schema_defn) {
706
+ <<-GRAPHQL
707
+ type Todo {text: String, from_context: String}
708
+ type Query { all_todos: [Todo]}
709
+ type Mutation { todo_add(text: String!): Todo}
710
+ GRAPHQL
711
+ }
712
+
713
+ Todo = Struct.new(:text, :from_context)
714
+
715
+ class RootResolver
716
+ attr_accessor :todos
717
+
718
+ def initialize
719
+ @todos = [Todo.new("Pay the bills.")]
720
+ end
721
+
722
+ def all_todos
723
+ @todos
724
+ end
725
+
726
+ def todo_add(args, ctx) # this is a method and accepting arguments
727
+ todo = Todo.new(args[:text], ctx[:context_value])
728
+ @todos << todo
729
+ todo
730
+ end
731
+ end
732
+
733
+ it "calls methods with args if args are defined" do
734
+ schema = GraphQL::Schema.from_definition(schema_defn)
735
+ root_values = RootResolver.new
736
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", root_value: root_values, context: {context_value: "bar"})
737
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: root_values)
738
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
739
+ end
740
+
741
+ describe "hash of resolvers" do
742
+ let(:todos) { [Todo.new("Pay the bills.")] }
743
+ let(:schema) { GraphQL::Schema.from_definition(schema_defn, default_resolve: resolve_hash) }
744
+ let(:resolve_hash) {
745
+ h = base_hash
746
+ h["Query"] ||= {}
747
+ h["Query"]["all_todos"] = ->(obj, args, ctx) { obj }
748
+ h["Mutation"] ||= {}
749
+ h["Mutation"]["todo_add"] = ->(obj, args, ctx) {
750
+ todo = Todo.new(args[:text], ctx[:context_value])
751
+ obj << todo
752
+ todo
753
+ }
754
+ h
755
+ }
756
+ describe "with defaults" do
757
+ let(:base_hash) {
758
+ # Fallback is to resolve by sending the field name
759
+ Hash.new { |h, k| h[k] = Hash.new { |h2, k2| ->(obj, args, ctx) { obj.public_send(k2) } } }
760
+ }
761
+
762
+ it "accepts a hash of resolve functions" do
763
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
764
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: todos)
765
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
766
+ end
767
+ end
768
+
769
+ describe "wihtout defaults" do
770
+ let(:base_hash) { {} }
771
+ it "raises a KeyError" do
772
+ assert_raises(KeyError) do
773
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
774
+ end
775
+ end
776
+ end
777
+ end
778
+
779
+ describe "custom resolve behavior" do
780
+ class AppResolver
781
+ def initialize
782
+ @todos = [Todo.new("Pay the bills.")]
783
+ @resolves = {
784
+ "Query" => {
785
+ "all_todos" => ->(obj, args, ctx) { @todos },
786
+ },
787
+ "Mutation" => {
788
+ "todo_add" => ->(obj, args, ctx) {
789
+ todo = Todo.new(args[:text], ctx[:context_value])
790
+ @todos << todo
791
+ todo
792
+ },
793
+ },
794
+ "Todo" => {
795
+ "text" => ->(obj, args, ctx) { obj.text },
796
+ "from_context" => ->(obj, args, ctx) { obj.from_context },
797
+ }
798
+ }
799
+ end
800
+
801
+ def call(type, field, obj, args, ctx)
802
+ @resolves
803
+ .fetch(type.name)
804
+ .fetch(field.name)
805
+ .call(obj, args, ctx)
806
+ end
807
+ end
808
+
809
+ it "accepts a default_resolve callable" do
810
+ schema = GraphQL::Schema.from_definition(schema_defn, default_resolve: AppResolver.new)
811
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"})
812
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }")
813
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
814
+ end
815
+ end
816
+ end
703
817
  end