graphql#execute TypeScript Examples

The following examples show how to use graphql#execute. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: example_api_combined.test.ts    From graphql-mesh with MIT License 6 votes vote down vote up
describe('Example API Combined', () => {
  let createdSchema: GraphQLSchema;
  beforeAll(async () => {
    await startServer(PORT);
    createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      oasFilePath,
      baseUrl,
      fetch,
    });
  });
  afterAll(async () => {
    await stopServer();
  });
  it('should generate correct schema', () => {
    expect(printSchemaWithDirectives(createdSchema)).toMatchSnapshot('example_oas_combined-schema');
  });
  it('should handle allOf correctly', async () => {
    const query = /* GraphQL */ `
      query {
        getAllCars {
          model
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toMatchSnapshot('example_oas_combined-query-result');
  });
});
Example #2
Source File: index.ts    From graphql-typed-document-node with MIT License 6 votes vote down vote up
async function main(): Promise<void> {
  // We are using the regular `execute` from `graphql` library, but with patch applied.
  const result = await execute({
    document: RatesDocument, // This is the TypedDocumentNode containting the result type and variables type
    schema,
    variableValues: {
      currency: 'USD'
    }
  });

  result.data.rates

  // We now have types support and auto complete for the
  // result type, just by passing `RatesDocument` as `document` to execute function.
  const rates = result.data.rates;
  const currency = rates[0].rate; 
}
Example #3
Source File: service.ts    From one-platform with MIT License 5 votes vote down vote up
SubscriptionServer.create(
  { schema, execute, subscribe } as any,
  { server: httpServer, path: '/subscriptions' },
);
Example #4
Source File: transform.spec.ts    From graphql-mesh with MIT License 5 votes vote down vote up
describe('transform', () => {
  const baseDir: string = undefined;

  it('should handle composition functions from external modules', async () => {
    const transform = new ResolversCompositionTransform({
      cache: new InMemoryLRUCache(),
      pubsub: new PubSub(),
      config: [
        {
          resolver: 'Query.foo',
          composer: join(__dirname, './fixtures/composer.js'),
        },
      ],
      baseDir,
      apiName: '',
      importFn: m => import(m),
    });
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => 'BAR',
        },
      },
    });
    const transformedSchema = transform.transformSchema(schema);
    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          foo
        }
      `),
    });
    expect(result.data?.foo).toBe('FOO');
  });
  it('should handle composition functions from functions', async () => {
    const composer: ResolversComposition = next => (root, args, context, info) => 'FOO';
    const transform = new ResolversCompositionTransform({
      cache: new InMemoryLRUCache(),
      pubsub: new PubSub(),
      config: [
        {
          resolver: 'Query.foo',
          composer,
        },
      ],
      baseDir,
      apiName: '',
      importFn: m => import(m),
    });
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => 'BAR',
        },
      },
    });
    const transformedSchema = transform.transformSchema(schema);
    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          foo
        }
      `),
    });
    expect(result.data?.foo).toBe('FOO');
  });
});
Example #5
Source File: getComposerFromSchema.test.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('getComposerFromJSONSchema', () => {
  const logger = new DefaultLogger('getComposerFromJSONSchema - test');
  it('should return JSON scalar if given schema is boolean true', async () => {
    const result = await getComposerFromJSONSchema(true, logger);
    expect(result.input.getType()).toBe(GraphQLJSON);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLJSON);
  });
  it('should generate a new scalar type that validates the value against the given pattern in string schema', async () => {
    const pattern = '^\\d{10}$';
    const title = 'ExampleRegEx';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      pattern,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('not-valid-phone-number')).toThrow();
    expect(serializeFn('1231231234')).toBe('1231231234');
  });
  it('should generate a new scalar type that validates the value against the given const in string schema', async () => {
    const constStr = 'FOO';
    const title = 'ExampleConst';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      const: constStr,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Scalar types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ScalarTypeComposer;
    expect(isScalarType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    const serializeFn = outputComposer.getSerialize();
    expect(() => serializeFn('bar')).toThrow();
    expect(serializeFn(constStr)).toBe(constStr);
  });
  it('should generate a new enum type from enum schema', async () => {
    const enumValues = ['foo', 'bar', 'qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toBe(
      /* GraphQL */ `
enum ExampleEnum {
  foo
  bar
  qux
}`.trim()
    );
  });
  it('should generate a new enum type from enum schema by sanitizing enum keys', async () => {
    const enumValues = ['0-foo', '1+bar', '2)qux'];
    const title = 'ExampleEnum';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      enum: enumValues,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    // Enum types are both input and output types
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as EnumTypeComposer;
    expect(outputComposer.toSDL()).toMatchInlineSnapshot(`
      "enum ExampleEnum {
        _0_foo
        _1_PLUS_bar
        _2_RIGHT_PARENTHESIS_qux
      }"
    `);
  });
  it('should generate union types from oneOf object types', async () => {
    const inputSchema: JSONSchema = {
      title: 'User',
      type: 'object',
      oneOf: [
        {
          title: 'Writer',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            posts: {
              type: 'array',
              items: {
                title: 'Post',
                type: 'object',
                properties: {
                  id: {
                    type: 'string',
                  },
                  title: {
                    type: 'string',
                  },
                  content: {
                    type: 'string',
                  },
                },
              },
            },
          },
        },
        {
          title: 'Admin',
          type: 'object',
          properties: {
            id: {
              type: 'string',
            },
            name: {
              type: 'string',
            },
            permissions: {
              type: 'array',
              items: {
                title: 'AdminPermission',
                type: 'string',
                enum: ['edit', 'delete'],
              },
            },
          },
        },
      ],
    };
    const outputSchema = /* GraphQL */ `
union User = Writer | Admin

type Writer {
  id: String
  name: String
  posts: [Post]
}

${printType(GraphQLString)}

type Post {
  id: String
  title: String
  content: String
}

type Admin {
  id: String
  name: String
  permissions: [AdminPermission]
}

