@apollo/federation-internals 2.4.5 → 2.4.6

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.
@@ -1,982 +0,0 @@
1
- import {
2
- Schema,
3
- ObjectType,
4
- Type,
5
- DirectiveDefinition,
6
- InterfaceType,
7
- EnumType,
8
- SchemaElement,
9
- UnionType,
10
- InputObjectType,
11
- } from '../../dist/definitions';
12
- import {
13
- printSchema as printGraphQLjsSchema,
14
- } from 'graphql';
15
- import { defaultPrintOptions, printSchema } from '../../dist/print';
16
- import { buildSchema } from '../../dist/buildSchema';
17
- import { buildSubgraph, federationMetadata, newEmptyFederation2Schema } from '../../dist/federation';
18
- import './matchers';
19
-
20
- function parseSchema(schema: string): Schema {
21
- try {
22
- return buildSchema(schema);
23
- } catch (e) {
24
- throw new Error(e.toString());
25
- }
26
- }
27
-
28
- function expectObjectType(type?: Type): asserts type is ObjectType {
29
- expect(type).toBeDefined();
30
- expect(type!.kind).toBe('ObjectType');
31
- }
32
-
33
- function expectInterfaceType(type?: Type): asserts type is InterfaceType {
34
- expect(type).toBeDefined();
35
- expect(type!.kind).toBe('InterfaceType');
36
- }
37
-
38
- function expectUnionType(type?: Type): asserts type is UnionType {
39
- expect(type).toBeDefined();
40
- expect(type!.kind).toBe('UnionType');
41
- }
42
-
43
- function expectEnumType(type?: Type): asserts type is EnumType {
44
- expect(type).toBeDefined();
45
- expect(type!.kind).toBe('EnumType');
46
- }
47
-
48
- declare global {
49
- namespace jest {
50
- interface Matchers<R> {
51
- toHaveField(name: string, type?: Type): R;
52
- toHaveDirective<TArgs extends {[key: string]: any}>(directive: DirectiveDefinition<TArgs>, args?: TArgs): R;
53
- }
54
- }
55
- }
56
-
57
- expect.extend({
58
- toHaveField(parentType: ObjectType | InterfaceType, name: string, type?: Type) {
59
- const field = parentType.field(name);
60
- if (!field) {
61
- return {
62
- message: () => `Cannot find field '${name}' in Object Type ${parentType} with fields [${[...parentType.fields()]}]`,
63
- pass: false
64
- };
65
- }
66
- if (field.name != name) {
67
- return {
68
- message: () => `Type ${parentType} has a field linked to name ${name} but that field name is actually ${field.name}`,
69
- pass: false
70
- };
71
- }
72
- if (type && field.type != type) {
73
- return {
74
- message: () => `Expected field ${parentType}.${name} to have type ${type} but got type ${field.type}`,
75
- pass: false
76
- };
77
- }
78
- return {
79
- message: () => `Expected ${parentType} not to have field ${name} but it does (${field})`,
80
- pass: true
81
- }
82
- },
83
-
84
- toHaveDirective(element: SchemaElement<any, any>, definition: DirectiveDefinition, args?: Record<string, any>) {
85
- const directives = element.appliedDirectivesOf(definition);
86
- if (directives.length == 0) {
87
- return {
88
- message: () => `Cannot find directive @${definition} applied to element ${element} (whose applied directives are [${element.appliedDirectives.join(', ')}]`,
89
- pass: false
90
- };
91
- }
92
- if (!args) {
93
- return {
94
- message: () => `Expected directive @${definition} to not be applied to ${element} but it is`,
95
- pass: true
96
- };
97
- }
98
-
99
- for (const directive of directives) {
100
- if (directive.matchArguments(args)) {
101
- return {
102
- // Not 100% certain that message is correct but I don't think it's going to be used ...
103
- message: () => `Expected directive ${directive.name} applied to ${element} to have arguments ${JSON.stringify(args)} but got ${JSON.stringify(directive.arguments)}`,
104
- pass: true
105
- };
106
- }
107
- }
108
- return {
109
- message: () => `Element ${element} has application of directive @${definition} but not with the requested arguments. Got applications: [${directives.join(', ')}]`,
110
- pass: false
111
- }
112
- },
113
- });
114
-
115
- test('building a simple schema programatically', () => {
116
- const schema = newEmptyFederation2Schema();
117
- const queryType = schema.schemaDefinition.setRoot('query', schema.addType(new ObjectType('Query'))).type;
118
- const typeA = schema.addType(new ObjectType('A'));
119
- const key = federationMetadata(schema)!.keyDirective();
120
-
121
- queryType.addField('a', typeA);
122
- typeA.addField('q', queryType);
123
- typeA.applyDirective(key, { fields: 'a'});
124
-
125
- expect(queryType).toBe(schema.schemaDefinition.root('query')!.type);
126
- expect(queryType).toHaveField('a', typeA);
127
- expect(typeA).toHaveField('q', queryType);
128
- expect(typeA).toHaveDirective(key, { fields: 'a'});
129
- });
130
-
131
-
132
- test('parse schema and modify', () => {
133
- const sdl = `
134
- schema {
135
- query: MyQuery
136
- }
137
-
138
- directive @inaccessible on FIELD_DEFINITION | ARGUMENT_DEFINITION
139
-
140
- type A {
141
- f1(x: Int @inaccessible): String
142
- f2: String @inaccessible
143
- }
144
-
145
- type MyQuery {
146
- a: A
147
- b: Int
148
- }`;
149
- const schema = parseSchema(sdl);
150
-
151
- const queryType = schema.type('MyQuery')!;
152
- const typeA = schema.type('A')!;
153
- const inaccessibleDirective = schema.directive('inaccessible')!;
154
- expectObjectType(queryType);
155
- expectObjectType(typeA);
156
- expect(schema.schemaDefinition.root('query')!.type).toBe(queryType);
157
- expect(queryType).toHaveField('a', typeA);
158
- const f2 = typeA.field('f2');
159
- expect(f2).toHaveDirective(inaccessibleDirective);
160
- expect(printSchema(schema)).toMatchString(sdl);
161
-
162
- expect(typeA).toHaveField('f1');
163
- typeA.field('f1')!.remove();
164
- expect(typeA).not.toHaveField('f1');
165
- });
166
-
167
- test('removal of all directives of a schema', () => {
168
- const subgraph = buildSubgraph('foo', '', `
169
- schema @foo {
170
- query: Query
171
- }
172
-
173
- type Query {
174
- a(id: String @bar): A @inaccessible
175
- }
176
-
177
- type A {
178
- a1: String @foo
179
- a2: [Int]
180
- }
181
-
182
- type B @foo {
183
- b: String @bar
184
- }
185
-
186
- union U @foobar = A | B
187
-
188
- directive @inaccessible on FIELD_DEFINITION
189
- directive @foo on SCHEMA | FIELD_DEFINITION | OBJECT
190
- directive @foobar on UNION
191
- directive @bar on ARGUMENT_DEFINITION | FIELD_DEFINITION
192
- `).validate();
193
-
194
- const schema = subgraph.schema;
195
- for (const element of schema.allSchemaElement()) {
196
- element.appliedDirectives.forEach(d => d.remove());
197
- }
198
-
199
- expect(subgraph.toString()).toMatchString(`
200
- directive @inaccessible on FIELD_DEFINITION
201
-
202
- directive @foo on SCHEMA | FIELD_DEFINITION | OBJECT
203
-
204
- directive @foobar on UNION
205
-
206
- directive @bar on ARGUMENT_DEFINITION | FIELD_DEFINITION
207
-
208
- type Query {
209
- a(id: String): A
210
- }
211
-
212
- type A {
213
- a1: String
214
- a2: [Int]
215
- }
216
-
217
- type B {
218
- b: String
219
- }
220
-
221
- union U = A | B`);
222
- });
223
-
224
- test('removal of an enum type should remove enum values', () => {
225
- const schema = buildSchema(`
226
- type Query {
227
- someField: String!
228
- }
229
-
230
- enum Enum {
231
- SOME_VALUE
232
- OTHER_VALUE
233
- }
234
- `);
235
-
236
- const enumType = schema.type("Enum");
237
- expectEnumType(enumType)
238
- const enumValues = Array.from(enumType.values);
239
- enumType.remove()
240
- for (const value of enumValues) {
241
- expect(value.isAttached()).toBe(false)
242
- }
243
- });
244
-
245
- test('removal of all inaccessible elements of a schema', () => {
246
- const subgraph = buildSubgraph('foo', '', `
247
- schema @foo {
248
- query: Query
249
- }
250
-
251
- type Query {
252
- a(id: String @bar, arg: Int @inaccessible): A
253
- }
254
-
255
- type A {
256
- a1: String @inaccessible
257
- a2: [Int]
258
- }
259
-
260
- type B @inaccessible {
261
- b: String @bar
262
- }
263
-
264
- union U @inaccessible = A | B
265
-
266
- directive @inaccessible on FIELD_DEFINITION | OBJECT | ARGUMENT_DEFINITION | UNION
267
- directive @foo on SCHEMA | FIELD_DEFINITION
268
- directive @bar on ARGUMENT_DEFINITION | FIELD_DEFINITION
269
- `);
270
-
271
- const schema = subgraph.schema;
272
- const inaccessibleDirective = schema.directive('inaccessible')!;
273
- for (const element of schema.allNamedSchemaElement()) {
274
- if (element.hasAppliedDirective(inaccessibleDirective)) {
275
- element.remove();
276
- }
277
- }
278
-
279
- expect(subgraph.toString()).toMatchString(`
280
- schema
281
- @foo
282
- {
283
- query: Query
284
- }
285
-
286
- directive @inaccessible on FIELD_DEFINITION | OBJECT | ARGUMENT_DEFINITION | UNION
287
-
288
- directive @foo on SCHEMA | FIELD_DEFINITION
289
-
290
- directive @bar on ARGUMENT_DEFINITION | FIELD_DEFINITION
291
-
292
- type Query {
293
- a(id: String @bar): A
294
- }
295
-
296
- type A {
297
- a2: [Int]
298
- }
299
- `);
300
- });
301
-
302
- test('handling of interfaces', () => {
303
- const schema = parseSchema(`
304
- type Query {
305
- bestIs: [I!]!
306
- }
307
-
308
- interface B {
309
- a: Int
310
- }
311
-
312
- interface I implements B {
313
- a: Int
314
- b: String
315
- }
316
-
317
- type T1 implements B & I {
318
- a: Int
319
- b: String
320
- c: Int
321
- }
322
-
323
- type T2 implements B & I {
324
- a: Int
325
- b: String
326
- c: String
327
- }
328
- `);
329
-
330
- const b = schema.type('B');
331
- const i = schema.type('I');
332
- const t1 = schema.type('T1');
333
- const t2 = schema.type('T2');
334
- expectInterfaceType(b);
335
- expectInterfaceType(i);
336
- expectObjectType(t1);
337
- expectObjectType(t2);
338
-
339
- for (const t of [b, i, t1, t2]) {
340
- expect(t).toHaveField('a', schema.intType());
341
- }
342
- for (const t of [i, t1, t2]) {
343
- expect(t).toHaveField('b', schema.stringType());
344
- }
345
- expect(t1).toHaveField('c', schema.intType());
346
- expect(t2).toHaveField('c', schema.stringType());
347
-
348
- expect(i.implementsInterface(b.name)).toBeTruthy();
349
- expect(t1.implementsInterface(b.name)).toBeTruthy();
350
- expect(t1.implementsInterface(i.name)).toBeTruthy();
351
- expect(t2.implementsInterface(b.name)).toBeTruthy();
352
- expect(t2.implementsInterface(i.name)).toBeTruthy();
353
-
354
- expect(b.allImplementations()).toEqual([i, t1, t2]);
355
- expect(i.allImplementations()).toEqual([t1, t2]);
356
-
357
- for (const itf of [b, i]) {
358
- expect(itf.possibleRuntimeTypes()).toEqual([t1, t2]);
359
- }
360
-
361
- b.remove();
362
-
363
- expect(printSchema(schema)).toMatchString(`
364
- type Query {
365
- bestIs: [I!]!
366
- }
367
-
368
- interface I {
369
- a: Int
370
- b: String
371
- }
372
-
373
- type T1 implements I {
374
- a: Int
375
- b: String
376
- c: Int
377
- }
378
-
379
- type T2 implements I {
380
- a: Int
381
- b: String
382
- c: String
383
- }`);
384
- });
385
-
386
- test('handling of enums', () => {
387
- const schema = parseSchema(`
388
- type Query {
389
- a: A
390
- }
391
-
392
- enum E {
393
- V1
394
- V2
395
- }
396
-
397
- type A {
398
- a: Int
399
- e: E
400
- }
401
- `);
402
-
403
- const a = schema.type('A');
404
- const e = schema.type('E');
405
- expectObjectType(a);
406
- expectEnumType(e);
407
-
408
- expect(a).toHaveField('e', e);
409
- const v1 = e.value('V1');
410
- const v2 = e.value('V2');
411
- expect(v1).toBeDefined();
412
- expect(v2).toBeDefined();
413
- expect(e.values).toEqual([v1, v2]);
414
- });
415
-
416
- test('handling of descriptions', () => {
417
- const sdl = `
418
- """A super schema full of great queries"""
419
- schema {
420
- query: ASetOfQueries
421
- }
422
-
423
- """Marks field that are deemed more important than others"""
424
- directive @Important(
425
- """The reason for the importance of this field"""
426
- why: String = "because!"
427
- ) on FIELD_DEFINITION
428
-
429
- """The actual queries of the schema"""
430
- type ASetOfQueries {
431
- """Returns a set of products"""
432
- bestProducts: [Product!]!
433
-
434
- """Finds a product by ID"""
435
- product(
436
- """The ID identifying the product"""
437
- id: ID!
438
- ): Product
439
- }
440
-
441
- """A product that is also a book"""
442
- type Book implements Product {
443
- id: ID!
444
- description: String!
445
-
446
- """
447
- Number of pages in the book. Good so the customer knows its buying a 1000 page book for instance
448
- """
449
- pages: Int @Important
450
- }
451
-
452
- extend type Book {
453
- author: String
454
- }
455
-
456
- type DVD implements Product {
457
- id: ID!
458
- description: String!
459
-
460
- """The film author"""
461
- author: String @Important(why: "it's good to give credit!")
462
- }
463
-
464
- """Common interface to all our products"""
465
- interface Product {
466
- """Identifies the product"""
467
- id: ID!
468
-
469
- """
470
- Something that explains what the product is. This can just be the title of the product, but this can be more than that if we want to. But it should be useful you know, otherwise our customer won't buy it.
471
- """
472
- description: String!
473
- }`;
474
- const schema = parseSchema(sdl);
475
-
476
- // Checking we get back the schema through printing it is mostly good enough, but let's just
477
- // make sure long descriptions don't get annoying formatting newlines for instance when acessed on the
478
- // schema directly.
479
- const longComment = "Something that explains what the product is. This can just be the title of the product, but this can be more than that if we want to. But it should be useful you know, otherwise our customer won't buy it.";
480
- const product = schema.type('Product');
481
- expectInterfaceType(product);
482
- expect(product.field('description')!.description).toBe(longComment);
483
-
484
- expect(printSchema(schema)).toMatchString(sdl);
485
- });
486
-
487
- test('handling of extensions', () => {
488
- const sdl = `
489
- directive @foo on SCALAR
490
-
491
- type Query {
492
- f: Int
493
- }
494
-
495
- interface AInterface {
496
- i1: Int
497
- }
498
-
499
- extend interface AInterface {
500
- i2: Int
501
- }
502
-
503
- scalar AScalar
504
-
505
- extend scalar AScalar
506
- @foo
507
-
508
- extend type AType {
509
- t1: Int
510
- t2: String
511
- }
512
-
513
- type AType2 {
514
- t1: String
515
- }
516
-
517
- type AType3 {
518
- t2: Int
519
- }
520
-
521
- union AUnion = AType | AType2
522
-
523
- extend union AUnion = AType3
524
- `;
525
-
526
- // Note that we mark it as a subgraph because validation of extension is relaxed. In other words, it's
527
- // expected that this will fail validation as a normal schema even though we don't use any
528
- // federation directives.
529
- const subgraph = buildSubgraph('foo', '', sdl);
530
- expect(subgraph.toString()).toMatchString(sdl);
531
- const schema = subgraph.schema;
532
-
533
- const atype = schema.type('AType');
534
- expectObjectType(atype);
535
- expect(atype).toHaveField('t1', schema.intType());
536
- expect(atype).toHaveField('t2', schema.stringType());
537
-
538
- const aunion = schema.type('AUnion');
539
- expectUnionType(aunion);
540
- expect([...aunion.types()].map(t => t.name)).toEqual(['AType', 'AType2', 'AType3']);
541
-
542
- expect(subgraph.toString({ ...defaultPrintOptions, mergeTypesAndExtensions: true })).toMatchString(`
543
- directive @foo on SCALAR
544
-
545
- type Query {
546
- f: Int
547
- }
548
-
549
- interface AInterface {
550
- i1: Int
551
- i2: Int
552
- }
553
-
554
- scalar AScalar
555
- @foo
556
-
557
- type AType {
558
- t1: Int
559
- t2: String
560
- }
561
-
562
- type AType2 {
563
- t1: String
564
- }
565
-
566
- type AType3 {
567
- t2: Int
568
- }
569
-
570
- union AUnion = AType | AType2 | AType3
571
- `);
572
- });
573
-
574
- describe('type extension where definition is empty', () => {
575
- it('works for object types', () => {
576
- const sdl = `
577
- type Query {
578
- foo: Foo
579
- }
580
-
581
- type Foo
582
-
583
- extend type Foo {
584
- baz: String
585
- }
586
- `;
587
-
588
- const schema = buildSchema(sdl);
589
- expect(printSchema(schema)).toMatchString(sdl);
590
- expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy();
591
- expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy();
592
- });
593
-
594
- it('works for union', () => {
595
- const sdl = `
596
- type Query {
597
- foo: Foo
598
- }
599
-
600
- union Foo
601
-
602
- extend union Foo = Bar
603
-
604
- type Bar {
605
- x: Int
606
- }
607
- `;
608
-
609
- const schema = buildSchema(sdl);
610
- expect(printSchema(schema)).toMatchString(sdl);
611
- expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy();
612
- expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy();
613
- });
614
-
615
- it('works for enum', () => {
616
- const sdl = `
617
- type Query {
618
- foo: Foo
619
- }
620
-
621
- enum Foo
622
-
623
- extend enum Foo {
624
- Bar
625
- }
626
- `;
627
-
628
- const schema = buildSchema(sdl);
629
- expect(printSchema(schema)).toMatchString(sdl);
630
- expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy();
631
- expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy();
632
- });
633
-
634
- it('works for input object types', () => {
635
- const sdl = `
636
- type Query {
637
- foo: Int
638
- }
639
-
640
- input Foo
641
-
642
- extend input Foo {
643
- bar: Int
644
- }
645
- `;
646
-
647
- const schema = buildSchema(sdl);
648
- expect(printSchema(schema)).toMatchString(sdl);
649
- expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy();
650
- expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy();
651
- });
652
-
653
- it('works for scalar type', () => {
654
- const sdl = `
655
- type Query {
656
- foo: Int
657
- }
658
-
659
- scalar Foo
660
-
661
- extend scalar Foo
662
- @specifiedBy(url: "something")
663
- `;
664
-
665
- const schema = buildSchema(sdl);
666
- expect(printSchema(schema)).toMatchString(sdl);
667
- expect(schema.type('Foo')?.hasNonExtensionElements()).toBeTruthy();
668
- expect(schema.type('Foo')?.hasExtensionElements()).toBeTruthy();
669
- });
670
- })
671
-
672
- test('reject type defined multiple times', () => {
673
- const sdl = `
674
- type Query {
675
- foo: Foo
676
- }
677
-
678
- type Foo {
679
- bar: String
680
- }
681
-
682
- type Foo {
683
- baz: String
684
- }
685
- `;
686
-
687
- expect(() => buildSchema(sdl).validate()).toThrow('There can be only one type named "Foo"');
688
- });
689
-
690
- test('default arguments for directives', () => {
691
- const sdl = `
692
- directive @Example(inputObject: ExampleInputObject! = {}) on FIELD_DEFINITION
693
-
694
- type Query {
695
- v1: Int @Example
696
- v2: Int @Example(inputObject: {})
697
- v3: Int @Example(inputObject: {number: 3})
698
- }
699
-
700
- input ExampleInputObject {
701
- number: Int! = 3
702
- }
703
- `;
704
-
705
- const schema = parseSchema(sdl);
706
- expect(printSchema(schema)).toMatchString(sdl);
707
-
708
- const query = schema.schemaDefinition.root('query')!.type;
709
- const exampleDirective = schema.directive('Example')!;
710
- expect(query).toHaveField('v1');
711
- expect(query).toHaveField('v2');
712
- expect(query).toHaveField('v3');
713
- const v1 = query.field('v1')!;
714
- const v2 = query.field('v2')!;
715
- const v3 = query.field('v3')!;
716
-
717
- const d1 = v1.appliedDirectivesOf(exampleDirective)[0];
718
- const d2 = v2.appliedDirectivesOf(exampleDirective)[0];
719
- const d3 = v3.appliedDirectivesOf(exampleDirective)[0];
720
-
721
- expect(d1.arguments()).toEqual({});
722
- expect(d2.arguments()).toEqual({ inputObject: {}});
723
- expect(d3.arguments()).toEqual({ inputObject: { number: 3 }});
724
-
725
- expect(d1.arguments(true)).toEqual({ inputObject: { number: 3 }});
726
- expect(d2.arguments(true)).toEqual({ inputObject: { number: 3 }});
727
- expect(d3.arguments(true)).toEqual({ inputObject: { number: 3 }});
728
- });
729
-
730
- describe('clone', () => {
731
- it('should allow directive application before definition', () => {
732
- const schema = buildSchema(`
733
- directive @foo(arg: String @wizz(arg: "foo")) on FIELD_DEFINITION
734
- directive @wizz(arg: String @fuzz(arg: "wizz")) on ARGUMENT_DEFINITION
735
- directive @fuzz(arg: String @buzz(arg: "fuzz")) on ARGUMENT_DEFINITION
736
- directive @buzz(arg: String @baz(arg: "buzz")) on ARGUMENT_DEFINITION
737
- directive @baz(arg: String @bar) on ARGUMENT_DEFINITION
738
- directive @bar on ARGUMENT_DEFINITION
739
-
740
- type Query {
741
- foo: String! @foo(arg: "query")
742
- }
743
- `).clone();
744
-
745
- expect(schema.elementByCoordinate("@foo")).toBeDefined();
746
- expect(schema.elementByCoordinate("@wizz")).toBeDefined();
747
- expect(schema.elementByCoordinate("@fuzz")).toBeDefined();
748
- expect(schema.elementByCoordinate("@buzz")).toBeDefined();
749
- expect(schema.elementByCoordinate("@baz")).toBeDefined();
750
- expect(schema.elementByCoordinate("@bar")).toBeDefined();
751
- });
752
-
753
- // https://github.com/apollographql/federation/issues/1794
754
- it('should allow using an imported federation diretive in another directive', () => {
755
- const schema = buildSubgraph('foo', "", `
756
- extend schema
757
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
758
-
759
- directive @foo(arg: String @tag(name: "tag")) on FIELD_DEFINITION
760
-
761
- type Query {
762
- hi: String! @foo
763
- }
764
- `).schema.clone();
765
- expect(schema.elementByCoordinate("@foo")).toBeDefined();
766
- expect(schema.elementByCoordinate("@tag")).toBeDefined();
767
- });
768
-
769
- it('should allow type use in directives', () => {
770
- const schema = buildSchema(`
771
- directive @foo(arg: Thing!) on FIELD_DEFINITION
772
- scalar Thing
773
-
774
- type Query {
775
- foo: String! @foo(arg: "sunshine")
776
- }
777
- `).clone();
778
-
779
- expect(schema.elementByCoordinate("@foo")).toBeDefined();
780
- });
781
-
782
- it('should allow recursive directive definitions', () => {
783
- const schema = buildSchema(`
784
- directive @foo(a: Int @bar) on ARGUMENT_DEFINITION
785
- directive @bar(b: Int @foo) on ARGUMENT_DEFINITION
786
-
787
- type Query {
788
- getData(arg: String @foo): String!
789
- }
790
- `).clone();
791
- expect(schema.elementByCoordinate("@foo")).toBeDefined();
792
- expect(schema.elementByCoordinate("@bar")).toBeDefined();
793
- });
794
- });
795
-
796
- describe('Conversion to graphQL-js schema', () => {
797
- test('works on simple schema', () => {
798
- const sdl = `
799
- schema {
800
- query: MyQuery
801
- }
802
-
803
- directive @foo on FIELD
804
-
805
- type A {
806
- f1(x: Int): String
807
- f2: String
808
- }
809
-
810
- type MyQuery {
811
- a: A
812
- b: Int
813
- }
814
- `;
815
- const schema = parseSchema(sdl);
816
-
817
- const graphqQLSchema = schema.toGraphQLJSSchema();
818
- expect(printGraphQLjsSchema(graphqQLSchema)).toMatchString(sdl);
819
- });
820
-
821
- test('can optionally add @defer and/or @streams definition(s)', () => {
822
- const sdl = `
823
- type Query {
824
- x: Int
825
- }
826
- `;
827
- const schema = parseSchema(sdl);
828
-
829
- expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true }))).toMatchString(`
830
- directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT
831
-
832
- type Query {
833
- x: Int
834
- }
835
- `);
836
-
837
- expect(printGraphQLjsSchema(schema.toGraphQLJSSchema({ includeDefer: true, includeStream: true }))).toMatchString(`
838
- directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT
839
-
840
- directive @stream(label: String, initialCount: Int = 0, if: Boolean! = true) on FIELD
841
-
842
- type Query {
843
- x: Int
844
- }
845
- `);
846
- });
847
-
848
- test('adding @defer and/or @streams definition(s) works properly for API schema of supergraphs with their own @defer definition', () => {
849
- // Note that this test doesn't use a valid supergraph schema, but that doesn't really matter in this test. All that matter
850
- // is that taking `toAPISchema()` followed by `toGraphQLJSSchema(config)` works properly when the original schema already has
851
- // a `@defer` definition.
852
- const sdl = `
853
- directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT
854
- directive @stream(label: String, initialCount: Int = 0, if: Boolean! = true) on FIELD
855
-
856
- type Query {
857
- x: Int
858
- }
859
- `;
860
-
861
- // Note that with API schema, we want the @defer/@stream definitions from the original schema to essentially be ignored.
862
- // So whether or not the @defer/@stream definition ends up in the output of `toGraphQLJSSchema` (of that API schema) should
863
- // solely depend on the config provided to that method.
864
- const apiSchema = parseSchema(sdl).toAPISchema();
865
-
866
- expect(printGraphQLjsSchema(apiSchema.toGraphQLJSSchema())).toMatchString(`
867
- type Query {
868
- x: Int
869
- }
870
- `);
871
-
872
- expect(printGraphQLjsSchema(apiSchema.toGraphQLJSSchema({ includeDefer: true }))).toMatchString(`
873
- directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT
874
-
875
- type Query {
876
- x: Int
877
- }
878
- `);
879
-
880
- expect(printGraphQLjsSchema(apiSchema.toGraphQLJSSchema({ includeDefer: true, includeStream: true }))).toMatchString(`
881
- directive @defer(label: String, if: Boolean! = true) on FRAGMENT_SPREAD | INLINE_FRAGMENT
882
-
883
- directive @stream(label: String, initialCount: Int = 0, if: Boolean! = true) on FIELD
884
-
885
- type Query {
886
- x: Int
887
- }
888
- `);
889
- });
890
- });
891
-
892
-
893
- test('retrieving elements by coordinate', () => {
894
- const sdl = `
895
- directive @foo(bar: Int) on FIELD
896
-
897
- type Query {
898
- t: T
899
- }
900
-
901
- type T {
902
- f1(x: Int): String
903
- f2: String
904
- }
905
-
906
- interface I {
907
- i: String
908
- }
909
-
910
- input O {
911
- x: Int
912
- }
913
-
914
- enum E {
915
- FOO
916
- BAR
917
- }
918
-
919
- scalar Date
920
- `;
921
- const schema = parseSchema(sdl);
922
-
923
- expect(schema.elementByCoordinate('Query')).toBe(schema.schemaDefinition.rootType('query'));
924
- expect(schema.elementByCoordinate('Query.t')).toBe(schema.schemaDefinition.rootType('query')?.field('t'));
925
-
926
- const typeT = schema.type('T') as ObjectType;
927
- expect(schema.elementByCoordinate('T')).toBe(typeT);
928
- expect(schema.elementByCoordinate('T.f1')).toBe(typeT.field('f1'));
929
- expect(schema.elementByCoordinate('T.f2')).toBe(typeT.field('f2'));
930
- expect(schema.elementByCoordinate('T.f1(x:)')).toBe(typeT.field('f1')?.argument('x'));
931
-
932
- const typeI = schema.type('I') as InterfaceType;
933
- expect(schema.elementByCoordinate('I')).toBe(typeI);
934
- expect(schema.elementByCoordinate('I.i')).toBe(typeI.field('i'));
935
-
936
- const typeO = schema.type('O') as InputObjectType;
937
- expect(schema.elementByCoordinate('O')).toBe(typeO);
938
- expect(schema.elementByCoordinate('O.x')).toBe(typeO.field('x'));
939
-
940
- const typeE = schema.type('E') as EnumType;
941
- expect(schema.elementByCoordinate('E')).toBe(typeE);
942
- expect(schema.elementByCoordinate('E.FOO')).toBe(typeE.value('FOO'));
943
-
944
- expect(schema.elementByCoordinate('Date')).toBe(schema.type('Date'));
945
-
946
- const directiveFoo = schema.directive('foo')!;
947
- expect(schema.elementByCoordinate('@foo')).toBe(directiveFoo);
948
- expect(schema.elementByCoordinate('@foo(bar:)')).toBe(directiveFoo.argument('bar'));
949
-
950
- expect(schema.elementByCoordinate('SomeType')).toBeUndefined();
951
- expect(schema.elementByCoordinate('T.f3')).toBeUndefined();
952
- expect(schema.elementByCoordinate('T.f1(y:)')).toBeUndefined();
953
- expect(schema.elementByCoordinate('I.j')).toBeUndefined();
954
- expect(schema.elementByCoordinate('I.j(x:)')).toBeUndefined();
955
- expect(schema.elementByCoordinate('O.z')).toBeUndefined();
956
- expect(schema.elementByCoordinate('@bar')).toBeUndefined();
957
- expect(schema.elementByCoordinate('@foo(unknown:)')).toBeUndefined();
958
-
959
- expect(() => schema.elementByCoordinate('foo bar')).toThrow();
960
- expect(() => schema.elementByCoordinate('@foo.bar')).toThrow();
961
- // Note that because 'O' is an input type, it's field can't have args
962
- expect(() => schema.elementByCoordinate('O.x(foo:)')).toThrow();
963
- // Note that because 'Date' is a scalar, it cannot have fields
964
- expect(() => schema.elementByCoordinate('Date.bar')).toThrow();
965
- })
966
-
967
- test('parse error', () => {
968
- const schema = `
969
- extend schema
970
- @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@tag"])
971
- {
972
- query: Query
973
- }
974
-
975
- type Query {
976
- hello: String
977
- }
978
- `;
979
- const subgraph = buildSubgraph('test', '', schema);
980
-
981
- expect(subgraph.toString()).toMatchString(schema);
982
- });