graphql 1.5.15 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +4 -19
  3. data/lib/graphql/analysis/analyze_query.rb +27 -2
  4. data/lib/graphql/analysis/query_complexity.rb +10 -11
  5. data/lib/graphql/argument.rb +7 -6
  6. data/lib/graphql/backwards_compatibility.rb +47 -0
  7. data/lib/graphql/compatibility/execution_specification.rb +14 -0
  8. data/lib/graphql/compatibility/execution_specification/specification_schema.rb +6 -1
  9. data/lib/graphql/compatibility/lazy_execution_specification.rb +19 -0
  10. data/lib/graphql/compatibility/lazy_execution_specification/lazy_schema.rb +15 -6
  11. data/lib/graphql/directive.rb +1 -6
  12. data/lib/graphql/execution.rb +1 -0
  13. data/lib/graphql/execution/execute.rb +174 -160
  14. data/lib/graphql/execution/field_result.rb +5 -1
  15. data/lib/graphql/execution/lazy.rb +2 -2
  16. data/lib/graphql/execution/lazy/resolve.rb +8 -11
  17. data/lib/graphql/execution/multiplex.rb +134 -0
  18. data/lib/graphql/execution/selection_result.rb +5 -0
  19. data/lib/graphql/field.rb +1 -8
  20. data/lib/graphql/filter.rb +53 -0
  21. data/lib/graphql/internal_representation/node.rb +11 -6
  22. data/lib/graphql/internal_representation/rewrite.rb +3 -3
  23. data/lib/graphql/query.rb +160 -78
  24. data/lib/graphql/query/arguments.rb +14 -25
  25. data/lib/graphql/query/arguments_cache.rb +6 -13
  26. data/lib/graphql/query/context.rb +28 -10
  27. data/lib/graphql/query/executor.rb +1 -0
  28. data/lib/graphql/query/literal_input.rb +10 -4
  29. data/lib/graphql/query/null_context.rb +1 -1
  30. data/lib/graphql/query/serial_execution/field_resolution.rb +5 -1
  31. data/lib/graphql/query/validation_pipeline.rb +12 -7
  32. data/lib/graphql/query/variables.rb +1 -1
  33. data/lib/graphql/rake_task.rb +140 -0
  34. data/lib/graphql/relay/array_connection.rb +29 -48
  35. data/lib/graphql/relay/base_connection.rb +9 -7
  36. data/lib/graphql/relay/mutation.rb +0 -11
  37. data/lib/graphql/relay/mutation/instrumentation.rb +2 -2
  38. data/lib/graphql/relay/mutation/resolve.rb +7 -10
  39. data/lib/graphql/relay/relation_connection.rb +98 -61
  40. data/lib/graphql/scalar_type.rb +1 -15
  41. data/lib/graphql/schema.rb +90 -25
  42. data/lib/graphql/schema/build_from_definition.rb +22 -23
  43. data/lib/graphql/schema/build_from_definition/resolve_map.rb +70 -0
  44. data/lib/graphql/schema/build_from_definition/resolve_map/default_resolve.rb +47 -0
  45. data/lib/graphql/schema/middleware_chain.rb +1 -1
  46. data/lib/graphql/schema/printer.rb +2 -1
  47. data/lib/graphql/schema/timeout_middleware.rb +6 -6
  48. data/lib/graphql/schema/type_map.rb +1 -1
  49. data/lib/graphql/schema/warden.rb +5 -9
  50. data/lib/graphql/static_validation/definition_dependencies.rb +1 -1
  51. data/lib/graphql/version.rb +1 -1
  52. data/spec/graphql/analysis/analyze_query_spec.rb +2 -2
  53. data/spec/graphql/analysis/max_query_complexity_spec.rb +28 -0
  54. data/spec/graphql/argument_spec.rb +3 -3
  55. data/spec/graphql/execution/lazy_spec.rb +8 -114
  56. data/spec/graphql/execution/multiplex_spec.rb +131 -0
  57. data/spec/graphql/internal_representation/rewrite_spec.rb +10 -0
  58. data/spec/graphql/query/arguments_spec.rb +14 -16
  59. data/spec/graphql/query/context_spec.rb +14 -1
  60. data/spec/graphql/query/literal_input_spec.rb +19 -13
  61. data/spec/graphql/query/variables_spec.rb +1 -1
  62. data/spec/graphql/query_spec.rb +12 -1
  63. data/spec/graphql/rake_task_spec.rb +57 -0
  64. data/spec/graphql/relay/array_connection_spec.rb +24 -3
  65. data/spec/graphql/relay/connection_instrumentation_spec.rb +23 -0
  66. data/spec/graphql/relay/mutation_spec.rb +2 -10
  67. data/spec/graphql/relay/page_info_spec.rb +2 -2
  68. data/spec/graphql/relay/relation_connection_spec.rb +167 -3
  69. data/spec/graphql/schema/build_from_definition_spec.rb +93 -19
  70. data/spec/graphql/schema/warden_spec.rb +80 -0
  71. data/spec/graphql/schema_spec.rb +26 -2
  72. data/spec/spec_helper.rb +4 -2
  73. data/spec/support/lazy_helpers.rb +152 -0
  74. data/spec/support/star_wars/schema.rb +23 -0
  75. metadata +28 -3
  76. data/lib/graphql/schema/mask.rb +0 -55
