graphql 1.5.15 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Execution::Multiplex do
5
+ def multiplex(*a)
6
+ LazyHelpers::LazySchema.multiplex(*a)
7
+ end
8
+
9
+ let(:q1) { <<-GRAPHQL
10
+ query Q1 {
11
+ nestedSum(value: 3) {
12
+ value
13
+ nestedSum(value: 7) {
14
+ value
15
+ }
16
+ }
17
+ }
18
+ GRAPHQL
19
+ }
20
+ let(:q2) { <<-GRAPHQL
21
+ query Q2 {
22
+ nestedSum(value: 2) {
23
+ value
24
+ nestedSum(value: 11) {
25
+ value
26
+ }
27
+ }
28
+ }
29
+ GRAPHQL
30
+ }
31
+ let(:q3) { <<-GRAPHQL
32
+ query Q3 {
33
+ listSum(values: [1,2]) {
34
+ nestedSum(value: 3) {
35
+ value
36
+ }
37
+ }
38
+ }
39
+ GRAPHQL
40
+ }
41
+
42
+ let(:queries) { [{query: q1}, {query: q2}, {query: q3}] }
43
+
44
+ describe "multiple queries in the same lazy context" do
45
+ it "runs multiple queries in the same lazy context" do
46
+ expected_data = [
47
+ {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}},
48
+ {"data"=>{"nestedSum"=>{"value"=>14, "nestedSum"=>{"value"=>46}}}},
49
+ {"data"=>{"listSum"=>[{"nestedSum"=>{"value"=>14}}, {"nestedSum"=>{"value"=>14}}]}},
50
+ ]
51
+
52
+ res = multiplex(queries)
53
+ assert_equal expected_data, res
54
+ end
55
+ end
56
+
57
+ describe "when some have validation errors or runtime errors" do
58
+ let(:q1) { " { success: nullableNestedSum(value: 1) { value } }" }
59
+ let(:q2) { " { runtimeError: nullableNestedSum(value: 13) { value } }" }
60
+ let(:q3) { "{
61
+ invalidNestedNull: nullableNestedSum(value: 1) {
62
+ value
63
+ nullableNestedSum(value: 2) {
64
+ nestedSum(value: 13) {
65
+ value
66
+ }
67
+ }
68
+ }
69
+ }" }
70
+ let(:q4) { " { validationError: nullableNestedSum(value: true) }"}
71
+
72
+ it "returns a mix of errors and values" do
73
+ expected_res = [
74
+ {
75
+ "data"=>{"success"=>{"value"=>2}}
76
+ },
77
+ {
78
+ "data"=>{"runtimeError"=>nil},
79
+ "errors"=>[{
80
+ "message"=>"13 is unlucky",
81
+ "locations"=>[{"line"=>1, "column"=>4}],
82
+ "path"=>["runtimeError"]
83
+ }]
84
+ },
85
+ {
86
+ "data"=>{"invalidNestedNull"=>{"value" => 2,"nullableNestedSum" => nil}},
87
+ "errors"=>[{"message"=>"Cannot return null for non-nullable field LazySum.nestedSum"}],
88
+ },
89
+ {
90
+ "errors" => [{
91
+ "message"=>"Objects must have selections (field 'nullableNestedSum' returns LazySum but has no selections)",
92
+ "locations"=>[{"line"=>1, "column"=>4}],
93
+ "fields"=>["query", "validationError"]
94
+ }]
95
+ },
96
+ ]
97
+
98
+ res = multiplex([
99
+ {query: q1},
100
+ {query: q2},
101
+ {query: q3},
102
+ {query: q4},
103
+ ])
104
+ assert_equal expected_res, res
105
+ end
106
+ end
107
+
108
+ describe "context shared by a multiplex run" do
109
+ it "is provided as context:" do
110
+ checks = []
111
+ multiplex(queries, context: { instrumentation_checks: checks })
112
+ assert_equal ["before multiplex 1", "before multiplex 2", "after multiplex 2", "after multiplex 1"], checks
113
+ end
114
+ end
115
+
116
+ describe "instrumenting a multiplex run" do
117
+ it "runs query instrumentation for each query and multiplex-level instrumentation" do
118
+ checks = []
119
+ queries_with_context = queries.map { |q| q.merge(context: { instrumentation_checks: checks }) }
120
+ multiplex(queries_with_context, context: { instrumentation_checks: checks })
121
+ assert_equal [
122
+ "before multiplex 1",
123
+ "before multiplex 2",
124
+ "before Q1", "before Q2", "before Q3",
125
+ "after Q1", "after Q2", "after Q3",
126
+ "after multiplex 2",
127
+ "after multiplex 1",
128
+ ], checks
129
+ end
130
+ end
131
+ end
@@ -148,6 +148,16 @@ describe GraphQL::InternalRepresentation::Rewrite do
148
148
  assert_equal habitats_selection, seasons_selection.parent
