Passed
Push — master ( 2f73bb...616fc1 )
by Vladimir
14:32 queued 11:17
created

testShouldDetectIfANullableFieldArgumentWasAdded()

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 53
c 0
b 0
f 0
nc 1
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GraphQL\Tests\Utils;
6
7
use GraphQL\Language\DirectiveLocation;
8
use GraphQL\Type\Definition\Directive;
9
use GraphQL\Type\Definition\EnumType;
10
use GraphQL\Type\Definition\FieldArgument;
11
use GraphQL\Type\Definition\InputObjectType;
12
use GraphQL\Type\Definition\InterfaceType;
13
use GraphQL\Type\Definition\ObjectType;
14
use GraphQL\Type\Definition\Type;
15
use GraphQL\Type\Definition\UnionType;
16
use GraphQL\Type\Schema;
17
use GraphQL\Utils\BreakingChangesFinder;
18
use PHPUnit\Framework\TestCase;
19
use function sprintf;
20
21
class BreakingChangesFinderTest extends TestCase
22
{
23
    /** @var ObjectType */
24
    private $queryType;
25
26
    public function setUp()
27
    {
28
        $this->queryType = new ObjectType([
29
            'name'   => 'Query',
30
            'fields' => [
31
                'field1' => [
32
                    'type' => Type::string(),
33
                ],
34
            ],
35
        ]);
36
    }
37
38
    //DESCRIBE: findBreakingChanges
39
40
    /**
41
     * @see it('should detect if a type was removed or not')
42
     */
43
    public function testShouldDetectIfTypeWasRemovedOrNot() : void
44
    {
45
        $type1     = new ObjectType([
46
            'name'   => 'Type1',
47
            'fields' => [
48
                'field1' => ['type' => Type::string()],
49
            ],
50
        ]);
51
        $type2     = new ObjectType([
52
            'name'   => 'Type2',
53
            'fields' => [
54
                'field1' => ['type' => Type::string()],
55
            ],
56
        ]);
57
        $oldSchema = new Schema([
58
            'query' => $this->queryType,
59
            'types' => [$type1, $type2],
60
        ]);
61
        $newSchema = new Schema([
62
            'query' => $this->queryType,
63
            'types' => [$type2],
64
        ]);
65
66
        $expected = [
67
            [
68
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
69
                'description' => 'Type1 was removed.',
70
            ],
71
        ];
72
73
        self::assertEquals(
74
            $expected,
75
            BreakingChangesFinder::findRemovedTypes($oldSchema, $newSchema)
76
        );
77
78
        self::assertEquals([], BreakingChangesFinder::findRemovedTypes($oldSchema, $oldSchema));
79
    }
80
81
    /**
82
     * @see it('should detect if a type changed its type')
83
     */
84
    public function testShouldDetectIfATypeChangedItsType() : void
85
    {
86
        $objectType = new ObjectType([
87
            'name'   => 'ObjectType',
88
            'fields' => [
89
                'field1' => ['type' => Type::string()],
90
            ],
91
        ]);
92
93
        $interfaceType = new InterfaceType([
94
            'name'   => 'Type1',
95
            'fields' => [
96
                'field1' => ['type' => Type::string()],
97
            ],
98
        ]);
99
100
        $unionType = new UnionType([
101
            'name'  => 'Type1',
102
            'types' => [$objectType],
103
        ]);
104
105
        $oldSchema = new Schema([
106
            'query' => $this->queryType,
107
            'types' => [$interfaceType],
108
        ]);
109
110
        $newSchema = new Schema([
111
            'query' => $this->queryType,
112
            'types' => [$unionType],
113
        ]);
114
115
        $expected = [
116
            [
117
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_CHANGED_KIND,
118
                'description' => 'Type1 changed from an Interface type to a Union type.',
119
            ],
120
        ];
121
122
        self::assertEquals(
123
            $expected,
124
            BreakingChangesFinder::findTypesThatChangedKind($oldSchema, $newSchema)
125
        );
126
    }
127
128
    /**
129
     * We need to compare type of class A (old type) and type of class B (new type)
130
     * Class B extends A but are evaluated as same types (if all properties match).
131
     * The reason is that when constructing schema from remote schema,
132
     * we have no certain way to get information about our classes.
133
     * Thus object types from remote schema are constructed as Object Type
134
     * while their local counterparts are usually a subclass of Object Type.
135
     *
136
     * @see https://github.com/webonyx/graphql-php/pull/431
137
     */
138
    public function testShouldNotMarkTypesWithInheritedClassesAsChanged() : void
139
    {
140
        $objectTypeConstructedFromRemoteSchema = new ObjectType([
141
            'name'   => 'ObjectType',
142
            'fields' => [
143
                'field1' => ['type' => Type::string()],
144
            ],
145
        ]);
146
147
        $localObjectType = new class([
148
            'name'   => 'ObjectType',
149
            'fields' => [
150
                'field1' => ['type' => Type::string()],
151
            ],
152
        ]) extends ObjectType{
153
        };
154
155
        $schemaA = new Schema([
156
            'query' => $this->queryType,
157
            'types' => [$objectTypeConstructedFromRemoteSchema],
158
        ]);
159
160
        $schemaB = new Schema([
161
            'query' => $this->queryType,
162
            'types' => [$localObjectType],
163
        ]);
164
165
        self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaA, $schemaB));
166
        self::assertEmpty(BreakingChangesFinder::findTypesThatChangedKind($schemaB, $schemaA));
167
    }
168
169
    /**
170
     * @see it('should detect if a field on a type was deleted or changed type')
171
     */
172
    public function testShouldDetectIfAFieldOnATypeWasDeletedOrChangedType() : void
