-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathentities_field_spec.rb
362 lines (308 loc) · 11.3 KB
/
entities_field_spec.rb
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# frozen_string_literal: true
require 'spec_helper'
require 'graphql'
require 'apollo-federation/schema'
require 'apollo-federation/field'
require 'apollo-federation/object'
RSpec.describe ApolloFederation::EntitiesField do
shared_examples 'entities field' do
let(:base_object) do
base_field = Class.new(GraphQL::Schema::Field) do
include ApolloFederation::Field
end
Class.new(GraphQL::Schema::Object) do
include ApolloFederation::Object
field_class base_field
end
end
context 'when a type with the key directive doesn\'t exist' do
it 'does not add the _entities field' do
schema = Class.new(base_schema) do
end
expect(schema.to_definition).to match_sdl(
<<~GRAPHQL,
type Query {
_service: _Service!
}
"""
The sdl representing the federated service capabilities. Includes federation
directives, removes federation types, and includes rest of full schema after
schema directives have been applied
"""
type _Service {
sdl: String
}
GRAPHQL
)
end
end
context 'when a type with the key directive exists' do
let(:type_with_key) do
Class.new(base_object) do
graphql_name 'TypeWithKey'
key fields: 'id'
field :id, 'ID', null: false
field :other_field, 'String', null: true
end
end
context 'when a Query object is provided' do
let(:query) do
type_with_key_class = type_with_key
Class.new(base_object) do
graphql_name 'Query'
field :type_with_key, type_with_key_class, null: true
end
end
let(:schema) do
query_class = query
Class.new(base_schema) do
query query_class
end
end
it 'sets the Query as the owner to the _entities field' do
expect(
schema.graphql_definition
.types['Query']
.fields['_entities']
.metadata[:type_class]
.owner.graphql_name,
).to eq('Query')
end
it 'adds an _entities field to the Query object' do
expect(schema.to_definition).to match_sdl(
<<~GRAPHQL,
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
typeWithKey: TypeWithKey
}
type TypeWithKey {
id: ID!
otherField: String
}
scalar _Any
union _Entity = TypeWithKey
"""
The sdl representing the federated service capabilities. Includes federation
directives, removes federation types, and includes rest of full schema after
schema directives have been applied
"""
type _Service {
sdl: String
}
GRAPHQL
)
end
end
context 'when a Query object is not provided' do
let(:mutation) do
# creating a mutation with the TypeWithKey object so it gets included in the schema
type_with_key_class = type_with_key
Class.new(base_object) do
graphql_name 'Mutation'
field :type_with_key, type_with_key_class, null: true
end
end
let(:schema) do
mutation_class = mutation
Class.new(base_schema) do
mutation mutation_class
end
end
it 'creates a Query object and adds an _entities field to it' do
s = schema
expect(s.to_definition).to match_sdl(
<<~GRAPHQL,
type Mutation {
typeWithKey: TypeWithKey
}
type Query {
_entities(representations: [_Any!]!): [_Entity]!
_service: _Service!
}
type TypeWithKey {
id: ID!
otherField: String
}
scalar _Any
union _Entity = TypeWithKey
"""
The sdl representing the federated service capabilities. Includes federation
directives, removes federation types, and includes rest of full schema after
schema directives have been applied
"""
type _Service {
sdl: String
}
GRAPHQL
)
end
describe 'resolver for _entities' do
subject(:entities_result) { execute_query['data']['_entities'] }
let(:query) do
<<~GRAPHQL
query EntitiesQuery($representations: [_Any!]!) {
_entities(representations: $representations) {
... on TypeWithKey {
id
otherField
}
}
}
GRAPHQL
end
let(:execute_query) do
schema.execute(query, variables: { representations: representations })
end
let(:errors) { execute_query['errors'] }
context 'when representations is empty' do
let(:representations) { [] }
it { is_expected.to match_array [] }
it { expect(errors).to be_nil }
end
context 'when representations is not empty' do
let(:representations) { [{ __typename: typename, id: id }] }
let(:id) { 123 }
context 'when typename corresponds to a type that does not exist in the schema' do
let(:typename) { 'TypeNotInSchema' }
it 'raises' do
expect(-> { execute_query }).to raise_error(
/The _entities resolver tried to load an entity for type "TypeNotInSchema"/,
)
end
end
context 'when typename corresponds to a type that exists in the schema' do
let(:typename) { type_with_key.graphql_name }
context 'when the type does not define a resolve_reference method' do
it { is_expected.to match_array [{ 'id' => id.to_s, 'otherField' => nil }] }
it { expect(errors).to be_nil }
end
context 'when the type defines a resolve_reference method' do
let(:type_with_key) do
Class.new(base_object) do
graphql_name 'TypeWithKey'
key fields: 'id'
field :id, 'ID', null: false
field :other_field, 'String', null: false
def self.resolve_reference(reference, _context)
{ id: 123, other_field: 'data!' } if reference[:id] == 123
end
end
end
it { is_expected.to match_array [{ 'id' => id.to_s, 'otherField' => 'data!' }] }
it { expect(errors).to be_nil }
context 'when resolve_reference returns a lazy object' do
let(:lazy_entity) do
Class.new do
def initialize(callable)
@callable = callable
end
def load_entity
@callable.call
end
end
end
let(:schema) do
lazy_entity_class = lazy_entity
type_with_key_class = type_with_key
Class.new(base_schema) do
lazy_resolve(lazy_entity_class, :load_entity)
orphan_types type_with_key_class
end
end
let(:resolve_method) do
lazy_entity_class = lazy_entity
lambda do |reference, _context|
if reference[:id] == 123
lazy_entity_class.new(-> { { id: 123, other_field: 'data!' } })
end
end
end
let(:type_with_key) do
resolve_method_pointer = resolve_method
Class.new(base_object) do
graphql_name 'TypeWithKey'
key fields: 'id'
field :id, 'ID', null: false
field :other_field, 'String', null: false
define_singleton_method :resolve_reference, &resolve_method_pointer
end
end
it { is_expected.to match_array [{ 'id' => id.to_s, 'otherField' => 'data!' }] }
it { expect(errors).to be_nil }
context 'when lazy object raises an error' do
let(:base_schema) do
Class.new(GraphQL::Schema) do
include ApolloFederation::Schema
end
end
let(:id1) { 123 }
let(:id2) { 321 }
let(:representations) do
[
{ __typename: typename, id: id1 },
{ __typename: typename, id: id2 },
]
end
let(:resolve_method) do
lazy_entity_class = lazy_entity
lambda do |reference, _context|
case reference[:id]
when 123
lazy_entity_class.new(-> { { id: 123, other_field: 'more data' } })
when 321
lazy_entity_class.new(-> { raise(GraphQL::ExecutionError, 'error') })
end
end
end
specify do
expect(execute_query.to_h).to match(
'data' => {
'_entities' => [
{ 'id' => id.to_s, 'otherField' => 'more data' },
nil,
],
},
'errors' => [
{
'locations' => [{ 'column' => 3, 'line' => 2 }],
'message' => 'error',
'path' => ['_entities', 1],
},
],
)
end
end
end
end
end
end
end
end
end
end
if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('1.12.0')
context 'with the original runtime' do
it_behaves_like 'entities field' do
let(:base_schema) do
Class.new(GraphQL::Schema) do
include ApolloFederation::Schema
end
end
end
end
end
context 'with the interpreter runtime' do
it_behaves_like 'entities field' do
let(:base_schema) do
Class.new(GraphQL::Schema) do
if Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('1.12.0')
use GraphQL::Execution::Interpreter
use GraphQL::Analysis::AST
end
include ApolloFederation::Schema
end
end
end
end
end