Skip to content

Commit 20dfa44

Browse files
committed
integration test.
1 parent 4cb6ae5 commit 20dfa44

File tree

8 files changed

+190
-115
lines changed

8 files changed

+190
-115
lines changed

lib/graphql/stitching/client.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@ class Client
1313
# Builds a new client instance. Either `supergraph` or `locations` configuration is required.
1414
# @param supergraph [Supergraph] optional, a pre-composed supergraph that bypasses composer setup.
1515
# @param locations [Hash<Symbol, Hash<Symbol, untyped>>] optional, composer configurations for each graph location.
16-
# @param composer [Composer] optional, a pre-configured composer instance for use with `locations` configuration.
17-
def initialize(locations: nil, supergraph: nil, composer: nil)
16+
# @param composer_options [Hash] optional, composer options for configuring composition.
17+
def initialize(locations: nil, supergraph: nil, composer_options: {})
1818
@supergraph = if locations && supergraph
1919
raise ArgumentError, "Cannot provide both locations and a supergraph."
2020
elsif supergraph && !supergraph.is_a?(Supergraph)
2121
raise ArgumentError, "Provided supergraph must be a GraphQL::Stitching::Supergraph instance."
22+
elsif supergraph && composer_options.any?
23+
raise ArgumentError, "Cannot provide composer options with a pre-built supergraph."
2224
elsif supergraph
2325
supergraph
2426
else
25-
composer ||= Composer.new
27+
composer = Composer.new(**composer_options)
2628
composer.perform(locations)
2729
end
2830

lib/graphql/stitching/directives.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ class Stitch < GraphQL::Schema::Directive
1313

1414
class Visibility < GraphQL::Schema::Directive
1515
graphql_name "visibility"
16-
locations(OBJECT, INTERFACE, UNION, INPUT_OBJECT, ENUM, SCALAR,
17-
FIELD_DEFINITION, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION, ENUM_VALUE)
16+
locations(
17+
OBJECT, INTERFACE, UNION, INPUT_OBJECT, ENUM, SCALAR,
18+
FIELD_DEFINITION, ARGUMENT_DEFINITION, INPUT_FIELD_DEFINITION, ENUM_VALUE
19+
)
1820
argument :profiles, [String, null: false], required: true
1921
end
2022

lib/graphql/stitching/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
module GraphQL
44
module Stitching
5-
VERSION = "1.6.2"
5+
VERSION = "1.6.3"
66
end
77
end