@@ -57,4 +57,27 @@ describe GraphQL::Relay::ConnectionInstrumentation do
57
57
  assert_instance_of GraphQL::Relay::ConnectionResolve, connection_field.resolve_proc
58
58
  assert_instance_of GraphQL::Relay::ConnectionResolve, redefined_connection_field.resolve_proc
59
59
  end
60
+
61
+ describe "after_built_ins instrumentation" do
62
+ it "has access to connection objects" do
63
+ query_str = <<-GRAPHQL
64
+ {
65
+ rebels {
66
+ ships {
67
+ pageInfo {
68
+ __typename
69
+ }
70
+ }
71
+ }
72
+ }
73
+ GRAPHQL
74
+ ctx = { before_built_ins: [], after_built_ins: [] }
75
+ star_wars_query(query_str, {}, context: ctx)
76
+ # The second item is different here:
77
+ # Before the object is wrapped in a connection, the instrumentation sees `Array`
78
+ assert_equal ["StarWars::FactionRecord", "Array", "GraphQL::Relay::ArrayConnection"], ctx[:before_built_ins]
79
+ # After the object is wrapped in a connection, it sees the connection object
80
+ assert_equal ["StarWars::FactionRecord", "GraphQL::Relay::ArrayConnection", "GraphQL::Relay::ArrayConnection"], ctx[:after_built_ins]
81
+ end
82
+ end
60
83
  end
@@ -282,11 +282,7 @@ describe GraphQL::Relay::Mutation do
282
282
 