149
149
  assert_equal habitats_selection, average_weight_selection.parent
150
150
  end
151
+
152
+ it "tracks field return type" do
153
+ doc = rewrite_result.operation_definitions["getPlant"]
154
+
155
+ assert plant_selection = doc.typed_children[schema.types["Query"]]["plant"]
156
+ assert_equal "Plant", plant_selection.return_type.to_s
157
+
158
+ assert tree_selection = plant_selection.typed_children[schema.types["Fruit"]]
159
+ assert_equal "[Int!]!", tree_selection["color"].return_type.to_s
160
+ end
151
161
  end
152
162
 
153
163
  describe "tracking directives on fragment spreads" do
@@ -26,8 +26,8 @@ describe GraphQL::Query::Arguments do
26
26
  }, argument_definitions: test_input_2.arguments)
27
27
  }
28
28
 
29
- it "returns keys as strings" do
30
- assert_equal(["a", "b", "c"], arguments.keys)
29
+ it "returns keys as strings, with aliases" do
30
+ assert_equal(["a", "b", "inputObject"], arguments.keys)
31
31
  end
32
32
 
33
33
  it "delegates values to values hash" do
@@ -39,11 +39,11 @@ describe GraphQL::Query::Arguments do
39
39
  arguments.each do |key, value|
40
40
  pairs << [key, value]
41
41
  end
42
- assert_equal([["a", 1], ["b", 2], ["c", {"d" => 3, "e" => 4}]], pairs)
42
+ assert_equal([["a", 1], ["b", 2], ["inputObject", {"d" => 3, "e" => 4}]], pairs)
43
43
  end
44
44
 
45
- it "returns original Ruby hash values with to_h" do
46
- assert_equal({ a: 1, b: 2, c: { d: 3, e: 4 } }, arguments.to_h)
45
+ it "returns a stringified, aliased hash with to_h" do
46
+ assert_equal({ "a"=> 1, "b" => 2, "inputObject" => { "d" => 3, "e" => 4 } }, arguments.to_h)
47
47
  end
48
48
 
49
49
  it "yields key, value, and arg_defnition" do
@@ -52,10 +52,11 @@ describe GraphQL::Query::Arguments do
52
52
  value = arg_value.value.is_a?(GraphQL::Query::Arguments) ? arg_value.value.to_h : arg_value.value
53
53
  type_info << [arg_value.key, value, arg_value.definition.type.unwrap.name]
54
54
  end
55
+
55
56
  expected_type_info =[
56
57
  ["a", 1, "Int"],
57
58
  ["b", 2, "Int"],
58
- ["inputObject", { d: 3, e: 4 }, "TestInput1"],
59
+ ["inputObject", { "d" => 3, "e" => 4 }, "TestInput1"],
59
60
  ]
60
61
  assert_equal expected_type_info, type_info
61
62
  end
@@ -65,14 +66,18 @@ describe GraphQL::Query::Arguments do
65
66
  types = {}
66
67
  arguments.each_value do |arg_value|
67
68
  transformed_args[arg_value.key.upcase] = arg_value.value
