// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: openapi-to-graphql
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict'

/* globals beforeAll, test, expect */

import { graphql } from 'graphql'
import * as openAPIToGraphQL from '../lib/index'

const oas = require('./fixtures/example_oas4.json')

// This test suite is used to verify the behavior of anyOf and oneOf handling

let createdSchema

/**
 * Set up the schema
 */
beforeAll(() => {
  return openAPIToGraphQL
    .createGraphQLSchema(oas)
    .then(({ schema, report }) => {
      createdSchema = schema
    })
})

const anyOfQuery = `{
  __schema {
    queryType {
      fields {
        name
        description
        type {
          name
          kind
          fields {
            name
            type {
              name
            }
          }
        }
      }
    }
  }
}`

const oneOfQuery = `{
  __schema {
    queryType {
      fields {
        name
        description
        type {
          name
          kind
          possibleTypes {
            name
            fields {
              type {
                name
              }
            }
          }
        }
      }
    }
  }
}`

/**
 * anyOf contains two member schemas
 *
 * Both member schemas contain the same field 'commonAttribute'
 *
 * Because they are the same, the created GraphQL object should only have one
 * 'commonAttribute' field
 */
test('Basic anyOf test using the same member schemas\n\nEquivalent to GET /anyOf', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf'
      })
    ).toEqual({
      name: 'anyOf',
      description:
        'Basic anyOf test using the same member schemas\n\nEquivalent to GET /anyOf',
      type: {
        name: 'AnyOf',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'String'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas
 *
 * One member schema contains a 'commonAttribute' field and the other
 * member schema contains a 'differentAttribute' field
 *
 * Because they are the different, the created GraphQL object should have both
 * fields
 */
test('Basic anyOf test with different member schemas\n\nEquivalent to GET /anyOf2', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf2'
      })
    ).toEqual({
      name: 'anyOf2',
      description:
        'Basic anyOf test with different member schemas\n\nEquivalent to GET /anyOf2',
      type: {
        name: 'AnyOf2',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'String'
            }
          },
          {
            name: 'differentAttribute',
            type: {
              name: 'String'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas
 *
 * Both member schemas contain the same complex nested field
 *
 * Because they are the same, the created GraphQL object should only have one
 * field
 */
test('anyOf test with the same nested member schemas\n\nEquivalent to GET /anyOf3', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf3'
      })
    ).toEqual({
      name: 'anyOf3',
      description:
        'anyOf test with the same nested member schemas\n\nEquivalent to GET /anyOf3',
      type: {
        name: 'AnyOf3',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'CommonAttribute'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas
 *
 * The member schemas contain complex nested fields that are different at the root
 * level.
 *
 * Because they are different at the root level, the created GraphQL object
 * should have two fields.
 */
test('anyOf test with different nested member schemas\n\nEquivalent to GET /anyOf4', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf4'
      })
    ).toEqual({
      name: 'anyOf4',
      description:
        'anyOf test with different nested member schemas\n\nEquivalent to GET /anyOf4',
      type: {
        name: 'AnyOf4',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'CommonAttribute'
            }
          },
          {
            name: 'differentAttribute',
            type: {
              name: 'DifferentAttribute'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas
 *
 * The member schemas contain complex nested fields that are same at the root
 * level but different at other levels.
 *
 * This leads to a conlict because the same field has different schemas. As a
 * result, the field will use the arbitrary JSON type.
 */
test('anyOf test with different nested member schemas, leading to conflict\n\nEquivalent to GET /anyOf5', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf5'
      })
    ).toEqual({
      name: 'anyOf5',
      description:
        'anyOf test with different nested member schemas, leading to conflict\n\nEquivalent to GET /anyOf5',
      type: {
        name: 'AnyOf5',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'JSON'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas
 *
 * The member schemas are of different types. One is an object type and the other
 * is an scalar type.
 *
 * This leads to a conlict. As a result, the field will use the arbitrary JSON
 * type.
 */
test('anyOf test with incompatible member schema types\n\nEquivalent to GET /anyOf6', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf6'
      })
    ).toEqual({
      name: 'anyOf6',
      description:
        'anyOf test with incompatible member schema types\n\nEquivalent to GET /anyOf6',
      type: {
        name: 'JSON',
        kind: 'SCALAR',
        fields: null
      }
    })
  })
})

/**
 * anyOf contains three member schemas
 *
 * Only one of the member schemas is an object type schema.
 *
 * The created type should be able to pick out the object type schema without
 * defaulting to the arbitrary JSON type.
 */
test('anyOf test with some extraneous member schemas\n\nEquivalent to GET /anyOf7', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf7'
      })
    ).toEqual({
      name: 'anyOf7',
      description:
        'anyOf test with some extraneous member schemas\n\nEquivalent to GET /anyOf7',
      type: {
        name: 'AnyOf7',
        kind: 'OBJECT',
        fields: [
          {
            name: 'commonAttribute',
            type: {
              name: 'String'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains three member schemas
 *
 * None of the member schemas are object type schemas. As a result, it defaults to the
 * arbitrary JSON type.
 */
test('anyOf test with no object type member schemas\n\nEquivalent to GET /anyOf8', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf8'
      })
    ).toEqual({
      name: 'anyOf8',
      description:
        'anyOf test with no object type member schemas\n\nEquivalent to GET /anyOf8',
      type: {
        name: 'JSON',
        kind: 'SCALAR',
        fields: null
      }
    })
  })
})