test/graphql/stitching/client_test.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,4 +355,24 @@ def test_errors_trigger_hooks_that_may_return_a_custom_message
355355
assert_nil result["data"]
356356
assert_equal expected_errors, result["errors"]
357357
end
358+
359+
def test_passes_composer_options_through_to_composition
360+
client = GraphQL::Stitching::Client.new(
361+
locations: { products: { schema: Schemas::Example::Products } },
362+
composer_options: { query_name: "SuperQuery" },
363+
)
364+
365+
assert_equal "SuperQuery", client.supergraph.schema.query.graphql_name
366+
end
367+
368+
def test_errors_for_composer_options_given_with_precomposed_supergraph
369+
supergraph = compose_definitions({ "a" => Schemas::Example::Products })
370+
371+
assert_error "Cannot provide composer options with a pre-built supergraph" do
372+
GraphQL::Stitching::Client.new(
373+
supergraph: supergraph,
374+
composer_options: { query_name: "SuperQuery" },
375+
)
376+
end
377+
end
358378
end
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
require_relative "../../../schemas/visibility"
5+
6+
describe 'GraphQL::Stitching, visibility' do
7+
def setup
8+
skip unless GraphQL::Stitching.supports_visibility?
9+
10+
@supergraph = compose_definitions({
11+
"price" => Schemas::Visibility::PriceSchema,
12+
"inventory" => Schemas::Visibility::InventorySchema,
13+
}, {
14+
visibility_profiles: ["public", "private"],
15+
})
16+
17+
@full_query = %|{
18+
sprocket(id: "1") {
19+
id
20+
price
21+
msrp
22+
quantityAvailable
23+
quantityInStock
24+
}
25+
sprockets(ids: ["1"]) {
26+
id
27+
price
28+
msrp
29+
quantityAvailable
30+
quantityInStock
31+
}
32+
}|
33+
34+
@full_record = {
35+
"id" => "1",
36+
"price" => 20.99,
37+
"msrp" => 10.99,
38+
"quantityAvailable" => 23,
39+
"quantityInStock" => 35,
40+
}
41+
end
42+
43+
def test_fully_accessible_with_no_visibility_profile
44+
request = GraphQL::Stitching::Request.new(@supergraph, @full_query, context: {})
45+
assert request.validate.empty?
46+
47+
expected = {
48+
"sprocket" => @full_record,
49+
"sprockets" => [@full_record],
50+
}
51+
52+
assert_equal expected, request.execute.dig("data")
53+
end
54+
55+
def test_no_private_or_hidden_fields_for_public_profile
56+
request = GraphQL::Stitching::Request.new(@supergraph, @full_query, context: {
57+
visibility_profile: "public",
58+
})
59+
60+
expected = [
61+
{ "code" => "undefinedField", "typeName" => "Sprocket", "fieldName" => "id" },
62+
{ "code" => "undefinedField", "typeName" => "Sprocket", "fieldName" => "msrp" },
63+
{ "code" => "undefinedField", "typeName" => "Sprocket", "fieldName" => "quantityInStock" },
64+
{ "code" => "undefinedField", "typeName" => "Query", "fieldName" => "sprockets" },
65+
]
66+
67+
assert_equal expected, request.validate.map(&:to_h).map { _1["extensions"] }
68+
end
69+
70+
def test_no_hidden_fields_for_private_profile
71+
request = GraphQL::Stitching::Request.new(@supergraph, @full_query, context: {
72+
visibility_profile: "private",
73+
})
74+
75+
expected = [
76+
{ "code" => "undefinedField", "typeName" => "Sprocket", "fieldName" => "id" },
77+
{ "code" => "undefinedField", "typeName" => "Sprocket", "fieldName" => "id" },
78+
]
79+
80+
assert_equal expected, request.validate.map(&:to_h).map { _1["extensions"] }
81+
end
82+
83+
def test_accesses_stitched_data_in_public_profile
84+
query = %|{
85+
sprocket(id: "1") {
86+
price
87+
quantityAvailable
88+
}
89+
}|
90+
91+
request = GraphQL::Stitching::Request.new(@supergraph, query, context: {
92+
visibility_profile: "public",
93+
})
94+
95+
expected = {
96+
"sprocket" => {
97+
"price" => 20.99,
98+
"quantityAvailable" => 23,
99+
},
100+
}
101+
102+
assert_equal expected, request.execute.dig("data")
103+
end
104+
105+
def test_accesses_stitched_data_in_private_profile
106+
query = %|{
107+
sprockets(ids: ["1"]) {
108+
price
109+
msrp
110+
quantityAvailable
111+
quantityInStock
112+
}
113+
}|
114+
115+
request = GraphQL::Stitching::Request.new(@supergraph, query, context: {
116+
visibility_profile: "private",
117+
})
118+
119+
expected = {
120+
"sprockets" => [{
121+
"price" => 20.99,
122+
"msrp" => 10.99,
123+
"quantityAvailable" => 23,
124+
"quantityInStock" => 35,
125+
}],
126+
}
127+
128+
assert_equal expected, request.execute.dig("data")
129+
end
130+
end

test/graphql/stitching/supergraph/from_definition_test.rb

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def setup
1818
@schema_sdl = @supergraph.to_definition
1919
end
2020

21+
# is this a composer test now...?
2122
def test_to_definition_annotates_schema
2223
@schema_sdl = squish_string(@schema_sdl)
2324
assert @schema_sdl.include?("directive @key")
@@ -42,16 +43,6 @@ def test_to_definition_annotates_schema
4243
assert @schema_sdl.include?(%|b(id: ID!): T @source(location: "bravo")|)
4344
end
4445

45-
def test_to_definition_annotations_are_idempotent
46-
@supergraph.to_definition
47-
assert_equal 4, @supergraph.schema.get_type("T").directives.length
48-
assert_equal 2, @supergraph.schema.get_type("T").get_field("id").directives.length
49-
50-
@supergraph.to_definition
51-
assert_equal 4, @supergraph.schema.get_type("T").directives.length
52-
assert_equal 2, @supergraph.schema.get_type("T").get_field("id").directives.length
53-
end
54-
5546
def test_from_definition_restores_supergraph
5647
supergraph_import = GraphQL::Stitching::Supergraph.from_definition(@schema_sdl, executables: {
5748
"alpha" => Proc.new { true },
@@ -89,4 +80,4 @@ def test_errors_for_missing_executables
8980
})
9081
end
9182
end
92-
end
83+
end

test/graphql/stitching/supergraph/visibility_test.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
describe "GraphQL::Stitching::Supergraph visibility controls" do
66
def setup
7+
skip unless GraphQL::Stitching.supports_visibility?
8+
79
@exec = { alpha: Proc.new { true } }
810
end
911

test/schemas/visibility.rb

Lines changed: 26 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,59 @@
11
# frozen_string_literal: true
22

33
module Schemas
4-
module VisibilityDirective
5-
class StitchDirective < GraphQL::Schema::Directive
6-
graphql_name "stitch"
7-
locations FIELD_DEFINITION
8-
argument :key, String
9-
repeatable true
10-
end
11-
12-
class VisibilityDirective < GraphQL::Schema::Directive
13-
graphql_name "visibility"
14-
locations FIELD_DEFINITION, ARGUMENT_DEFINITION, ENUM_VALUE, OBJECT
15-
argument :profiles, [String]
16-
end
4+
module Visibility
5+
RECORDS = [
6+
{ id: "1", price: 20.99, msrp: 10.99, quantity_available: 23, quantity_in_stock: 35 },
7+
{ id: "2", price: 99.99, msrp: 69.99, quantity_available: 77, quantity_in_stock: 100 },
8+
].freeze
179

