Skip to content

Commit c29e1e3

Browse files
committed
add validation to prevent incompatible streams on the same field
1 parent 835ae90 commit c29e1e3

File tree

3 files changed

+383
-86
lines changed

3 files changed

+383
-86
lines changed

src/__tests__/starWarsStreamQuery-test.js

Lines changed: 276 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,134 @@ describe('Star Wars Query Stream Tests', () => {
6969
});
7070
});
7171

72-
describe('Basic Queries', () => {
73-
it('Can @stream an array field', async () => {
72+
describe('@stream conflict validation', () => {
73+
it('Does not allow a mix of @stream and no @stream on the same field', async () => {
7474
const query = `
7575
query HeroFriendsQuery {
7676
hero {
77-
friends @stream(initial_count: 2, label: "HeroFriends") {
77+
friends {
78+
id
79+
}
80+
...FriendsName
81+
}
82+
}
83+
fragment FriendsName on Character {
84+
friends @stream(label: "nameLabel", initial_count: 1) {
85+
name
86+
}
87+
}
88+
`;
89+
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
90+
expect(result).to.deep.equal({
91+
errors: [
92+
{
93+
message:
94+
'Fields "friends" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
95+
locations: [
96+
{
97+
line: 4,
98+
column: 13,
99+
},
100+
{
101+
line: 11,
102+
column: 11,
103+
},
104+
],
105+
},
106+
],
107+
});
108+
});
109+
it('Does not allow multiple @stream with different initial_count', async () => {
110+
const query = `
111+
query HeroFriendsQuery {
112+
hero {
113+
friends @stream(label: "sameLabel", initial_count: 3) {
114+
id
115+
}
116+
...FriendsName
117+
}
118+
}
119+
fragment FriendsName on Character {
120+
friends @stream(label: "sameLabel", initial_count: 1) {
121+
name
122+
}
123+
}
124+
`;
125+
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
126+
expect(result).to.deep.equal({
127+
errors: [
128+
{
129+
message:
130+
'Fields "friends" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
131+
locations: [
132+
{
133+
line: 4,
134+
column: 13,
135+
},
136+
{
137+
line: 11,
138+
column: 11,
139+
},
140+
],
141+
},
142+
],
143+
});
144+
});
145+
it('Does not allow multiple @stream with different label', async () => {
146+
const query = `
147+
query HeroFriendsQuery {
148+
hero {
149+
friends @stream(label: "idLabel", initial_count: 1) {
78150
id
79-
name
80151
}
152+
...FriendsName
153+
}
154+
}
155+
fragment FriendsName on Character {
156+
friends @stream(label: "nameLabel", initial_count: 1) {
157+
name
81158
}
82159
}
83160
`;
161+
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
162+
expect(result).to.deep.equal({
163+
errors: [
164+
{
165+
message:
166+
'Fields "friends" conflict because they have differing stream directives. Use different aliases on the fields to fetch both if this was intentional.',
167+
locations: [
168+
{
169+
line: 4,
170+
column: 13,
171+
},
172+
{
173+
line: 11,
174+
column: 11,
175+
},
176+
],
177+
},
178+
],
179+
});
180+
});
181+
it('Does allow multiple @stream with same label and initial_count', async () => {
182+
const query = `
183+
query HeroFriendsQuery {
184+
hero {
185+
friends @stream(label: "sameLabel", initial_count: 2) {
186+
id
187+
}
188+
...FriendsName
189+
}
190+
}
191+
fragment FriendsName on Character {
192+
friends @stream(label: "sameLabel", initial_count: 2) {
193+
name
194+
}
195+
}
196+
`;
84197
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
85198
const { patches: patchesIterable, ...initial } = result;
199+
86200
expect(initial).to.deep.equal({
87201
data: {
88202
hero: {
@@ -99,44 +213,99 @@ describe('Star Wars Query Stream Tests', () => {
99213
},
100214
},
101215
});
102-
103216
const patches = [];
104-
105217
if (patchesIterable) {
106218
await forAwaitEach(patchesIterable, patch => {
107219
patches.push(patch);
108220
});
109221
}
110-
111222
expect(patches).to.have.lengthOf(1);
112223
expect(patches[0]).to.deep.equal({
113-
label: 'HeroFriends',
224+
data: {
225+
id: '1003',
226+
name: 'Leia Organa',
227+
},
114228
path: ['hero', 'friends', 2],
229+
label: 'sameLabel',
230+
});
231+
});
232+
it('Does allow multiple @stream with different label and initial_count when fields are aliased', async () => {
233+
const query = `
234+
query HeroFriendsQuery {
235+
hero {
236+
friends @stream(label: "idLabel", initial_count: 2) {
237+
id
238+
}
239+
...FriendsName
240+
}
241+
}
242+
fragment FriendsName on Character {
243+
namedFriends: friends @stream(label: "nameLabel", initial_count: 1) {
244+
name
245+
}
246+
}
247+
`;
248+
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
249+
const { patches: patchesIterable, ...initial } = result;
250+
251+
expect(initial).to.deep.equal({
252+
data: {
253+
hero: {
254+
friends: [
255+
{
256+
id: '1000',
257+
},
258+
{
259+
id: '1002',
260+
},
261+
],
262+
namedFriends: [
263+
{
264+
name: 'Luke Skywalker',
265+
},
266+
],
267+
},
268+
},
269+
});
270+
const patches = [];
271+
if (patchesIterable) {
272+
await forAwaitEach(patchesIterable, patch => {
273+
patches.push(patch);
274+
});
275+
}
276+
expect(patches).to.have.lengthOf(3);
277+
expect(patches[0]).to.deep.equal({
115278
data: {
116279
id: '1003',
280+
},
281+
path: ['hero', 'friends', 2],
282+
label: 'idLabel',
283+
});
284+
expect(patches[1]).to.deep.equal({
285+
data: {
286+
name: 'Han Solo',
287+
},
288+
path: ['hero', 'namedFriends', 1],
289+
label: 'nameLabel',
290+
});
291+
expect(patches[2]).to.deep.equal({
292+
data: {
117293
name: 'Leia Organa',
118294
},
295+
path: ['hero', 'namedFriends', 2],
296+
label: 'nameLabel',
119297
});
120298
});
121-
it('Can @stream multiple selections on the same field', async () => {
299+
});
300+
describe('Basic Queries', () => {
301+
it('Can @stream an array field', async () => {
122302
const query = `
123303
query HeroFriendsQuery {
124304
hero {
125-
friends {
305+
friends @stream(initial_count: 2, label: "HeroFriends") {
126306
id
307+
name
127308
}
128-
...FriendsName
129-
...FriendsAppearsIn
130-
}
131-
}
132-
fragment FriendsName on Character {
133-
friends @stream(label: "nameLabel", initial_count: 1) {
134-
name
135-
}
136-
}
137-
fragment FriendsAppearsIn on Character {
138-
friends @stream(label: "appearsInLabel", initial_count: 2) {
139-
appearsIn
140309
}
141310
}
142311
`;
@@ -148,15 +317,11 @@ describe('Star Wars Query Stream Tests', () => {
148317
friends: [
149318
{
150319
id: '1000',
151-
appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
152320
name: 'Luke Skywalker',
153321
},
154322
{
155323
id: '1002',
156-
appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
157-
},
158-
{
159-
id: '1003',
324+
name: 'Han Solo',
160325
},
161326
],
162327
},
@@ -171,29 +336,103 @@ describe('Star Wars Query Stream Tests', () => {
171336
});
172337
}
173338

174-
expect(patches).to.have.lengthOf(3);
339+
expect(patches).to.have.lengthOf(1);
175340
expect(patches[0]).to.deep.equal({
341+
label: 'HeroFriends',
342+
path: ['hero', 'friends', 2],
176343
data: {
177-
name: 'Han Solo',
344+
id: '1003',
345+
name: 'Leia Organa',
346+
},
347+
});
348+
});
349+
it('Errors are added to the correct patch', async () => {
350+
const query = `
351+
query HeroFriendsQuery {
352+
hero {
353+
friends @stream(initial_count: 0, label: "HeroFriends") {
354+
... on Human {
355+
secretFriend
356+
}
357+
}
358+
}
359+
}
360+
`;
361+
const result = await graphql(StarWarsSchemaDeferStreamEnabled, query);
362+
const { patches: patchesIterable, ...initial } = result;
363+
expect(initial).to.deep.equal({
364+
data: {
365+
hero: {
366+
friends: [],
367+
},
178368
},
179-
path: ['hero', 'friends', 1],
180-
label: 'nameLabel',
181369
});
182370

371+
const patches = [];
372+
373+
if (patchesIterable) {
374+
await forAwaitEach(patchesIterable, patch => {
375+
patches.push(patch);
376+
});
377+
}
378+
379+
expect(patches).to.have.lengthOf(3);
380+
expect(patches[0]).to.deep.equal({
381+
data: {
382+
secretFriend: null,
383+
},
384+
path: ['hero', 'friends', 0],
385+
label: 'HeroFriends',
386+
errors: [
387+
{
388+
message: 'secretFriend is secret.',
389+
locations: [
390+
{
391+
line: 6,
392+
column: 17,
393+
},
394+
],
395+
path: ['hero', 'friends', 0, 'secretFriend'],
396+
},
397+
],
398+
});
183399
expect(patches[1]).to.deep.equal({
184400
data: {
185-
name: 'Leia Organa',
401+
secretFriend: null,
186402
},
187-
path: ['hero', 'friends', 2],
188-
label: 'nameLabel',
403+
path: ['hero', 'friends', 1],
404+
label: 'HeroFriends',
405+
errors: [
406+
{
407+
message: 'secretFriend is secret.',
408+
locations: [
409+
{
410+
line: 6,
411+
column: 17,
412+
},
413+
],
414+
path: ['hero', 'friends', 1, 'secretFriend'],
415+
},
416+
],
189417
});
190-
191418
expect(patches[2]).to.deep.equal({
192419
data: {
193-
appearsIn: ['NEW_HOPE', 'EMPIRE', 'JEDI'],
420+
secretFriend: null,
194421
},
195422
path: ['hero', 'friends', 2],
196-
label: 'appearsInLabel',
423+
label: 'HeroFriends',
424+
errors: [
425+
{
426+
message: 'secretFriend is secret.',
427+
locations: [
428+
{
429+
line: 6,
430+
column: 17,
431+
},
432+
],
433+
path: ['hero', 'friends', 2, 'secretFriend'],
434+
},
435+
],
197436
});
198437
});
199438
});

0 commit comments

Comments
 (0)