/**
 * anyOf contains three member schemas
 *
 * None of the member schemas are object type schemas but because there is an
 * external type provided in the root schema, it can utilize the proper typing.
 */
test('anyOf test with extraneous member schemas with external type\n\nEquivalent to GET /anyOf9', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf9'
      })
    ).toEqual({
      name: 'anyOf9',
      description:
        'anyOf test with extraneous member schemas with external type\n\nEquivalent to GET /anyOf9',
      type: {
        name: 'Int',
        kind: 'SCALAR',
        fields: null
      }
    })
  })
})

/**
 * anyOf contains two member schemas and allOf contains an additional one
 *
 * None of the schemas have conflicts so all three should be utilized
 */
test('Basic anyOf test with allOf\n\nEquivalent to GET /anyOf10', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf10'
      })
    ).toEqual({
      name: 'anyOf10',
      description: 'Basic anyOf test with allOf\n\nEquivalent to GET /anyOf10',
      type: {
        name: 'AnyOf10',
        kind: 'OBJECT',
        fields: [
          {
            name: 'anotherAttribute',
            type: {
              name: 'String'
            }
          },
          {
            name: 'commonAttribute',
            type: {
              name: 'String'
            }
          },
          {
            name: 'differentAttribute',
            type: {
              name: 'String'
            }
          }
        ]
      }
    })
  })
})

/**
 * anyOf contains two member schemas and allOf contains an additional one that
 * is nested in another anyOf
 *
 * Resolving the allOf should correctly collapse all of the (nested) anyOfs
 * and allow all three schemas to be utilized
 */
test('anyOf test with allOf, requiring anyOf collapse\n\nEquivalent to GET /anyOf11', () => {
  return graphql(createdSchema, anyOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'anyOf11'
      })
    ).toEqual({
      name: 'anyOf11',
      description:
        'anyOf test with allOf, requiring anyOf collapse\n\nEquivalent to GET /anyOf11',
      type: {
        name: 'AnyOf11',
        kind: 'OBJECT',
        fields: [
          {
            name: 'anotherAttribute',
            type: {
              name: 'String'
            }
          },
          {
            name: 'commonAttribute',
            type: {
              name: 'String'
            }
          },
          {
            name: 'differentAttribute',
            type: {
              name: 'String'
            }
          }
        ]
      }
    })
  })
})