68
- types[arg_value.key.upcase] = arg_value.definition
69
+ defn = arg_value.definition
70
+ types[arg_value.key.upcase] = defn.redefine(
71
+ name: defn.name.upcase,
72
+ as: defn.as ? defn.as.to_s.upcase : nil,
73
+ )
69
74
  end
70
75
 
71
76
  new_arguments = GraphQL::Query::Arguments.new(transformed_args, argument_definitions: types)
72
77
  expected_hash = {
73
78
  "A" => 1,
74
79
  "B" => 2,
75
- "INPUTOBJECT" => { d: 3 , e: 4 },
80
+ "INPUTOBJECT" => { "d" => 3 , "e" => 4 },
76
81
  }
77
82
  assert_equal expected_hash, new_arguments.to_h
78
83
  end
@@ -176,6 +181,7 @@ describe GraphQL::Query::Arguments do
176
181
  last_args = arg_values.last
177
182
 
178
183
  assert_equal true, last_args.key?(:specialKeyName)
184
+ assert_equal true, last_args.key?("specialKeyName")
179
185
  end
180
186
 
181
187
  it "works from query literals" do
@@ -190,14 +196,6 @@ describe GraphQL::Query::Arguments do
190
196
  assert_equal({"a" => 1, "b" => 2}, last_args.to_h)
191
197
  end
192
198
 
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
-
201
199
  it "works from variables" do
202
200
  variables = { "arg" => { "a" => 1, "d" => nil } }
203
201
  schema.execute("query ArgTest($arg: TestInput){ argTest(d: $arg) }", variables: variables)
@@ -75,8 +75,10 @@ describe GraphQL::Query::Context do
75
75
  describe "empty values" do
76
76
  let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil) }
77
77
 
78
- it "returns nil for any key" do
78
+ it "returns returns nil and reports key? => false" do
79
79
  assert_equal(nil, context[:some_key])
80
+ assert_equal(false, context.key?(:some_key))
81
+ assert_raises(KeyError) { context.fetch(:some_key) }
80
82
  end
81
83
  end
82
84
 
@@ -88,6 +90,17 @@ describe GraphQL::Query::Context do
88
90
  context[:some_key] = "wow!"
89
91
  assert_equal("wow!", context[:some_key])
90
92
  end
93
+
94
+ describe "namespaces" do
95
+ let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: {a: 1}) }
96
+
97
+ it "doesn't conflict with base values" do
98
+ ns = context.namespace(:stuff)
99
+ ns[:b] = 2
100
+ assert_equal({a: 1}, context.to_h)
101
+ assert_equal({b: 2}, context.namespace(:stuff))
102
+ end
103
+ end
91
104
  end
92
105
 
93
106
  describe "accessing context after the fact" do
@@ -8,13 +8,14 @@ describe GraphQL::Query::LiteralInput do
8
8
  query = GraphQL::ObjectType.define do
9
9
  name "Query"
10
10
 
11
- field :addOneToArgumentValue do
11
+ field :addToArgumentValue do
12
12
  type !types.Int
13
13
  argument :value do
14
- type !types.Int
15
- prepare ->(arg) do
14
+ type types.Int
15
+ default_value 3
16
+ prepare ->(arg, ctx) do
16
17
  return GraphQL::ExecutionError.new("Can't return more than 3 digits") if arg > 998
17
- arg + 1
18
+ arg + ctx[:val]
18
19
  end
19
20
  end
20
21
  resolve ->(t, a, c) { a[:value] }
@@ -25,26 +26,31 @@ describe GraphQL::Query::LiteralInput do
25
26
  }
26
27
 
27
28
  it "prepares values from query literals" do
28
- result = schema.execute("{ addOneToArgumentValue(value: 1) }")
29
- assert_equal(result["data"]["addOneToArgumentValue"], 2)
29
+ result = schema.execute("{ addToArgumentValue(value: 1) }", context: { val: 1 })
30
+ assert_equal(result["data"]["addToArgumentValue"], 2)
31
+ end
32
+
33
+ it "prepares default values" do
34
+ result = schema.execute("{ addToArgumentValue }", context: { val: 4 })
35
+ assert_equal(7, result["data"]["addToArgumentValue"])
30
36
  end
