Failed Conditions
Pull Request — master (#328)
by Šimon
04:02
created

testShouldDetectIfADirectiveWasExplicitlyRemoved()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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