UNPKG

openapi-to-graphql-harshith

Version:

Generates a GraphQL schema for a given OpenAPI Specification (OAS)

2,216 lines (2,061 loc) 55.5 kB
// 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' import { graphql, GraphQLInputObjectType, GraphQLInputObjectTypeConfig, GraphQLObjectTypeConfig, GraphQLSchema, parse, validate } from 'graphql' import { afterAll, beforeAll, expect, test } from '@jest/globals' import * as openAPIToGraphQL from '../src/index' import { Options } from '../src/types/options' import { startServer, stopServer } from './example_api_server' import { GraphQLOperationType } from '../src/types/graphql' const oas = require('./fixtures/example_oas.json') const PORT = 3002 // Update PORT for this test case: oas.servers[0].variables.port.default = String(PORT) let createdSchema: GraphQLSchema // Set up the schema first and run example API server beforeAll(() => { return Promise.all([ openAPIToGraphQL .createGraphQLSchema(oas, { fillEmptyResponses: true }) .then(({ schema, report }) => { createdSchema = schema }), startServer(PORT) ]) }) // Shut down API server afterAll(() => { return stopServer() }) test('Get descriptions', () => { // Get all the descriptions of the fields on the GraphQL object type Car const query = `{ __type(name: "Car") { name fields { description } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { __type: { name: 'Car', fields: [ { description: 'The color of the car.' }, { description: null }, { description: null }, { description: 'The model of the car.' }, { description: 'The rating of the car.' }, { description: 'Arbitrary (string) tags describing an entity.' } ] } } }) }) }) test('Get resource (incl. enum)', () => { // Status is an enum const query = `{ user (username: "arlene") { name status } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', status: 'STAFF' } } }) }) }) test('Get resource 2', () => { const query = `{ company (id: "binsol") { legalForm } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { company: { legalForm: 'public' } } }) }) }) // OAS allows you to define response objects with HTTP code with the XX wildcard syntax test('Get resource with status code: 2XX', () => { const query = `{ papers { name published } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { papers: [ { name: 'Deliciousness of apples', published: true }, { name: 'How much coffee is too much coffee?', published: false }, { name: 'How many tennis balls can fit into the average building?', published: true } ] } }) }) }) /** * Some operations do not have a response body. The option fillEmptyResponses * allows OpenAPI-to-GraphQL to handle these cases. */ test('Get resource with no response schema and status code: 204 and fillEmptyResponses', () => { const query = `{ bonuses }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { bonuses: null } }) }) }) // Link objects in the OAS allow OpenAPI-to-GraphQL to create nested GraphQL objects that resolve on different API calls test('Get nested resource via link $response.body#/...', () => { const query = `{ user (username: "arlene") { name employerCompany { legalForm } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', employerCompany: { legalForm: 'public' } } } }) }) }) test('Get nested resource via link $request.path#/... and $request.query#/', () => { const query = `{ productWithId (productId: "123" productTag: "blah") { productName reviews { text } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { productWithId: { productName: 'Super Product', reviews: [{ text: 'Great product' }, { text: 'I love it' }] } } }) }) }) // Both an operationId and an operationRef can be used to create a link object test('Get nested resource via link operationRef', () => { const query = `{ productWithId (productId: "123" productTag: "blah") { productName reviewsWithOperationRef { text } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { productWithId: { productName: 'Super Product', reviewsWithOperationRef: [ { text: 'Great product' }, { text: 'I love it' } ] } } }) }) }) test('Get nested lists of resources', () => { const query = `{ user(username: "arlene") { name friends { name friends { name friends { name } } } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'Heather J Tate', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'Heather J Tate', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] } ] }, { name: 'Heather J Tate', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] }, { name: 'Heather J Tate', friends: [ { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] } ] } ] } } }) }) }) // Links can be defined with some parameters as constants or variables test('Link parameters as constants and variables', () => { const query = `{ scanner(query: "hello") { body basicLink{ body } variableLink{ body } constantLink{ body } everythingLink{ body } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { scanner: { body: 'hello', basicLink: { body: 'hello' }, variableLink: { body: '_hello_hellohelloabchello123' }, constantLink: { body: '123' }, everythingLink: { body: 'http://localhost:3002/api/scanner_get_200_hello_application/json_close' } } } }) }) }) test('Nested links with constants and variables', () => { const query = `{ scanner(query: "val") { body basicLink{ body basicLink{ body basicLink{ body } } } variableLink{ body constantLink{ body everythingLink{ body everythingLink{ body } } } } constantLink{ body } everythingLink{ body } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { scanner: { body: 'val', basicLink: { body: 'val', basicLink: { body: 'val', basicLink: { body: 'val' } } }, variableLink: { body: '_val_valvalabcval123', constantLink: { body: '123', everythingLink: { body: 'http://localhost:3002/api/copier_get_200_123_application/json_close', everythingLink: { body: 'http://localhost:3002/api/copier_get_200_http://localhost:3002/api/copier_get_200_123_application/json_close_application/json_close' } } } }, constantLink: { body: '123' }, everythingLink: { body: 'http://localhost:3002/api/scanner_get_200_val_application/json_close' } } } }) }) }) test('Link parameters as constants and variables with request payload', () => { const query = `mutation { postScanner(query: "query", path: "path", textPlainInput: "body") { body everythingLink2 { body } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postScanner: { body: 'req.body: body, req.query.query: query, req.path.path: path', everythingLink2: { body: 'http://localhost:3002/api/scanner/path_post_200_body_query_path_application/json_req.body: body, req.query.query: query, req.path.path: path_query_path_close' } } } }) }) }) test('Get response without providing parameter with default value', () => { const query = `{ productReviews (id: "100") { text } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { productReviews: [{ text: 'Great product' }, { text: 'I love it' }] } }) }) }) test('Get response with header parameters', () => { const query = `{ snack(snackType: CHIPS, snackSize: SMALL) }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { snack: 'Here is a small chips' } }) }) }) /** * Content-type and accept headers should not change because they are * linked to GraphQL object types with static schemas */ test('Get JSON response even with non-JSON accept header', () => { const query = `{ office (id: 2) { employerId roomNumber, } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { office: { employerId: 'binsol', roomNumber: 102 } } }) }) }) test('Get response with cookies', () => { const query = `{ cookie (cookieType: CHOCOLATE_CHIP, cookieSize: MEGA_SIZED) }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { cookie: `You ordered a mega-sized chocolate chip cookie!` } }) }) }) /** * GraphQL (input) object type also consider the preferred name when generating * a name */ test('Ensure good naming for operations with duplicated schemas', () => { const query = `query { cleanDesks dirtyDesks }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { cleanDesks: '5 clean desks', dirtyDesks: '5 dirty desks' } }) }) }) /** * CASE: 64 bit int - return number instead of integer, leading to use of * GraphQLFloat, which can support 64 bits: */ test('Get response containing 64-bit integer (using GraphQLBigInt)', () => { const query = `{ productReviews (id: "100") { timestamp } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { productReviews: [ { timestamp: BigInt('1502787600000000') }, { timestamp: BigInt('1502787400000000') } ] } }) }) }) test('Get array of strings', () => { const query = `{ user (username: "arlene") { hobbies } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { user: { hobbies: ['tap dancing', 'bowling'] } } }) }) }) test('Get array of objects', () => { const query = `{ company (id: "binsol") { offices{ street } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { company: { offices: [ { street: '122 Elk Rd Little' }, { street: '124 Elk Rd Little' } ] } } }) }) }) test('Get single resource', () => { const query = `{ user(username: "arlene"){ name address{ street }, address2{ city } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', address: { street: '4656 Cherry Camp Road' }, address2: { city: 'Macomb' } } } }) }) }) test('Post resource', () => { const query = `mutation { postUser (userInput: { name: "Mr. New Guy" address: { street: "Home streeet 1" city: "Hamburg" } employerId: "binsol" hobbies: "soccer" }) { name } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postUser: { name: 'Mr. New Guy' } } }) }) }) test('Post resource and get nested resource back', () => { const query = `mutation { postUser (userInput: { name: "Mr. New Guy" address: { street: "Home streeet 1" city: "Hamburg" } employerId: "binsol" hobbies: "soccer" }) { name employerCompany { ceoUser { name } } } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postUser: { name: 'Mr. New Guy', employerCompany: { ceoUser: { name: 'John C Barnes' } } } } }) }) }) test('Post resource with non-application/json content-type request and response bodies', () => { const query = `mutation { postPaper(textPlainInput: "happy") }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postPaper: 'You sent the paper idea: happy' } }) }) }) test( 'Operation id is correctly sanitized, schema names and fields are ' + 'correctly sanitized, path and query parameters are correctly sanitized, ' + 'received data is correctly sanitized', () => { const query = `{ productWithId(productId: "this-path", productTag: "And a tag") { productId productTag } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { productWithId: { productId: 'this-path', productTag: 'And a tag' } } }) }) } ) test('Request data is correctly de-sanitized to be sent', () => { const query = `mutation { postProductWithId (productWithIdInput: { productName: "Soccer ball" productId: "ball123" productTag:"sports" }) { productName productId productTag } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postProductWithId: { productName: 'Soccer ball', productId: 'ball123', productTag: 'sports' } } }) }) }) test('Fields with arbitrary JSON (e.g., maps) can be returned', () => { // Testing additionalProperties field in schemas const query = `{ cars { tags } }` // Testing empty properties field const query2 = `{ cars { features } }` const promise = graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { cars: [ { tags: null }, { tags: { speed: 'extreme' } }, { tags: { impression: 'decadent', condition: 'slightly beat-up' } }, { tags: { impression: 'decadent' } } ] } }) }) const promise2 = graphql(createdSchema, query2, null, {}).then((result) => { expect(result).toEqual({ data: { cars: [ { features: { color: 'banana yellow to be specific' } }, { features: null }, { features: null }, { features: null } ] } }) }) return Promise.all([promise, promise2]) }) test('Capitalized enum values can be returned', () => { const query = `{ car (username: "arlene") { kind } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { car: { kind: 'SEDAN' } } }) }) }) test('Enum values that started as numbers in OAS can be returned as strings', () => { const query = `{ car (username: "arlene") { rating } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { car: { rating: '_100' } } }) }) }) test('Define header and query options', () => { const options: Options<any, any, any> = { headers: { exampleHeader: 'some-value' }, qs: { limit: '30' } } const query = `{ status2 (globalquery: "test") }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { // validate that 'limit' parameter is covered by options: const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { status2: 'Ok' } }) }) }) }) test('Resolve simple allOf', () => { const query = `{ user (username: "arlene") { name nomenclature { genus species } } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', nomenclature: { genus: 'Homo', species: 'sapiens' } } } }) }) }) // The $ref is contained in the suborder field test('Resolve ref in allOf', () => { const query = `{ user (username: "arlene") { name nomenclature { suborder genus species } } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', nomenclature: { suborder: 'Haplorhini', genus: 'Homo', species: 'sapiens' } } } }) }) }) // The nested allOf is contained in the family field test('Resolve nested allOf', () => { const query = `{ user (username: "arlene") { name nomenclature { family genus species } } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', nomenclature: { family: 'Hominidae', genus: 'Homo', species: 'sapiens' } } } }) }) }) // The circular nested allOf is contained in the familyCircular field test('Resolve circular allOf', () => { const query = `{ __type(name: "FamilyObject") { fields { name type { name } } } }` return graphql(createdSchema, query, null, {}).then((result) => { expect( result.data['__type']['fields'].find((field) => { return field.name === 'familyCircular' }) ).toEqual({ name: 'familyCircular', type: { name: 'FamilyObject' } }) }) }) test('Resolve oneOf, which becomes a union type', () => { const query = `{ __type(name: "AssetListItem") { kind possibleTypes { name description } } }` return graphql(createdSchema, query, null, {}).then((result) => { type carType = { name: string description: string } // Sort result because the order of the possibleTypes can change depending on Node version const possibleTypes = result['data']['__type']['possibleTypes'] as carType[] possibleTypes.sort((a, b) => { return a.name.localeCompare(b.name) }) expect(result).toEqual({ data: { __type: { kind: 'UNION', possibleTypes: [ { name: 'Car', description: 'A car' }, { name: 'Trashcan', description: null }, { name: 'User', description: 'A user represents a natural person' } ] } } }) }) }) test('Union type', () => { const query = `{ asset(companyId: "binsol") { ... on User { name address { city } } ... on Trashcan { contents } } }` return graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { asset: [ { name: 'Arlene L McMahon', address: { city: 'Elk Grove Village' } }, {}, { contents: [ { type: 'apple', message: 'Half-eaten' }, { type: 'sock', message: 'Lost one' } ] }, { name: 'William B Ropp', address: { city: 'Macomb' } }, {}, { contents: [ { type: 'sock', message: 'Lost one' } ] }, { name: 'John C Barnes', address: { city: 'Tucson' } }, {}, { contents: [] } ] } }) }) }) // Extensions provide more information about failed API calls test('Error contains extension', () => { const query = `query { user(username: "abcdef") { name } }` return graphql(createdSchema, query, null, {}).then((error) => { const extensions = error.errors[0].extensions expect(extensions).toBeDefined() // Remove headers because it contains fields that may change from run to run delete extensions.responseHeaders expect(extensions).toEqual({ method: 'get', path: '/users/{username}', statusCode: 404, responseBody: { message: 'Wrong username' }, statusText: 'Not Found', url: 'http://localhost:3002/api/users/abcdef' }) }) }) test('Option provideErrorExtensions should prevent error extensions from being created', () => { const options: Options<any, any, any> = { provideErrorExtensions: false } const query = `query { user(username: "abcdef") { name } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ errors: [ { message: 'Could not invoke operation GET /users/{username}', locations: [ { line: 2, column: 5 } ], path: ['user'] } ], data: { user: null } }) }) }) }) test('Option customResolver', () => { const options: Options<any, any, any> = { customResolvers: { 'Example API': { '/users/{username}': { get: () => { return { name: 'Jenifer Aldric' } } } } } } const query = `query { user(username: "abcdef") { name } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Jenifer Aldric' } } }) }) }) }) test('Option customResolver with links', () => { const options: Options<any, any, any> = { customResolvers: { 'Example API': { '/users/{username}': { get: () => { return { name: 'Jenifer Aldric', employerId: 'binsol' } } } } } } const query = `query { user(username: "abcdef") { name employerId employerCompany { name ceoUsername ceoUser { name } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Jenifer Aldric', employerId: 'binsol', employerCompany: { name: 'Binary Solutions', ceoUsername: 'johnny', ceoUser: { name: 'Jenifer Aldric' } } } } }) }) }) }) test('Option customResolver using resolver arguments', () => { const options: Options<any, any, any> = { customResolvers: { 'Example API': { '/users/{username}': { get: (obj, args, context, info) => { return { name: args['username'] } } } } } } const query = `query { user(username: "abcdef") { name } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'abcdef' } } }) }) }) }) test('Option customResolver using resolver arguments that are sanitized', () => { const options: Options<any, any, any> = { customResolvers: { 'Example API': { '/products/{product-id}': { get: (obj, args, context, info) => { return { // Note that the argument name is sanitized productName: 'abcdef' } } } } } } const query = `{ productWithId (productId: "123" productTag: "blah") { productName } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { productWithId: { productName: 'abcdef' } } }) }) }) }) test('Option addLimitArgument', () => { const options: Options<any, any, any> = { addLimitArgument: true } const query = `query { user(username: "arlene") { name friends (limit: 3) { name friends (limit: 2) { name friends (limit: 1) { name } } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect(result).toEqual({ data: { user: { name: 'Arlene L McMahon', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' } ] } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' } ] } ] }, { name: 'Heather J Tate', friends: [ { name: 'William B Ropp', friends: [ { name: 'William B Ropp' } ] }, { name: 'John C Barnes', friends: [ { name: 'William B Ropp' } ] } ] } ] } } }) }) }) }) test('Content property in parameter object', () => { const query = `{ coordinates(lat: 3, long: 5) { lat, long } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { coordinates: { lat: 8, long: 10 } } }) }) }) test('Handle objects without defined properties with arbitrary GraphQL JSON type', () => { const query = `{ trashcan(username:"arlene") { brand, contents } trashcans { contents } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { trashcan: { brand: 'Garbage Emporium', contents: [ { type: 'apple', message: 'Half-eaten' }, { type: 'sock', message: 'Lost one' } ] }, trashcans: [ { contents: [ { type: 'apple', message: 'Half-eaten' }, { type: 'sock', message: 'Lost one' } ] }, { contents: [ { type: 'sock', message: 'Lost one' } ] }, { contents: [] }, { contents: [ { type: 'tissue', message: 'Used' } ] } ] } }) }) }) test('Handle input objects without defined properties with arbitrary GraphQL JSON type', () => { const query = `mutation { postOfficeTrashCan(trashcan2Input: { type: "sandwich", message: "moldy", tasteRating: 0 }, username: "arlene") { brand contents } }` return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { postOfficeTrashCan: { brand: 'Garbage Emporium', contents: [ { type: 'apple', message: 'Half-eaten' }, { type: 'sock', message: 'Lost one' }, { type: 'sandwich', message: 'moldy', tasteRating: 0 } ] } } }) }) }) test('Operation returning arbitrary JSON type should not include _openAPIToGraphQL field', () => { const query = `{ random }` /** * There should only be the random and status fields but no _openAPIToGraphQL * field. */ return graphql(createdSchema, query).then((result) => { expect(result).toEqual({ data: { random: { status: 'success' } } }) }) }) test('Generate "Equivalent to..." messages', () => { const options: Options<any, any, any> = { // Used to simplify test. Otherwise viewers will polute query/mutation fields. viewer: false } // Check if query/mutation fields have the message const query = `query { __schema { queryType { fields { type { name } description } } mutationType { fields { type { name } description } } } }` const promise = openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { // Make sure all query fields have the message expect( result.data['__schema']['queryType']['fields'].every((field) => { return field.description.includes('\n\nEquivalent to GET ') }) ).toBe(true) // Make sure all mutation fields have the message expect( result.data['__schema']['mutationType']['fields'].every((field) => { return field.description.includes('\n\nEquivalent to ') }) ).toBe(true) // Check full message on a particular field expect( result.data['__schema']['queryType']['fields'].find((field) => { return field.type.name === 'Car' }) ).toEqual({ type: { name: 'Car' }, description: 'Returns a car to test nesting of sub operations\n\nEquivalent to GET /users/{username}/car' }) }) }) // Check link field description const query2 = `query { __type(name: "User") { fields { type { name } description } } }` const promise2 = graphql(createdSchema, query2).then((result) => { expect( result.data['__type']['fields'].find((field) => { return field.type.name === 'Company' }) ).toEqual({ type: { name: 'Company' }, description: "Allows to fetch the user's employer company.\n\nEquivalent to GET /companies/{id}" }) }) return Promise.all([promise, promise2]) }) test('Withhold "Equivalent to..." messages', () => { const options: Options<any, any, any> = { // Used to simplify test. Otherwise viewers will polute query/mutation fields. viewer: false, equivalentToMessages: false } // Check query/mutation field descriptions const query = `query { __schema { queryType { fields { type { name } description } } mutationType { fields { type { name } description } } } }` const promise = openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__schema']['queryType']['fields'].every((field) => { return field.description.includes('\n\nEquivalent to GET ') }) ).toBe(false) expect( result.data['__schema']['mutationType']['fields'].every((field) => { return field.description.includes('\n\nEquivalent to ') }) ).toBe(false) }) }) // Check link field description const query2 = `query { __type(name: "User") { fields { type { name } description } } }` const promise2 = openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query2).then((result) => { expect( result.data['__type']['fields'].find((field) => { return field.type.name === 'Company' }) ).toEqual({ type: { name: 'Company' }, description: "Allows to fetch the user's employer company." }) }) }) return Promise.all([promise, promise2]) }) test('UUID format becomes GraphQL ID type', () => { let query = `{ __type(name: "Company") { fields { name type { name kind } } } }` return graphql(createdSchema, query).then((result) => { expect( result.data['__type'].fields.find((field) => { return field.name === 'id' }) ).toEqual({ name: 'id', type: { name: 'ID', kind: 'SCALAR' } }) }) }) test('Option idFormats', () => { const options: Options<any, any, any> = { idFormats: ['specialIdFormat'] } // Check query/mutation field descriptions const query = `{ __type(name: "PatentWithId") { fields { name type { kind ofType { name kind } } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__type'].fields.find((field) => { return field.name === 'patentId' }) ).toEqual({ name: 'patentId', type: { kind: 'NON_NULL', ofType: { name: 'ID', kind: 'SCALAR' } } }) }) }) }) test('Required properties for input object types', () => { const userInputType = createdSchema.getType('UserInput') // The exclamation mark shows that it is a required (non-nullable) property expect((userInputType.toConfig() as GraphQLInputObjectTypeConfig).fields['address'].type.toString()).toEqual( 'AddressInput!' ) expect((userInputType.toConfig() as GraphQLInputObjectTypeConfig).fields['address2'].type.toString()).toEqual( 'AddressInput' ) }) test('Option selectQueryOrMutationField', () => { const query = `{ __schema { queryType { fields { name description } } mutationType { fields { name description } } } }` // The users field should exist as a Query field const promise = graphql(createdSchema, query).then((result) => { expect( result.data['__schema'].queryType.fields.find((field) => { return field.name === 'user' }) ).toEqual({ name: 'user', description: 'Returns a user from the system.\n\nEquivalent to GET /users/{username}' }) expect( result.data['__schema'].mutationType.fields.find((field) => { return field.name === 'user' }) ).toEqual(undefined) }) const options: Options<any, any, any> = { selectQueryOrMutationField: { 'Example API': { '/users/{username}': { get: GraphQLOperationType.Mutation } } } } // The users (now named getUserByUsername) field should exist as a Mutation field const promise2 = openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__schema'].queryType.fields.find((field) => { return field.name === 'getUserByUsername' }) ).toEqual(undefined) expect( result.data['__schema'].mutationType.fields.find((field) => { return field.name === 'getUserByUsername' }) ).toEqual({ name: 'getUserByUsername', description: 'Returns a user from the system.\n\nEquivalent to GET /users/{username}' }) }) }) return Promise.all([promise, promise2]) }) test('Header arguments are not created when they are provided through headers option', () => { // The GET snack operation has a snack_type and snack_size header arguments const options: Options<any, any, any> = { headers: { snack_type: 'chips', snack_size: 'large' } } const query = `{ __schema { queryType { fields { name args { name } } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__schema'].queryType.fields.find((field) => { return field.name === 'snack' }) ).toEqual({ name: 'snack', args: [] // No arguments }) }) }) }) test('Header arguments are not created when they are provided through requestOptions option', () => { // The GET snack operation has a snack_type and snack_size header arguments const options: Options<any, any, any> = { requestOptions: { headers: { snack_type: 'chips', snack_size: 'large' } } } const query = `{ __schema { queryType { fields { name args { name } } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__schema'].queryType.fields.find((field) => { return field.name === 'snack' }) ).toEqual({ name: 'snack', args: [] // No arguments }) }) }) }) // NOTE: This only tests how requestOptions affects schema creation, not resolver creation test('Query string arguments are not created when they are provided through qs option', () => { // The GET status operation has a limit query string parameter const options: Options<any, any, any> = { qs: { limit: '10' } } const query = `{ __schema { queryType { fields { name args { name } } } } }` return openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query).then((result) => { expect( result.data['__schema'].queryType.fields.find((field) => { return field.name === 'users' }) ).toEqual({ name: 'users', args: [] // No arguments }) }) }) }) test('Query string arguments are not created when they are provided through requestOptions option', () => { const query = `{ users(limit: 10) { name } }` const promise = graphql(createdSchema, query, null, {}).then((result) => { expect(result).toEqual({ data: { users: [ { name: 'Arlene L McMahon' }, { name: 'William B Ropp' }, { name: 'John C Barnes' }, { name: 'Heather J Tate' } ] } }) }) // The GET status operation has a limit query string parameter const options: Options<any, any, any> = { requestOptions: { qs: { limit: '10' } } } const query2 = `{ users { name } }` const promise2 = openAPIToGraphQL .createGraphQLSchema(oas, options) .then(({ schema }) => { const ast = parse(query2) const errors = validate(schema, ast) expect(errors).toEqual([]) return graphql(schema, query2).then((resu