173
    {
174
        $typeA = new ObjectType([
175
            'name'   => 'TypeA',
176
            'fields' => [
177
                'field1' => ['type' => Type::string()],
178
            ],
179
        ]);
180
        // logically equivalent to TypeA; findBreakingFieldChanges shouldn't
181
        // treat this as different than TypeA
182
        $typeA2   = new ObjectType([
183
            'name'   => 'TypeA',
184
            'fields' => [
185
                'field1' => ['type' => Type::string()],
186
            ],
187
        ]);
188
        $typeB    = new ObjectType([
189
            'name'   => 'TypeB',
190
            'fields' => [
191
                'field1' => ['type' => Type::string()],
192
            ],
193
        ]);
194
        $oldType1 = new InterfaceType([
195
            'name'   => 'Type1',
196
            'fields' => [
197
                'field1'  => ['type' => $typeA],
198
                'field2'  => ['type' => Type::string()],
199
                'field3'  => ['type' => Type::string()],
200
                'field4'  => ['type' => $typeA],
201
                'field6'  => ['type' => Type::string()],
202
                'field7'  => ['type' => Type::listOf(Type::string())],
203
                'field8'  => ['type' => Type::int()],
204
                'field9'  => ['type' => Type::nonNull(Type::int())],
205
                'field10' => ['type' => Type::nonNull(Type::listOf(Type::int()))],
206
                'field11' => ['type' => Type::int()],
207
                'field12' => ['type' => Type::listOf(Type::int())],
208
                'field13' => ['type' => Type::listOf(Type::nonNull(Type::int()))],
209
                'field14' => ['type' => Type::listOf(Type::int())],
210
                'field15' => ['type' => Type::listOf(Type::listOf(Type::int()))],
211
                'field16' => ['type' => Type::nonNull(Type::int())],
212
                'field17' => ['type' => Type::listOf(Type::int())],
213
                'field18' => [
214
                    'type' => Type::listOf(Type::nonNull(
215
                        Type::listOf(Type::nonNull(Type::int()))
216
                    )),
217
                ],
218
            ],
219
        ]);
220
        $newType1 = new InterfaceType([
221
            'name'   => 'Type1',
222
            'fields' => [
223
                'field1'  => ['type' => $typeA2],
224
                'field3'  => ['type' => Type::boolean()],
225
                'field4'  => ['type' => $typeB],
226
                'field5'  => ['type' => Type::string()],
227
                'field6'  => ['type' => Type::listOf(Type::string())],
228
                'field7'  => ['type' => Type::string()],
229
                'field8'  => ['type' => Type::nonNull(Type::int())],
230
                'field9'  => ['type' => Type::int()],
231
                'field10' => ['type' => Type::listOf(Type::int())],
232
                'field11' => ['type' => Type::nonNull(Type::listOf(Type::int()))],
233
                'field12' => ['type' => Type::listOf(Type::nonNull(Type::int()))],
234
                'field13' => ['type' => Type::listOf(Type::int())],
235
                'field14' => ['type' => Type::listOf(Type::listOf(Type::int()))],
236
                'field15' => ['type' => Type::listOf(Type::int())],
237
                'field16' => ['type' => Type::nonNull(Type::listOf(Type::int()))],
238
                'field17' => ['type' => Type::nonNull(Type::listOf(Type::int()))],
239
                'field18' => [
240
                    'type' => Type::listOf(
241
                        Type::listOf(Type::nonNull(Type::int()))
242
                    ),
243
                ],
244
            ],
245
        ]);
246
247
        $oldSchema = new Schema([
248
            'query' => $this->queryType,
249
            'types' => [$oldType1],
250
        ]);
251
252
        $newSchema = new Schema([
253
            'query' => $this->queryType,
254
            'types' => [$newType1],
255
        ]);
256
257
        $expectedFieldChanges = [
258
            [
259
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
260
                'description' => 'Type1.field2 was removed.',
261
            ],
262
            [
263
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
264
                'description' => 'Type1.field3 changed type from String to Boolean.',
265
            ],
266
            [
267
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
268
                'description' => 'Type1.field4 changed type from TypeA to TypeB.',
269
            ],
270
            [
271
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
272
                'description' => 'Type1.field6 changed type from String to [String].',
273
            ],
274
            [
275
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
276
                'description' => 'Type1.field7 changed type from [String] to String.',
277
            ],
278
            [
279
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
280
                'description' => 'Type1.field9 changed type from Int! to Int.',
281
            ],
282
            [
283
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
284
                'description' => 'Type1.field10 changed type from [Int]! to [Int].',
285
            ],
286
            [
287
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
288
                'description' => 'Type1.field11 changed type from Int to [Int]!.',
289
            ],
290
            [
291
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
292
                'description' => 'Type1.field13 changed type from [Int!] to [Int].',
293
            ],
294
            [
295
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
296
                'description' => 'Type1.field14 changed type from [Int] to [[Int]].',
297
            ],
298
            [
299
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
300
                'description' => 'Type1.field15 changed type from [[Int]] to [Int].',
301
            ],
302
            [
303
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
304
                'description' => 'Type1.field16 changed type from Int! to [Int]!.',
305
            ],
306
            [
307
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
308
                'description' => 'Type1.field18 changed type from [[Int!]!] to [[Int!]].',
309
            ],
310
        ];
311
312
        self::assertEquals(
313
            $expectedFieldChanges,
314
            BreakingChangesFinder::findFieldsThatChangedTypeOnObjectOrInterfaceTypes($oldSchema, $newSchema)
315
        );
316
    }
317
318
    /**
319
     * @see it('should detect if fields on input types changed kind or were removed')
320
     */
321
    public function testShouldDetectIfFieldsOnInputTypesChangedKindOrWereRemoved() : void
322
    {
323
        $oldInputType = new InputObjectType([
324
            'name'   => 'InputType1',
325
            'fields' => [
326
                'field1'  => [
327
                    'type' => Type::string(),
328
                ],
329
                'field2'  => [
330
                    'type' => Type::boolean(),
331
                ],
332
                'field3'  => [
333
                    'type' => Type::listOf(Type::string()),
334
                ],
335
                'field4'  => [
336
                    'type' => Type::nonNull(Type::string()),
337
                ],
338
                'field5'  => [
339
                    'type' => Type::string(),
340
                ],
341
                'field6'  => [
342
                    'type' => Type::listOf(Type::int()),
343
                ],
344
                'field7'  => [
345
                    'type' => Type::nonNull(Type::listOf(Type::int())),
346
                ],
347
                'field8'  => [
348
                    'type' => Type::int(),
349
                ],
350
                'field9'  => [
351
                    'type' => Type::listOf(Type::int()),
352
                ],
353
                'field10' => [
354
                    'type' => Type::listOf(Type::nonNull(Type::int())),
355
                ],
356
                'field11' => [
357
                    'type' => Type::listOf(Type::int()),
358
                ],
359
                'field12' => [
360
                    'type' => Type::listOf(Type::listOf(Type::int())),
361
                ],
362
                'field13' => [
363
                    'type' => Type::nonNull(Type::int()),
364
                ],
365
                'field14' => [
366
                    'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))),
367
                ],
368
                'field15' => [
369
                    'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))),
370
                ],
371
            ],
372
        ]);
373
374
        $newInputType = new InputObjectType([
375
            'name'   => 'InputType1',
376
            'fields' => [
377
                'field1'  => [
378
                    'type' => Type::int(),
379
                ],
380
                'field3'  => [
381
                    'type' => Type::string(),
382
                ],
383
                'field4'  => [
384
                    'type' => Type::string(),
385
                ],
386
                'field5'  => [
387
                    'type' => Type::nonNull(Type::string()),
388
                ],
389
                'field6'  => [
390
                    'type' => Type::nonNull(Type::listOf(Type::int())),
391
                ],
392
                'field7'  => [
393
                    'type' => Type::listOf(Type::int()),
394
                ],
395
                'field8'  => [
396
                    'type' => Type::nonNull(Type::listOf(Type::int())),
397
                ],
398
                'field9'  => [
399
                    'type' => Type::listOf(Type::nonNull(Type::int())),
400
                ],
401
                'field10' => [
402
                    'type' => Type::listOf(Type::int()),
403
                ],
404
                'field11' => [
405
                    'type' => Type::listOf(Type::listOf(Type::int())),
406
                ],
407
                'field12' => [
408
                    'type' => Type::listOf(Type::int()),
409
                ],
410
                'field13' => [
411
                    'type' => Type::nonNull(Type::listOf(Type::int())),
412
                ],
413
                'field14' => [
414
                    'type' => Type::listOf(Type::listOf(Type::int())),
415
                ],
416
                'field15' => [
417
                    'type' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))),
418
                ],
419
            ],
420
        ]);
421
422
        $oldSchema = new Schema([
423
            'query' => $this->queryType,
424
            'types' => [$oldInputType],
425
        ]);
426
427
        $newSchema = new Schema([
428
            'query' => $this->queryType,
429
            'types' => [$newInputType],
430
        ]);
431
432
        $expectedFieldChanges = [
433
            [
434
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
435
                'description' => 'InputType1.field1 changed type from String to Int.',
436
            ],
437
            [
438
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
439
                'description' => 'InputType1.field2 was removed.',
440
            ],
441
            [
442
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
443
                'description' => 'InputType1.field3 changed type from [String] to String.',
444
            ],
445
            [
446
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
447
                'description' => 'InputType1.field5 changed type from String to String!.',
448
            ],
449
            [
450
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
451
                'description' => 'InputType1.field6 changed type from [Int] to [Int]!.',
452
            ],
453
            [
454
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
455
                'description' => 'InputType1.field8 changed type from Int to [Int]!.',
456
            ],
457
            [
458
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
459
                'description' => 'InputType1.field9 changed type from [Int] to [Int!].',
460
            ],
461
            [
462
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
463
                'description' => 'InputType1.field11 changed type from [Int] to [[Int]].',
464
            ],
465
            [
466
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
467
                'description' => 'InputType1.field12 changed type from [[Int]] to [Int].',
468
            ],
469
            [
470
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
471
                'description' => 'InputType1.field13 changed type from Int! to [Int]!.',
472
            ],
473
            [
474
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
475
                'description' => 'InputType1.field15 changed type from [[Int]!] to [[Int!]!].',
476
            ],
477
        ];