/**
 * oneOf contains two member schemas
 *
 * Because the schemas are different object types, the created GraphQL union
 * type has two differnet member types.
 */
test('Basic oneOf test\n\nEquivalent to GET /oneOf', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf'
      })
    ).toEqual({
      name: 'oneOf',
      description: 'Basic oneOf test\n\nEquivalent to GET /oneOf',
      type: {
        name: 'OneOf',
        kind: 'UNION',
        possibleTypes: [
          {
            name: 'CommonAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          },
          {
            name: 'DifferentAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          }
        ]
      }
    })
  })
})

/**
 * oneOf contains two member schemas
 *
 * Because one of the member schemas is not an object type, then default to
 * the arbitrary JSON type.
 */
test('oneOf test with non-object type member schema\n\nEquivalent to GET /oneOf2', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf2'
      })
    ).toEqual({
      name: 'oneOf2',
      description:
        'oneOf test with non-object type member schema\n\nEquivalent to GET /oneOf2',
      type: {
        name: 'JSON',
        kind: 'SCALAR',
        possibleTypes: null
      }
    })
  })
})

/**
 * oneOf contains two member schemas
 *
 * None of the member schemas are object types, therefore default to
 * the arbitrary JSON type.
 */
test('oneOf test with no object type member schemas\n\nEquivalent to GET /oneOf3', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf3'
      })
    ).toEqual({
      name: 'oneOf3',
      description:
        'oneOf test with no object type member schemas\n\nEquivalent to GET /oneOf3',
      type: {
        name: 'JSON',
        kind: 'SCALAR',
        possibleTypes: null
      }
    })
  })
})

/**
 * oneOf contains two member schemas
 *
 * The member schemas contain extranous data but because the root schema contains a
 * type, it is able to utilize the proper type.
 */
test('oneOf test with extraneous member schemas\n\nEquivalent to GET /oneOf4', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf4'
      })
    ).toEqual({
      name: 'oneOf4',
      description:
        'oneOf test with extraneous member schemas\n\nEquivalent to GET /oneOf4',
      type: {
        name: 'Int',
        kind: 'SCALAR',
        possibleTypes: null
      }
    })
  })
})

/**
 * oneOf contains two member schemas and an allOf
 *
 * Only schemas within the oneOf should be utilized
 *
 * TODO: verify this behavior and also create a test with additional root properties
 */
test('Basic oneOf test with allOf\n\nEquivalent to GET /oneOf5', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf5'
      })
    ).toEqual({
      name: 'oneOf5',
      description: 'Basic oneOf test with allOf\n\nEquivalent to GET /oneOf5',
      type: {
        name: 'OneOf5',
        kind: 'UNION',
        possibleTypes: [
          {
            name: 'CommonAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          },
          {
            name: 'DifferentAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          }
        ]
      }
    })
  })
})

/**
 * oneOf contains two member schemas and allOf contains an additional one that
 * is nested in another oneOf
 *
 * Resolving the allOf should correctly collapse all of the (nested) oneOfs
 * and allow all three schemas to be utilized
 */
test('oneOf test with allOf, requiring oneOf collapse\n\nEquivalent to GET /oneOf6', () => {
  return graphql(createdSchema, oneOfQuery).then(result => {
    expect(
      result.data['__schema'].queryType.fields.find(field => {
        return field.name === 'oneOf6'
      })
    ).toEqual({
      name: 'oneOf6',
      description:
        'oneOf test with allOf, requiring oneOf collapse\n\nEquivalent to GET /oneOf6',
      type: {
        name: 'OneOf6',
        kind: 'UNION',
        possibleTypes: [
          {
            name: 'CommonAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          },
          {
            name: 'DifferentAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          },
          {
            name: 'AnotherAttributeObject',
            fields: [
              {
                type: {
                  name: 'String'
                }
              }
            ]
          }
        ]
      }
    })
  })
})