18-
class Alpha < GraphQL::Schema
10+
class PriceSchema < GraphQL::Schema
1911
class Sprocket < GraphQL::Schema::Object
2012
field :id, ID, null: false do |f|
21-
f.directive(VisibilityDirective, profiles: ["private"])
22-
end
23-
24-
field :color, String, null: false do |f|
25-
f.directive(VisibilityDirective, profiles: ["public"])
26-
end
27-
28-
field :size, Integer, null: false do |f|
29-
f.directive(VisibilityDirective, profiles: ["public", "private"])
30-
end
31-
end
32-
33-
class Widget < GraphQL::Schema::Object
34-
directive(VisibilityDirective, profiles: ["public"])
35-
field :id, ID, null: false
36-
end
37-
38-
class Toggle < GraphQL::Schema::Enum
39-
value "YES" do |v|
40-
v.directive(VisibilityDirective, profiles: ["public"])
13+
f.directive(GraphQL::Stitching::Directives::Visibility, profiles: [])
4114
end
4215

43-
value "NO" do |v|
44-
v.directive(VisibilityDirective, profiles: ["private"])
45-
end
16+
field :price, Float, null: false
4617

47-
value "MAYBE" do |v|
48-
v.directive(VisibilityDirective, profiles: ["public", "private"])
18+
field :msrp, Float, null: false do |f|
19+
f.directive(GraphQL::Stitching::Directives::Visibility, profiles: ["private"])
4920
end
50-
51-
value "ANY"
5221
end
5322

5423
class Query < GraphQL::Schema::Object
55-
field :sprocket_a, Sprocket, null: false do |f|
56-
f.directive(StitchDirective, key: "id")
24+
field :sprocket, Sprocket, null: true do |f|
25+
f.directive(GraphQL::Stitching::Directives::Stitch, key: "id")
5726
f.argument(:id, ID, required: true)
5827
end
5928

60-
def sprocket_a(id:)
61-
{ id: id, color: "red", size: 2 }
62-
end
63-
64-
field :widget_a, Widget, null: false
65-
66-
def widget_a
67-
{ id: 1 }
68-
end
69-
70-
field :args, String, null: false do |f|
71-
f.argument(:id, ID, required: false) do |a|
72-
a.directive(VisibilityDirective, profiles: ["public"])
73-
end
74-
f.argument(:enum, Toggle, required: false) do |a|
75-
a.directive(VisibilityDirective, profiles: ["private"])
76-
end
77-
end
78-
79-
def args(id:, enum:)
80-
id.to_s
29+
def sprocket(id:)
30+
RECORDS.find { _1[:id] == id }
8131
end
8232
end
8333

8434
query Query
8535
end
8636

87-
class Bravo < GraphQL::Schema
37+
class InventorySchema < GraphQL::Schema
8838
class Sprocket < GraphQL::Schema::Object
8939
field :id, ID, null: false
90-
field :color, String, null: false
91-
field :weight, Integer, null: false
92-
end
40+
41+
field :quantity_available, Integer, null: false
9342

94-
class Widget < GraphQL::Schema::Object
95-
directive(VisibilityDirective, profiles: ["private"])
96-
field :id, ID, null: false
97-
end
98-
99-
class Toggle < GraphQL::Schema::Enum
100-
value "YES"
101-
value "NO"
102-
value "MAYBE"
103-
value "ANY"
43+
field :quantity_in_stock, Integer, null: false do |f|
44+
f.directive(GraphQL::Stitching::Directives::Visibility, profiles: ["private"])
45+
end
10446
end
10547

10648
class Query < GraphQL::Schema::Object
107-
field :sprocket_b, Sprocket, null: false do |f|
108-
f.directive(StitchDirective, key: "id")
109-
f.argument(:id, ID, required: true)
110-
end
111-
112-
def sprocket_b(id:)
113-
{ id: id, color: "red", weight: 3 }
114-
end
115-
116-
field :widget_b, Widget, null: false
117-
118-
def widget_b
119-
{ id: 1 }
120-
end
121-
122-
field :args, String, null: false do |f|
123-
f.argument(:id, ID, required: false)
124-
f.argument(:enum, Toggle, required: false)
49+
field :sprockets, [Sprocket], null: false do |f|
50+
f.directive(GraphQL::Stitching::Directives::Stitch, key: "id")
51+
f.directive(GraphQL::Stitching::Directives::Visibility, profiles: ["private"])
52+
f.argument(:ids, [ID, null: false], required: true)
12553
end
12654

127-
def args(id:, enum:)
128-
id.to_s
55+
def sprockets(ids:)
56+
ids.map { |id| RECORDS.find { _1[:id] == id } }
12957
end
13058
end
13159

0 commit comments

Comments
 (0)