478
479
        self::assertEquals(
480
            $expectedFieldChanges,
481
            BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes(
482
                $oldSchema,
483
                $newSchema
484
            )['breakingChanges']
485
        );
486
    }
487
488
    /**
489
     * @see it('should detect if a non-null field is added to an input type')
490
     */
491
    public function testShouldDetectIfANonNullFieldIsAddedToAnInputType() : void
492
    {
493
        $oldInputType = new InputObjectType([
494
            'name'   => 'InputType1',
495
            'fields' => [
496
                'field1' => Type::string(),
497
            ],
498
        ]);
499
500
        $newInputType = new InputObjectType([
501
            'name'   => 'InputType1',
502
            'fields' => [
503
                'field1'        => Type::string(),
504
                'requiredField' => Type::nonNull(Type::int()),
505
                'optionalField' => Type::boolean(),
506
            ],
507
        ]);
508
509
        $oldSchema = new Schema([
510
            'query' => $this->queryType,
511
            'types' => [$oldInputType],
512
        ]);
513
514
        $newSchema = new Schema([
515
            'query' => $this->queryType,
516
            'types' => [$newInputType],
517
        ]);
518
519
        $expected = [
520
            [
521
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED,
522
                'description' => 'A non-null field requiredField on input type InputType1 was added.',
523
            ],
524
        ];
525
526
        self::assertEquals(
527
            $expected,
528
            BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes(
529
                $oldSchema,
530
                $newSchema
531
            )['breakingChanges']
532
        );
533
    }
534
535
    /**
536
     * @see it('should detect if a type was removed from a union type')
537
     */
538
    public function testShouldRetectIfATypeWasRemovedFromAUnionType() : void
539
    {
540
        $type1 = new ObjectType([
541
            'name'   => 'Type1',
542
            'fields' => [
543
                'field1' => Type::string(),
544
            ],
545
        ]);
546
        // logially equivalent to type1; findTypesRemovedFromUnions should not
547
        // treat this as different than type1
548
        $type1a = new ObjectType([
549
            'name'   => 'Type1',
550
            'fields' => [
551
                'field1' => Type::string(),
552
            ],
553
        ]);
554
        $type2  = new ObjectType([
555
            'name'   => 'Type2',
556
            'fields' => [
557
                'field1' => Type::string(),
558
            ],
559
        ]);
560
        $type3  = new ObjectType([
561
            'name'   => 'Type3',
562
            'fields' => [
563
                'field1' => Type::string(),
564
            ],
565
        ]);
566
567
        $oldUnionType = new UnionType([
568
            'name'  => 'UnionType1',
569
            'types' => [$type1, $type2],
570
        ]);
571
        $newUnionType = new UnionType([
572
            'name'  => 'UnionType1',
573
            'types' => [$type1a, $type3],
574
        ]);
575
576
        $oldSchema = new Schema([
577
            'query' => $this->queryType,
578
            'types' => [$oldUnionType],
579
        ]);
580
        $newSchema = new Schema([
581
            'query' => $this->queryType,
582
            'types' => [$newUnionType],
583
        ]);
584
585
        $expected = [
586
            [
587
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION,
588
                'description' => 'Type2 was removed from union type UnionType1.',
589
            ],
590
        ];
591
592
        self::assertEquals(
593
            $expected,
594
            BreakingChangesFinder::findTypesRemovedFromUnions($oldSchema, $newSchema)
595
        );
596
    }
597
598
    /**
599
     * @see it('should detect if a value was removed from an enum type')
600
     */
601
    public function testShouldDetectIfAValueWasRemovedFromAnEnumType() : void
602
    {
603
        $oldEnumType = new EnumType([
604
            'name'   => 'EnumType1',
605
            'values' => [
606
                'VALUE0' => 0,
607
                'VALUE1' => 1,
608
                'VALUE2' => 2,
609
            ],
610
        ]);
611
        $newEnumType = new EnumType([
612
            'name'   => 'EnumType1',
613
            'values' => [
614
                'VALUE0' => 0,
615
                'VALUE2' => 1,
616
                'VALUE3' => 2,
617
            ],
618
        ]);
619
620
        $oldSchema = new Schema([
621
            'query' => $this->queryType,
622
            'types' => [$oldEnumType],
623
        ]);
624
625
        $newSchema = new Schema([
626
            'query' => $this->queryType,
627
            'types' => [$newEnumType],
628
        ]);
629
630
        $expected = [
631
            [
632
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM,
633
                'description' => 'VALUE1 was removed from enum type EnumType1.',
634
            ],
635
        ];
636
637
        self::assertEquals(
638
            $expected,
639
            BreakingChangesFinder::findValuesRemovedFromEnums($oldSchema, $newSchema)
640
        );
641
    }
642
643
    /**
644
     * @see it('should detect if a field argument was removed')
645
     */
646
    public function testShouldDetectIfAFieldArgumentWasRemoved() : void
647
    {
648
        $oldType = new ObjectType([
649
            'name'   => 'Type1',
650
            'fields' => [
651
                'field1' => [
652
                    'type' => Type::string(),
653
                    'args' => [
654
                        'name' => Type::string(),
655
                    ],
656
                ],
657
            ],
658
        ]);
659
660
        $inputType = new InputObjectType([
661
            'name'   => 'InputType1',
662
            'fields' => [
663
                'field1' => Type::string(),
664
            ],
665
        ]);
666
667
        $oldInterfaceType = new InterfaceType([
668
            'name'   => 'Interface1',
669
            'fields' => [
670
                'field1' => [
671
                    'type' => Type::string(),
672
                    'args' => [
673
                        'arg1'      => Type::boolean(),
674
                        'objectArg' => $inputType,
675
                    ],
676
                ],
677
            ],
678
        ]);
679
680
        $newType = new ObjectType([
681
            'name'   => 'Type1',
682
            'fields' => [
683
                'field1' => [
684
                    'type' => Type::string(),
685
                    'args' => [],
686
                ],
687
            ],
688
        ]);
689
690
        $newInterfaceType = new InterfaceType([
691
            'name'   => 'Interface1',
692
            'fields' => [
693
                'field1' => Type::string(),
694
            ],
695
        ]);
696
697
        $oldSchema = new Schema([
698
            'query' => $this->queryType,
699
            'types' => [$oldType, $oldInterfaceType],
700
        ]);
701
702
        $newSchema = new Schema([
703
            'query' => $this->queryType,
704
            'types' => [$newType, $newInterfaceType],
705
        ]);
706
707
        $expectedChanges = [
708
            [
709
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
710
                'description' => 'Type1.field1 arg name was removed',
711
            ],
712
            [
713
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
714
                'description' => 'Interface1.field1 arg arg1 was removed',
715
            ],
716
            [
717
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_REMOVED,
718
                'description' => 'Interface1.field1 arg objectArg was removed',
719
            ],
720
        ];
721
722
        self::assertEquals(
723
            $expectedChanges,
724
            BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']
725
        );
726
    }
727
728
    /**
729
     * @see it('should detect if a field argument has changed type')
730
     */
731
    public function testShouldDetectIfAFieldArgumentHasChangedType() : void
732
    {
733
        $oldType = new ObjectType([
734
            'name'   => 'Type1',
735
            'fields' => [
736
                'field1' => [
737
                    'type' => Type::string(),
738
                    'args' => [
739
                        'arg1'  => Type::string(),
740
                        'arg2'  => Type::string(),
741
                        'arg3'  => Type::listOf(Type::string()),
742
                        'arg4'  => Type::string(),
743
                        'arg5'  => Type::nonNull(Type::string()),
744
                        'arg6'  => Type::nonNull(Type::string()),
745
                        'arg7'  => Type::nonNull(Type::listOf(Type::int())),
746
                        'arg8'  => Type::int(),
747
                        'arg9'  => Type::listOf(Type::int()),
748
                        'arg10' => Type::listOf(Type::nonNull(Type::int())),
749
                        'arg11' => Type::listOf(Type::int()),
750
                        'arg12' => Type::listOf(Type::listOf(Type::int())),
751
                        'arg13' => Type::nonNull(Type::int()),
752
                        'arg14' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))),
753
                        'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))),
754
                    ],
755
                ],
756
            ],