31
37
 
32
38
  it "prepares values from variables" do
33
- result = schema.execute("query ($value: Int!) { addOneToArgumentValue(value: $value) }", variables: { "value" => 1} )
34
- assert_equal(result["data"]["addOneToArgumentValue"], 2)
39
+ result = schema.execute("query ($value: Int!) { addToArgumentValue(value: $value) }", variables: { "value" => 1}, context: { val: 2 } )
40
+ assert_equal(result["data"]["addToArgumentValue"], 3)
35
41
  end
36
42
 
37
43
  it "prepares values correctly if called multiple times with different arguments" do
38
- result = schema.execute("{ first: addOneToArgumentValue(value: 1) second: addOneToArgumentValue(value: 2) }")
39
- assert_equal(result["data"]["first"], 2)
40
- assert_equal(result["data"]["second"], 3)
44
+ result = schema.execute("{ first: addToArgumentValue(value: 1) second: addToArgumentValue(value: 2) }", context: { val: 3 })
45
+ assert_equal(result["data"]["first"], 4)
46
+ assert_equal(result["data"]["second"], 5)
41
47
  end
42
48
 
43
49
  it "adds message to errors key if an ExecutionError is returned from the prepare function" do
44
- result = schema.execute("{ addOneToArgumentValue(value: 999) }")
50
+ result = schema.execute("{ addToArgumentValue(value: 999) }")
45
51
  assert_equal(result["errors"][0]["message"], "Can't return more than 3 digits")
46
52
  assert_equal(result["errors"][0]["locations"][0]["line"], 1)
47
- assert_equal(result["errors"][0]["locations"][0]["column"], 25)
53
+ assert_equal(result["errors"][0]["locations"][0]["column"], 22)
48
54
  end
49
55
  end
50
56
  end
@@ -19,7 +19,7 @@ describe GraphQL::Query::Variables do
19
19
  let(:variables) { GraphQL::Query::Variables.new(
20
20
  OpenStruct.new({
21
21
  schema: schema,
22
- warden: GraphQL::Schema::Warden.new(schema.default_mask, schema: schema, context: nil),
22
+ warden: GraphQL::Schema::Warden.new(schema.default_filter, schema: schema, context: nil),
23
23
  }),
24
24
  ast_variables,
25
25
  provided_variables)
@@ -52,9 +52,20 @@ describe GraphQL::Query do
52
52
  variables: query_variables,
53
53
  operation_name: operation_name,
54
54
  max_depth: max_depth,
55
- )
55
+ ).result
56
56
  }
57
57
  end
58
+
59
+ it 'can be assigned later' do
60
+ query = GraphQL::Query.new(
61
+ schema,
62
+ variables: query_variables,
63
+ operation_name: operation_name,
64
+ max_depth: max_depth,
65
+ )
66
+ query.query_string = '{ __type(name: "Cheese") { name } }'
67
+ assert_equal "Cheese", query.result["data"] ["__type"]["name"]
68
+ end
58
69
  end
59
70
 