283
283
  expected = {
284
284
  "data" => {
285
- "introduceShip" => {
286
- "clientMutationId" => "5678",
287
- "shipEdge" => nil,
288
- "faction" => nil,
289
- }
285
+ "introduceShip" => nil,
290
286
  },
291
287
  "errors" => [
292
288
  {
@@ -305,11 +301,7 @@ describe GraphQL::Relay::Mutation do
305
301
 
306
302
  expected = {
307
303
  "data" => {
308
- "introduceShip" => {
309
- "clientMutationId" => "5678",
310
- "shipEdge" => nil,
311
- "faction" => nil,
312
- }
304
+ "introduceShip" => nil,
313
305
  },
314
306
  "errors" => [
315
307
  {
@@ -75,8 +75,8 @@ describe GraphQL::Relay::PageInfo do
75
75
  result = star_wars_query(query_string, "last" => 1, "first" => 1, "before" => cursor_of_last_base)
76
76
  assert_equal(true, get_page_info(result)["hasNextPage"])
77
77
  assert_equal(true, get_page_info(result)["hasPreviousPage"])
78
- assert_equal("Mg==", get_page_info(result)["startCursor"])
79
- assert_equal("Mg==", get_page_info(result)["endCursor"])
78
+ assert_equal("MQ==", get_page_info(result)["startCursor"])
79
+ assert_equal("MQ==", get_page_info(result)["endCursor"])
80
80
  end
81
81
 
82
82
  it "startCursor and endCursor are the cursors of the first and last edge" do
@@ -104,6 +104,25 @@ describe GraphQL::Relay::RelationConnection do
104
104
  assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
105
105
  end
106
106
 
107
+ it 'works with before and after specified together' do
108
+ result = star_wars_query(query_string, "first" => 2)
109
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
110
+
111
+ first_cursor = get_last_cursor(result)
112
+
113
+ # There is no records between before and after if they point to the same cursor
114
+ result = star_wars_query(query_string, "before" => first_cursor, "after" => first_cursor, "last" => 2)
115
+ assert_equal([], get_names(result))
116
+
117
+ result = star_wars_query(query_string, "after" => first_cursor, "first" => 2)
118
+ assert_equal(["Headquarters"], get_names(result))
119
+
120
+ second_cursor = get_last_cursor(result)
121
+
122
+ result = star_wars_query(query_string, "after" => first_cursor, "before" => second_cursor, "first" => 3)
123
+ assert_equal([], get_names(result))
124
+ end
125
+
107
126
  it 'handles cursors beyond the bounds of the array' do
108
127
  overreaching_cursor = Base64.strict_encode64("100")
109
128
  result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
@@ -186,18 +205,19 @@ describe GraphQL::Relay::RelationConnection do
186
205
  end
187
206
 
188
207
  it "applies to queries by `last`" do
189
- last_cursor = "Ng=="
190
208
  second_to_last_two_names = ["Death Star", "Shield Generator"]
209
+ first_and_second_names = ["Yavin", "Echo Base"]
210
+
211
+ last_cursor = "Ng=="
191
212
  result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
192
213
  assert_equal(second_to_last_two_names, get_names(result))
193
214
  assert_equal(true, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"])
194
215
 
195
216
  result = star_wars_query(query_string, "before" => last_cursor)
196
- assert_equal(second_to_last_two_names, get_names(result))
217
+ assert_equal(first_and_second_names, get_names(result))
197
218
  assert_equal(false, result["data"]["empire"]["bases"]["pageInfo"]["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
198
219
 
199
220
  third_cursor = "Mw=="
200
- first_and_second_names = ["Yavin", "Echo Base"]
201
221
  result = star_wars_query(query_string, "last" => 100, "before" => third_cursor)
202
222
  assert_equal(first_and_second_names, get_names(result))
203
223
 
@@ -400,4 +420,148 @@ describe GraphQL::Relay::RelationConnection do
400
420
  connection = GraphQL::Relay::BaseConnection.connection_for_nodes(relation)
401
421
  assert_equal GraphQL::Relay::RelationConnection, connection
402
422
  end
423
+
424
+ describe "for an ActiveRecord::Relation" do
425
+ describe "#edge_nodes" do
426
+ it "returns the nodes for the current page" do
427
+ # Offset
428
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), {})
429
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes,
430
+
431
+ cursor1 = connection.cursor_from_node(StarWars::Base.find(3))
432
+ cursor2 = connection.cursor_from_node(StarWars::Base.find(4))
433
+ cursor3 = connection.cursor_from_node(StarWars::Base.find(5))
434
+ cursor4 = connection.cursor_from_node(StarWars::Base.find(6))
435
+
436
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 3 })
437
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
438
+
439
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(3))
440
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
441
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
442
+
443
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { last: 3 })
444
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
445
+
446
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
447
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
448
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
449
+
450
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { last: 2 })
451
+ assert_equal [StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
452
+
453
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
454
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
455
+
456
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 3, last: 1 })
457
+ assert_equal [StarWars::Base.find(5)], connection.edge_nodes
458
+
459
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
460
+
461
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { first: 2, last: 1 })
462
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
463
+
464
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
465
+
466
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1 })
467
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5), StarWars::Base.find(6)], connection.edge_nodes
468
+
469
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
470
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
471
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(6))
472
+
473
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor1 })
474
+ assert_equal [], connection.edge_nodes
475
+
476
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor3 })
477
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
478
+
479
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
480
+
481
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2), { after: cursor1, before: cursor4 })
482
+ assert_equal [StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
483
+
484
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(4))
485
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(5))
486
+
487
+
488
+ # Limit
489
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), {})
490
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2), StarWars::Base.find(3), StarWars::Base.find(4), StarWars::Base.find(5)], connection.edge_nodes
491
+
492
+ cursor1 = connection.cursor_from_node(StarWars::Base.find(1))
493
+ cursor2 = connection.cursor_from_node(StarWars::Base.find(2))
494
+ cursor3 = connection.cursor_from_node(StarWars::Base.find(3))
495
+ cursor4 = connection.cursor_from_node(StarWars::Base.find(4))
496
+
497
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2 })
498
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2)], connection.edge_nodes
499
+
500
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
501
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
502
+
503
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2, last: 1 })
504
+ assert_equal [StarWars::Base.find(2)], connection.edge_nodes
505
+
506
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
507
+
508
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor2, first: 2 })
509
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
510
+
511
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
512
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(4))
513
+
514
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor2, first: 2, last: 1 })
515
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
516
+
517
+ assert_equal cursor4, connection.cursor_from_node(StarWars::Base.find(4))
518
+
519
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 2, last: 5 })
520
+ assert_equal [StarWars::Base.find(1), StarWars::Base.find(2)], connection.edge_nodes
521
+
522
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
523
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
524
+
525
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { first: 1, last: 5 })
526
+ assert_equal [StarWars::Base.find(1)], connection.edge_nodes
527
+
528
+ assert_equal cursor1, connection.cursor_from_node(StarWars::Base.find(1))
529
+
530
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor1 })
531
+ assert_equal [], connection.edge_nodes
532
+
533
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor3 })
534
+ assert_equal [StarWars::Base.find(2)], connection.edge_nodes
535
+
536
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
537
+
538
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { after: cursor1, before: cursor4 })
539
+ assert_equal [StarWars::Base.find(2), StarWars::Base.find(3)], connection.edge_nodes
540
+
541
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
542
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
543
+
544
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.limit(5), { last: 2, before: cursor4 })
545
+ assert_equal [StarWars::Base.find(2), StarWars::Base.find(3)], connection.edge_nodes
546
+
547
+ assert_equal cursor2, connection.cursor_from_node(StarWars::Base.find(2))
548
+ assert_equal cursor3, connection.cursor_from_node(StarWars::Base.find(3))
549
+
550
+
551
+ # Limit and offset
552
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2 })
553
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
554
+
555
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2, last: 1 })
556
+ assert_equal [StarWars::Base.find(4)], connection.edge_nodes
557
+
558
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 2, last: 5 })
559
+ assert_equal [StarWars::Base.find(3), StarWars::Base.find(4)], connection.edge_nodes
560
+
561
+ connection = GraphQL::Relay::RelationConnection.new(StarWars::Base.offset(2).limit(3), { first: 1, last: 5 })
562
+ assert_equal [StarWars::Base.find(3)], connection.edge_nodes
563
+
564
+ end
565
+ end
566
+ end
403
567
  end