757
        ]);
758
759
        $newType = new ObjectType([
760
            'name'   => 'Type1',
761
            'fields' => [
762
                'field1' => [
763
                    'type' => Type::string(),
764
                    'args' => [
765
                        'arg1'  => Type::int(),
766
                        'arg2'  => Type::listOf(Type::string()),
767
                        'arg3'  => Type::string(),
768
                        'arg4'  => Type::nonNull(Type::string()),
769
                        'arg5'  => Type::int(),
770
                        'arg6'  => Type::nonNull(Type::int()),
771
                        'arg7'  => Type::listOf(Type::int()),
772
                        'arg8'  => Type::nonNull(Type::listOf(Type::int())),
773
                        'arg9'  => Type::listOf(Type::nonNull(Type::int())),
774
                        'arg10' => Type::listOf(Type::int()),
775
                        'arg11' => Type::listOf(Type::listOf(Type::int())),
776
                        'arg12' => Type::listOf(Type::int()),
777
                        'arg13' => Type::nonNull(Type::listOf(Type::int())),
778
                        'arg14' => Type::listOf(Type::listOf(Type::int())),
779
                        'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))),
780
                    ],
781
                ],
782
            ],
783
        ]);
784
785
        $oldSchema = new Schema([
786
            'query' => $this->queryType,
787
            'types' => [$oldType],
788
        ]);
789
790
        $newSchema = new Schema([
791
            'query' => $this->queryType,
792
            'types' => [$newType],
793
        ]);
794
795
        $expectedChanges = [
796
            [
797
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
798
                'description' => 'Type1.field1 arg arg1 has changed type from String to Int',
799
            ],
800
            [
801
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
802
                'description' => 'Type1.field1 arg arg2 has changed type from String to [String]',
803
            ],
804
            [
805
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
806
                'description' => 'Type1.field1 arg arg3 has changed type from [String] to String',
807
            ],
808
            [
809
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
810
                'description' => 'Type1.field1 arg arg4 has changed type from String to String!',
811
            ],
812
            [
813
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
814
                'description' => 'Type1.field1 arg arg5 has changed type from String! to Int',
815
            ],
816
            [
817
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
818
                'description' => 'Type1.field1 arg arg6 has changed type from String! to Int!',
819
            ],
820
            [
821
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
822
                'description' => 'Type1.field1 arg arg8 has changed type from Int to [Int]!',
823
            ],
824
            [
825
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
826
                'description' => 'Type1.field1 arg arg9 has changed type from [Int] to [Int!]',
827
            ],
828
            [
829
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
830
                'description' => 'Type1.field1 arg arg11 has changed type from [Int] to [[Int]]',
831
            ],
832
            [
833
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
834
                'description' => 'Type1.field1 arg arg12 has changed type from [[Int]] to [Int]',
835
            ],
836
            [
837
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
838
                'description' => 'Type1.field1 arg arg13 has changed type from Int! to [Int]!',
839
            ],
840
            [
841
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
842
                'description' => 'Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!]',
843
            ],
844
        ];
845
846
        self::assertEquals(
847
            $expectedChanges,
848
            BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']
849
        );
850
    }
851
852
    /**
853
     * @see it('should detect if a non-null field argument was added')
854
     */
855
    public function testShouldDetectIfANonNullFieldArgumentWasAdded() : void
856
    {
857
        $oldType   = new ObjectType([
858
            'name'   => 'Type1',
859
            'fields' => [
860
                'field1' => [
861
                    'type' => Type::string(),
862
                    'args' => [
863
                        'arg1' => Type::string(),
864
                    ],
865
                ],
866
            ],
867
        ]);
868
        $newType   = new ObjectType([
869
            'name'   => 'Type1',
870
            'fields' => [
871
                'field1' => [
872
                    'type' => Type::string(),
873
                    'args' => [
874
                        'arg1'           => Type::string(),
875
                        'newRequiredArg' => Type::nonNull(Type::string()),
876
                        'newOptionalArg' => Type::int(),
877
                    ],
878
                ],
879
            ],
880
        ]);
881
        $oldSchema = new Schema([
882
            'query' => $this->queryType,
883
            'types' => [$oldType],
884
        ]);
885
        $newSchema = new Schema([
886
            'query' => $this->queryType,
887
            'types' => [$newType],
888
        ]);
889
890
        $expected = [
891
            [
892
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
893
                'description' => 'A non-null arg newRequiredArg on Type1.field1 was added',
894
            ],
895
        ];
896
897
        self::assertEquals(
898
            $expected,
899
            BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']
900
        );
901
    }
902
903
    /**
904
     * @see it('should not flag args with the same type signature as breaking')
905
     */
906
    public function testShouldNotFlagArgsWithTheSameTypeSignatureAsBreaking() : void