enum AdminPermission {
  edit
  delete
}
    `.trim();

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const unionComposer = result.output as UnionTypeComposer;
    expect(
      unionComposer.toSDL({
        deep: true,
      })
    ).toBe(outputSchema);
  });
  it('should generate an input union type for oneOf definitions that contain scalar types', async () => {
    const title = 'ExampleOneOf';
    const inputSchema: JSONSchema = {
      title,
      oneOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.input as InputTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
input ExampleOneOf_Input @oneOf {
  String: String
  ExampleObject_Input: ExampleObject_Input
}

${printType(GraphQLString)}

input ExampleObject_Input {
  id: String
}
    `.trim()
    );
  });
  it('should generate merged object types from allOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAllOf',
      allOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAllOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAllOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for allOf definitions that contain scalar types', async () => {
    const title = 'ExampleAllOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should generate correct types for anyOf definitions', async () => {
    const inputSchema: JSONSchema = {
      title: 'ExampleAnyOf',
      anyOf: [
        {
          type: 'object',
          title: 'Foo',
          properties: {
            id: {
              type: 'string',
            },
          },
          required: ['id'],
        },
        {
          type: 'object',
          title: 'Bar',
          properties: {
            name: {
              type: 'string',
            },
          },
          required: ['name'],
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleAnyOf_Input {
  id: String!
  name: String!
}
    `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleAnyOf {
  id: String!
  name: String!
}
    `.trim()
    );
  });
  it('should generate container types and fields for anyOf definitions that contain scalar types', async () => {
    const title = 'ExampleAnyOf';
    const inputSchema: JSONSchema = {
      title,
      allOf: [
        {
          type: 'string',
        },
        {
          type: 'object',
          title: 'ExampleObject',
          properties: {
            id: {
              type: 'string',
            },
          },
        },
      ],
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const outputComposer = result.output as ObjectTypeComposer;
    expect(isObjectType(outputComposer.getType())).toBeTruthy();
    expect(outputComposer.getTypeName()).toBe(title);
    expect(outputComposer.getFieldNames().includes('String')).toBeTruthy();
    expect(outputComposer.getFieldNames().includes('id')).toBeTruthy();
  });
  it('should return Boolean for boolean definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'boolean',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBoolean);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBoolean);
  });
  it('should return Void for null definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'null',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType().name).toBe('Void');
    expect((result.output as ScalarTypeComposer).getType().name).toBe('Void');
  });
  it('should return BigInt for int64 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int64',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLBigInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLBigInt);
  });
  it('should return Int for int32 definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
      format: 'int32',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Int for integer definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'integer',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLInt);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLInt);
  });
  it('should return Float for number definition', async () => {
    const inputSchema: JSONSchema = {
      type: 'number',
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    expect(result.input.getType()).toBe(GraphQLFloat);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLFloat);
  });
  it('should generate scalar types for minLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('aa')).toBe('aa');
  });
  it('should generate scalar types for maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should generate scalar types for both minLength and maxLength definition', async () => {
    const title = 'NonEmptyString';
    const inputSchema: JSONSchema = {
      title,
      type: 'string',
      minLength: 1,
      maxLength: 2,
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    const inputComposer = result.input as ScalarTypeComposer;
    expect(inputComposer).toBe(result.output);
    expect(inputComposer.getTypeName()).toBe(title);
    const serializeFn = inputComposer.getSerialize();
    expect(() => serializeFn('aaa')).toThrow();
    expect(() => serializeFn('')).toThrow();
    expect(serializeFn('a')).toBe('a');
  });
  it('should return DateTime scalar for date-time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'date-time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLDateTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLDateTime);
  });
  it('should return Time scalar for time format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'time',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLTime);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLTime);
  });
  it('should return EmailAddress scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'email',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLEmailAddress);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLEmailAddress);
  });
  it('should return IPv4 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv4',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv4);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv4);
  });
  it('should return IPv6 scalar for email format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'ipv6',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLIPv6);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLIPv6);
  });
  it('should return URL scalar for uri format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
      format: 'uri',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLURL);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLURL);
  });
  it('should return String for string definitions without format', async () => {
    const inputSchema: JSONSchema = {
      type: 'string',
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input.getType()).toBe(GraphQLString);
    expect((result.output as ScalarTypeComposer).getType()).toBe(GraphQLString);
  });
  it('should return list type for array definitions with items as object', async () => {
    const inputSchema: JSONSchema = {
      type: 'array',
      items: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(isListType(result.input.getType())).toBeTruthy();
    expect((result.input as ListComposer).ofType.getType()).toBe(GraphQLString);
    expect(isListType((result.output as ListComposer).getType())).toBeTruthy();
    expect((result.output as ListComposer).ofType.getType()).toBe(GraphQLString);
  });
  it('should return generic JSON type for array definitions with contains', async () => {
    const title = 'ExampleArray';
    const inputSchema: JSONSchema = {
      title,
      type: 'array',
      contains: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(result.input).toBe(result.output);
    const outputComposer = result.output as ListComposer;
    expect(isListType(outputComposer.getType())).toBeTruthy();
    expect(isScalarType(outputComposer.ofType.getType())).toBeTruthy();
    expect(outputComposer.ofType.getTypeName()).toBe(title);
  });
  it('should return union type inside a list type if array definition has items as an array', async () => {
    const title = 'FooOrBar';
    const inputSchema: JSONSchema = {
      title: 'ExampleObject',
      type: 'object',
      properties: {
        fooOrBar: {
          title,
          type: 'array',
          items: [
            {
              title: 'Foo',
              type: 'object',
              properties: {
                id: {
                  type: 'string',
                },
              },
            },
            {
              title: 'Bar',
              type: 'object',
              properties: {
                name: {
                  type: 'string',
                },
              },
            },
          ],
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect(
      (result.output as ObjectTypeComposer).toSDL({
        deep: true,
      })
    ).toBe(
      /* GraphQL */ `
type ExampleObject {
  fooOrBar: [FooOrBar]
}

union FooOrBar = Foo | Bar

type Foo {
  id: String
}

${printType(GraphQLString)}

type Bar {
  name: String
}
`.trim()
    );
  });
  it('should create correct object types from object definition', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
input ExampleObject_Input {
  id: String
}
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
}
     `.trim()
    );
  });
  it('should create correct object types from object definition with additionalProperties', async () => {
    const title = 'ExampleObject';
    const inputSchema: JSONSchema = {
      title,
      type: 'object',
      properties: {
        id: {
          type: 'string',
        },
      },
      additionalProperties: {
        type: 'string',
      },
    };
    const result = await getComposerFromJSONSchema(inputSchema, logger);
    expect((result.input as InputTypeComposer).toSDL()).toContain(
      /* GraphQL */ `
scalar ExampleObject_Input
     `.trim()
    );
    expect((result.output as InputTypeComposer).toSDL()).toBe(
      /* GraphQL */ `
type ExampleObject {
  id: String
  additionalProperties: JSON
}
     `.trim()
    );
  });
  it('should return GraphQLSchema if object definition given with _schema title', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Query type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Query',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Query {
  foo: String
}
     `.trim()
    );
  });
  it('should return Mutation type if object definition given with Query title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Mutation',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Mutation {
  foo: String
}
     `.trim()
    );
  });
  it('should return Subscription type if object definition given with Subscription title', async () => {
    const inputSchema: JSONSchema = {
      title: 'Subscription',
      type: 'object',
      properties: {
        foo: {
          type: 'string',
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof ObjectTypeComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toContain(
      /* GraphQL */ `
type Subscription {
  foo: String
}
     `.trim()
    );
  });
  it('should add arguments to Query fields with the object definition QueryTitle', async () => {
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            foo: {
              type: 'string',
            },
          },
        },
        queryInput: {
          title: 'QueryInput',
          type: 'object',
          properties: {
            foo: {
              title: 'Foo',
              type: 'object',
              properties: {
                bar: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(inputSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    expect((output as SchemaComposer).toSDL()).toBe(
      /* GraphQL */ `
type Query {
  foo(input: Foo_Input): String
}

${printType(GraphQLString)}

input Foo_Input {
  bar: String
}
     `.trim()
    );
  });
  it('should choose correct type in union type generated from oneOf', async () => {
    const FooOrBar = {
      title: 'FooOrBar',
      oneOf: [
        {
          title: 'Foo',
          type: 'object',
          properties: {
            fooId: {
              type: 'string',
            },
          },
          required: ['fooId'],
        },
        {
          title: 'Bar',
          type: 'object',
          properties: {
            barId: {
              type: 'string',
            },
          },
        },
      ],
    };
    const inputSchema: JSONSchema = {
      title: '_schema',
      type: 'object',
      properties: {
        query: {
          title: 'Query',
          type: 'object',
          properties: {
            fooOrBarButFoo: FooOrBar,
            fooOrBarButBar: FooOrBar,
          },
        },
      },
    };

    const result = await getComposerFromJSONSchema(inputSchema, logger);

    const schemaComposer = result.output as SchemaComposer;
    const fooId = 'FOO_ID';
    const barId = 'BAR_ID';
    schemaComposer.addResolveMethods({
      Query: {
        fooOrBarButFoo: () => ({
          fooId: 'FOO_ID',
        }),
        fooOrBarButBar: () => ({
          barId: 'BAR_ID',
        }),
      },
    });
    const schema = schemaComposer.buildSchema();
    const executionResponse: any = await execute({
      schema,
      document: parse(/* GraphQL */ `
        fragment FooOrBarFragment on FooOrBar {
          __typename
          ... on Foo {
            fooId
          }
          ... on Bar {
            barId
          }
        }
        query TestQuery {
          fooOrBarButFoo {
            ...FooOrBarFragment
          }
          fooOrBarButBar {
            ...FooOrBarFragment
          }
        }
      `),
    });
    expect(executionResponse?.data?.fooOrBarButFoo?.__typename).toBe('Foo');
    expect(executionResponse?.data?.fooOrBarButFoo?.fooId).toBe(fooId);
    expect(executionResponse?.data?.fooOrBarButBar?.__typename).toBe('Bar');
    expect(executionResponse?.data?.fooOrBarButBar?.barId).toBe(barId);
  });
  it('should handle non-string enum values', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: [-1, 1],
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "NEGATIVE_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": -1,
        },
        "_1": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": 1,
        },
      }
    `);
  });
  it('should handle strings with non-latin characters', async () => {
    const FooEnum = {
      title: 'FooEnum',
      type: 'string' as const,
      enum: ['לא', 'כן'],
    };

    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    expect(enumValuesMap).toMatchInlineSnapshot(`
      Object {
        "_1499__1503_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "כן",
        },
        "_1500__1488_": Object {
          "deprecationReason": undefined,
          "description": undefined,
          "directives": Array [],
          "extensions": Object {},
          "value": "לא",
        },
      }
    `);
  });
  it('should handle invalid property names', async () => {
    const jsonSchema: JSONSchemaObject = {
      type: 'object',
      title: '_schema',
      properties: {
        query: {
          type: 'object',
          title: 'Query',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo',
              properties: {
                '0Bar': {
                  type: 'object',
                  title: 'Bar',
                  properties: {
                    barId: {
                      type: 'string',
                    },
                  },
                },
                '1Baz': {
                  type: 'object',
                  title: 'Baz',
                  properties: {
                    bazId: {
                      type: 'string',
                    },
                  },
                },
              },
            },
          },
        },
        queryInput: {
          type: 'object',
          title: 'QueryInput',
          properties: {
            foo: {
              type: 'object',
              title: 'Foo_Input',
              properties: {
                '0BarId': {
                  type: 'string',
                },
                '1BazId': {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    };
    const { output } = await getComposerFromJSONSchema(jsonSchema, logger);
    expect(output instanceof SchemaComposer).toBeTruthy();
    const schema = (output as SchemaComposer).buildSchema();
    expect(printSchemaWithDirectives(schema)).toMatchInlineSnapshot(`
      "schema {
        query: Query
      }

      type Query {
        foo(input: Foo_Input_Input): Foo
      }

      type Foo {
        _0Bar: Bar
        _1Baz: Baz
      }

      type Bar {
        barId: String
      }

      type Baz {
        bazId: String
      }

      input Foo_Input_Input {
        _0BarId: String
        _1BazId: String
      }"
    `);
  });
  it('should workaround GraphQLjs falsy enum values bug', async () => {
    const values = [0, false, ''];
    const FooEnum = {
      title: 'FooEnum',
      type: ['number', 'boolean', 'string'] as any,
      enum: values,
    };
    const { output } = await getComposerFromJSONSchema(FooEnum, logger);
    expect(output instanceof EnumTypeComposer).toBeTruthy();
    const enumTypeComposer = output as EnumTypeComposer;
    const enumValuesMap = enumTypeComposer.getFields();
    Object.values(enumValuesMap).forEach((valueConfig, i) => {
      expect(valueConfig.value).toBe(values[i]?.toString());
    });
    expect.assertions(4);
  });
});
Example #6
Source File: authentication.test.ts    From graphql-mesh with MIT License 4 votes vote down vote up
// We don't create viewers for each security scheme definition in OAS like openapi-to-graphql
// But instead we let user to define them with string interpolation
// No need to test every single query and mutation because this only tests the interpolation behavior

describe('Authentication', () => {
  /**
   * Set up the schema first and run example API server
   */
  beforeAll(async () => {
    await startServer(PORT);
  });
  /**
   * Shut down API server
   */
  afterAll(async () => {
    await stopServer();
  });
  it('should get patent using basic auth', async () => {
    const query = /* GraphQL */ `
      query {
        get_patent_with_id(patent_id: "100", usernameAndPassword: "arlene123:password123") {
          patent_id
        }
      }
    `;
    const createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      oasFilePath,
      baseUrl,
      operationHeaders: {
        authorization: 'Basic {args.usernameAndPassword|base64}',
      },
      fetch,
    });
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        get_patent_with_id: {
          patent_id: '100',
        },
      },
    });
  });
  it('Get patent using API key in the header', async () => {
    const query = /* GraphQL */ `
      query {
        get_patent_with_id(patent_id: "100", apiKey: "abcdef") {
          patent_id
        }
      }
    `;
    const createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      oasFilePath,
      baseUrl,
      operationHeaders: {
        access_token: '{args.apiKey}',
      },
      fetch,
    });
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });

    expect(result).toEqual({
      data: {
        get_patent_with_id: {
          patent_id: '100',
        },
      },
    });
  });
  it('Get patent using API key in the cookie', async () => {
    const query = /* GraphQL */ `
      query {
        get_patent_with_id(patent_id: "100", apiKey: "abcdef") {
          patent_id
        }
      }
    `;
    const createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      oasFilePath,
      baseUrl,
      operationHeaders: {
        cookie: 'access_token={args.apiKey}',
      },
      fetch,
    });
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });

    expect(result).toEqual({
      data: {
        get_patent_with_id: {
          patent_id: '100',
        },
      },
    });
  });
  it('Get patent using API key in the query string', async () => {
    const query = /* GraphQL */ `
      query {
        get_patent_with_id(patent_id: "100", apiKey: "abcdef") {
          patent_id
        }
      }
    `;
    const createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      oasFilePath,
      baseUrl,
      queryParams: {
        access_token: '{args.apiKey}',
      },
      fetch,
    });
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });

    expect(result).toEqual({
      data: {
        get_patent_with_id: {
          patent_id: '100',
        },
      },
    });
  });
});
Example #7
Source File: example_api.test.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('example_api', () => {
  beforeAll(async () => {
    createdSchema = await loadGraphQLSchemaFromOpenAPI('test', {
      fetch,
      baseUrl,
      oasFilePath: join(__dirname, '../../../handlers/openapi/test/fixtures/example_oas.json'),
    });
    await startServer(PORT);
  });
  afterAll(() => stopServer());

  it('should get descriptions', async () => {
    // Get all the descriptions of the fields on the GraphQL object type car
    const carType = createdSchema.getType('car') as GraphQLObjectType;
    expect(carType).toBeDefined();
    const carFields = carType.getFields();
    expect(carFields).toBeDefined();
    expect(carFields.model.description).toBe('The model of the car.');
    expect(carFields.color.description).toBe('The color of the car.');
  });

  it('should get resource (incl. enum)', async () => {
    // Status is an enum
    const query = /* GraphQL */ `
      {
        getUserByUsername(username: "arlene") {
          name
          status
        }
      }
    `;

    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });

    expect(result).toEqual({
      data: { getUserByUsername: { name: 'Arlene L McMahon', status: 'staff' } },
    });
  });

  // OAS allows you to define response objects with HTTP code with the XX wildcard syntax
  it('should get resource with status code: 2XX', async () => {
    const query = /* GraphQL */ `
      {
        getPapers {
          name
          published
        }
      }
    `;

    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        getPapers: [
          { 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.
   */
  it('should get resource with no response schema and status code: 204', async () => {
    const query = /* GraphQL */ `
      {
        getBonuses
      }
    `;

    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        getBonuses: '',
      },
    });
  });

  // Link objects in the OAS allow OtG to create nested GraphQL objects that resolve on different API calls
  it('should get nested resource via link $response.body#/...', async () => {
    const query = /* GraphQL */ `
      {
        getUserByUsername(username: "arlene") {
          name
          employerCompany {
            legalForm
          }
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        getUserByUsername: {
          name: 'Arlene L McMahon',
          employerCompany: {
            legalForm: 'public',
          },
        },
      },
    });
  });

  it('should get nested resource via link $request.path#/... and $request.query#/', async () => {
    const query = /* GraphQL */ `
      {
        get_product_with_id(product_id: "123", input: { product_tag: "blah" }) {
          product_name
          reviews {
            text
          }
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        get_product_with_id: {
          product_name: 'Super Product',
          reviews: [{ text: 'Great product' }, { text: 'I love it' }],
        },
      },
    });
  });

  // Both an operationId and an operationRef can be used to create a link object
  it('should get nested resource via link operationRef', async () => {
    const query = /* GraphQL */ `
      {
        get_product_with_id(product_id: "123", input: { product_tag: "blah" }) {
          product_name
          reviewsWithOperationRef {
            text
          }
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        get_product_with_id: {
          product_name: 'Super Product',
          reviewsWithOperationRef: [{ text: 'Great product' }, { text: 'I love it' }],
        },
      },
    });
  });

  it('should get nested lists of resources', async () => {
    const query = /* GraphQL */ `
      {
        getUserByUsername(username: "arlene") {
          name
          friends {
            name
            friends {
              name
              friends {
                name
              }
            }
          }
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });
    expect(result).toEqual({
      data: {
        getUserByUsername: {
          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',
                    },
                  ],
                },
              ],
            },
          ],
        },
      },
    });
  });

  it('should get nested lists of resources without specifying a path param for the parent resource', async () => {
    const query = /* GraphQL */ `
      {
        getUsers(input: { limit: 1 }) {
          name
          friends {
            name
            friends {
              name
              friends {
                name
              }
            }
          }
        }
      }
    `;
    const result = await execute({
      schema: createdSchema,
      document: parse(query),
    });

    expect(result).toEqual({
      data: {
        getUsers: [
          {
            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',
                      },
                    ],
                  },
                ],
              },
            ],
          },
        ],
      },
    });
  });
});
Example #8
Source File: index.ts    From graphql-mesh with MIT License 4 votes vote down vote up
async getUnifiedSchema({ rawSources, typeDefs, resolvers, transforms }: MeshMergerContext) {
    this.logger.debug(`Creating localServiceList for gateway`);
    const rawSourceMap = new Map<string, RawSourceOutput>();
    const localServiceList: { name: string; typeDefs: DocumentNode }[] = [];
    const sourceMap = new Map<RawSourceOutput, GraphQLSchema>();
    await Promise.all(
      rawSources.map(async rawSource => {
        const transformedSchema = wrapSchema(rawSource as any);
        rawSourceMap.set(rawSource.name, rawSource);
        sourceMap.set(rawSource, transformedSchema);
        const sdl = await this.store
          .proxy(`${rawSource.name}_sdl`, PredefinedProxyOptions.StringWithoutValidation)
          .getWithSet(async () => {
            this.logger.debug(`Fetching Apollo Federated Service SDL for ${rawSource.name}`);
            const sdlQueryResult: any = await execute({
              schema: transformedSchema,
              document: parse(SERVICE_DEFINITION_QUERY),
            });
            if (sdlQueryResult.errors?.length) {
              throw new AggregateError(sdlQueryResult.errors, `Failed on fetching Federated SDL for ${rawSource.name}`);
            }
            return sdlQueryResult.data._service.sdl;
          });
        localServiceList.push({
          name: rawSource.name,
          typeDefs: parse(sdl),
        });
      })
    );
    this.logger.debug(`Creating ApolloGateway`);
    const gateway = new ApolloGateway({
      localServiceList,
      buildService: ({ name }) => {
        this.logger.debug(`Building federation service: ${name}`);
        const rawSource = rawSourceMap.get(name);
        const transformedSchema = sourceMap.get(rawSource);
        return new LocalGraphQLDataSource(transformedSchema);
      },
      logger: this.logger,
      debug: !!process.env.DEBUG,
      serviceHealthCheck: true,
    });
    this.logger.debug(`Loading gateway`);
    const { schema, executor: gatewayExecutor } = await gateway.load();
    const schemaHash: any = printSchemaWithDirectives(schema);
    let remoteSchema: GraphQLSchema = schema;
    this.logger.debug(`Wrapping gateway executor in a unified schema`);
    remoteSchema = wrapSchema({
      schema: remoteSchema,
      executor: <TReturn>({ document, info, variables, context, operationName }: ExecutionRequest) => {
        const documentStr = printWithCache(document);
        const { operation } = info;
        // const operationName = operation.name?.value;
        return gatewayExecutor({
          document,
          request: {
            query: documentStr,
            operationName,
            variables,
          },
          operationName,
          cache: this.cache,
          context,
          queryHash: documentStr,
          logger: this.logger,
          metrics: {},
          source: documentStr,
          operation,
          schema,
          schemaHash,
          overallCachePolicy: {} as any,
        }) as ExecutionResult<TReturn>;
      },
      batch: true,
    });
    const id$ = this.pubsub.subscribe('destroy', () => {
      gateway.stop().catch(err => this.logger.error(err));
      id$.then(id => this.pubsub.unsubscribe(id)).catch(err => console.error(err));
    });
    this.logger.debug(`Applying additionalTypeDefs`);
    typeDefs?.forEach(typeDef => {
      remoteSchema = extendSchema(remoteSchema, typeDef);
    });
    if (resolvers) {
      this.logger.debug(`Applying additionalResolvers`);
      for (const resolversObj of asArray(resolvers)) {
        remoteSchema = addResolversToSchema({
          schema: remoteSchema,
          resolvers: resolversObj,
          updateResolversInPlace: true,
        });
      }
    }
    if (transforms?.length) {
      this.logger.debug(`Applying root level transforms`);
      remoteSchema = wrapSchema({
        schema: remoteSchema,
        transforms: transforms as any[],
        batch: true,
      });
    }
    this.logger.debug(`Attaching sourceMap to the unified schema`);
    remoteSchema.extensions = remoteSchema.extensions || {};
    Object.defineProperty(remoteSchema.extensions, 'sourceMap', {
      get: () => sourceMap,
    });
    return remoteSchema;
  }