60
71
  describe "operation_name" do
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ rake_task_schema_defn = <<-GRAPHQL
5
+ type Query {
6
+ allowed(allowed: ID!, excluded: ID!): Int
7
+ excluded(excluded: ID!): Boolean
8
+ ignored: Float
9
+ }
10
+ GRAPHQL
11
+
12
+ RakeTaskSchema = GraphQL::Schema.from_definition(rake_task_schema_defn)
13
+
14
+ # Default task
15
+ GraphQL::RakeTask.new(schema_name: "RakeTaskSchema")
16
+ # Configured task
17
+ GraphQL::RakeTask.new(idl_outfile: "tmp/configured_schema.graphql") do |t|
18
+ t.namespace = "graphql_custom"
19
+ t.load_context = ->(task) { {filtered: true} }
20
+ t.only = ->(member, ctx) { member.is_a?(GraphQL::ScalarType) || (ctx[:filtered] && ["Query", "allowed"].include?(member.name)) }
21
+ t.load_schema = ->(task) { RakeTaskSchema }
22
+ end
23
+
24
+ describe GraphQL::RakeTask do
25
+ describe "default settings" do
26
+ after do
27
+ FileUtils.rm_rf("./schema.json")
28
+ FileUtils.rm_rf("./schema.graphql")
29
+ end
30
+
31
+ it "writes JSON" do
32
+ capture_io do
33
+ Rake::Task["graphql:schema:dump"].invoke
34
+ end
35
+ dumped_json = File.read("./schema.json")
36
+ expected_json = JSON.pretty_generate(RakeTaskSchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY))
37
+ assert_equal(expected_json, dumped_json)
38
+
39
+ dumped_idl = File.read("./schema.graphql")
40
+ expected_idl = rake_task_schema_defn.chomp
41
+ assert_equal(expected_idl, dumped_idl)
42
+ end
43
+ end
44
+
45
+ describe "customized settings" do
46
+ it "writes GraphQL" do
47
+ capture_io do
48
+ Rake::Task["graphql_custom:schema:idl"].invoke
49
+ end
50
+ dumped_idl = File.read("./tmp/configured_schema.graphql")
51
+ expected_idl = "type Query {
52
+ allowed(allowed: ID!): Int
53
+ }"
54
+ assert_equal expected_idl, dumped_idl
55
+ end
56
+ end
57
+ end
@@ -89,6 +89,27 @@ describe GraphQL::Relay::ArrayConnection do
89
89
  assert_equal(false, result["data"]["rebels"]["ships"]["pageInfo"]["hasPreviousPage"])
90
90
  end
91
91
 
92
+ it 'works with before and after specified together' do
93
+ result = star_wars_query(query_string, "first" => 1)
94
+ assert_equal(["X-Wing"], get_names(result))
95
+
96
+ first_cursor = get_last_cursor(result)
97
+
98
+ # There is no records between before and after if they point to the same cursor
99
+ result = star_wars_query(query_string, "before" => first_cursor, "after" => first_cursor, "last" => 2)
100
+ assert_equal([], get_names(result))
101
+
102
+ result = star_wars_query(query_string, "after" => first_cursor, "first" => 2)
103
+ assert_equal(["Y-Wing", "A-Wing"], get_names(result))
104
+
105
+ # After the last result, find the next 2:
106
+ second_cursor = get_last_cursor(result)
107
+
108
+ # There is only 2 results between the cursors
109
+ result = star_wars_query(query_string, "after" => first_cursor, "before" => second_cursor, "first" => 5)
110
+ assert_equal(["Y-Wing", "A-Wing"], get_names(result))
111
+ end
112
+
92
113
  it 'handles cursors beyond the bounds of the array' do
93
114
  overreaching_cursor = Base64.strict_encode64("100")
94
115
  result = star_wars_query(query_string, "after" => overreaching_cursor, "first" => 2)
@@ -157,13 +178,13 @@ describe GraphQL::Relay::ArrayConnection do
157
178
 
158
179
  it "applies to queries by `last`" do
159
180
  last_cursor = "Ng=="
160
- second_to_last_two_names = ["Death Star", "Shield Generator"]
181
+
161
182
  result = star_wars_query(query_string, "last" => 100, "before" => last_cursor)
162
- assert_equal(second_to_last_two_names, get_names(result))
183
+ assert_equal(["Death Star", "Shield Generator"], get_names(result))
163
184
  assert_equal(true, get_page_info(result)["hasPreviousPage"])
164
185
 
165
186
  result = star_wars_query(query_string, "before" => last_cursor)
166
- assert_equal(second_to_last_two_names, get_names(result))
187
+ assert_equal(["Yavin", "Echo Base"], get_names(result))
167
188
  assert_equal(false, get_page_info(result)["hasPreviousPage"], "hasPreviousPage is false when last is not specified")
168
189
 
169
190
  third_cursor = "Mw=="