@@ -701,6 +701,90 @@ SCHEMA
701
701
  end
702
702
  end
703
703
 
704
+ describe "executable schema with resolver maps" do
705
+ class Something
706
+ def capitalize(args)
707
+ args[:word].upcase
708
+ end
709
+ end
710
+
711
+ let(:definition) {
712
+ <<-GRAPHQL
713
+ scalar Date
714
+ scalar UndefinedScalar
715
+ type Something { capitalize(word:String!): String }
716
+ type A { a: String }
717
+ type B { b: String }
718
+ union Thing = A | B
719
+ type Query {
720
+ hello: Something
721
+ thing: Thing
722
+ add_week(in: Date!): Date!
723
+ undefined_scalar(str: String, int: Int): UndefinedScalar
724
+ }
725
+ GRAPHQL
726
+ }
727
+
728
+ let(:resolvers) {
729
+ {
730
+ Date: {
731
+ coerce_input: ->(val, ctx) {
732
+ Time.at(Float(val))
733
+ },
734
+ coerce_result: ->(val, ctx) {
735
+ val.to_f
736
+ }
737
+ },
738
+ resolve_type: ->(obj, ctx) {
739
+ return ctx.schema.types['A']
740
+ },
741
+ Query: {
742
+ add_week: ->(o,a,c) {
743
+ raise "No Time" unless a[:in].is_a? Time
744
+ a[:in]
745
+ },
746
+ hello: ->(o,a,c) {
747
+ Something.new
748
+ },
749
+ thing: ->(o,a,c) {
750
+ OpenStruct.new({a: "a"})
751
+ },
752
+ undefined_scalar: ->(o,a,c) {
753
+ a.values.first
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ let(:schema) { GraphQL::Schema.from_definition(definition, default_resolve: resolvers) }
760
+
761
+ it "resolves unions" do
762
+ result = schema.execute("query { thing { ... on A { a } } }")
763
+ assert_equal(result.to_json,'{"data":{"thing":{"a":"a"}}}')
764
+ end
765
+
766
+ it "resolves scalars" do
767
+ result = schema.execute("query { add_week(in: 392277600.0) }")
768
+ assert_equal(result.to_json,'{"data":{"add_week":392277600.0}}')
769
+ end
770
+
771
+ it "passes args from graphql to the object" do
772
+ result = schema.execute("query { hello { capitalize(word: \"hello\") }}")
773
+ assert_equal(result.to_json,'{"data":{"hello":{"capitalize":"HELLO"}}}')
774
+ end
775
+
776
+ it "handles undefined scalar resolution with identity function" do
777
+ result = schema.execute <<-GRAPHQL
778
+ {
779
+ str: undefined_scalar(str: "abc")
780
+ int: undefined_scalar(int: 123)
781
+ }
782
+ GRAPHQL
783
+
784
+ assert_equal({ "str" => "abc", "int" => 123 }, result["data"])
785
+ end
786
+ end
787
+
704
788
  describe "executable schemas from string" do
705
789
  let(:schema_defn) {
706
790
  <<-GRAPHQL
@@ -738,7 +822,7 @@ SCHEMA
738
822
  assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
739
823
  end
740
824
 
741
- describe "hash of resolvers" do
825
+ describe "hash of resolvers with defaults" do
742
826
  let(:todos) { [Todo.new("Pay the bills.")] }
743
827
  let(:schema) { GraphQL::Schema.from_definition(schema_defn, default_resolve: resolve_hash) }
744
828
  let(:resolve_hash) {
@@ -753,26 +837,16 @@ SCHEMA
753
837
  }
754
838
  h
755
839
  }
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
840
 
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
841
+ let(:base_hash) {
842
+ # Fallback is to resolve by sending the field name
843
+ Hash.new { |h, k| h[k] = Hash.new { |h2, k2| ->(obj, args, ctx) { obj.public_send(k2) } } }
844
+ }
768
845
 
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
846
+ it "accepts a hash of resolve functions" do
847
+ schema.execute("mutation { todoAdd: todo_add(text: \"Buy Milk\") { text } }", context: {context_value: "bar"}, root_value: todos)
848
+ result = schema.execute("query { allTodos: all_todos { text, from_context } }", root_value: todos)
849
+ assert_equal(result.to_json, '{"data":{"allTodos":[{"text":"Pay the bills.","from_context":null},{"text":"Buy Milk","from_context":"bar"}]}}')
776
850
  end
777
851
  end
778
852
 
@@ -103,11 +103,25 @@ module MaskHelpers
103
103
  end
104
104
  end
105
105
 
106
+ module FilterInstrumentation
107
+ def self.before_query(query)
108
+ if query.context[:filters]
109
+ query.merge_filters(
110
+ only: query.context[:filters][:only],
111
+ except: query.context[:filters][:except],
112
+ )
113
+ end
114
+ end
115
+
116
+ def self.after_query(q); end
117
+ end
118
+
106
119
  Schema = GraphQL::Schema.define do
107
120
  query QueryType
108
121
  mutation MutationType
109
122
  subscription MutationType
110
123
  resolve_type ->(obj, ctx) { PhonemeType }
124
+ instrument :query, FilterInstrumentation
111
125
  end
112
126
 
113
127
  module Data
@@ -591,4 +605,70 @@ describe GraphQL::Schema::Warden do
591
605
  refute_includes enum_values, "TRILL"
592
606
  end
593
607
  end
608
+
609
+ describe "multiple filters" do
610
+ let(:visible_enum_value) { ->(member, ctx) { !member.metadata[:hidden_enum_value] } }
611
+ let(:visible_abstract_type) { ->(member, ctx) { !member.metadata[:hidden_abstract_type] } }
612
+ let(:hidden_input_object) { ->(member, ctx) { member.metadata[:hidden_input_object_type] } }
613
+ let(:hidden_type) { ->(member, ctx) { member.metadata[:hidden_type] } }
614
+
615
+ let(:query_str) { <<-GRAPHQL
616
+ {
617
+ enum: __type(name: "Manner") { enumValues { name } }
618
+ input: __type(name: "WithinInput") { name }
619
+ abstractType: __type(name: "Grapheme") { interfaces { name } }
620
+ type: __type(name: "Phoneme") { name }
621
+ }
622
+ GRAPHQL
623
+ }
624
+
625
+ describe "multiple filters for execution" do
626
+ it "applies all of them" do
627
+ res = MaskHelpers.run_query(
628
+ query_str,
629
+ only: [visible_enum_value, visible_abstract_type],
630
+ except: [hidden_input_object, hidden_type],
631
+ )
632
+ assert_equal nil, res["data"]["input"]
633
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
634
+ assert_equal 5, enum_values.length
635
+ refute_includes enum_values, "TRILL"
636
+ # These are also filtered out:
637
+ assert_equal 0, res["data"]["abstractType"]["interfaces"].length
638
+ assert_equal nil, res["data"]["type"]
639
+ end
640
+ end
641
+
642
+ describe "adding filters in instrumentation" do
643
+ it "applies only/except filters" do
644
+ filters = {
645
+ only: visible_enum_value,
646
+ except: hidden_input_object,
647
+ }
648
+ res = MaskHelpers.run_query(query_str, context: { filters: filters })
649
+ assert_equal nil, res["data"]["input"]
650
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
651
+ assert_equal 5, enum_values.length
652
+ refute_includes enum_values, "TRILL"
653
+ # These are unaffected:
654
+ assert_includes res["data"]["abstractType"]["interfaces"].map { |i| i["name"] }, "LanguageMember"
655
+ assert_equal "Phoneme", res["data"]["type"]["name"]
656
+ end
657
+
658
+ it "applies multiple filters" do
659
+ filters = {
660
+ only: [visible_enum_value, visible_abstract_type],
661
+ except: [hidden_input_object, hidden_type],
662
+ }
663
+ res = MaskHelpers.run_query(query_str, context: { filters: filters })
664
+ assert_equal nil, res["data"]["input"]
665
+ enum_values = res["data"]["enum"]["enumValues"].map { |v| v["name"] }
666
+ assert_equal 5, enum_values.length
667
+ refute_includes enum_values, "TRILL"
668
+ # These are also filtered out:
669
+ assert_equal 0, res["data"]["abstractType"]["interfaces"].length
670
+ assert_equal nil, res["data"]["type"]
671
+ end
672
+ end
673
+ end
594
674
  end