import { expect } from 'chai' ; import { describe , it } from 'mocha' ; import { dedent } from '../../__testUtils__/dedent' ; import { invariant } from '../../jsutils/invariant' ; import type { Maybe } from '../../jsutils/Maybe' ; import type { ASTNode } from '../../language/ast' ; import { parse } from '../../language/parser' ; import { print } from '../../language/printer' ; import { assertEnumType , assertInputObjectType , assertInterfaceType , assertObjectType , assertScalarType , assertUnionType , } from '../../type/definition' ; import { assertDirective } from '../../type/directives' ; import { GraphQLBoolean , GraphQLFloat , GraphQLID , GraphQLInt , GraphQLString , } from '../../type/scalars' ; import { GraphQLSchema } from '../../type/schema' ; import { validateSchema } from '../../type/validate' ; import { graphqlSync } from '../../graphql' ; import { buildSchema } from '../buildASTSchema' ; import { concatAST } from '../concatAST' ; import { extendSchema } from '../extendSchema' ; import { printSchema } from '../printSchema' ; function expectExtensionASTNodes ( obj ) { return expect ( obj . extensionASTNodes . map ( print ) . join ( '\n\n' ) ) ; } function expectASTNode ( obj ) { invariant ( obj ?. astNode != null ) ; return expect ( print ( obj . astNode ) ) ; } function expectSchemaChanges ( schema , extendedSchema ) { const schemaDefinitions = parse ( printSchema ( schema ) ) . definitions . map ( print ) ; return expect ( parse ( printSchema ( extendedSchema ) ) . definitions . map ( print ) . filter ( ( def ) => ! schemaDefinitions . includes ( def ) ) . join ( '\n\n' ) ) ; } describe ( 'extendSchema' , ( ) => { it ( 'returns the original schema when there are no type definitions' , ( ) => { const schema = buildSchema ( 'type Query' ) ; const extendedSchema = extendSchema ( schema , parse ( '{ field }' ) ) ; expect ( extendedSchema ) . to . equal ( schema ) ; } ) ; it ( 'can be used for limited execution' , ( ) => { const schema = buildSchema ( 'type Query' ) ; const extendAST = parse ( `
      extend type Query {
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const result = graphqlSync ( { schema : extendedSchema , source : '{ newField }' , rootValue : { newField : 123 } , } ) ; expect ( result ) . to . deep . equal ( { data : { newField : '123' } , } ) ; } ) ; it ( 'extends objects by adding new fields' , ( ) => { const schema = buildSchema ( `
      type Query {
        someObject: SomeObject
      }

      type SomeObject implements AnotherInterface & SomeInterface {
        self: SomeObject
        tree: [SomeObject]!
        """Old field description."""
        oldField: String
      }

      interface SomeInterface {
        self: SomeInterface
      }

      interface AnotherInterface {
        self: SomeObject
      }
    ` ) ; const extensionSDL = dedent `
      extend type SomeObject {
        """New field description."""
        newField(arg: Boolean): String
      }
    ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject implements AnotherInterface & SomeInterface {
        self: SomeObject
        tree: [SomeObject]!
        """Old field description."""
        oldField: String
        """New field description."""
        newField(arg: Boolean): String
      }
    ` ) ; } ) ; it ( 'extends objects with standard type fields' , ( ) => { const schema = buildSchema ( 'type Query' ) ; expect ( schema . getType ( 'Int' ) ) . to . equal ( undefined ) ; expect ( schema . getType ( 'Float' ) ) . to . equal ( undefined ) ; expect ( schema . getType ( 'String' ) ) . to . equal ( GraphQLString ) ; expect ( schema . getType ( 'Boolean' ) ) . to . equal ( GraphQLBoolean ) ; expect ( schema . getType ( 'ID' ) ) . to . equal ( undefined ) ; const extendAST = parse ( `
      extend type Query {
        bool: Boolean
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expect ( extendedSchema . getType ( 'Int' ) ) . to . equal ( undefined ) ; expect ( extendedSchema . getType ( 'Float' ) ) . to . equal ( undefined ) ; expect ( extendedSchema . getType ( 'String' ) ) . to . equal ( GraphQLString ) ; expect ( extendedSchema . getType ( 'Boolean' ) ) . to . equal ( GraphQLBoolean ) ; expect ( extendedSchema . getType ( 'ID' ) ) . to . equal ( undefined ) ; const extendTwiceAST = parse ( `
      extend type Query {
        int: Int
        float: Float
        id: ID
      }
    ` ) ; const extendedTwiceSchema = extendSchema ( schema , extendTwiceAST ) ; expect ( validateSchema ( extendedTwiceSchema ) ) . to . deep . equal ( [ ] ) ; expect ( extendedTwiceSchema . getType ( 'Int' ) ) . to . equal ( GraphQLInt ) ; expect ( extendedTwiceSchema . getType ( 'Float' ) ) . to . equal ( GraphQLFloat ) ; expect ( extendedTwiceSchema . getType ( 'String' ) ) . to . equal ( GraphQLString ) ; expect ( extendedTwiceSchema . getType ( 'Boolean' ) ) . to . equal ( GraphQLBoolean ) ; expect ( extendedTwiceSchema . getType ( 'ID' ) ) . to . equal ( GraphQLID ) ; } ) ; it ( 'extends enums by adding new values' , ( ) => { const schema = buildSchema ( `
      type Query {
        someEnum(arg: SomeEnum): SomeEnum
      }

      directive @foo(arg: SomeEnum) on SCHEMA

      enum SomeEnum {
        """Old value description."""
        OLD_VALUE
      }
    ` ) ; const extendAST = parse ( `
      extend enum SomeEnum {
        """New value description."""
        NEW_VALUE
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      enum SomeEnum {
        """Old value description."""
        OLD_VALUE
        """New value description."""
        NEW_VALUE
      }
    ` ) ; } ) ; it ( 'extends unions by adding new types' , ( ) => { const schema = buildSchema ( `
      type Query {
        someUnion: SomeUnion
      }

      union SomeUnion = Foo | Biz

      type Foo { foo: String }
      type Biz { biz: String }
      type Bar { bar: String }
    ` ) ; const extendAST = parse ( `
      extend union SomeUnion = Bar
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      union SomeUnion = Foo | Biz | Bar
    ` ) ; } ) ; it ( 'allows extension of union by adding itself' , ( ) => { const schema = buildSchema ( `
      union SomeUnion
    ` ) ; const extendAST = parse ( `
      extend union SomeUnion = SomeUnion
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . have . lengthOf . above ( 0 ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      union SomeUnion = SomeUnion
    ` ) ; } ) ; it ( 'extends inputs by adding new fields' , ( ) => { const schema = buildSchema ( `
      type Query {
        someInput(arg: SomeInput): String
      }

      directive @foo(arg: SomeInput) on SCHEMA

      input SomeInput {
        """Old field description."""
        oldField: String
      }
    ` ) ; const extendAST = parse ( `
      extend input SomeInput {
        """New field description."""
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      input SomeInput {
        """Old field description."""
        oldField: String
        """New field description."""
        newField: String
      }
    ` ) ; } ) ; it ( 'extends scalars by adding new directives' , ( ) => { const schema = buildSchema ( `
      type Query {
        someScalar(arg: SomeScalar): SomeScalar
      }

      directive @foo(arg: SomeScalar) on SCALAR

      input FooInput {
        foo: SomeScalar
      }

      scalar SomeScalar
    ` ) ; const extensionSDL = dedent `
      extend scalar SomeScalar @foo
    ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; const someScalar = assertScalarType ( extendedSchema . getType ( 'SomeScalar' ) ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectExtensionASTNodes ( someScalar ) . to . equal ( extensionSDL ) ; } ) ; it ( 'extends scalars by adding specifiedBy directive' , ( ) => { const schema = buildSchema ( `
      type Query {
        foo: Foo
      }

      scalar Foo

      directive @foo on SCALAR
    ` ) ; const extensionSDL = dedent `
      extend scalar Foo @foo

      extend scalar Foo @specifiedBy(url: "https://example.com/foo_spec")
    ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; const foo = assertScalarType ( extendedSchema . getType ( 'Foo' ) ) ; expect ( foo . specifiedByURL ) . to . equal ( 'https://example.com/foo_spec' ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectExtensionASTNodes ( foo ) . to . equal ( extensionSDL ) ; } ) ; it ( 'correctly assign AST nodes to new and extended types' , ( ) => { const schema = buildSchema ( `
      type Query

      scalar SomeScalar
      enum SomeEnum
      union SomeUnion
      input SomeInput
      type SomeObject
      interface SomeInterface

      directive @foo on SCALAR
    ` ) ; const firstExtensionAST = parse ( `
      extend type Query {
        newField(testArg: TestInput): TestEnum
      }

      extend scalar SomeScalar @foo

      extend enum SomeEnum {
        NEW_VALUE
      }

      extend union SomeUnion = SomeObject

      extend input SomeInput {
        newField: String
      }

      extend interface SomeInterface {
        newField: String
      }

      enum TestEnum {
        TEST_VALUE
      }

      input TestInput {
        testInputField: TestEnum
      }
    ` ) ; const extendedSchema = extendSchema ( schema , firstExtensionAST ) ; const secondExtensionAST = parse ( `
      extend type Query {
        oneMoreNewField: TestUnion
      }

      extend scalar SomeScalar @test

      extend enum SomeEnum {
        ONE_MORE_NEW_VALUE
      }

      extend union SomeUnion = TestType

      extend input SomeInput {
        oneMoreNewField: String
      }

      extend interface SomeInterface {
        oneMoreNewField: String
      }

      union TestUnion = TestType

      interface TestInterface {
        interfaceField: String
      }

      type TestType implements TestInterface {
        interfaceField: String
      }

      directive @test(arg: Int) repeatable on FIELD | SCALAR
    ` ) ; const extendedTwiceSchema = extendSchema ( extendedSchema , secondExtensionAST ) ; const extendedInOneGoSchema = extendSchema ( schema , concatAST ( [ firstExtensionAST , secondExtensionAST ] ) ) ; expect ( printSchema ( extendedInOneGoSchema ) ) . to . equal ( printSchema ( extendedTwiceSchema ) ) ; const query = assertObjectType ( extendedTwiceSchema . getType ( 'Query' ) ) ; const someEnum = assertEnumType ( extendedTwiceSchema . getType ( 'SomeEnum' ) ) ; const someUnion = assertUnionType ( extendedTwiceSchema . getType ( 'SomeUnion' ) ) ; const someScalar = assertScalarType ( extendedTwiceSchema . getType ( 'SomeScalar' ) ) ; const someInput = assertInputObjectType ( extendedTwiceSchema . getType ( 'SomeInput' ) ) ; const someInterface = assertInterfaceType ( extendedTwiceSchema . getType ( 'SomeInterface' ) ) ; const testInput = assertInputObjectType ( extendedTwiceSchema . getType ( 'TestInput' ) ) ; const testEnum = assertEnumType ( extendedTwiceSchema . getType ( 'TestEnum' ) ) ; const testUnion = assertUnionType ( extendedTwiceSchema . getType ( 'TestUnion' ) ) ; const testType = assertObjectType ( extendedTwiceSchema . getType ( 'TestType' ) ) ; const testInterface = assertInterfaceType ( extendedTwiceSchema . getType ( 'TestInterface' ) ) ; const testDirective = assertDirective ( extendedTwiceSchema . getDirective ( 'test' ) ) ; expect ( testType . extensionASTNodes ) . to . deep . equal ( [ ] ) ; expect ( testEnum . extensionASTNodes ) . to . deep . equal ( [ ] ) ; expect ( testUnion . extensionASTNodes ) . to . deep . equal ( [ ] ) ; expect ( testInput . extensionASTNodes ) . to . deep . equal ( [ ] ) ; expect ( testInterface . extensionASTNodes ) . to . deep . equal ( [ ] ) ; expect ( [ testInput . astNode , testEnum . astNode , testUnion . astNode , testInterface . astNode , testType . astNode , testDirective . astNode , ... query . extensionASTNodes , ... someScalar . extensionASTNodes , ... someEnum . extensionASTNodes , ... someUnion . extensionASTNodes , ... someInput . extensionASTNodes , ... someInterface . extensionASTNodes , ] ) . to . have . members ( [ ... firstExtensionAST . definitions , ... secondExtensionAST . definitions , ] ) ; const newField = query . getFields ( ) . newField ; expectASTNode ( newField ) . to . equal ( 'newField(testArg: TestInput): TestEnum' ) ; expectASTNode ( newField . args [ 0 ] ) . to . equal ( 'testArg: TestInput' ) ; expectASTNode ( query . getFields ( ) . oneMoreNewField ) . to . equal ( 'oneMoreNewField: TestUnion' ) ; expectASTNode ( someEnum . getValue ( 'NEW_VALUE' ) ) . to . equal ( 'NEW_VALUE' ) ; expectASTNode ( someEnum . getValue ( 'ONE_MORE_NEW_VALUE' ) ) . to . equal ( 'ONE_MORE_NEW_VALUE' ) ; expectASTNode ( someInput . getFields ( ) . newField ) . to . equal ( 'newField: String' ) ; expectASTNode ( someInput . getFields ( ) . oneMoreNewField ) . to . equal ( 'oneMoreNewField: String' ) ; expectASTNode ( someInterface . getFields ( ) . newField ) . to . equal ( 'newField: String' ) ; expectASTNode ( someInterface . getFields ( ) . oneMoreNewField ) . to . equal ( 'oneMoreNewField: String' ) ; expectASTNode ( testInput . getFields ( ) . testInputField ) . to . equal ( 'testInputField: TestEnum' ) ; expectASTNode ( testEnum . getValue ( 'TEST_VALUE' ) ) . to . equal ( 'TEST_VALUE' ) ; expectASTNode ( testInterface . getFields ( ) . interfaceField ) . to . equal ( 'interfaceField: String' ) ; expectASTNode ( testType . getFields ( ) . interfaceField ) . to . equal ( 'interfaceField: String' ) ; expectASTNode ( testDirective . args [ 0 ] ) . to . equal ( 'arg: Int' ) ; } ) ; it ( 'builds types with deprecated fields/values' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const extendAST = parse ( `
      type SomeObject {
        deprecatedField: String @deprecated(reason: "not used anymore")
      }

      enum SomeEnum {
        DEPRECATED_VALUE @deprecated(reason: "do not use")
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const someType = assertObjectType ( extendedSchema . getType ( 'SomeObject' ) ) ; expect ( someType . getFields ( ) . deprecatedField ) . to . include ( { deprecationReason : 'not used anymore' , } ) ; const someEnum = assertEnumType ( extendedSchema . getType ( 'SomeEnum' ) ) ; expect ( someEnum . getValue ( 'DEPRECATED_VALUE' ) ) . to . include ( { deprecationReason : 'do not use' , } ) ; } ) ; it ( 'extends objects with deprecated fields' , ( ) => { const schema = buildSchema ( 'type SomeObject' ) ; const extendAST = parse ( `
      extend type SomeObject {
        deprecatedField: String @deprecated(reason: "not used anymore")
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const someType = assertObjectType ( extendedSchema . getType ( 'SomeObject' ) ) ; expect ( someType . getFields ( ) . deprecatedField ) . to . include ( { deprecationReason : 'not used anymore' , } ) ; } ) ; it ( 'extends enums with deprecated values' , ( ) => { const schema = buildSchema ( 'enum SomeEnum' ) ; const extendAST = parse ( `
      extend enum SomeEnum {
        DEPRECATED_VALUE @deprecated(reason: "do not use")
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const someEnum = assertEnumType ( extendedSchema . getType ( 'SomeEnum' ) ) ; expect ( someEnum . getValue ( 'DEPRECATED_VALUE' ) ) . to . include ( { deprecationReason : 'do not use' , } ) ; } ) ; it ( 'adds new unused types' , ( ) => { const schema = buildSchema ( `
      type Query {
        dummy: String
      }
    ` ) ; const extensionSDL = dedent `
      type DummyUnionMember {
        someField: String
      }

      enum UnusedEnum {
        SOME_VALUE
      }

      input UnusedInput {
        someField: String
      }

      interface UnusedInterface {
        someField: String
      }

      type UnusedObject {
        someField: String
      }

      union UnusedUnion = DummyUnionMember
    ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( extensionSDL ) ; } ) ; it ( 'extends objects by adding new fields with arguments' , ( ) => { const schema = buildSchema ( `
      type SomeObject

      type Query {
        someObject: SomeObject
      }
    ` ) ; const extendAST = parse ( `
      input NewInputObj {
        field1: Int
        field2: [Float]
        field3: String!
      }

      extend type SomeObject {
        newField(arg1: String, arg2: NewInputObj!): String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject {
        newField(arg1: String, arg2: NewInputObj!): String
      }

      input NewInputObj {
        field1: Int
        field2: [Float]
        field3: String!
      }
    ` ) ; } ) ; it ( 'extends objects by adding new fields with existing types' , ( ) => { const schema = buildSchema ( `
      type Query {
        someObject: SomeObject
      }

      type SomeObject
      enum SomeEnum { VALUE }
    ` ) ; const extendAST = parse ( `
      extend type SomeObject {
        newField(arg1: SomeEnum!): SomeEnum
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject {
        newField(arg1: SomeEnum!): SomeEnum
      }
    ` ) ; } ) ; it ( 'extends objects by adding implemented interfaces' , ( ) => { const schema = buildSchema ( `
      type Query {
        someObject: SomeObject
      }

      type SomeObject {
        foo: String
      }

      interface SomeInterface {
        foo: String
      }
    ` ) ; const extendAST = parse ( `
      extend type SomeObject implements SomeInterface
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject implements SomeInterface {
        foo: String
      }
    ` ) ; } ) ; it ( 'extends objects by including new types' , ( ) => { const schema = buildSchema ( `
      type Query {
        someObject: SomeObject
      }

      type SomeObject {
        oldField: String
      }
    ` ) ; const newTypesSDL = dedent `
      enum NewEnum {
        VALUE
      }

      interface NewInterface {
        baz: String
      }

      type NewObject implements NewInterface {
        baz: String
      }

      scalar NewScalar

      union NewUnion = NewObject` ; const extendAST = parse ( `
      ${ newTypesSDL }
      extend type SomeObject {
        newObject: NewObject
        newInterface: NewInterface
        newUnion: NewUnion
        newScalar: NewScalar
        newEnum: NewEnum
        newTree: [SomeObject]!
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject {
        oldField: String
        newObject: NewObject
        newInterface: NewInterface
        newUnion: NewUnion
        newScalar: NewScalar
        newEnum: NewEnum
        newTree: [SomeObject]!
      }

      ${ newTypesSDL }
    ` ) ; } ) ; it ( 'extends objects by adding implemented new interfaces' , ( ) => { const schema = buildSchema ( `
      type Query {
        someObject: SomeObject
      }

      type SomeObject implements OldInterface {
        oldField: String
      }

      interface OldInterface {
        oldField: String
      }
    ` ) ; const extendAST = parse ( `
      extend type SomeObject implements NewInterface {
        newField: String
      }

      interface NewInterface {
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      type SomeObject implements OldInterface & NewInterface {
        oldField: String
        newField: String
      }

      interface NewInterface {
        newField: String
      }
    ` ) ; } ) ; it ( 'extends different types multiple times' , ( ) => { const schema = buildSchema ( `
      type Query {
        someScalar: SomeScalar
        someObject(someInput: SomeInput): SomeObject
        someInterface: SomeInterface
        someEnum: SomeEnum
        someUnion: SomeUnion
      }

      scalar SomeScalar

      type SomeObject implements SomeInterface {
        oldField: String
      }

      interface SomeInterface {
        oldField: String
      }

      enum SomeEnum {
        OLD_VALUE
      }

      union SomeUnion = SomeObject

      input SomeInput {
        oldField: String
      }
    ` ) ; const newTypesSDL = dedent `
      scalar NewScalar

      scalar AnotherNewScalar

      type NewObject {
        foo: String
      }

      type AnotherNewObject {
        foo: String
      }

      interface NewInterface {
        newField: String
      }

      interface AnotherNewInterface {
        anotherNewField: String
      }
    ` ; const schemaWithNewTypes = extendSchema ( schema , parse ( newTypesSDL ) ) ; expectSchemaChanges ( schema , schemaWithNewTypes ) . to . equal ( newTypesSDL ) ; const extendAST = parse ( `
      extend scalar SomeScalar @specifiedBy(url: "http://example.com/foo_spec")

      extend type SomeObject implements NewInterface {
        newField: String
      }

      extend type SomeObject implements AnotherNewInterface {
        anotherNewField: String
      }

      extend enum SomeEnum {
        NEW_VALUE
      }

      extend enum SomeEnum {
        ANOTHER_NEW_VALUE
      }

      extend union SomeUnion = NewObject

      extend union SomeUnion = AnotherNewObject

      extend input SomeInput {
        newField: String
      }

      extend input SomeInput {
        anotherNewField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schemaWithNewTypes , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      scalar SomeScalar @specifiedBy(url: "http://example.com/foo_spec")

      type SomeObject implements SomeInterface & NewInterface & AnotherNewInterface {
        oldField: String
        newField: String
        anotherNewField: String
      }

      enum SomeEnum {
        OLD_VALUE
        NEW_VALUE
        ANOTHER_NEW_VALUE
      }

      union SomeUnion = SomeObject | NewObject | AnotherNewObject

      input SomeInput {
        oldField: String
        newField: String
        anotherNewField: String
      }

      ${ newTypesSDL }
    ` ) ; } ) ; it ( 'extends interfaces by adding new fields' , ( ) => { const schema = buildSchema ( `
      interface SomeInterface {
        oldField: String
      }

      interface AnotherInterface implements SomeInterface {
        oldField: String
      }

      type SomeObject implements SomeInterface & AnotherInterface {
        oldField: String
      }

      type Query {
        someInterface: SomeInterface
      }
    ` ) ; const extendAST = parse ( `
      extend interface SomeInterface {
        newField: String
      }

      extend interface AnotherInterface {
        newField: String
      }

      extend type SomeObject {
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      interface SomeInterface {
        oldField: String
        newField: String
      }

      interface AnotherInterface implements SomeInterface {
        oldField: String
        newField: String
      }

      type SomeObject implements SomeInterface & AnotherInterface {
        oldField: String
        newField: String
      }
    ` ) ; } ) ; it ( 'extends interfaces by adding new implemented interfaces' , ( ) => { const schema = buildSchema ( `
      interface SomeInterface {
        oldField: String
      }

      interface AnotherInterface implements SomeInterface {
        oldField: String
      }

      type SomeObject implements SomeInterface & AnotherInterface {
        oldField: String
      }

      type Query {
        someInterface: SomeInterface
      }
    ` ) ; const extendAST = parse ( `
      interface NewInterface {
        newField: String
      }

      extend interface AnotherInterface implements NewInterface {
        newField: String
      }

      extend type SomeObject implements NewInterface {
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      interface AnotherInterface implements SomeInterface & NewInterface {
        oldField: String
        newField: String
      }

      type SomeObject implements SomeInterface & AnotherInterface & NewInterface {
        oldField: String
        newField: String
      }

      interface NewInterface {
        newField: String
      }
    ` ) ; } ) ; it ( 'allows extension of interface with missing Object fields' , ( ) => { const schema = buildSchema ( `
      type Query {
        someInterface: SomeInterface
      }

      type SomeObject implements SomeInterface {
        oldField: SomeInterface
      }

      interface SomeInterface {
        oldField: SomeInterface
      }
    ` ) ; const extendAST = parse ( `
      extend interface SomeInterface {
        newField: String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . have . lengthOf . above ( 0 ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      interface SomeInterface {
        oldField: SomeInterface
        newField: String
      }
    ` ) ; } ) ; it ( 'extends interfaces multiple times' , ( ) => { const schema = buildSchema ( `
      type Query {
        someInterface: SomeInterface
      }

      interface SomeInterface {
        some: SomeInterface
      }
    ` ) ; const extendAST = parse ( `
      extend interface SomeInterface {
        newFieldA: Int
      }

      extend interface SomeInterface {
        newFieldB(test: Boolean): String
      }
    ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( dedent `
      interface SomeInterface {
        some: SomeInterface
        newFieldA: Int
        newFieldB(test: Boolean): String
      }
    ` ) ; } ) ; it ( 'may extend mutations and subscriptions' , ( ) => { const mutationSchema = buildSchema ( `
      type Query {
        queryField: String
      }

      type Mutation {
        mutationField: String
      }

      type Subscription {
        subscriptionField: String
      }
    ` ) ; const ast = parse ( `
      extend type Query {
        newQueryField: Int
      }

      extend type Mutation {
        newMutationField: Int
      }

      extend type Subscription {
        newSubscriptionField: Int
      }
    ` ) ; const originalPrint = printSchema ( mutationSchema ) ; const extendedSchema = extendSchema ( mutationSchema , ast ) ; expect ( extendedSchema ) . to . not . equal ( mutationSchema ) ; expect ( printSchema ( mutationSchema ) ) . to . equal ( originalPrint ) ; expect ( printSchema ( extendedSchema ) ) . to . equal ( dedent `
      type Query {
        queryField: String
        newQueryField: Int
      }

      type Mutation {
        mutationField: String
        newMutationField: Int
      }

      type Subscription {
        subscriptionField: String
        newSubscriptionField: Int
      }
    ` ) ; } ) ; it ( 'may extend directives with new directive' , ( ) => { const schema = buildSchema ( `
      type Query {
        foo: String
      }
    ` ) ; const extensionSDL = dedent `
      """New directive."""
      directive @new(enable: Boolean!, tag: String) repeatable on QUERY | FIELD
    ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; expect ( validateSchema ( extendedSchema ) ) . to . deep . equal ( [ ] ) ; expectSchemaChanges ( schema , extendedSchema ) . to . equal ( extensionSDL ) ; } ) ; it ( 'Rejects invalid SDL' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const extendAST = parse ( 'extend schema @unknown' ) ; expect ( ( ) => extendSchema ( schema , extendAST ) ) . to . throw ( 'Unknown directive "@unknown".' ) ; } ) ; it ( 'Allows to disable SDL validation' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const extendAST = parse ( 'extend schema @unknown' ) ; extendSchema ( schema , extendAST , { assumeValid : true } ) ; extendSchema ( schema , extendAST , { assumeValidSDL : true } ) ; } ) ; it ( 'Throws on unknown types' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const ast = parse ( `
      type Query {
        unknown: UnknownType
      }
    ` ) ; expect ( ( ) => extendSchema ( schema , ast , { assumeValidSDL : true } ) ) . to . throw ( 'Unknown type: "UnknownType".' ) ; } ) ; it ( 'Rejects invalid AST' , ( ) => { const schema = new GraphQLSchema ( { } ) ; expect ( ( ) => extendSchema ( schema , null ) ) . to . throw ( 'Must provide valid Document AST' ) ; expect ( ( ) => extendSchema ( schema , { } ) ) . to . throw ( 'Must provide valid Document AST' ) ; } ) ; it ( 'does not allow replacing a default directive' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const extendAST = parse ( `
      directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD
    ` ) ; expect ( ( ) => extendSchema ( schema , extendAST ) ) . to . throw ( 'Directive "@include" already exists in the schema. It cannot be redefined.' ) ; } ) ; it ( 'does not allow replacing an existing enum value' , ( ) => { const schema = buildSchema ( `
      enum SomeEnum {
        ONE
      }
    ` ) ; const extendAST = parse ( `
      extend enum SomeEnum {
        ONE
      }
    ` ) ; expect ( ( ) => extendSchema ( schema , extendAST ) ) . to . throw ( 'Enum value "SomeEnum.ONE" already exists in the schema. It cannot also be defined in this type extension.' ) ; } ) ; describe ( 'can add additional root operation types' , ( ) => { it ( 'does not automatically include common root type names' , ( ) => { const schema = new GraphQLSchema ( { } ) ; const extendedSchema = extendSchema ( schema , parse ( 'type Mutation' ) ) ; expect ( extendedSchema . getType ( 'Mutation' ) ) . to . not . equal ( undefined ) ; expect ( extendedSchema . getMutationType ( ) ) . to . equal ( undefined ) ; } ) ; it ( 'adds schema definition missing in the original schema' , ( ) => { const schema = buildSchema ( `
        directive @foo on SCHEMA
        type Foo
      ` ) ; expect ( schema . getQueryType ( ) ) . to . equal ( undefined ) ; const extensionSDL = dedent `
        schema @foo {
          query: Foo
        }
      ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; const queryType = extendedSchema . getQueryType ( ) ; expect ( queryType ) . to . include ( { name : 'Foo' } ) ; expectASTNode ( extendedSchema ) . to . equal ( extensionSDL ) ; } ) ; it ( 'adds new root types via schema extension' , ( ) => { const schema = buildSchema ( `
        type Query
        type MutationRoot
      ` ) ; const extensionSDL = dedent `
        extend schema {
          mutation: MutationRoot
        }
      ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; const mutationType = extendedSchema . getMutationType ( ) ; expect ( mutationType ) . to . include ( { name : 'MutationRoot' } ) ; expectExtensionASTNodes ( extendedSchema ) . to . equal ( extensionSDL ) ; } ) ; it ( 'adds directive via schema extension' , ( ) => { const schema = buildSchema ( `
        type Query

        directive @foo on SCHEMA
      ` ) ; const extensionSDL = dedent `
        extend schema @foo
      ` ; const extendedSchema = extendSchema ( schema , parse ( extensionSDL ) ) ; expectExtensionASTNodes ( extendedSchema ) . to . equal ( extensionSDL ) ; } ) ; it ( 'adds multiple new root types via schema extension' , ( ) => { const schema = buildSchema ( 'type Query' ) ; const extendAST = parse ( `
        extend schema {
          mutation: Mutation
          subscription: Subscription
        }

        type Mutation
        type Subscription
      ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const mutationType = extendedSchema . getMutationType ( ) ; expect ( mutationType ) . to . include ( { name : 'Mutation' } ) ; const subscriptionType = extendedSchema . getSubscriptionType ( ) ; expect ( subscriptionType ) . to . include ( { name : 'Subscription' } ) ; } ) ; it ( 'applies multiple schema extensions' , ( ) => { const schema = buildSchema ( 'type Query' ) ; const extendAST = parse ( `
        extend schema {
          mutation: Mutation
        }
        type Mutation

        extend schema {
          subscription: Subscription
        }
        type Subscription
      ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const mutationType = extendedSchema . getMutationType ( ) ; expect ( mutationType ) . to . include ( { name : 'Mutation' } ) ; const subscriptionType = extendedSchema . getSubscriptionType ( ) ; expect ( subscriptionType ) . to . include ( { name : 'Subscription' } ) ; } ) ; it ( 'schema extension AST are available from schema object' , ( ) => { const schema = buildSchema ( `
        type Query

        directive @foo on SCHEMA
      ` ) ; const extendAST = parse ( `
        extend schema {
          mutation: Mutation
        }
        type Mutation

        extend schema {
          subscription: Subscription
        }
        type Subscription
      ` ) ; const extendedSchema = extendSchema ( schema , extendAST ) ; const secondExtendAST = parse ( 'extend schema @foo' ) ; const extendedTwiceSchema = extendSchema ( extendedSchema , secondExtendAST ) ; expectExtensionASTNodes ( extendedTwiceSchema ) . to . equal ( dedent `
        extend schema {
          mutation: Mutation
        }

        extend schema {
          subscription: Subscription
        }

        extend schema @foo
      ` ) ; } ) ; } ) ; } ) ;