Example #9
Source File: cache.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('cache', () => {
  let schema: GraphQLSchema;
  let cache: KeyValueCache;
  let pubsub: MeshPubSub;
  const baseDir: string = undefined;

  beforeEach(() => {
    const baseSchema = buildSchema(/* GraphQL */ `
      type Query {
        user(id: ID!): User
        users(filter: SearchUsersInput): [User!]!
      }

      type Mutation {
        updateUser(userId: ID!, name: String!): User
        deleteUser(userIdToDelete: ID!): Boolean
      }

      input SearchUsersInput {
        username: String
        email: String
        name: String
        age: String
      }

      type User {
        id: ID!
        username: String!
        email: String!
        profile: Profile!
        friend(id: ID!): User
      }

      type Profile {
        name: String!
        age: Int!
      }
    `);

    schema = addResolversToSchema({
      schema: baseSchema,
      resolvers: spies,
    });

    cache = new LocalforageCache();
    pubsub = new PubSub();

    spies.Query.user.mockClear();
    spies.Query.users.mockClear();
  });

  describe('Resolvers Composition', () => {
    it('should replace resolvers correctly with a specific type and field', async () => {
      expect(schema.getQueryType()?.getFields().user.resolve.name).toBe(spies.Query.user.bind(null).name);

      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config: [
          {
            field: 'Query.user',
          },
        ],
        pubsub,
        baseDir,
      });
      const modifiedSchema = transform.transformSchema(schema);

      expect(modifiedSchema!.getQueryType()?.getFields().user.resolve.name).not.toBe(spies.Query.user.bind(null).name);
      expect(modifiedSchema!.getQueryType()?.getFields().users.resolve.name).toBe(spies.Query.users.bind(null).name);
    });

    it('should replace resolvers correctly with a wildcard', async () => {
      expect(schema.getQueryType()?.getFields().user.resolve.name).toBe(spies.Query.user.bind(null).name);
      expect(schema.getQueryType()?.getFields().users.resolve.name).toBe(spies.Query.users.bind(null).name);

      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config: [
          {
            field: 'Query.*',
          },
        ],
        pubsub,
        baseDir,
      });

      const modifiedSchema = transform.transformSchema(schema);

      expect(modifiedSchema!.getQueryType()?.getFields().user.resolve.name).not.toBe(spies.Query.user.bind(null).name);
      expect(modifiedSchema!.getQueryType()?.getFields().users.resolve.name).not.toBe(
        spies.Query.users.bind(null).name
      );
    });
  });

  describe('Cache Wrapper', () => {
    const checkCache = async (config: YamlConfig.CacheTransformConfig[], cacheKeyToCheck?: string) => {
      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        cache,
        config,
        pubsub,
        baseDir,
      });

      const modifiedSchema = transform.transformSchema(schema);

      const executeOptions = {
        schema: modifiedSchema!,
        document: parse(/* GraphQL */ `
          query user {
            user(id: 1) {
              id
              username
            }
          }
        `),
        contextValue: {},
      };

      const queryType = schema.getType('Query') as GraphQLObjectType;
      const queryFields = queryType.getFields();
      const operation = executeOptions.document.definitions[0] as OperationDefinitionNode;
      const cacheKey =
        cacheKeyToCheck ||
        computeCacheKey({
          keyStr: undefined,
          args: { id: '1' },
          info: {
            fieldName: 'user',
            parentType: queryType,
            returnType: queryFields.user.type,
            schema,
            fieldNodes: operation.selectionSet.selections as FieldNode[],
            fragments: {},
            rootValue: {},
            operation,
            variableValues: {},
            path: {
              prev: null,
              key: 'user',
            },
          } as any,
        });

      // No data in cache before calling it
      expect(await cache.get(cacheKey)).not.toBeDefined();
      // Run it for the first time
      await execute(executeOptions);
      // Original resolver should now be called
      expect(spies.Query.user.mock.calls.length).toBe(1);
      // Data should be stored in cache
      const data: any = await cache.get(cacheKey);
      const mockData = MOCK_DATA[0];
      expect(data.id).toBe(mockData.id);
      expect(data.username).toBe(mockData.username);
      // Running it again
      await execute(executeOptions);
      // No new calls to the original resolver
      expect(spies.Query.user.mock.calls.length).toBe(1);

      return {
        cache,
        executeAgain: () => execute(executeOptions),
        executeDocument: (operation: DocumentNode, variables: Record<string, any> = {}) =>
          execute({
            schema: modifiedSchema,
            document: operation,
            variableValues: variables,
          }),
      };
    };

    it('Should wrap resolver correctly with caching - without cacheKey', async () => {
      await checkCache([
        {
          field: 'Query.user',
        },
      ]);
    });

    it('Should wrap resolver correctly with caching with custom key', async () => {
      const cacheKey = `customUser`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey,
          },
        ],
        cacheKey
      );
    });

    it('Should wrap resolver correctly with caching with custom key', async () => {
      const cacheKey = `customUser`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey,
          },
        ],
        cacheKey
      );
    });

    it('Should clear cache correctly when TTL is set', async () => {
      const key = 'user-1';
      const { cache, executeAgain } = await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: key,
            invalidate: {
              ttl: 1,
            },
          },
        ],
        key
      );

      expect(await cache.get(key)).toBeDefined();
      await wait(1.1);
      expect(await cache.get(key)).not.toBeDefined();
      await executeAgain();
      expect(await cache.get(key)).toBeDefined();
    });

    it('Should wrap resolver correctly with caching with custom calculated key - and ensure calling resovler again when key is different', async () => {
      const { cache, executeDocument } = await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `query-user-{args.id}`,
          },
        ],
        'query-user-1'
      );

      const otherIdQuery = parse(/* GraphQL */ `
        query user {
          user(id: 2) {
            id
          }
        }
      `);

      expect(await cache.get('query-user-2')).not.toBeDefined();
      await executeDocument(otherIdQuery);
      const cachedObj: any = await cache.get('query-user-2');
      const mockObj = MOCK_DATA[1];
      expect(cachedObj.id).toBe(mockObj.id);
      expect(spies.Query.user.mock.calls.length).toBe(2);
      await executeDocument(otherIdQuery);
      expect(spies.Query.user.mock.calls.length).toBe(2);
    });

    it('Should work correctly with argsHash', async () => {
      const expectedHash = `query-user-${hashObject({ id: '1' })}`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `query-user-{argsHash}`,
          },
        ],
        expectedHash
      );
    });

    it('Should work correctly with hash helper', async () => {
      const expectedHash = hashObject('1');

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `{args.id|hash}`,
          },
        ],
        expectedHash
      );
    });

    it('Should work correctly with date helper', async () => {
      const expectedHash = `1-${dayjs(new Date()).format(`yyyy-MM-dd`)}`;

      await checkCache(
        [
          {
            field: 'Query.user',
            cacheKey: `{args.id}-{yyyy-MM-dd|date}`,
          },
        ],
        expectedHash
      );
    });
  });

  describe('Opration-based invalidation', () => {
    it('Should invalidate cache when mutation is done based on key', async () => {
      const transform = new CacheTransform({
        apiName: 'test',
        importFn,
        config: [
          {
            field: 'Query.user',
            cacheKey: 'query-user-{args.id}',
            invalidate: {
              effectingOperations: [
                {
                  operation: 'Mutation.updateUser',
                  matchKey: 'query-user-{args.userId}',
                },
                {
                  operation: 'Mutation.deleteUser',
                  matchKey: 'query-user-{args.userIdToDelete}',
                },
              ],
            },
          },
        ],
        cache,
        pubsub,
        baseDir,
      });

      const schemaWithCache = transform.transformSchema(schema);

      const expectedCacheKey = `query-user-1`;

      const executeOptions = {
        schema: schemaWithCache!,
        document: parse(/* GraphQL */ `
          query user {
            user(id: 1) {
              id
              name
            }
          }
        `),
      };

      // Make sure cache works as needed, runs resolvers logic only once
      expect(await cache.get(expectedCacheKey)).not.toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(0);
      await execute(executeOptions);
      expect(await cache.get(expectedCacheKey)).toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(1);
      await execute(executeOptions);
      expect(spies.Query.user.mock.calls.length).toBe(1);

      // Run effecting mutation
      await execute({
        schema: schemaWithCache!,
        document: parse(/* GraphQL */ `
          mutation updateUser {
            updateUser(userId: 1, name: "test new") {
              id
              name
            }
          }
        `),
      });

      // Cache should be empty now, no calls for resolvers since then
      expect(await cache.get(expectedCacheKey)).not.toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(1);

      // Running again query, cache should be filled and resolver should get called again
      await execute(executeOptions);
      expect(await cache.get(expectedCacheKey)).toBeDefined();
      expect(spies.Query.user.mock.calls.length).toBe(2);
    });

    describe('Subfields', () => {
      it('Should cache queries including subfield arguments', async () => {
        const transform = new CacheTransform({
          apiName: 'test',
          importFn,
          config: [{ field: 'Query.user' }],
          cache,
          pubsub,
          baseDir,
        });
        const schemaWithCache = transform.transformSchema(schema);

        // First query should call resolver and fill cache
        const executeOptions1 = {
          schema: schemaWithCache,
          document: parse(/* GraphQL */ `
            query {
              user(id: 1) {
                friend(id: 2) {
                  id
                }
              }
            }
          `),
        };
        const { data: actual1 }: any = await execute(executeOptions1);
        expect(spies.Query.user.mock.calls.length).toBe(1);
        expect(actual1.user.friend.id).toBe('2');

        // Second query should call resolver and also fill cache
        const executeOptions2 = {
          schema: schemaWithCache,
          document: parse(/* GraphQL */ `
            query {
              user(id: 1) {
                friend(id: 3) {
                  id
                }
              }
            }
          `),
        };
        const { data: actual2 }: any = await execute(executeOptions2);
        expect(spies.Query.user.mock.calls.length).toBe(2);
        expect(actual2.user.friend.id).toBe('3');

        // Repeat both queries, no new calls for resolver
        const { data: repeat1 }: any = await execute(executeOptions1);
        const { data: repeat2 }: any = await execute(executeOptions2);
        expect(spies.Query.user.mock.calls.length).toBe(2);
        expect(repeat1.user.friend.id).toBe('2');
        expect(repeat2.user.friend.id).toBe('3');
      });
    });

    describe('Race condition', () => {
      it('should wait for local cache transform to finish writing the entry', async () => {
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };

        let callCount = 0;
        const schema = makeExecutableSchema({
          typeDefs: /* GraphQL */ `
            type Query {
              foo: String
            }
          `,
          resolvers: {
            Query: {
              foo: () => new Promise(resolve => setTimeout(() => resolve((callCount++).toString()), 300)),
            },
          },
        });
        const transform = new CacheTransform(options);
        const transformedSchema = transform.transformSchema(schema);
        const query = /* GraphQL */ `
          {
            foo1: foo
            foo2: foo
          }
        `;
        const result = await execute({
          schema: transformedSchema,
          document: parse(query),
        });

        expect(result.data.foo2).toBe(result.data.foo1);
      });

      it('should wait for other cache transform to finish writing the entry when delay >= safe threshold)', async () => {
        let callCount = 0;
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };
        function getNewSchema() {
          return makeExecutableSchema({
            typeDefs: /* GraphQL */ `
              type Query {
                foo: String
              }
            `,
            resolvers: {
              Query: {
                foo: async () => {
                  callCount++;
                  await new Promise(resolve => setTimeout(resolve, 300));
                  return 'FOO';
                },
              },
            },
          });
        }
        const transform1 = new CacheTransform(options);
        const transformedSchema1 = transform1.transformSchema(getNewSchema());
        const transform2 = new CacheTransform(options);
        const transformedSchema2 = transform2.transformSchema(getNewSchema());
        const query = /* GraphQL */ `
          {
            foo
          }
        `;

        await execute({
          schema: transformedSchema1,
          document: parse(query),
        });
        await wait(0);
        await execute({
          schema: transformedSchema2,
          document: parse(query),
        });

        expect(callCount).toBe(1);
      });

      it('should fail to wait for other cache transform to finish writing the entry when delay < safe threshold', async () => {
        let callCount = 0;
        const options: MeshTransformOptions<YamlConfig.CacheTransformConfig[]> = {
          apiName: 'test',
          importFn,
          config: [
            {
              field: 'Query.foo',
              cacheKey: 'random',
            },
          ],
          cache,
          pubsub,
          baseDir,
        };
        function getNewSchema() {
          return makeExecutableSchema({
            typeDefs: /* GraphQL */ `
              type Query {
                foo: String
              }
            `,
            resolvers: {
              Query: {
                foo: async () => {
                  callCount++;
                  await new Promise(resolve => setTimeout(resolve, 300));
                  return 'FOO';
                },
              },
            },
          });
        }
        const transform1 = new CacheTransform(options);
        const transformedSchema1 = transform1.transformSchema(getNewSchema());
        const transform2 = new CacheTransform(options);
        const transformedSchema2 = transform2.transformSchema(getNewSchema());
        const query = /* GraphQL */ `
          {
            foo
          }
        `;
        await Promise.all([
          execute({
            schema: transformedSchema1,
            document: parse(query),
          }),
          execute({
            schema: transformedSchema2,
            document: parse(query),
          }),
        ]);
        expect(callCount).toBe(2);
      });
    });
  });
});
Example #10
Source File: encapsulate.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('encapsulate', () => {
  const baseDir: string = undefined;
  const importFn: ImportFn = m => import(m);
  const schema = makeExecutableSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        getSomething: String
        getSomethingElse: String
      }

      type Mutation {
        doSomething: String
        doSomethingElse: String
      }

      type Subscription {
        notify: String!
      }
    `,
    resolvers: {
      Query: {
        getSomething: () => 'boop',
      },
      Mutation: {
        doSomething: () => 'noop',
      },
    },
  });
  let cache: InMemoryLRUCache;
  let pubsub: MeshPubSub;

  beforeEach(() => {
    cache = new InMemoryLRUCache();
    pubsub = new PubSub();
  });

  it('should wrap the schema and group Mutation correctly', async () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    expect(newSchema.getMutationType().getFields().test).toBeDefined();
    expect(newSchema.getMutationType().getFields().notify).not.toBeDefined();
    expect(newSchema.getMutationType().getFields().test.type.toString()).toBe('testMutation!');
  });

  it('should wrap the schema and group Subscription correctly', async () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    expect(newSchema.getSubscriptionType().getFields().test).toBeDefined();
    expect(newSchema.getSubscriptionType().getFields().getSomething).not.toBeDefined();
    expect(newSchema.getSubscriptionType().getFields().test.type.toString()).toBe('testSubscription!');
  });

  it('should wrap the schema and group Query correctly', async () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    expect(newSchema.getQueryType().getFields().test).toBeDefined();
    expect(newSchema.getQueryType().getFields().getSomething).not.toBeDefined();
    expect(newSchema.getQueryType().getFields().test.type.toString()).toBe('testQuery!');
  });

  it('should execute queries the same way and preserve execution flow', async () => {
    const { data: resultBefore } = await execute({
      schema,
      document: parse(`{ getSomething }`),
    });
    expect(resultBefore.getSomething).toBe('boop');

    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    const { data: resultAfter }: any = await execute({
      schema: newSchema,
      document: parse(`{ test { getSomething } }`),
    });

    expect(resultAfter.test.getSomething).toBe('boop');
  });

  it('should execute mutations the same way and preserve execution flow', async () => {
    const { data: resultBefore } = await execute({
      schema,
      document: parse(`mutation { doSomething }`),
    });
    expect(resultBefore.doSomething).toBe('noop');

    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    const { data: resultAfter }: any = await execute({
      schema: newSchema,
      document: parse(`mutation { test { doSomething } }`),
    });

    expect(resultAfter.test.doSomething).toBe('noop');
  });

  it("should be able to introspect even it's partial", async () => {
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          getSomething: String
          getSomethingElse: String
        }
      `,
      resolvers: {
        Query: {
          getSomething: () => 'boop',
        },
      },
    });

    const newSchema = wrapSchema({
      schema,
      transforms: [
        new Transform({
          config: {},
          cache,
          pubsub,
          baseDir,
          apiName: 'test',
          importFn,
        }),
      ],
    });

    const { data } = await execute({
      schema: newSchema,
      document: parse(getIntrospectionQuery()),
    });

    expect(data).not.toBeNull();
  });
});
Example #11
Source File: mocking.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('mocking', () => {
  let cache: InMemoryLRUCache;
  let pubsub: MeshPubSub;
  const baseDir: string = __dirname;
  const importFn: ImportFn = m => import(m);

  beforeEach(() => {
    cache = new InMemoryLRUCache();
    pubsub = new PubSub();
  });

  it('should mock fields and resolvers should not get called', async () => {
    let queryUserCalled = false;
    let userFullNameCalled = false;
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type User {
          id: ID
          fullName: String
        }
        type Query {
          users: [User]
        }
      `,
      resolvers: {
        Query: {
          users: () => {
            queryUserCalled = true;
            return [{}, {}, {}, {}, {}, {}];
          },
        },
        User: {
          id: () => 'NOTID',
          fullName: () => {
            userFullNameCalled = true;
            return 'fullName';
          },
        },
      },
    });
    const mockingConfig: YamlConfig.MockingConfig = {
      mocks: [
        {
          apply: 'User.fullName',
          faker: '{{name.lastName}}, {{name.firstName}} {{name.suffix}}',
        },
      ],
    };
    const transformedSchema = await wrapSchema({
      schema,
      transforms: [
        new MockingTransform({
          config: mockingConfig,
          cache,
          pubsub,
          baseDir,
          apiName: '',
          importFn,
        }),
      ],
    });
    const result = await graphql({
      schema: transformedSchema,
      source: /* GraphQL */ `
        {
          users {
            id
            fullName
          }
        }
      `,
      contextValue: {},
    });

    const users = result.data?.users;
    expect(users).toBeTruthy();
    expect(users[0]).toBeTruthy();
    expect(users[0].id).not.toBe('NOTID');
    expect(users[0].fullName).not.toBe('fullName');
    expect(queryUserCalled).toBeFalsy();
    expect(userFullNameCalled).toBeFalsy();
  });

  it('should mock fields by using the "custom" property', async () => {
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type User {
          id: ID
          fullName: String
        }
        type Query {
          users: [User]
        }
      `,
      resolvers: {
        Query: {
          users: () => [],
        },
        User: {
          id: () => 'sample-id-coming-from-the-resolver',
          fullName: () => 'Sample name coming from the resolver',
        },
      },
    });

    const mockingConfig: YamlConfig.MockingConfig = {
      mocks: [
        {
          apply: 'User.id',
          custom: './mocks.ts#id',
        },
        {
          apply: 'User.fullName',
          custom: './mocks.ts#fullName',
        },
      ],
    };

    const transformedSchema = await wrapSchema({
      schema,
      transforms: [
        new MockingTransform({
          config: mockingConfig,
          cache,
          pubsub,
          baseDir,
          apiName: '',
          importFn,
        }),
      ],
    });

    const result = await graphql({
      schema: transformedSchema,
      source: /* GraphQL */ `
        {
          users {
            id
            fullName
          }
        }
      `,
      contextValue: {},
    });

    const users = result.data?.users;
    expect(users).toBeTruthy();
    expect(users[0]).toBeTruthy();
    expect(users[0].id).toBe('sample-id');
    expect(users[0].fullName).toBe('John Snow');
  });
  it('should custom resolvers work with mock store', async () => {
    const schema = buildSchema(/* GraphQL */ `
      type Query {
        user(id: ID): User
      }
      type Mutation {
        addUser(name: String): User
        updateUser(id: ID, name: String): User
      }
      type User {
        id: ID
        name: String
      }
    `);
    const mockedSchema = wrapSchema({
      schema,
      transforms: [
        new MockingTransform({
          config: {
            mocks: [
              {
                apply: 'Query.user',
                custom: './mocks.ts#GetUserMock',
              },
              {
                apply: 'Mutation.addUser',
                custom: './mocks.ts#AddUserMock',
              },
              {
                apply: 'Mutation.updateUser',
                custom: './mocks.ts#UpdateUserMock',
              },
            ],
          },
          cache,
          pubsub,
          baseDir,
          apiName: '',
          importFn,
        }),
      ],
    });
    const ADD_USER = parse(/* GraphQL */ `
      mutation AddUser {
        addUser(name: "John Doe") {
          id
          name
        }
      }
    `);
    const addUserResult: any = await execute({
      schema: mockedSchema,
      document: ADD_USER,
    });
    expect(addUserResult?.data?.addUser?.name).toBe('John Doe');
    const addedUserId = addUserResult.data.addUser.id;
    const GET_USER = parse(/* GraphQL */ `
      query GetUser {
        user(id: "${addedUserId}") {
          id
          name
        }
      }
    `);
    const getUserResult: any = await execute({ schema: mockedSchema, document: GET_USER });
    expect(getUserResult?.data?.user?.id).toBe(addedUserId);
    expect(getUserResult?.data?.user?.name).toBe('John Doe');
    const UPDATE_USER = parse(/* GraphQL */ `
      mutation UpdateUser {
        updateUser(id: "${addedUserId}", name: "Jane Doe") {
          id
          name
        }
      }
    `);
    const updateUserResult: any = await execute({ schema: mockedSchema, document: UPDATE_USER });
    expect(updateUserResult?.data?.updateUser?.id).toBe(addedUserId);
    expect(updateUserResult?.data?.updateUser?.name).toBe('Jane Doe');
  });
  it('should declarative API work with mock store', async () => {
    const schema = buildSchema(/* GraphQL */ `
      type Query {
        user(id: ID): User
      }
      type Mutation {
        addUser(name: String): User
        updateUser(id: ID, name: String): User
      }
      type User {
        id: ID
        name: String
      }
    `);
    const mockedSchema = wrapSchema({
      schema,
      transforms: [
        new MockingTransform({
          config: {
            mocks: [
              {
                apply: 'Query.user',
                store: {
                  type: 'User',
                  key: '{args.id}',
                },
              },
              {
                apply: 'Mutation.addUser',
                updateStore: [
                  {
                    type: 'User',
                    key: '{random}',
                    fieldName: 'name',
                    value: '{args.name}',
                  },
                ],
                store: {
                  type: 'User',
                  key: '{random}',
                },
              },
              {
                apply: 'Mutation.updateUser',
                updateStore: [
                  {
                    type: 'User',
                    key: '{args.id}',
                    fieldName: 'name',
                    value: '{args.name}',
                  },
                ],
                store: {
                  type: 'User',
                  key: '{args.id}',
                },
              },
            ],
          },
          cache,
          pubsub,
          baseDir,
          apiName: '',
          importFn,
        }),
      ],
    });
    const ADD_USER = parse(/* GraphQL */ `
      mutation AddUser {
        addUser(name: "John Doe") {
          id
          name
        }
      }
    `);
    const addUserResult: any = await execute({ schema: mockedSchema, document: ADD_USER });
    expect(addUserResult?.data?.addUser?.name).toBe('John Doe');
    const addedUserId = addUserResult.data.addUser.id;
    const GET_USER = parse(/* GraphQL */ `
      query GetUser {
        user(id: "${addedUserId}") {
          id
          name
        }
      }
    `);
    const getUserResult: any = await execute({ schema: mockedSchema, document: GET_USER });
    expect(getUserResult?.data?.user?.id).toBe(addedUserId);
    expect(getUserResult?.data?.user?.name).toBe('John Doe');
    const UPDATE_USER = parse(/* GraphQL */ `
      mutation UpdateUser {
        updateUser(id: "${addedUserId}", name: "Jane Doe") {
          id
          name
        }
      }
    `);
    const updateUserResult: any = await execute({ schema: mockedSchema, document: UPDATE_USER });
    expect(updateUserResult?.data?.updateUser?.id).toBe(addedUserId);
    expect(updateUserResult?.data?.updateUser?.name).toBe('Jane Doe');
  });
});
Example #12
Source File: naming-convention.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('namingConvention', () => {
  const schema = buildSchema(/* GraphQL */ `
    type Query {
      user: user!
      userById(userId: ID!): user!
    }
    type user {
      Id: ID!
      Type: userType
    }
    enum userType {
      admin
      moderator
      newbie
    }
  `);
  let cache: InMemoryLRUCache;
  let pubsub: MeshPubSub;
  const baseDir: string = undefined;
  const importFn: ImportFn = m => import(m);

  beforeEach(() => {
    cache = new InMemoryLRUCache();
    pubsub = new PubSub();
  });

  it('should change the name of a types, enums, fields and fieldArguments', () => {
    const newSchema = wrapSchema({
      schema,
      transforms: [
        new NamingConventionTransform({
          apiName: '',
          importFn,
          config: {
            typeNames: 'pascalCase',
            enumValues: 'upperCase',
            fieldNames: 'camelCase',
            fieldArgumentNames: 'snakeCase',
          },
          cache,
          pubsub,
          baseDir,
        }),
      ],
    });

    expect(newSchema.getType('user')).toBeUndefined();
    const userObjectType = newSchema.getType('User') as GraphQLObjectType;
    expect(userObjectType).toBeDefined();

    const userObjectTypeFields = userObjectType.getFields();
    expect(userObjectTypeFields.Id).toBeUndefined();
    expect(userObjectTypeFields.id).toBeDefined();

    expect(newSchema.getType('userType')).toBeUndefined();
    const userTypeEnumType = newSchema.getType('UserType') as GraphQLEnumType;
    expect(userTypeEnumType).toBeDefined();
    expect(userTypeEnumType.getValue('Admin')).toBeUndefined();
    const adminValue = userTypeEnumType.getValue('ADMIN');
    expect(adminValue).toBeDefined();
    // expect(adminValue.value).toBe('admin');
    expect(printSchema(newSchema)).toMatchSnapshot();
  });
  it('should execute the transformed schema properly', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type Query {
        user(input: UserSearchInput): User
        userById(userId: ID!): User
      }
      type User {
        id: ID
        first_name: String
        last_name: String
      }
      input UserSearchInput {
        id: ID
        first_name: String
        last_name: String
      }
    `);
    let userInput;
    schema = addResolversToSchema({
      schema,
      resolvers: {
        Query: {
          user: (root, args, context, info) => {
            userInput = args?.input;
            return userInput;
          },
          userById: (root, args, context, info) => {
            return { id: args.userId, first_name: 'John', last_name: 'Doe' };
          },
        },
      },
    });
    schema = wrapSchema({
      schema,
      transforms: [
        new NamingConventionTransform({
          apiName: '',
          importFn,
          cache,
          pubsub,
          config: {
            fieldNames: 'camelCase',
            fieldArgumentNames: 'snakeCase',
          },
          baseDir,
        }),
      ],
    });
    const result = await execute({
      schema,
      document: parse(/* GraphQL */ `
        {
          user(input: { id: "0", firstName: "John", lastName: "Doe" }) {
            id
            firstName
            lastName
          }
        }
      `),
    });
    // Pass non-transformed input to the real schema
    expect(userInput).toEqual({
      id: '0',
      first_name: 'John',
      last_name: 'Doe',
    });
    // Pass transformed output to the client
    expect(result?.data?.user).toEqual({
      id: '0',
      firstName: 'John',
      lastName: 'Doe',
    });

    const result2 = await execute({
      schema,
      document: parse(/* GraphQL */ `
        {
          userById(user_id: "1") {
            id
            firstName
            lastName
          }
        }
      `),
    });
    // Pass transformed output to the client
    expect(result2.data?.userById).toEqual({
      id: '1',
      firstName: 'John',
      lastName: 'Doe',
    });
  });
  it('should be skipped if the result gonna be empty string', async () => {
    let schema = buildSchema(/* GraphQL */ `
      type Query {
        _: String!
      }
    `);
    schema = addResolversToSchema({
      schema,
      resolvers: {
        Query: {
          _: (root, args, context, info) => {
            return 'test';
          },
        },
      },
    });
    schema = wrapSchema({
      schema,
      transforms: [
        new NamingConventionTransform({
          apiName: '',
          importFn,
          cache,
          pubsub,
          config: {
            fieldNames: 'camelCase',
          },
          baseDir,
        }),
      ],
    });
    const { data } = await execute({
      schema,
      document: parse(/* GraphQL */ `
        {
          _
        }
      `),
    });
    expect(data?._).toEqual('test');
  });
  it('should skip fields of Federation spec', async () => {
    const typeDefs = /* GraphQL */ `
type Query {
  _service: String!
  _entities: [String!]!
}`.trim();
    const schema = wrapSchema({
      schema: buildSchema(typeDefs),
      transforms: [
        new NamingConventionTransform({
          apiName: '',
          importFn,
          cache,
          pubsub,
          config: {
            fieldNames: 'snakeCase',
          },
          baseDir,
        }),
      ],
    });
    expect(printSchema(schema)).toBe(typeDefs);
  });
});
Example #13
Source File: rate-limit.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('Rate Limit Transform', () => {
  let pubsub: PubSub;
  let cache: InMemoryLRUCache;

  beforeEach(() => {
    pubsub = new PubSub();
    cache = new InMemoryLRUCache();
  });

  afterEach(() => {
    pubsub
      .publish('destroy', {} as any)
      .catch(e => console.warn(`Error on emitting destroy: ${e.stack || e.message || e}`));
  });

  const baseDir = process.cwd();
  const importFn = defaultImportFn;
  const apiName = 'rate-limit-test';
  it('should throw an error if the rate limit is exceeded', async () => {
    let numberOfCalls = 0;
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => {
            numberOfCalls++;
            return 'bar';
          },
        },
      },
    });
    const rateLimitTransform = new RateLimitTransform({
      apiName,
      config: [
        {
          type: 'Query',
          field: 'foo',
          max: 5,
          ttl: 5000,
          identifier: '{context.userId}',
        },
      ],
      baseDir,
      cache,
      pubsub,
      importFn,
    });
    const wrappedSchema = wrapSchema({
      schema,
      transforms: [rateLimitTransform],
    });
    const query = /* GraphQL */ `
      {
        foo
      }
    `;
    const executeQuery = () =>
      execute({
        schema: wrappedSchema,
        document: parse(query),
        contextValue: {
          userId: '1',
        },
      });
    for (let i = 0; i < 5; i++) {
      const result = await executeQuery();

      expect(result).toEqual({
        data: {
          foo: 'bar',
        },
      });
    }
    const result = await executeQuery();

    // Resolver shouldn't be called
    expect(numberOfCalls).toBe(5);
    expect(result.data?.foo).toBeNull();
    const firstError = result.errors?.[0];
    expect(firstError.message).toBe('Rate limit of "Query.foo" exceeded for "1"');
    expect(firstError.path).toEqual(['foo']);
  });
  it('should reset tokens when the ttl is expired', async () => {
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => 'bar',
        },
      },
    });
    const rateLimitTransform = new RateLimitTransform({
      apiName,
      config: [
        {
          type: 'Query',
          field: 'foo',
          max: 5,
          ttl: 1000,
          identifier: '{context.userId}',
        },
      ],
      baseDir,
      cache,
      pubsub,
      importFn,
    });
    const wrappedSchema = wrapSchema({
      schema,
      transforms: [rateLimitTransform],
    });
    const query = /* GraphQL */ `
      {
        foo
      }
    `;
    const executeQuery = () =>
      execute({
        schema: wrappedSchema,
        document: parse(query),
        contextValue: {
          userId: '1',
        },
      });
    for (let i = 0; i < 5; i++) {
      const result = await executeQuery();

      expect(result).toEqual({
        data: {
          foo: 'bar',
        },
      });
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    const result = await executeQuery();

    expect(result.errors?.length).toBeFalsy();
    expect(result.data?.foo).toBe('bar');
  });
  it('should provide different tokens for different identifiers', async () => {
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => 'bar',
        },
      },
    });
    const rateLimitTransform = new RateLimitTransform({
      apiName,
      config: [
        {
          type: 'Query',
          field: 'foo',
          max: 1,
          ttl: 1000,
          identifier: '{context.userId}',
        },
      ],
      baseDir,
      cache,
      pubsub,
      importFn,
    });
    const wrappedSchema = wrapSchema({
      schema,
      transforms: [rateLimitTransform],
    });
    const query = /* GraphQL */ `
      {
        foo
      }
    `;

    for (let i = 0; i < 2; i++) {
      const executeQuery = () =>
        execute({
          schema: wrappedSchema,
          document: parse(query),
          contextValue: {
            userId: `User${i}`,
          },
        });
      const resultSuccessful = await executeQuery();

      expect(resultSuccessful).toEqual({
        data: {
          foo: 'bar',
        },
      });

      const resultFails = await execute({
        schema: wrappedSchema,
        document: parse(query),
        contextValue: {
          userId: `User${i}`,
        },
      });

      expect(resultFails.data?.foo).toBeNull();
      const firstError = resultFails.errors?.[0];
      expect(firstError.message).toBe(`Rate limit of "Query.foo" exceeded for "User${i}"`);
      expect(firstError.path).toEqual(['foo']);
    }

    expect.assertions(8);
  });
  it('should return other fields even if one of them has fails', async () => {
    const schema = makeExecutableSchema({
      typeDefs: /* GraphQL */ `
        type Query {
          foo: String
          bar: String
        }
      `,
      resolvers: {
        Query: {
          foo: () => 'FOO',
          bar: () => 'BAR',
        },
      },
    });

    const rateLimitTransform = new RateLimitTransform({
      apiName,
      config: [
        {
          type: 'Query',
          field: 'foo',
          max: 1,
          ttl: 1000,
          identifier: '{context.userId}',
        },
      ],
      baseDir,
      cache,
      pubsub,
      importFn,
    });

    const wrappedSchema = wrapSchema({
      schema,
      transforms: [rateLimitTransform],
    });

    const executeQuery = () =>
      execute({
        schema: wrappedSchema,
        document: parse(/* GraphQL */ `
          query TestQuery {
            foo
            bar
          }
        `),
        contextValue: {
          userId: 'MYUSER',
        },
      });

    await executeQuery();
    const result = await executeQuery();
    expect(result.data.bar).toBe('BAR');
    expect(result.errors?.[0]?.message).toBe(`Rate limit of "Query.foo" exceeded for "MYUSER"`);
  });
});
Example #14
Source File: replace-field.spec.ts    From graphql-mesh with MIT License 4 votes vote down vote up
describe('replace-field', () => {
  const mockQueryBooks = jest.fn().mockImplementation(() => ({ books: [{ title: 'abc' }, { title: 'def' }] }));
  const mockBooksApiResponseBooks = jest.fn().mockImplementation(() => [{ title: 'ghi' }, { title: 'lmn' }]);

  const schemaDefs = /* GraphQL */ `
    type Query {
      books: BooksApiResponse
    }

    type BooksApiResponse {
      """
      Retrieve a list of Books
      """
      books(maxResults: Int, orderBy: String): [Book]
    }

    type Book {
      title: String!
      author: Author!
      code: String
    }

    type Author {
      name: String!
      age: Int!
    }
  `;
  let cache: InMemoryLRUCache;
  let pubsub: MeshPubSub;
  const baseDir: string = undefined;

  beforeEach(() => {
    jest.clearAllMocks();
  });

  afterEach(() => {
    cache = new InMemoryLRUCache();
    pubsub = new PubSub();
  });

  it('should replace correctly field Type only', async () => {
    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.type.toString()).toBe('[Book]');
    expect(printSchema(transformedSchema)).toMatchSnapshot();
  });

  it('should replace correctly field Type with additional type definitions', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', author: { age: 50 } },
        { title: 'def', author: {} },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        typeDefs: /* GraphQL */ `
          type NewAuthor {
            age: String
          }
        `,
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            scope: 'hoistValue',
          },
          {
            from: {
              type: 'Author',
              field: 'age',
            },
            to: {
              type: 'NewAuthor',
              field: 'age',
            },
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect((transformedSchema.getType('Author') as GraphQLObjectType).getFields().age.type.toString()).toBe('String');
    expect(printSchema(transformedSchema)).toMatchSnapshot();

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            title
            author {
              age
            }
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.books).toEqual([
      { title: 'abc', author: { age: '50' } },
      { title: 'def', author: { age: null } },
    ]);
  });

  it('should replace correctly field with hoistValue and resolver function', async () => {
    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            scope: 'hoistValue',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.type.toString()).toBe('[Book]');

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            title
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.books).toEqual([{ title: 'abc' }, { title: 'def' }]);
  });

  it('should replace correctly field with hoistValue and default-field-resolver', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', author: { name: 'abra' } },
        { title: 'def', author: { name: 'cadabra' } },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Book',
              field: 'author',
            },
            to: {
              type: 'Author',
              field: 'name',
            },
            scope: 'hoistValue',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('Author')).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().author.type.toString()).toBe('String!');

    const result: any = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            books {
              title
              author
            }
          }
        }
      `),
    });
    expect(result.data.books.books).toEqual([
      { title: 'abc', author: 'abra' },
      { title: 'def', author: 'cadabra' },
    ]);
  });

  it('should replace correctly mtultiple fields with hoistValue and defined resolver function as well as default-field-resolver', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', author: { name: 'abra' } },
        { title: 'def', author: { name: 'cadabra' } },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            scope: 'hoistValue',
          },
          {
            from: {
              type: 'Book',
              field: 'author',
            },
            to: {
              type: 'Author',
              field: 'name',
            },
            scope: 'hoistValue',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect(transformedSchema.getType('Author')).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books.type.toString()).toBe('[Book]');
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().author.type.toString()).toBe('String!');
    expect(printSchema(transformedSchema)).toMatchSnapshot();

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            title
            author
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.books).toEqual([
      { title: 'abc', author: 'abra' },
      { title: 'def', author: 'cadabra' },
    ]);
  });

  it('should replace correctly field with composer wrapping resolver function', async () => {
    mockQueryBooks.mockReturnValueOnce({
      availableBooks: [{ title: 'abc' }, { title: 'def' }],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            composer: join(__dirname, './fixtures/composer.js#books'),
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            title
            isAvailable
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.books).toEqual([{ title: 'abc' }, { title: 'def' }]);
  });

  it('should replace correctly field with composer wrapping default-field-resolver', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', code: 'def' },
        { title: 'ghi', code: 'lmn' },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        typeDefs: /* GraphQL */ `
          type NewBook {
            code: String!
          }
        `,
        replacements: [
          {
            from: {
              type: 'Book',
              field: 'code',
            },
            to: {
              type: 'NewBook',
              field: 'code',
            },
            composer: join(__dirname, './fixtures/composer.js#code'),
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().code.type.toString()).toBe('String!');

    const result: any = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            books {
              title
              code
            }
          }
        }
      `),
    });
    expect(result.data.books.books).toEqual([
      { title: 'abc', code: 'store001_def' },
      { title: 'ghi', code: 'store001_lmn' },
    ]);
  });

  it('should replace correctly renamed field with Type only', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', author: { name: 'abra' } },
        { title: 'def', author: { name: 'cadabra' } },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        typeDefs: /* GraphQL */ `
          type NewAuthor {
            name: String
          }
        `,
        replacements: [
          {
            from: {
              type: 'Author',
              field: 'name',
            },
            to: {
              type: 'NewAuthor',
              field: 'name',
            },
            name: 'fullName',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect((transformedSchema.getType('Author') as GraphQLObjectType).getFields().name).toBeUndefined();
    expect((transformedSchema.getType('Author') as GraphQLObjectType).getFields().fullName.type.toString()).toBe(
      'String'
    );

    const result: any = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            books {
              title
              author {
                fullName
              }
            }
          }
        }
      `),
    });
    expect(result.data.books.books).toEqual([
      { title: 'abc', author: { fullName: 'abra' } },
      { title: 'def', author: { fullName: 'cadabra' } },
    ]);
  });

  it('should replace correctly renamed field with hoistValue and resolver function', async () => {
    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            scope: 'hoistValue',
            name: 'ourBooks',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().ourBooks.type.toString()).toBe(
      '[Book]'
    );

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          ourBooks {
            title
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.ourBooks).toEqual([{ title: 'abc' }, { title: 'def' }]);
  });

  it('should replace correctly renamed field with hoistValue and default-field-resolver', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', author: { name: 'abra' } },
        { title: 'def', author: { name: 'cadabra' } },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Book',
              field: 'author',
            },
            to: {
              type: 'Author',
              field: 'name',
            },
            name: 'authorName',
            scope: 'hoistValue',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('Author')).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().author).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().authorName.type.toString()).toBe(
      'String!'
    );

    const result: any = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            books {
              title
              authorName
            }
          }
        }
      `),
    });
    expect(result.data.books.books).toEqual([
      { title: 'abc', authorName: 'abra' },
      { title: 'def', authorName: 'cadabra' },
    ]);
  });

  it('should replace correctly renamed field with composer wrapping resolver function', async () => {
    mockQueryBooks.mockReturnValueOnce({
      availableBooks: [{ title: 'abc' }, { title: 'def' }],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            name: 'ourBooks',
            composer: join(__dirname, './fixtures/composer.js#books'),
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().books).toBeUndefined();
    expect((transformedSchema.getType('Query') as GraphQLObjectType).getFields().ourBooks.type.toString()).toBe(
      '[Book]'
    );

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          ourBooks {
            title
            isAvailable
          }
        }
      `),
    });
    expect(mockQueryBooks).toHaveBeenCalledTimes(1);
    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    expect(result.data.ourBooks).toEqual([{ title: 'abc' }, { title: 'def' }]);
  });

  it('should replace correctly renamed field with composer wrapping default-field-resolver', async () => {
    mockQueryBooks.mockReturnValueOnce({
      books: [
        { title: 'abc', code: undefined },
        { title: 'def', code: 'ghi' },
      ],
    });

    const transform = new ReplaceFieldTransform({
      config: {
        typeDefs: /* GraphQL */ `
          type NewBook {
            isAvailable: Boolean
          }
        `,
        replacements: [
          {
            from: {
              type: 'Book',
              field: 'code',
            },
            to: {
              type: 'NewBook',
              field: 'isAvailable',
            },
            name: 'isAvailable',
            composer: join(__dirname, './fixtures/composer.js#isAvailable'),
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: {
          books: mockQueryBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().code).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().isAvailable.type.toString()).toBe(
      'Boolean'
    );

    const result: any = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            books {
              title
              isAvailable
            }
          }
        }
      `),
    });
    expect(result.data.books.books).toEqual([
      { title: 'abc', isAvailable: false },
      { title: 'def', isAvailable: true },
    ]);
  });

  it('should replace correctly whole field config', async () => {
    const transform = new ReplaceFieldTransform({
      config: {
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
            scope: 'config',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
      resolvers: {
        Query: { books: mockQueryBooks },
        BooksApiResponse: {
          books: mockBooksApiResponseBooks,
        },
      },
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));
    const queryBooks = (transformedSchema.getType('Query') as GraphQLObjectType).getFields().books;

    expect(printSchema(transformedSchema)).toMatchSnapshot();

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect(queryBooks.type.toString()).toBe('[Book]');
    expect(queryBooks.description).toBe('Retrieve a list of Books');
    expect(queryBooks.args).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: 'maxResults' }),
        expect.objectContaining({ name: 'orderBy' }),
      ])
    );

    expect(mockBooksApiResponseBooks).not.toHaveBeenCalled();
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    await queryBooks.resolve();
    expect(mockBooksApiResponseBooks).toHaveBeenCalledTimes(1);

    const result = await execute({
      schema: transformedSchema,
      document: parse(/* GraphQL */ `
        {
          books {
            title
          }
        }
      `),
    });
    expect(mockQueryBooks).not.toHaveBeenCalled();
    expect(mockBooksApiResponseBooks).toHaveBeenCalledTimes(2);
    expect(result.data.books).toEqual([{ title: 'ghi' }, { title: 'lmn' }]);
  });

  it('applies multiple replaces to obtain a cleaner schema', () => {
    const transform = new ReplaceFieldTransform({
      config: {
        typeDefs: /* GraphQL */ `
          type NewBook {
            isAvailable: Boolean
          }
        `,
        replacements: [
          {
            from: {
              type: 'Query',
              field: 'books',
            },
            to: {
              type: 'BooksApiResponse',
              field: 'books',
            },
          },
          {
            from: {
              type: 'Book',
              field: 'code',
            },
            to: {
              type: 'NewBook',
              field: 'isAvailable',
            },
            name: 'isAvailable',
            composer: join(__dirname, './fixtures/composer.js#isAvailable'),
          },
          {
            from: {
              type: 'Book',
              field: 'author',
            },
            to: {
              type: 'Author',
              field: 'name',
            },
            scope: 'hoistValue',
          },
        ],
      },
      cache,
      pubsub,
      baseDir,
      apiName: '',
      importFn,
    });
    const schema = makeExecutableSchema({
      typeDefs: schemaDefs,
    });
    const transformedSchema = pruneSchema(transform.transformSchema(schema));

    expect(transformedSchema.getType('BooksApiResponse')).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().code).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().isAvailable.type.toString()).toBe(
      'Boolean'
    );
    expect(transformedSchema.getType('Author')).toBeUndefined();
    expect((transformedSchema.getType('Book') as GraphQLObjectType).getFields().author.type.toString()).toBe('String!');

    expect(printSchema(transformedSchema)).toMatchSnapshot();
  });
});
Example #15
Source File: index.ts    From hono with MIT License 4 votes vote down vote up
graphqlServer = (options: Options) => {
  const schema = options.schema
  const rootValue = options.rootValue
  const pretty = options.pretty ?? false
  const validationRules = options.validationRules ?? []
  // const showGraphiQL = options.graphiql ?? false

  return async (c: Context, next: Next) => {
    // GraphQL HTTP only supports GET and POST methods.
    if (c.req.method !== 'GET' && c.req.method !== 'POST') {
      return c.json(errorMessages(['GraphQL only supports GET and POST requests.']), 405, {
        Allow: 'GET, POST',
      })
    }

    let params: GraphQLParams
    try {
      params = await getGraphQLParams(c.req)
    } catch (e) {
      if (e instanceof Error) {
        console.error(`${e.stack || e.message}`)
        return c.json(errorMessages([e.message], [e]), 400)
      }
      throw e
    }

    const { query, variables, operationName } = params

    if (query == null) {
      return c.json(errorMessages(['Must provide query string.']), 400)
    }

    const schemaValidationErrors = validateSchema(schema)
    if (schemaValidationErrors.length > 0) {
      // Return 500: Internal Server Error if invalid schema.
      return c.json(
        errorMessages(['GraphQL schema validation error.'], schemaValidationErrors),
        500
      )
    }

    let documentAST: DocumentNode
    try {
      documentAST = parse(new Source(query, 'GraphQL request'))
    } catch (syntaxError: unknown) {
      // Return 400: Bad Request if any syntax errors errors exist.
      if (syntaxError instanceof Error) {
        console.error(`${syntaxError.stack || syntaxError.message}`)
        const e = new GraphQLError(syntaxError.message, {
          originalError: syntaxError,
        })
        return c.json(errorMessages(['GraphQL syntax error.'], [e]), 400)
      }
      throw syntaxError
    }

    // Validate AST, reporting any errors.
    const validationErrors = validate(schema, documentAST, [...specifiedRules, ...validationRules])

    if (validationErrors.length > 0) {
      // Return 400: Bad Request if any validation errors exist.
      return c.json(errorMessages(['GraphQL validation error.'], validationErrors), 400)
    }

    if (c.req.method === 'GET') {
      // Determine if this GET request will perform a non-query.
      const operationAST = getOperationAST(documentAST, operationName)
      if (operationAST && operationAST.operation !== 'query') {
        /*
        Now , does not support GraphiQL
        if (showGraphiQL) {
          //return respondWithGraphiQL(response, graphiqlOptions, params)
        }
        */

        // Otherwise, report a 405: Method Not Allowed error.
        return c.json(
          errorMessages([
            `Can only perform a ${operationAST.operation} operation from a POST request.`,
          ]),
          405,
          { Allow: 'POST' }
        )
      }
    }

    let result: FormattedExecutionResult

    try {
      result = await execute({
        schema,
        document: documentAST,
        rootValue,
        variableValues: variables,
        operationName: operationName,
      })
    } catch (contextError: unknown) {
      if (contextError instanceof Error) {
        console.error(`${contextError.stack || contextError.message}`)
        const e = new GraphQLError(contextError.message, {
          originalError: contextError,
          nodes: documentAST,
        })
        // Return 400: Bad Request if any execution context errors exist.
        return c.json(errorMessages(['GraphQL execution context error.'], [e]), 400)
      }
      throw contextError
    }

    if (!result.data) {
      if (result.errors) {
        return c.json(errorMessages([result.errors.toString()], result.errors), 500)
      }
    }

    /*
    Now, does not support GraphiQL
    if (showGraphiQL) {
    }
    */

    if (pretty) {
      const payload = JSON.stringify(result, null, pretty ? 2 : 0)
      return c.text(payload, 200, {
        'Content-Type': 'application/json',
      })
    } else {
      return c.json(result)
    }

    await next() // XXX
  }
}
Example #16
Source File: server.ts    From client-side-databases with Apache License 2.0 4 votes vote down vote up
export async function run() {
    const startData = exampleData;
    const state: {
        users: WithDeleted<User>[],
        messages: WithDeleted<Message>[]
    } = {
        users: startData.users.map(user => {
            const userWithDeleted = Object.assign({ deleted: false }, user);
            return userWithDeleted;
        }),
        messages: []
    };
    // to faster access all messages for a specific user,
    // we index the message by user-id here
    const messagesByUser: { [userId: string]: Message[] } = {};
    const getMessageArrayOfUser = (userId: string) => {
        if (!messagesByUser[userId]) {
            messagesByUser[userId] = [];
        }
        return messagesByUser[userId];
    };
    const addMessageToState = (message: Message) => {
        const messageWithDeleted = Object.assign({ deleted: false }, message);
        state.messages.push(messageWithDeleted);
        getMessageArrayOfUser(message.sender).push(message);
        getMessageArrayOfUser(message.reciever).push(message);
    };

    const app: Express = express();
    // cache options request
    app.options('*', (req, res, done) => {
        if (req.method === 'OPTIONS') {
            log('OPTIONS request');
            const headers = {
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Methods': 'POST, GET, PUT, OPTIONS, DELETE',
                'Access-Control-Allow-Credentials': 'false',
                'Access-Control-Max-Age': '86400',
                'Access-Control-Allow-Headers': 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'
            };
            res.writeHead(200, headers);
            res.end();
        } else {
            done();
        }
    });
    app.use(cors());


    const generatedSchema = graphQLSchemaFromRxSchema({
        users: {
            schema: RxUsersSchema,
            feedKeys: [
                'id',
                'createdAt'
            ],
            deletedFlag: 'deleted'
        },
        messages: {
            schema: RxMessagesSchema,
            feedKeys: [
                'id',
                'createdAt'
            ],
            deletedFlag: 'deleted'
        }
    });
    const graphQLSchema = generatedSchema.asString;
    console.log('GraphQL schema:');
    console.log(graphQLSchema);
    const schema = buildSchema(graphQLSchema);

    const pubsub = new PubSub();

    // The root provides a resolver function for each API endpoint
    const root = {
        info: () => 1,
        feedUsers: (args: any) => {
            log('## feedUsers()');
            console.dir(args);
            const users = state.users;
            const ret = filterForFeedResult(
                users,
                args.createdAt,
                args.id,
                args.limit
            );
            log('got ' + ret.length + ' users');
            return ret;
        },
        feedMessages: (args: any) => {
            log('## feedMessages()');
            const ret = filterForFeedResult(
                state.messages,
                args.createdAt,
                args.id,
                args.limit
            );
            console.dir(args);
            log('got ' + ret.length + ' messages');
            return ret;
        },
        setMessages: (args: {
            messages: Message[]
        }) => {
            const messages: Message[] = args.messages;
            messages.forEach(message => {
                log('## addMessage() ' + message.id + ' from ' + message.sender);

                // overwrite timestamp
                message.createdAt = unixInSeconds();

                // log(message);
                addMessageToState(message);

                pubsub.publish(
                    'changedMessages',
                    {
                        changedMessages: message
                    }
                );
            });
        },
        setUsers: (args: any) => {
            log('## registerUser()');
            const time = (new Date().getTime() / 1000);
            const user = {
                id: 'u' + time,
                name: args.name,
                createdAt: time,
                deleted: false
            };

            state.users.push(user);
            pubsub.publish(
                'changedUsers',
                {
                    changedUsers: user
                }
            );
            return user;
        },
        changedUsers: () => pubsub.asyncIterator('changedUsers'),
        changedMessages: () => pubsub.asyncIterator('changedMessages')
    };

    // add start-messages to state
    root.setMessages({
        messages: startData.messages
    });


    // server graphql-endpoint
    app.use(GRAPHQL_PATH, graphqlHTTP({
        schema,
        rootValue: root,
        graphiql: true,
    }));

    app.listen(GRAPHQL_PORT, () => {
        log('Started graphql-endpoint at http://localhost:' +
            GRAPHQL_PORT + GRAPHQL_PATH
        );
    });

    const appSubscription = express();
    appSubscription.use(cors);
    const serverSubscription = createServer(appSubscription);
    serverSubscription.listen(GRAPHQL_SUBSCRIPTION_PORT, () => {
        log(
            'Started graphql-subscription endpoint at http://localhost:' +
            GRAPHQL_SUBSCRIPTION_PORT + GRAPHQL_SUBSCRIPTION_PATH
        );
        const subServer = new SubscriptionServer(
            {
                execute,
                subscribe,
                schema,
                rootValue: root
            },
            {
                server: serverSubscription,
                path: GRAPHQL_SUBSCRIPTION_PATH,
            }
        );
        return subServer;
    });


    // comment this in for testing of the subscriptions
    /*
    setInterval(() => {
        const flag = new Date().getTime();
        pubsub.publish(
            'humanChanged',
            {
                humanChanged: {
                    id: 'foobar-' + flag,
                    name: 'name-' + flag
                }
            }
        );
        console.log('published humanChanged ' + flag);
    }, 1000);*/
}