queryType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string() ] ] ]); } public function testShouldDetectIfTypeWasRemoved() { $type1 = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $type2 = new ObjectType([ 'name' => 'Type2', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $type1, 'type2' => $type2 ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type2' => $type2 ] ]) ]); $this->assertEquals(['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED, 'description' => 'Type1 was removed.'], FindBreakingChanges::findRemovedTypes($oldSchema, $newSchema)[0] ); $this->assertEquals([], FindBreakingChanges::findRemovedTypes($oldSchema, $oldSchema)); } public function testShouldDetectTypeChanges() { $objectType = new ObjectType([ 'name' => 'ObjectType', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $interfaceType = new InterfaceType([ 'name' => 'Type1', 'fields' => [ 'field1' => ['type' => Type::string()] ] ]); $unionType = new UnionType([ 'name' => 'Type1', 'types' => [new ObjectType(['name' => 'blah'])], ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $interfaceType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $unionType ] ]) ]); $this->assertEquals( ['type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_CHANGED, 'description' => 'Type1 changed from an Interface type to a Union type.'], FindBreakingChanges::findTypesThatChangedKind($oldSchema, $newSchema)[0] ); } public function testShouldDetectFieldChangesAndDeletions() { $typeA1 = new ObjectType([ 'name' => 'TypeA', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $typeA2 = new ObjectType([ 'name' => 'TypeA', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $typeB = new ObjectType([ 'name' => 'TypeB', 'fields' => [ 'field1' => ['type' => Type::string()], ] ]); $oldType1 = new InterfaceType([ 'name' => 'Type1', 'fields' => [ 'field1' => ['type' => $typeA1], 'field2' => ['type' => Type::string()], 'field3' => ['type' => Type::string()], 'field4' => ['type' => $typeA1], 'field6' => ['type' => Type::string()], 'field7' => ['type' => Type::listOf(Type::string())], 'field8' => ['type' => Type::int()], 'field9' => ['type' => Type::nonNull(Type::int())], 'field10' => ['type' => Type::nonNull(Type::listOf(Type::int()))], 'field11' => ['type' => Type::int()], 'field12' => ['type' => Type::listOf(Type::int())], 'field13' => ['type' => Type::listOf(Type::nonNull(Type::int()))], 'field14' => ['type' => Type::listOf(Type::int())], 'field15' => ['type' => Type::listOf(Type::listOf(Type::int()))], 'field16' => ['type' => Type::nonNull(Type::int())], 'field17' => ['type' => Type::listOf(Type::int())], 'field18' => [ 'type' => Type::listOf(Type::nonNull( Type::listOf(Type::nonNull(Type::int())))), ], ] ]); $newType1 = new InterfaceType([ 'name' => 'Type1', 'fields' => [ 'field1' => ['type' => $typeA2], 'field3' => ['type' => Type::boolean()], 'field4' => ['type' => $typeB], 'field5' => ['type' => Type::string()], 'field6' => ['type' => Type::listOf(Type::string())], 'field7' => ['type' => Type::string()], 'field8' => ['type' => Type::nonNull(Type::int())], 'field9' => ['type' => Type::int()], 'field10' => ['type' => Type::listOf(Type::int())], 'field11' => ['type' => Type::nonNull(Type::listOf(Type::int()))], 'field12' => ['type' => Type::listOf(Type::nonNull(Type::int()))], 'field13' => ['type' => Type::listOf(Type::int())], 'field14' => ['type' => Type::listOf(Type::listOf(Type::int()))], 'field15' => ['type' => Type::listOf(Type::int())], 'field16' => ['type' => Type::nonNull(Type::listOf(Type::int()))], 'field17' => ['type' => Type::nonNull(Type::listOf(Type::int()))], 'field18' => [ 'type' => Type::listOf( Type::listOf(Type::nonNull(Type::int()))), ], ] ]); $expectedFieldChanges = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, 'description' => 'Type1->field2 was removed.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field3 changed type from String to Boolean.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field4 changed type from TypeA to TypeB.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field6 changed type from String to [String].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field7 changed type from [String] to String.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field9 changed type from Int! to Int.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field10 changed type from [Int]! to [Int].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field11 changed type from Int to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field13 changed type from [Int!] to [Int].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field14 changed type from [Int] to [[Int]].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field15 changed type from [[Int]] to [Int].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field16 changed type from Int! to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'Type1->field18 changed type from [[Int!]!] to [[Int!]].', ], ]; $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'Type1' => $oldType1 ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'Type1' => $newType1 ] ]) ]); $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); } public function testShouldDetectInputFieldChanges() { $oldInputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ 'field1' => [ 'type' => Type::string(), ], 'field2' => [ 'type' => Type::boolean(), ], 'field3' => [ 'type' => Type::listOf(Type::string()) ], 'field4' => [ 'type' => Type::nonNull(Type::string()), ], 'field5' => [ 'type' => Type::string(), ], 'field6' => [ 'type' => Type::listOf(Type::int()) ], 'field7' => [ 'type' => Type::nonNull(Type::listOf(Type::int())) ], 'field8' => [ 'type' => Type::int(), ], 'field9' => [ 'type' => Type::listOf(Type::int()) ], 'field10' => [ 'type' => Type::listOf(Type::nonNull(Type::int())) ], 'field11' => [ 'type' => Type::listOf(Type::int()) ], 'field12' => [ 'type' => Type::listOf(Type::listOf(Type::int())) ], 'field13' => [ 'type' => Type::nonNull(Type::int()) ], 'field14' => [ 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) ], 'field15' => [ 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) ] ] ]); $newInputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ 'field1' => [ 'type' => Type::int(), ], 'field3' => [ 'type' => Type::string() ], 'field4' => [ 'type' => Type::string() ], 'field5' => [ 'type' => Type::nonNull(Type::string()) ], 'field6' => [ 'type' => Type::nonNull(Type::listOf(Type::int())) ], 'field7' => [ 'type' => Type::listOf(Type::int()) ], 'field8' => [ 'type' => Type::nonNull(Type::listOf(Type::int())) ], 'field9' => [ 'type' => Type::listOf(Type::nonNull(Type::int())) ], 'field10' => [ 'type' => Type::listOf(Type::int()) ], 'field11' => [ 'type' => Type::listOf(Type::listOf(Type::int())) ], 'field12' => [ 'type' => Type::listOf(Type::int()) ], 'field13' => [ 'type' => Type::nonNull(Type::listOf(Type::int())) ], 'field14' => [ 'type' => Type::listOf(Type::listOf(Type::int())) ], 'field15' => [ 'type' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))) ] ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldInputType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newInputType ] ]) ]); $expectedFieldChanges = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field1 changed type from String to Int.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_REMOVED, 'description' => 'InputType1->field2 was removed.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field3 changed type from [String] to String.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field5 changed type from String to String!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field6 changed type from [Int] to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field8 changed type from Int to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field9 changed type from [Int] to [Int!].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field11 changed type from [Int] to [[Int]].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field12 changed type from [[Int]] to [Int].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field13 changed type from Int! to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_FIELD_CHANGED, 'description' => 'InputType1->field15 changed type from [[Int]!] to [[Int!]!].', ], ]; $this->assertEquals($expectedFieldChanges, FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)); } public function testDetectsNonNullFieldAddedToInputType() { $oldInputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ 'field1' => Type::string() ] ]); $newInputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ 'field1' => Type::string(), 'requiredField' => Type::nonNull(Type::int()), 'optionalField' => Type::boolean() ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldInputType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newInputType ] ]) ]); $this->assertEquals( [ 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_INPUT_FIELD_ADDED, 'description' => 'A non-null field requiredField on input type InputType1 was added.' ], FindBreakingChanges::findFieldsThatChangedType($oldSchema, $newSchema)[0] ); } public function testDetectsIfTypeWasRemovedFromUnion() { $type1 = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => Type::string() ] ]); $type1a = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => Type::string() ] ]); $type2 = new ObjectType([ 'name' => 'Type2', 'fields' => [ 'field1' => Type::string() ] ]); $type3 = new ObjectType([ 'name' => 'Type3', 'fields' => [ 'field1' => Type::string() ] ]); $oldUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1, $type2], 'resolveType' => function () { } ]); $newUnionType = new UnionType([ 'name' => 'UnionType1', 'types' => [$type1a, $type3], 'resolveType' => function () { } ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldUnionType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newUnionType ] ]) ]); $this->assertEquals( [ 'type' => FindBreakingChanges::BREAKING_CHANGE_TYPE_REMOVED_FROM_UNION, 'description' => 'Type2 was removed from union type UnionType1.' ], FindBreakingChanges::findTypesRemovedFromUnions($oldSchema, $newSchema)[0] ); } public function testDetectsValuesRemovedFromEnum() { $oldEnumType = new EnumType([ 'name' => 'EnumType1', 'values' => [ 'VALUE0' => 0, 'VALUE1' => 1, 'VALUE2' => 2 ] ]); $newEnumType = new EnumType([ 'name' => 'EnumType1', 'values' => [ 'VALUE0' => 0, 'VALUE2' => 1, 'VALUE3' => 2 ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldEnumType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newEnumType ] ]) ]); $this->assertEquals( [ 'type' => FindBreakingChanges::BREAKING_CHANGE_VALUE_REMOVED_FROM_ENUM, 'description' => 'VALUE1 was removed from enum type EnumType1.' ], FindBreakingChanges::findValuesRemovedFromEnums($oldSchema, $newSchema)[0] ); } public function testDetectsRemovalOfFieldArgument() { $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'name' => Type::string() ] ] ] ]); $inputType = new InputObjectType([ 'name' => 'InputType1', 'fields' => [ 'field1' => Type::string() ] ]); $oldInterfaceType = new InterfaceType([ 'name' => 'Interface1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'arg1' => Type::boolean(), 'objectArg' => $inputType ] ] ] ]); $newType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [] ] ] ]); $newInterfaceType = new InterfaceType([ 'name' => 'Interface1', 'fields' => [ 'field1' => Type::string() ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldType, 'type2' => $oldInterfaceType ], 'types' => [$oldType, $oldInterfaceType] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newType, 'type2' => $newInterfaceType ], 'types' => [$newType, $newInterfaceType] ]) ]); $expectedChanges = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'description' => 'Type1->field1 arg name was removed', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'description' => 'Interface1->field1 arg arg1 was removed', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_REMOVED, 'description' => 'Interface1->field1 arg objectArg was removed', ] ]; $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } public function testDetectsFieldArgumentTypeChange() { $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'arg1' => Type::string(), 'arg2' => Type::string(), 'arg3' => Type::listOf(Type::string()), 'arg4' => Type::string(), 'arg5' => Type::nonNull(Type::string()), 'arg6' => Type::nonNull(Type::string()), 'arg7' => Type::nonNull(Type::listOf(Type::int())), 'arg8' => Type::int(), 'arg9' => Type::listOf(Type::int()), 'arg10' => Type::listOf(Type::nonNull(Type::int())), 'arg11' => Type::listOf(Type::int()), 'arg12' => Type::listOf(Type::listOf(Type::int())), 'arg13' => Type::nonNull(Type::int()), 'arg14' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))), 'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::int()))) ] ] ] ]); $newType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'arg1' => Type::int(), 'arg2' => Type::listOf(Type::string()), 'arg3' => Type::string(), 'arg4' => Type::nonNull(Type::string()), 'arg5' => Type::int(), 'arg6' => Type::nonNull(Type::int()), 'arg7' => Type::listOf(Type::int()), 'arg8' => Type::nonNull(Type::listOf(Type::int())), 'arg9' => Type::listOf(Type::nonNull(Type::int())), 'arg10' => Type::listOf(Type::int()), 'arg11' => Type::listOf(Type::listOf(Type::int())), 'arg12' => Type::listOf(Type::int()), 'arg13' => Type::nonNull(Type::listOf(Type::int())), 'arg14' => Type::listOf(Type::listOf(Type::int())), 'arg15' => Type::listOf(Type::nonNull(Type::listOf(Type::nonNull(Type::int())))) ] ] ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldType ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newType ] ]) ]); $expectedChanges = [ [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg1 has changed type from String to Int.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg2 has changed type from String to [String].' ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg3 has changed type from [String] to String.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg4 has changed type from String to String!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg5 has changed type from String! to Int.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg6 has changed type from String! to Int!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg8 has changed type from Int to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg9 has changed type from [Int] to [Int!].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg11 has changed type from [Int] to [[Int]].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg12 has changed type from [[Int]] to [Int].', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg13 has changed type from Int! to [Int]!.', ], [ 'type' => FindBreakingChanges::BREAKING_CHANGE_ARG_CHANGED, 'description' => 'Type1->field1 arg arg15 has changed type from [[Int]!] to [[Int!]!].', ], ]; $this->assertEquals($expectedChanges, FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges']); } public function testDetectsAdditionOfFieldArg() { $oldType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'arg1' => Type::string() ]] ] ]); $newType = new ObjectType([ 'name' => 'Type1', 'fields' => [ 'field1' => [ 'type' => Type::string(), 'args' => [ 'arg1' => Type::string(), 'newRequiredArg' => Type::nonNull(Type::string()), 'newOptionalArg' => Type::int() ]] ] ]); $oldSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $oldType, ] ]) ]); $newSchema = new Schema([ 'query' => new ObjectType([ 'name' => 'root', 'fields' => [ 'type1' => $newType ] ]) ]); $this->assertEquals( [ 'type' => FindBreakingChanges::BREAKING_CHANGE_NON_NULL_ARG_ADDED, 'description' => 'A non-null arg newRequiredArg on Type1->field1 was added.' ], FindBreakingChanges::findArgChanges($oldSchema, $newSchema)['breakingChanges'][0]); } }