907
    {
908
        $inputType1a = new InputObjectType([
909
            'name'   => 'InputType1',
910
            'fields' => [
911
                'field1' => Type::string(),
912
            ],
913
        ]);
914
915
        $inputType1b = new InputObjectType([
916
            'name'   => 'InputType1',
917
            'fields' => [
918
                'field1' => Type::string(),
919
            ],
920
        ]);
921
922
        $oldType = new ObjectType([
923
            'name'   => 'Type1',
924
            'fields' => [
925
                'field1' => [
926
                    'type' => Type::int(),
927
                    'args' => [
928
                        'arg1' => Type::nonNull(Type::int()),
929
                        'arg2' => $inputType1a,
930
                    ],
931
                ],
932
            ],
933
        ]);
934
935
        $newType = new ObjectType([
936
            'name'   => 'Type1',
937
            'fields' => [
938
                'field1' => [
939
                    'type' => Type::int(),
940
                    'args' => [
941
                        'arg1' => Type::nonNull(Type::int()),
942
                        'arg2' => $inputType1b,
943
                    ],
944
                ],
945
            ],
946
        ]);
947
948
        $oldSchema = new Schema([
949
            'query' => $this->queryType,
950
            'types' => [$oldType],
951
        ]);
952
        $newSchema = new Schema([
953
            'query' => $this->queryType,
954
            'types' => [$newType],
955
        ]);
956
957
        self::assertEquals([], BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
958
    }
959
960
    /**
961
     * @see it('should consider args that move away from NonNull as non-breaking')
962
     */
963
    public function testShouldConsiderArgsThatMoveAwayFromNonNullAsNonBreaking() : void
964
    {
965
        $oldType = new ObjectType([
966
            'name'   => 'Type1',
967
            'fields' => [
968
                'field1' => [
969
                    'type' => Type::string(),
970
                    'args' => [
971
                        'arg1' => Type::nonNull(Type::string()),
972
                    ],
973
                ],
974
            ],
975
        ]);
976
        $newType = new ObjectType([
977
            'name'   => 'Type1',
978
            'fields' => [
979
                'field1' => [
980
                    'type' => Type::string(),
981
                    'args' => [
982
                        'arg1' => Type::string(),
983
                    ],
984
                ],
985
            ],
986
        ]);
987
988
        $oldSchema = new Schema([
989
            'query' => $this->queryType,
990
            'types' => [$oldType],
991
        ]);
992
        $newSchema = new Schema([
993
            'query' => $this->queryType,
994
            'types' => [$newType],
995
        ]);
996
997
        self::assertEquals([], BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['breakingChanges']);
998
    }
999
1000
    /**
1001
     * @see it('should detect interfaces removed from types')
1002
     */
1003
    public function testShouldDetectInterfacesRemovedFromTypes() : void
1004
    {
1005
        $interface1 = new InterfaceType([
1006
            'name'   => 'Interface1',
1007
            'fields' => [
1008
                'field1' => Type::string(),
1009
            ],
1010
        ]);
1011
        $oldType    = new ObjectType([
1012
            'name'       => 'Type1',
1013
            'interfaces' => [$interface1],
1014
            'fields'     => [
1015
                'field1' => Type::string(),
1016
            ],
1017
        ]);
1018
        $newType    = new ObjectType([
1019
            'name'   => 'Type1',
1020
            'fields' => [
1021
                'field1' => Type::string(),
1022
            ],
1023
        ]);
1024
1025
        $oldSchema = new Schema([
1026
            'query' => $this->queryType,
1027
            'types' => [$oldType],
1028
        ]);
1029
        $newSchema = new Schema([
1030
            'query' => $this->queryType,
1031
            'types' => [$newType],
1032
        ]);
1033
1034
        $expected = [
1035
            [
1036
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT,
1037
                'description' => 'Type1 no longer implements interface Interface1.',
1038
            ],
1039
        ];
1040
1041
        self::assertEquals(
1042
            $expected,
1043
            BreakingChangesFinder::findInterfacesRemovedFromObjectTypes($oldSchema, $newSchema)
1044
        );
1045
    }
1046
1047
    /**
1048
     * @see it('should detect all breaking changes')
1049
     */
1050
    public function testShouldDetectAllBreakingChanges() : void
1051
    {
1052
        $typeThatGetsRemoved = new ObjectType([
1053
            'name'   => 'TypeThatGetsRemoved',
1054
            'fields' => [
1055
                'field1' => Type::string(),
1056
            ],
1057
        ]);
1058
1059
        $argThatChanges = new ObjectType([
1060
            'name'   => 'ArgThatChanges',
1061
            'fields' => [
1062
                'field1' => [
1063
                    'type' => Type::string(),
1064
                    'args' => [
1065
                        'id' => Type::int(),
1066
                    ],
1067
                ],
1068
            ],
1069
        ]);
1070
1071
        $argChanged = new ObjectType([
1072
            'name'   => 'ArgThatChanges',
1073
            'fields' => [
1074
                'field1' => [
1075
                    'type' => Type::string(),
1076
                    'args' => [
1077
                        'id' => Type::string(),
1078
                    ],
1079
                ],
1080
            ],
1081
        ]);
1082
1083
        $typeThatChangesTypeOld = new ObjectType([
1084
            'name'   => 'TypeThatChangesType',
1085
            'fields' => [
1086
                'field1' => Type::string(),
1087
            ],
1088
        ]);
1089
1090
        $typeThatChangesTypeNew = new InterfaceType([
1091
            'name'   => 'TypeThatChangesType',
1092
            'fields' => [
1093
                'field1' => Type::string(),
1094
            ],
1095
        ]);
1096
1097
        $typeThatHasBreakingFieldChangesOld = new InterfaceType([
1098
            'name'   => 'TypeThatHasBreakingFieldChanges',
1099
            'fields' => [
1100
                'field1' => Type::string(),
1101
                'field2' => Type::string(),
1102
            ],
1103
        ]);
1104
1105
        $typeThatHasBreakingFieldChangesNew = new InterfaceType([
1106
            'name'   => 'TypeThatHasBreakingFieldChanges',
1107
            'fields' => [
1108
                'field2' => Type::boolean(),
1109
            ],
1110
        ]);
1111
1112
        $typeInUnion1 = new ObjectType([
1113
            'name'   => 'TypeInUnion1',
1114
            'fields' => [
1115
                'field1' => Type::string(),
1116
            ],
1117
        ]);
1118
1119
        $typeInUnion2 = new ObjectType([
1120
            'name'   => 'TypeInUnion2',
1121
            'fields' => [
1122
                'field1' => Type::string(),
1123
            ],
1124
        ]);
1125
1126
        $unionTypeThatLosesATypeOld = new UnionType([
1127
            'name'  => 'UnionTypeThatLosesAType',
1128
            'types' => [$typeInUnion1, $typeInUnion2],
1129
        ]);
1130
1131
        $unionTypeThatLosesATypeNew = new UnionType([
1132
            'name'  => 'UnionTypeThatLosesAType',
1133
            'types' => [$typeInUnion1],
1134
        ]);
1135
1136
        $enumTypeThatLosesAValueOld = new EnumType([
1137
            'name'   => 'EnumTypeThatLosesAValue',
1138
            'values' => [
1139
                'VALUE0' => 0,
1140
                'VALUE1' => 1,
1141
                'VALUE2' => 2,
1142
            ],
1143
        ]);
1144
1145
        $enumTypeThatLosesAValueNew = new EnumType([
1146
            'name'   => 'EnumTypeThatLosesAValue',
1147
            'values' => [
1148
                'VALUE1' => 1,
1149
                'VALUE2' => 2,
1150
            ],
1151
        ]);
1152
1153
        $interface1 = new InterfaceType([
1154
            'name'   => 'Interface1',
1155
            'fields' => [
1156
                'field1' => Type::string(),
1157
            ],
1158
        ]);
1159
1160
        $typeThatLosesInterfaceOld = new ObjectType([
1161
            'name'       => 'TypeThatLosesInterface1',
1162
            'interfaces' => [$interface1],
1163
            'fields'     => [
1164
                'field1' => Type::string(),
1165
            ],
1166
        ]);
1167
1168
        $typeThatLosesInterfaceNew = new ObjectType([
1169
            'name'   => 'TypeThatLosesInterface1',
1170
            'fields' => [
1171
                'field1' => Type::string(),
1172
            ],
1173
        ]);
1174
1175
        $directiveThatIsRemoved      = Directive::skipDirective();
1176
        $directiveThatRemovesArgOld  = new Directive([
1177
            'name'      => 'DirectiveThatRemovesArg',
1178
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1179
            'args'      => FieldArgument::createMap([
1180
                'arg1' => ['name' => 'arg1'],
1181
            ]),
1182
        ]);
1183
        $directiveThatRemovesArgNew  = new Directive([
1184
            'name'      => 'DirectiveThatRemovesArg',
1185
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1186
        ]);
1187
        $nonNullDirectiveAddedOld    = new Directive([
1188
            'name'      => 'NonNullDirectiveAdded',
1189
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1190
        ]);
1191
        $nonNullDirectiveAddedNew    = new Directive([
1192
            'name'      => 'NonNullDirectiveAdded',
1193
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1194
            'args'      => FieldArgument::createMap([
1195
                'arg1' => [
1196
                    'name' => 'arg1',
1197
                    'type' => Type::nonNull(Type::boolean()),
1198
                ],
1199
            ]),
1200
        ]);
1201
        $directiveRemovedLocationOld = new Directive([
1202
            'name'      => 'Directive Name',
1203
            'locations' => [DirectiveLocation::FIELD_DEFINITION, DirectiveLocation::QUERY],
1204
        ]);
1205
        $directiveRemovedLocationNew = new Directive([
1206
            'name'      => 'Directive Name',
1207
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1208
        ]);
1209
1210
        $oldSchema = new Schema([
1211
            'query'      => $this->queryType,
1212
            'types'      => [
1213
                $typeThatGetsRemoved,
1214
                $typeThatChangesTypeOld,
1215
                $typeThatHasBreakingFieldChangesOld,
1216
                $unionTypeThatLosesATypeOld,
1217
                $enumTypeThatLosesAValueOld,
1218
                $argThatChanges,
1219
                $typeThatLosesInterfaceOld,
1220
            ],
1221
            'directives' => [
1222
                $directiveThatIsRemoved,
1223
                $directiveThatRemovesArgOld,
1224
                $nonNullDirectiveAddedOld,
1225
                $directiveRemovedLocationOld,
1226
            ],
1227
        ]);
1228
1229
        $newSchema = new Schema([
1230
            'query'      => $this->queryType,
1231
            'types'      => [
1232
                $typeThatChangesTypeNew,
1233
                $typeThatHasBreakingFieldChangesNew,
1234
                $unionTypeThatLosesATypeNew,
1235
                $enumTypeThatLosesAValueNew,
1236
                $argChanged,
1237
                $typeThatLosesInterfaceNew,
1238
                $interface1,
1239
            ],
1240
            'directives' => [
1241
                $directiveThatRemovesArgNew,
1242
                $nonNullDirectiveAddedNew,
1243
                $directiveRemovedLocationNew,
1244
            ],
1245
        ]);
1246
1247
        $expectedBreakingChanges = [
1248
            [
1249
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
1250
                'description' => 'TypeThatGetsRemoved was removed.',
1251
            ],
1252
            [
1253
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED,
1254
                'description' => 'TypeInUnion2 was removed.',
1255
            ],
1256
            /* This is reported in the js version because builtin sclar types are added on demand
1257
               and not like here always
1258
             [
1259
                'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED,
1260
                'description' => 'Int was removed.'
1261
            ],*/
1262
            [
1263
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_CHANGED_KIND,
1264
                'description' => 'TypeThatChangesType changed from an Object type to an Interface type.',
1265
            ],
1266
            [
1267
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_REMOVED,
1268
                'description' => 'TypeThatHasBreakingFieldChanges.field1 was removed.',
1269
            ],
1270
            [
1271
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_FIELD_CHANGED_KIND,
1272
                'description' => 'TypeThatHasBreakingFieldChanges.field2 changed type from String to Boolean.',
1273
            ],
1274
            [
1275
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION,
1276
                'description' => 'TypeInUnion2 was removed from union type UnionTypeThatLosesAType.',
1277
            ],
1278
            [
1279
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM,
1280
                'description' => 'VALUE0 was removed from enum type EnumTypeThatLosesAValue.',
1281
            ],
1282
            [
1283
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_ARG_CHANGED_KIND,
1284
                'description' => 'ArgThatChanges.field1 arg id has changed type from Int to String',
1285
            ],
1286
            [
1287
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_INTERFACE_REMOVED_FROM_OBJECT,
1288
                'description' => 'TypeThatLosesInterface1 no longer implements interface Interface1.',
1289
            ],
1290
            [
1291
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
1292
                'description' => 'skip was removed',
1293
            ],
1294
            [
1295
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED,
1296
                'description' => 'arg1 was removed from DirectiveThatRemovesArg',
1297
            ],
1298
            [
1299
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED,
1300
                'description' => 'A non-null arg arg1 on directive NonNullDirectiveAdded was added',
1301
            ],
1302
            [
1303
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED,
1304
                'description' => 'QUERY was removed from Directive Name',
1305
            ],
1306
        ];
1307
1308
        self::assertEquals(
1309
            $expectedBreakingChanges,
1310
            BreakingChangesFinder::findBreakingChanges($oldSchema, $newSchema)
1311
        );
1312
    }
1313
1314
    /**
1315
     * @see it('should detect if a directive was explicitly removed')
1316
     */
1317
    public function testShouldDetectIfADirectiveWasExplicitlyRemoved() : void
1318
    {
1319
        $oldSchema = new Schema([
1320
            'directives' => [Directive::skipDirective(), Directive::includeDirective()],
1321
        ]);
1322
1323
        $newSchema = new Schema([
1324
            'directives' => [Directive::skipDirective()],
1325
        ]);
1326
1327
        $includeDirective = Directive::includeDirective();
1328
1329
        $expectedBreakingChanges = [
1330
            [
1331
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
1332
                'description' => sprintf('%s was removed', $includeDirective->name),
1333
            ],
1334
        ];
1335
1336
        self::assertEquals(
1337
            $expectedBreakingChanges,
1338
            BreakingChangesFinder::findRemovedDirectives($oldSchema, $newSchema)
1339
        );
1340
    }
1341
1342
    /**
1343
     * @see it('should detect if a directive was implicitly removed')
1344
     */
1345
    public function testShouldDetectIfADirectiveWasImplicitlyRemoved() : void
1346
    {
1347
        $oldSchema = new Schema([]);
1348
1349
        $newSchema = new Schema([
1350
            'directives' => [Directive::skipDirective(), Directive::includeDirective()],
1351
        ]);
1352
1353
        $deprecatedDirective = Directive::deprecatedDirective();
1354
1355
        $expectedBreakingChanges = [
1356
            [
1357
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_REMOVED,
1358
                'description' => sprintf('%s was removed', $deprecatedDirective->name),
1359
            ],
1360
        ];
1361
1362
        self::assertEquals(
1363
            $expectedBreakingChanges,
1364
            BreakingChangesFinder::findRemovedDirectives($oldSchema, $newSchema)
1365
        );
1366
    }
1367
1368
    /**
1369
     * @see it('should detect if a directive argument was removed')
1370
     */
1371
    public function testShouldDetectIfADirectiveArgumentWasRemoved() : void
1372
    {
1373
        $oldSchema = new Schema([
1374
            'directives' => [
1375
                new Directive([
1376
                    'name'      => 'DirectiveWithArg',
1377
                    'locations' => [DirectiveLocation::FIELD_DEFINITION],
1378
                    'args'      => FieldArgument::createMap([
1379
                        'arg1' => ['name' => 'arg1'],
1380
                    ]),
1381
                ]),
1382
            ],
1383
        ]);
1384
1385
        $newSchema = new Schema([
1386
            'directives' => [
1387
                new Directive([
1388
                    'name'      => 'DirectiveWithArg',
1389
                    'locations' => [DirectiveLocation::FIELD_DEFINITION],
1390
                ]),
1391
            ],
1392
        ]);
1393
1394
        $expectedBreakingChanges = [
1395
            [
1396
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_ARG_REMOVED,
1397
                'description' => 'arg1 was removed from DirectiveWithArg',
1398
            ],
1399
        ];
1400
1401
        self::assertEquals(
1402
            $expectedBreakingChanges,
1403
            BreakingChangesFinder::findRemovedDirectiveArgs($oldSchema, $newSchema)
1404
        );
1405
    }
1406
1407
    /**
1408
     * @see it('should detect if a non-nullable directive argument was added')
1409
     */
1410
    public function testShouldDetectIfANonNullableDirectiveArgumentWasAdded() : void
1411
    {
1412
        $oldSchema = new Schema([
1413
            'directives' => [
1414
                new Directive([
1415
                    'name'      => 'DirectiveName',
1416
                    'locations' => [DirectiveLocation::FIELD_DEFINITION],
1417
                ]),
1418
            ],
1419
        ]);
1420
1421
        $newSchema = new Schema([
1422
            'directives' => [
1423
                new Directive([
1424
                    'name'      => 'DirectiveName',
1425
                    'locations' => [DirectiveLocation::FIELD_DEFINITION],
1426
                    'args'      => FieldArgument::createMap([
1427
                        'arg1' => [
1428
                            'name' => 'arg1',
1429
                            'type' => Type::nonNull(Type::boolean()),
1430
                        ],
1431
                    ]),
1432
                ]),
1433
            ],
1434
        ]);
1435
1436
        $expectedBreakingChanges = [
1437
            [
1438
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_NON_NULL_DIRECTIVE_ARG_ADDED,
1439
                'description' => 'A non-null arg arg1 on directive DirectiveName was added',
1440
            ],
1441
        ];
1442
1443
        self::assertEquals(
1444
            $expectedBreakingChanges,
1445
            BreakingChangesFinder::findAddedNonNullDirectiveArgs($oldSchema, $newSchema)
1446
        );
1447
    }
1448
1449
    /**
1450
     * @see it('should detect locations removed from a directive')
1451
     */
1452
    public function testShouldDetectLocationsRemovedFromADirective() : void
1453
    {
1454
        $d1 = new Directive([
1455
            'name'      => 'Directive Name',
1456
            'locations' => [DirectiveLocation::FIELD_DEFINITION, DirectiveLocation::QUERY],
1457
        ]);
1458
1459
        $d2 = new Directive([
1460
            'name'      => 'Directive Name',
1461
            'locations' => [DirectiveLocation::FIELD_DEFINITION],
1462
        ]);
1463
1464
        self::assertEquals(
1465
            [DirectiveLocation::QUERY],
1466
            BreakingChangesFinder::findRemovedLocationsForDirective($d1, $d2)
1467
        );
1468
    }
1469
1470
    /**
1471
     * @see it('should detect locations removed directives within a schema')
1472
     */
1473
    public function testShouldDetectLocationsRemovedDirectiveWithinASchema() : void
1474
    {
1475
        $oldSchema = new Schema([
1476
            'directives' => [
1477
                new Directive([
1478
                    'name'      => 'Directive Name',
1479
                    'locations' => [
1480
                        DirectiveLocation::FIELD_DEFINITION,
1481
                        DirectiveLocation::QUERY,
1482
                    ],
1483
                ]),
1484
            ],
1485
        ]);
1486
1487
        $newSchema = new Schema([
1488
            'directives' => [
1489
                new Directive([
1490
                    'name'      => 'Directive Name',
1491
                    'locations' => [DirectiveLocation::FIELD_DEFINITION],
1492
                ]),
1493
            ],
1494
        ]);
1495
1496
        $expectedBreakingChanges = [
1497
            [
1498
                'type'        => BreakingChangesFinder::BREAKING_CHANGE_DIRECTIVE_LOCATION_REMOVED,
1499
                'description' => 'QUERY was removed from Directive Name',
1500
            ],
1501
        ];
1502
1503
        self::assertEquals(
1504
            $expectedBreakingChanges,
1505
            BreakingChangesFinder::findRemovedDirectiveLocations($oldSchema, $newSchema)
1506
        );
1507
    }
1508
1509
    // DESCRIBE: findDangerousChanges
1510
    // DESCRIBE: findArgChanges
1511
1512
    /**
1513
     * @see it('should detect if an argument's defaultValue has changed')
1514
     */
1515
    public function testShouldDetectIfAnArgumentsDefaultValueHasChanged() : void
1516
    {
1517
        $oldType = new ObjectType([
1518
            'name'   => 'Type1',
1519
            'fields' => [
1520
                'field1' => [
1521
                    'type' => Type::string(),
1522
                    'args' => [
1523
                        'name' => [
1524
                            'type'         => Type::string(),
1525
                            'defaultValue' => 'test',
1526
                        ],
1527
                    ],
1528
                ],
1529
            ],
1530
        ]);
1531
1532
        $newType = new ObjectType([
1533
            'name'   => 'Type1',
1534
            'fields' => [
1535
                'field1' => [
1536
                    'type' => Type::string(),
1537
                    'args' => [
1538
                        'name' => [
1539
                            'type'         => Type::string(),
1540
                            'defaultValue' => 'Test',
1541
                        ],
1542
                    ],
1543
                ],
1544
            ],
1545
        ]);
1546
1547
        $oldSchema = new Schema([
1548
            'query' => $this->queryType,
1549
            'types' => [$oldType],
1550
        ]);
1551
1552
        $newSchema = new Schema([
1553
            'query' => $this->queryType,
1554
            'types' => [$newType],
1555
        ]);
1556
1557
        $expected = [
1558
            [
1559
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED,
1560
                'description' => 'Type1.field1 arg name has changed defaultValue',
1561
            ],
1562
        ];
1563
1564
        self::assertEquals(
1565
            $expected,
1566
            BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['dangerousChanges']
1567
        );
1568
    }
1569
1570
    /**
1571
     * @see it('should detect if a value was added to an enum type')
1572
     */
1573
    public function testShouldDetectIfAValueWasAddedToAnEnumType() : void
1574
    {
1575
        $oldEnumType = new EnumType([
1576
            'name'   => 'EnumType1',
1577
            'values' => [
1578
                'VALUE0' => 0,
1579
                'VALUE1' => 1,
1580
            ],
1581
        ]);
1582
        $newEnumType = new EnumType([
1583
            'name'   => 'EnumType1',
1584
            'values' => [
1585
                'VALUE0' => 0,
1586
                'VALUE1' => 1,
1587
                'VALUE2' => 2,
1588
            ],
1589
        ]);
1590
1591
        $oldSchema = new Schema([
1592
            'query' => $this->queryType,
1593
            'types' => [$oldEnumType],
1594
        ]);
1595
1596
        $newSchema = new Schema([
1597
            'query' => $this->queryType,
1598
            'types' => [$newEnumType],
1599
        ]);
1600
1601
        $expected = [
1602
            [
1603
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM,
1604
                'description' => 'VALUE2 was added to enum type EnumType1.',
1605
            ],
1606
        ];
1607
1608
        self::assertEquals(
1609
            $expected,
1610
            BreakingChangesFinder::findValuesAddedToEnums($oldSchema, $newSchema)
1611
        );
1612
    }
1613
1614
    /**
1615
     * @see it('should detect interfaces added to types')
1616
     */
1617
    public function testShouldDetectInterfacesAddedToTypes() : void
1618
    {
1619
        $interface1 = new InterfaceType([
1620
            'name'   => 'Interface1',
1621
            'fields' => [
1622
                'field1' => Type::string(),
1623
            ],
1624
        ]);
1625
        $oldType    = new ObjectType([
1626
            'name'   => 'Type1',
1627
            'fields' => [
1628
                'field1' => Type::string(),
1629
            ],
1630
        ]);
1631
1632
        $newType = new ObjectType([
1633
            'name'       => 'Type1',
1634
            'interfaces' => [$interface1],
1635
            'fields'     => [
1636
                'field1' => Type::string(),
1637
            ],
1638
        ]);
1639
1640
        $oldSchema = new Schema([
1641
            'query' => $this->queryType,
1642
            'types' => [$oldType],
1643
        ]);
1644
1645
        $newSchema = new Schema([
1646
            'query' => $this->queryType,
1647
            'types' => [$newType],
1648
        ]);
1649
1650
        $expected = [
1651
            [
1652
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT,
1653
                'description' => 'Interface1 added to interfaces implemented by Type1.',
1654
            ],
1655
        ];
1656
1657
        self::assertEquals(
1658
            $expected,
1659
            BreakingChangesFinder::findInterfacesAddedToObjectTypes($oldSchema, $newSchema)
1660
        );
1661
    }
1662
1663
    /**
1664
     * @see it('should detect if a type was added to a union type')
1665
     */
1666
    public function testShouldDetectIfATypeWasAddedToAUnionType() : void
1667
    {
1668
        $type1 = new ObjectType([
1669
            'name'   => 'Type1',
1670
            'fields' => [
1671
                'field1' => Type::string(),
1672
            ],
1673
        ]);
1674
        // logially equivalent to type1; findTypesRemovedFromUnions should not
1675
        //treat this as different than type1
1676
        $type1a = new ObjectType([
1677
            'name'   => 'Type1',
1678
            'fields' => [
1679
                'field1' => Type::string(),
1680
            ],
1681
        ]);
1682
        $type2  = new ObjectType([
1683
            'name'   => 'Type2',
1684
            'fields' => [
1685
                'field1' => Type::string(),
1686
            ],
1687
        ]);
1688
1689
        $oldUnionType = new UnionType([
1690
            'name'  => 'UnionType1',
1691
            'types' => [$type1],
1692
        ]);
1693
        $newUnionType = new UnionType([
1694
            'name'  => 'UnionType1',
1695
            'types' => [$type1a, $type2],
1696
        ]);
1697
1698
        $oldSchema = new Schema([
1699
            'query' => $this->queryType,
1700
            'types' => [$oldUnionType],
1701
        ]);
1702
1703
        $newSchema = new Schema([
1704
            'query' => $this->queryType,
1705
            'types' => [$newUnionType],
1706
        ]);
1707
1708
        $expected = [
1709
            [
1710
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION,
1711
                'description' => 'Type2 was added to union type UnionType1.',
1712
            ],
1713
        ];
1714
1715
        self::assertEquals(
1716
            $expected,
1717
            BreakingChangesFinder::findTypesAddedToUnions($oldSchema, $newSchema)
1718
        );
1719
    }
1720
1721
    /**
1722
     * @see it('should detect if a nullable field was added to an input')
1723
     */
1724
    public function testShouldDetectIfANullableFieldWasAddedToAnInput() : void
1725
    {
1726
        $oldInputType = new InputObjectType([
1727
            'name'   => 'InputType1',
1728
            'fields' => [
1729
                'field1' => [
1730
                    'type' => Type::string(),
1731
                ],
1732
            ],
1733
        ]);
1734
        $newInputType = new InputObjectType([
1735
            'name'   => 'InputType1',
1736
            'fields' => [
1737
                'field1' => [
1738
                    'type' => Type::string(),
1739
                ],
1740
                'field2' => [
1741
                    'type' => Type::int(),
1742
                ],
1743
            ],
1744
        ]);
1745
1746
        $oldSchema = new Schema([
1747
            'query' => $this->queryType,
1748
            'types' => [$oldInputType],
1749
        ]);
1750
1751
        $newSchema = new Schema([
1752
            'query' => $this->queryType,
1753
            'types' => [$newInputType],
1754
        ]);
1755
1756
        $expectedFieldChanges = [
1757
            [
1758
                'description' => 'A nullable field field2 on input type InputType1 was added.',
1759
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_INPUT_FIELD_ADDED,
1760
            ],
1761
        ];
1762
1763
        self::assertEquals(
1764
            $expectedFieldChanges,
1765
            BreakingChangesFinder::findFieldsThatChangedTypeOnInputObjectTypes(
1766
                $oldSchema,
1767
                $newSchema
1768
            )['dangerousChanges']
1769
        );
1770
    }
1771
1772
    /**
1773
     * @see it('should find all dangerous changes')
1774
     */
1775
    public function testShouldFindAllDangerousChanges() : void
1776
    {
1777
        $enumThatGainsAValueOld = new EnumType([
1778
            'name'   => 'EnumType1',
1779
            'values' => [
1780
                'VALUE0' => 0,
1781
                'VALUE1' => 1,
1782
            ],
1783
        ]);
1784
        $enumThatGainsAValueNew = new EnumType([
1785
            'name'   => 'EnumType1',
1786
            'values' => [
1787
                'VALUE0' => 0,
1788
                'VALUE1' => 1,
1789
                'VALUE2' => 2,
1790
            ],
1791
        ]);
1792
1793
        $oldType = new ObjectType([
1794
            'name'   => 'Type1',
1795
            'fields' => [
1796
                'field1' => [
1797
                    'type' => Type::string(),
1798
                    'args' => [
1799
                        'name' => [
1800
                            'type'         => Type::string(),
1801
                            'defaultValue' => 'test',
1802
                        ],
1803
                    ],
1804
                ],
1805
            ],
1806
        ]);
1807
1808
        $typeInUnion1               = new ObjectType([
1809
            'name'   => 'TypeInUnion1',
1810
            'fields' => [
1811
                'field1' => Type::string(),
1812
            ],
1813
        ]);
1814
        $typeInUnion2               = new ObjectType([
1815
            'name'   => 'TypeInUnion2',
1816
            'fields' => [
1817
                'field1' => Type::string(),
1818
            ],
1819
        ]);
1820
        $unionTypeThatGainsATypeOld = new UnionType([
1821
            'name'  => 'UnionTypeThatGainsAType',
1822
            'types' => [$typeInUnion1],
1823
        ]);
1824
        $unionTypeThatGainsATypeNew = new UnionType([
1825
            'name'  => 'UnionTypeThatGainsAType',
1826
            'types' => [$typeInUnion1, $typeInUnion2],
1827
        ]);
1828
1829
        $newType = new ObjectType([
1830
            'name'   => 'Type1',
1831
            'fields' => [
1832
                'field1' => [
1833
                    'type' => Type::string(),
1834
                    'args' => [
1835
                        'name' => [
1836
                            'type'         => Type::string(),
1837
                            'defaultValue' => 'Test',
1838
                        ],
1839
                    ],
1840
                ],
1841
            ],
1842
        ]);
1843
1844
        $interface1 = new InterfaceType([
1845
            'name'   => 'Interface1',
1846
            'fields' => [
1847
                'field1' => Type::string(),
1848
            ],
1849
        ]);
1850
1851
        $typeThatGainsInterfaceOld = new ObjectType([
1852
            'name'   => 'TypeThatGainsInterface1',
1853
            'fields' => [
1854
                'field1' => Type::string(),
1855
            ],
1856
        ]);
1857
1858
        $typeThatGainsInterfaceNew = new ObjectType([
1859
            'name'       => 'TypeThatGainsInterface1',
1860
            'interfaces' => [$interface1],
1861
            'fields'     => [
1862
                'field1' => Type::string(),
1863
            ],
1864
        ]);
1865
1866
        $oldSchema = new Schema([
1867
            'query' => $this->queryType,
1868
            'types' => [
1869
                $oldType,
1870
                $enumThatGainsAValueOld,
1871
                $typeThatGainsInterfaceOld,
1872
                $unionTypeThatGainsATypeOld,
1873
            ],
1874
        ]);
1875
1876
        $newSchema = new Schema([
1877
            'query' => $this->queryType,
1878
            'types' => [
1879
                $newType,
1880
                $enumThatGainsAValueNew,
1881
                $typeThatGainsInterfaceNew,
1882
                $unionTypeThatGainsATypeNew,
1883
            ],
1884
        ]);
1885
1886
        $expectedDangerousChanges = [
1887
            [
1888
                'description' => 'Type1.field1 arg name has changed defaultValue',
1889
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_ARG_DEFAULT_VALUE_CHANGED,
1890
            ],
1891
            [
1892
                'description' => 'VALUE2 was added to enum type EnumType1.',
1893
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_VALUE_ADDED_TO_ENUM,
1894
            ],
1895
            [
1896
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_INTERFACE_ADDED_TO_OBJECT,
1897
                'description' => 'Interface1 added to interfaces implemented by TypeThatGainsInterface1.',
1898
            ],
1899
            [
1900
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_TYPE_ADDED_TO_UNION,
1901
                'description' => 'TypeInUnion2 was added to union type UnionTypeThatGainsAType.',
1902
            ],
1903
        ];
1904
1905
        self::assertEquals(
1906
            $expectedDangerousChanges,
1907
            BreakingChangesFinder::findDangerousChanges($oldSchema, $newSchema)
1908
        );
1909
    }
1910
1911
    /**
1912
     * @see it('should detect if a nullable field argument was added')
1913
     */
1914
    public function testShouldDetectIfANullableFieldArgumentWasAdded() : void
1915
    {
1916
        $oldType = new ObjectType([
1917
            'name'   => 'Type1',
1918
            'fields' => [
1919
                'field1' => [
1920
                    'type' => Type::string(),
1921
                    'args' => [
1922
                        'arg1' => [
1923
                            'type' => Type::string(),
1924
                        ],
1925
                    ],
1926
                ],
1927
            ],
1928
        ]);
1929
1930
        $newType = new ObjectType([
1931
            'name'   => 'Type1',
1932
            'fields' => [
1933
                'field1' => [
1934
                    'type' => Type::string(),
1935
                    'args' => [
1936
                        'arg1' => [
1937
                            'type' => Type::string(),
1938
                        ],
1939
                        'arg2' => [
1940
                            'type' => Type::string(),
1941
                        ],
1942
                    ],
1943
                ],
1944
            ],
1945
        ]);
1946
1947
        $oldSchema = new Schema([
1948
            'query' => $this->queryType,
1949
            'types' => [$oldType],
1950
        ]);
1951
1952
        $newSchema = new Schema([
1953
            'query' => $this->queryType,
1954
            'types' => [$newType],
1955
        ]);
1956
1957
        $expectedFieldChanges = [
1958
            [
1959
                'description' => 'A nullable arg arg2 on Type1.field1 was added',
1960
                'type'        => BreakingChangesFinder::DANGEROUS_CHANGE_NULLABLE_ARG_ADDED,
1961
            ],
1962
        ];
1963
1964
        self::assertEquals(
1965
            $expectedFieldChanges,
1966
            BreakingChangesFinder::findArgChanges($oldSchema, $newSchema)['dangerousChanges']
1967
        );
1968
    }
1969
}
1970