text: /* eslint-disable no-template-curly-in-string */
  const fs = require('fs');
  const chalk = require('chalk');
  const Serverless = require('serverless/lib/Serverless');
  const ServerlessAppsyncPlugin = require('./src');
  const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider.js');
  
  let serverless;
  let plugin;
  let config;
  
  // 2020-12-09T16:24:22+00:00
  jest.spyOn(Date, 'now').mockImplementation(() => 1607531062000);
  
  jest.mock('fs');
  jest.spyOn(fs, 'readFileSync').mockImplementation(path => `Content: ${path}`);
  
  beforeEach(() => {
    const cli = {
      log: jest.fn(),
      consoleLog: jest.fn(),
    };
    serverless = new Serverless();
    serverless.cli = cli;
  
    const options = {
      stage: 'dev',
      region: 'us-east-1',
    };
    serverless.setProvider('aws', new AwsProvider(serverless, options));
    plugin = new ServerlessAppsyncPlugin(serverless, {});
    config = {
      additionalAuthenticationProviders: [],
      name: 'api',
      dataSources: [],
      region: 'us-east-1',
      isSingleConfig: true,
      mappingTemplatesLocation: 'mapping-templates',
      defaultMappingTemplates: {},
      substitutions: {},
      allowHashDescription: false,
      xrayEnabled: false,
    };
  });
  
  describe('appsync display', () => {
    test('appsync api keys are displayed', () => {
      plugin.gatheredData.apiKeys = ['dummy-api-key-1', 'dummy-api-key-2'];
  
      let expectedMessage = '';
      expectedMessage += `${chalk.yellow('appsync api keys:')}`;
      expectedMessage += '\n  dummy-api-key-1';
      expectedMessage += '\n  dummy-api-key-2';
  
      expect(plugin.displayApiKeys()).toEqual(expectedMessage);
    });
  
    test('appsync api keys are hidden when `--conceal` is given', () => {
      plugin.options.conceal = true;
      plugin.gatheredData.apiKeys = ['dummy-api-key-1', 'dummy-api-key-2'];
  
      let expectedMessage = '';
      expectedMessage += `${chalk.yellow('appsync api keys:')}`;
      expectedMessage += '\n  *** (concealed)';
      expectedMessage += '\n  *** (concealed)';
  
      expect(plugin.displayApiKeys()).toEqual(expectedMessage);
    });
  });
  
  describe('appsync config', () => {
    test('appsync cloudwatch role is autogenerated', () => {
      Object.assign(
        config,
        {
          logConfig: {
            level: 'ALL',
          },
        },
      );
  
      const role = plugin.getCloudWatchLogsRole(config);
      expect(role).toMatchSnapshot();
    });
  
    test('appsync cloudwatch role is not autogenerated when Logs not enabled', () => {
      const role = plugin.getCloudWatchLogsRole(config);
      expect(role).toEqual({});
    });
  
    test('appsync cloudwatch role is not autogenerated when loggingRoleArn is specified', () => {
      Object.assign(
        config,
        {
          logConfig: {
            level: 'ALL',
            loggingRoleArn: 'arn:aws:iam::123456789012:role/service-role/appsyncRole',
          },
        },
      );
      const role = plugin.getCloudWatchLogsRole(config);
      expect(role).toEqual({});
    });
  
    test('appsync cloudwatch log group is not created when are not logs enabled', () => {
      const resources = plugin.getGraphQlApiEndpointResource(config);
      expect(resources.GraphQlApiLogGroup).toBeUndefined();
    });
  
    test('appsync cloudwatch log group is created when logs enabled', () => {
      serverless.service.provider.logRetentionInDays = 14;
      const resources = plugin.getGraphQlApiEndpointResource({
        ...config,
        logConfig: {
          level: 'ALL',
        },
      });
  
      expect(resources.GraphQlApiLogGroup).toMatchSnapshot();
    });
  
    test('Schema is transformed into App Sync compatible syntax', () => {
      Object.assign(
        config,
        {
          schema: `
            """A valid schema"""
            type Thing implements One & Another {
              hello: ID!
            }
            """A valid enum"""
            enum Method {
              DELETE # Delete something
              GET # Get something
            }
          `,
        },
      );
      const schema = plugin.getGraphQLSchemaResource(config);
      expect(schema).toMatchSnapshot();
    });
  
    test('Schema allow hash comments when using allowHashDescription true in config', () => {
      Object.assign(
        config,
        {
          schema: `
            """A valid schema"""
            type Thing implements One & Another {
              hello: ID!
            }
            # test enum
            enum Method {
              DELETE
              GET
            }
          `,
        },
      );
      config.allowHashDescription = true;
      const schema = plugin.getGraphQLSchemaResource(config);
      expect(schema).toMatchSnapshot();
    });
  
    test('Datasource generates lambdaFunctionArn from functionName', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AWS_LAMBDA',
              name: 'lambdaSource',
              description: 'lambdaSource Desc',
              config: {
                functionName: 'myFunc',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myLambdaRole',
              },
            },
          ],
        },
      );
  
      const dataSources = plugin.getDataSourceResources(config);
      expect(dataSources).toMatchSnapshot();
    });
  
    test('Datasource generates lambdaFunctionArn from functionName and functionAlias', () => {
      Object.assign(config, {
        dataSources: [
          {
            type: 'AWS_LAMBDA',
            name: 'lambdaSource',
            description: 'lambdaSource Desc',
            config: {
              functionName: 'myFunc',
              functionAlias: 'myAlias',
              serviceRoleArn:
                'arn:aws:iam::123456789012:role/service-role/myLambdaRole',
            },
          },
        ],
      });
  
      const dataSources = plugin.getDataSourceResources(config);
      expect(dataSources).toMatchSnapshot();
    });
  
    test('Datasource uses lambdaFunctionArn when provided', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AWS_LAMBDA',
              name: 'lambdaSource',
              description: 'lambdaSource Desc',
              config: {
                functionName: 'myDummyFunc',
                lambdaFunctionArn: { 'Fn::GetAtt': ['MyFuncLambdaFunction', 'Arn'] },
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myLambdaRole',
              },
            },
          ],
        },
      );
  
      const dataSources = plugin.getDataSourceResources(config);
      expect(dataSources).toMatchSnapshot();
    });
  
    test('Datasource generates HTTP authorization when authorizationConfig provided', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'HTTP',
              name: 'HTTPSource',
              description: 'HTTPSource Desc',
              config: {
                endpoint: 'https://www.example.com/api',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myHTTPRole',
                authorizationConfig: {
                  authorizationType: 'AWS_IAM',
                  awsIamConfig: {
                    signingRegion: 'us-east-1',
                    signingServiceName: 'ses',
                  },
                },
              },
            },
          ],
        },
      );
  
      const dataSources = plugin.getDataSourceResources(config);
      expect(dataSources).toMatchSnapshot();
    });
  
    test("HTTP Datasource defaults the IAM role signing region to the stack's region", () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'HTTP',
              name: 'HTTPSource',
              description: 'HTTPSource Desc',
              config: {
                endpoint: 'https://www.example.com/api',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myHTTPRole',
                authorizationConfig: {
                  authorizationType: 'AWS_IAM',
                  awsIamConfig: {
                    signingServiceName: 'ses',
                  },
                },
              },
            },
          ],
        },
      );
  
      const dataSources = plugin.getDataSourceResources(config);
      expect(dataSources).toMatchSnapshot();
    });
  
    test('appsync API is not created when ApiId is used', () => {
      Object.assign(
        config,
        {
          apiId: 'testApiId',
        },
      );
      const resources = plugin.getGraphQlApiEndpointResource(config);
      expect(resources).toBeNull();
    });
  
    test('Existing ApiId is used for all resources if provided', () => {
      const apiConfig = {
        ...config,
        apiId: 'testApiId',
        authenticationType: 'API_KEY',
        caching: {
          behavior: 'FULL_REQUEST_CACHING',
        },
        schema: `
            """A valid schema"""
            type Thing implements One & Another {
              hello: ID!
            }
            """A valid enum"""
            enum Method {
              DELETE # Delete something
              GET # Get something
            }
          `,
        dataSources: [
          {
            type: 'AMAZON_DYNAMODB',
            name: 'DynamoDbSource',
            config: {
              tableName: 'myTable',
              serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
              region: 'us-east-1',
            },
          },
        ],
        functionConfigurationsLocation: 'mapping-templates',
        functionConfigurations: [
          {
            dataSource: 'ds',
            name: 'pipeline',
            request: 'request.vtl',
            response: 'response.vtl',
          },
        ],
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            caching: true,
          },
        ],
      };
      const keyResources = plugin.getApiKeyResources(apiConfig);
      const cachingResources = plugin.getApiCachingResource(apiConfig);
      const schemaResources = plugin.getGraphQLSchemaResource(apiConfig);
      const dataSourceResources = plugin.getDataSourceResources(apiConfig);
      const functionConfigResources = plugin.getFunctionConfigurationResources(apiConfig);
      const resolverResources = plugin.getResolverResources(apiConfig);
      const outputs = plugin.getGraphQlApiOutputs(apiConfig);
  
      expect(keyResources).toHaveProperty('GraphQlApiKeyDefault.Properties.ApiId', apiConfig.apiId);
      expect(cachingResources).toHaveProperty('GraphQlCaching.Properties.ApiId', apiConfig.apiId);
      expect(schemaResources).toHaveProperty('GraphQlSchema.Properties.ApiId', apiConfig.apiId);
      expect(dataSourceResources).toHaveProperty('GraphQlDsDynamoDbSource.Properties.ApiId', apiConfig.apiId);
      expect(functionConfigResources).toHaveProperty('GraphQlFunctionConfigurationpipeline.Properties.ApiId', apiConfig.apiId);
      expect(resolverResources).toHaveProperty('GraphQlResolverQueryfield.Properties.ApiId', apiConfig.apiId);
      expect(outputs).toEqual({
        GraphQlApiId: {
          Value: apiConfig.apiId,
        },
      });
    });
  
    test('AMAZON_COGNITO_USER_POOLS config created', () => {
      const resources = plugin.getGraphQlApiEndpointResource({
        ...config,
        authenticationType: 'AMAZON_COGNITO_USER_POOLS',
        userPoolConfig: {
          defaultAction: 'ALLOW',
          awsRegion: 'eu-central-1',
          userPoolId: 'userPoolGenerateId',
          appIdClientRegex: 'appIdClientRegex',
        },
      });
      expect(resources.GraphQlApi.Properties.AuthenticationType).toBe('AMAZON_COGNITO_USER_POOLS');
      expect(resources.GraphQlApi.Properties.UserPoolConfig).toEqual({
        DefaultAction: 'ALLOW',
        AwsRegion: 'eu-central-1',
        UserPoolId: 'userPoolGenerateId',
        AppIdClientRegex: 'appIdClientRegex',
      });
    });
  
    test('xrayEnabled config created', () => {
      const resources = plugin.getGraphQlApiEndpointResource({
        ...config,
        xrayEnabled: true,
      });
      expect(resources.GraphQlApi.Properties.XrayEnabled).toEqual(true);
    });
  
    test('Tags config created', () => {
      const resources = plugin.getGraphQlApiEndpointResource({
        ...config,
        tags: {
          testKey: 'testValue',
        },
      });
      expect(resources.GraphQlApi.Properties.Tags).toEqual(expect.arrayContaining([{
        Key: 'testKey',
        Value: 'testValue',
      }]));
    });
  
    test('OPENID_CONNECT config created', () => {
      const resources = plugin.getGraphQlApiEndpointResource({
        ...config,
        authenticationType: 'OPENID_CONNECT',
        openIdConnectConfig: {
          issuer: 'issuer',
          clientId: 'clientId',
          iatTTL: 1000,
          authTTL: 1000,
        },
      });
      expect(resources.GraphQlApi.Properties.AuthenticationType).toBe('OPENID_CONNECT');
      expect(resources.GraphQlApi.Properties.OpenIDConnectConfig).toEqual({
        Issuer: 'issuer',
        ClientId: 'clientId',
        IatTTL: 1000,
        AuthTTL: 1000,
      });
    });
  
    test('Deault API_KEY config created', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
      };
      const apiResources = plugin.getGraphQlApiEndpointResource(apiConfig);
      const keyResources = plugin.getApiKeyResources(apiConfig);
      const outputs = plugin.getApiKeyOutputs(apiConfig);
  
      expect(apiResources.GraphQlApi.Properties.AuthenticationType).toBe('API_KEY');
      expect(keyResources).toMatchSnapshot();
      expect(outputs).toEqual({
        GraphQlApiKeyDefault: {
          Value: { 'Fn::GetAtt': ['GraphQlApiKeyDefault', 'ApiKey'] },
        },
      });
    });
  
    test('Additional authentication providers created', () => {
      const apiConfig = {
        ...config,
        additionalAuthenticationProviders: [
          {
            authenticationType: 'AMAZON_COGNITO_USER_POOLS',
            userPoolConfig: {
              awsRegion: 'eu-central-1',
              userPoolId: 'userPoolGenerateId',
              appIdClientRegex: 'appIdClientRegex',
            },
          },
          {
            authenticationType: 'OPENID_CONNECT',
            openIdConnectConfig: {
              issuer: 'issuer',
              clientId: 'clientId',
              iatTTL: 1000,
              authTTL: 1000,
            },
          },
          {
            authenticationType: 'API_KEY',
          },
          {
            authenticationType: 'AWS_IAM',
          },
        ],
      };
  
      const apiResources = plugin.getGraphQlApiEndpointResource(apiConfig);
      const keyResources = plugin.getApiKeyResources(apiConfig);
      const outputs = plugin.getApiKeyOutputs(apiConfig);
  
      expect(apiResources.GraphQlApi.Properties.AdditionalAuthenticationProviders).toMatchSnapshot();
      expect(keyResources).toHaveProperty('GraphQlApiKeyDefault');
      expect(keyResources.GraphQlApiKeyDefault).toMatchSnapshot();
      expect(outputs).toEqual({
        GraphQlApiKeyDefault: {
          Value: { 'Fn::GetAtt': ['GraphQlApiKeyDefault', 'ApiKey'] },
        },
      });
    });
  });
  
  describe('Caching', () => {
    test('Disabled', () => {
      const apiResources = plugin.getApiCachingResource(config);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Minimum configuration', () => {
      const apiConfig = {
        ...config,
        caching: {
          behavior: 'FULL_REQUEST_CACHING',
        },
      };
  
      const apiResources = plugin.getApiCachingResource(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Custom configuration', () => {
      const apiConfig = {
        ...config,
        caching: {
          behavior: 'FULL_REQUEST_CACHING',
          atRestEncryption: true,
          transitEncryption: true,
          ttl: 500,
          type: 'T2_MEDIUM',
        },
      };
  
      const apiResources = plugin.getApiCachingResource(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Resolver min config', () => {
      const apiConfig = {
        ...config,
        caching: {
          behavior: 'PER_RESOLVER_CACHING',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            caching: true,
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Resolver custom config', () => {
      const apiConfig = {
        ...config,
        caching: {
          behavior: 'PER_RESOLVER_CACHING',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            caching: {
              ttl: 1000,
              keys: [
                '$context.identity.sub',
                '$context.arguments.id',
              ],
            },
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Resolver with fallback', () => {
      const apiConfig = {
        ...config,
        caching: {
          behavior: 'PER_RESOLVER_CACHING',
          ttl: 2000,
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            caching: {
              keys: [
                '$context.identity.sub',
                '$context.arguments.id',
              ],
            },
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  });
  
  describe('iamRoleStatements', () => {
    test('getDataSourceIamRolesResouces with Specific statements', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AWS_LAMBDA',
              name: 'LambdaSource',
              description: 'My Lambda Source',
              config: {
                lambdaFunctionArn: { 'Fn::GetAtt': ['MyTestFunctionLambdaFunction', 'Arn'] },
                iamRoleStatements: [
                  {
                    Effect: 'Allow',
                    Action: ['lambda:invokeFunction'],
                    Resource: [
                      'arn:aws:lambda:us-east-1:123456789012:function:myTestFunction',
                    ],
                  },
                ],
              },
            },
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              description: 'My DynamoDb Source',
              config: {
                tableName: 'myTable',
                iamRoleStatements: [
                  {
                    Effect: 'Allow',
                    Action: [
                      'dynamodb:Query',
                      'dynamodb:Scan',
                    ],
                    Resource: [
                      'arn:aws:dynamodb:us-east-1:123456789012:table/myTable',
                      'arn:aws:dynamodb:us-east-1:123456789012:table/myTable/*',
                    ],
                  },
                ],
              },
            },
            {
              type: 'RELATIONAL_DATABASE',
              name: 'RelationalDatabaseSource',
              description: 'Relational database Source',
              config: {
                region: 'us-east-1',
                dbClusterIdentifier: 'aurora-cluster-id',
                databaseName: 'myDatabaseName',
                schema: 'mySchema',
                awsSecretStoreArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-cluster-secret-XuztPa',
                iamRoleStatements: [
                  {
                    Action: [
                      'rds-data:DeleteItems',
                      'rds-data:ExecuteSql',
                      'rds-data:ExecuteStatement',
                      'rds-data:GetItems',
                      'rds-data:InsertItems',
                      'rds-data:UpdateItems',
                    ],
                    Resource: [
                      'arn:aws:rds:us-east-1:123456789012:cluster:aurora-cluster-id',
                      'arn:aws:rds:us-east-1:123456789012:cluster:aurora-cluster-id:*',
                    ],
                    Effect: 'Allow',
                  },
                  {
                    Action: [
                      'secretsmanager:GetSecretValue',
                    ],
                    Resource: [
                      'arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-cluster-secret-XuztPa',
                      'arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-cluster-secret-XuztPa:*',
                    ],
                    Effect: 'Allow',
                  },
                ],
              },
            },
            {
              type: 'AMAZON_ELASTICSEARCH',
              name: 'ElasticSearchSource',
              description: 'My ElasticSearch Source',
              config: {
                region: 'us-east-1',
                endpoint: 'https://search-my-domain-abcdefghijklmnop.us-east-1.es.amazonaws.com',
                iamRoleStatements: [
                  {
                    Effect: 'Allow',
                    Action: [
                      'ES:ESHttpGet',
                    ],
                    Resource: [
                      'arn:aws:es:us-east-1:123456789012:domain/my-domain',
                    ],
                  },
                ],
              },
            },
            {
              type: 'AMAZON_ELASTICSEARCH',
              name: 'ElasticSearchSource2',
              description: 'other ES Source',
              config: {
                region: 'us-east-1',
                domain: {
                  Ref: 'ESDomain',
                },
                iamRoleStatements: [
                  {
                    Effect: 'Allow',
                    Action: [
                      'ES:ESHttpGet',
                    ],
                    Resource: [
                      {
                        'Fn::GetAtt': [
                          'EsResource',
                          'Arn',
                        ],
                      },
                    ],
                  },
                ],
              },
            },
          ],
        },
      );
  
      const roles = plugin.getDataSourceIamRolesResouces(config);
      expect(roles).toMatchSnapshot();
    });
  
  
    test('getDataSourceIamRolesResouces with Default generated statements', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AWS_LAMBDA',
              name: 'LambdaSource',
              description: 'My Lambda Source',
              config: {
                lambdaFunctionArn: { 'Fn::GetAtt': ['MyTestFunctionLambdaFunction', 'Arn'] },
              },
            },
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              description: 'My DynamoDb Source',
              config: {
                tableName: 'myTable',
                region: 'us-east-1',
              },
            },
            {
              type: 'RELATIONAL_DATABASE',
              name: 'RelationalDbSource',
              description: 'Relational Db Source',
              config: {
                region: 'us-east-1',
                dbClusterIdentifier: 'aurora-cluster-id',
                databaseName: 'myDatabaseName',
                schema: 'mySchema',
                awsSecretStoreArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-cluster-secret-XuztPa',
              },
            },
            {
              type: 'AMAZON_ELASTICSEARCH',
              name: 'ElasticSearchSource',
              description: 'My ElasticSearch Source',
              config: {
                region: 'us-east-1',
                endpoint: 'https://search-my-domain-abcdefghijklmnop.us-east-1.es.amazonaws.com',
              },
            },
            {
              type: 'AMAZON_ELASTICSEARCH',
              name: 'ElasticSearchSource2',
              description: 'other ES Source',
              config: {
                region: 'us-east-1',
                domain: {
                  Ref: 'ESDomain',
                },
              },
            },
          ],
        },
      );
  
      const roles = plugin.getDataSourceIamRolesResouces(config);
      expect(roles).toMatchSnapshot();
    });
  
    test('using a serviceRoleArn should not generate any new Role', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AWS_LAMBDA',
              name: 'LambdaSource',
              description: 'My Lambda Source',
              config: {
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myLambdaRole',
                lambdaFunctionArn: { 'Fn::GetAtt': ['MyTestFunctionLambdaFunction', 'Arn'] },
              },
            },
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              description: 'My DynamoDb Source',
              config: {
                tableName: 'myTable',
                region: 'us-east-1',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
              },
            },
            {
              type: 'RELATIONAL_DATABASE',
              name: 'RelationalDbSource',
              description: 'Relational Db Source',
              config: {
                region: 'us-east-1',
                dbClusterIdentifier: 'aurora-cluster-id',
                databaseName: 'myDatabaseName',
                schema: 'mySchema',
                awsSecretStoreArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:rds-cluster-secret-XuztPa',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myRelationalDbRole',
              },
            },
            {
              type: 'AMAZON_ELASTICSEARCH',
              name: 'ElasticSearchSource',
              description: 'My ElasticSearch Source',
              config: {
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myEsRole',
                region: 'us-east-1',
                endpoint: 'https://search-my-domain-abcdefghijklmnop.us-east-1.es.amazonaws.com',
              },
            },
            {
              type: 'HTTP',
              name: 'HTTPSource',
              description: 'My HTTP Source',
              config: {
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myHTTPRole',
                region: 'us-east-1',
                endpoint: 'https://www.example.com/api',
              },
            },
          ],
        },
      );
  
      const roles = plugin.getDataSourceIamRolesResouces(config);
      expect(roles).toEqual({});
    });
  
    test('Datasources of type NONE or HTTP should not generate any default role', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'NONE',
              name: 'NoneSource',
            },
            {
              type: 'HTTP',
              name: 'HttpSource',
              config: {
                endpoint: 'https://www.example.com/api',
              },
            },
          ],
        },
      );
  
      const roles = plugin.getDataSourceIamRolesResouces(config);
      expect(roles).toEqual({});
    });
  
    describe('template substitutions', () => {
      test('Templates with substitutions should be transformed into Fn::Join with Fn::Sub objects', () => {
        const template = '#set($partitionKey = "${globalPK}")\n' +
          '{\n' +
          '"version" : "2018-05-29",\n' +
          '"operation" : "GetItem",\n' +
          '"key" : {\n' +
          '"partitionKey": { "S": "${globalPK}" },\n' +
          '"sortKey": { "S": "${globalSK}" },\n' +
          '}\n' +
          '}';
  
        const variables =
        {
          globalPK: 'PK',
          globalSK: 'SK',
        };
  
        const transformedTemplate = plugin.substituteGlobalTemplateVariables(template, variables);
        expect(transformedTemplate).toMatchSnapshot();
      });
    });
  
    describe('individual template substitutions', () => {
      test('Substitutions for individual template should override global substitutions.', () => {
        const template = '#set($partitionKey = "${globalPK}")\n' +
          '{\n' +
          '"version" : "2018-05-29",\n' +
          '"operation" : "GetItem",\n' +
          '"key" : {\n' +
          '"partitionKey": { "S": "${globalPK}" },\n' +
          '"sortKey": { "S": "${globalSK}" },\n' +
          '}\n' +
          '}';
  
        const configuration =
        {
          substitutions:
          {
            globalPK: 'WrongValue',
            globalSK: 'WrongValue',
          },
        };
  
        const individualSubstitutions =
        {
          globalPK: 'PK',
          globalSK: 'SK',
        };
  
        const transformedTemplate = plugin.processTemplate(
          template,
          configuration,
          individualSubstitutions,
        );
        expect(transformedTemplate).toMatchSnapshot();
      });
    });
  });
  
  describe('Delta sync', () => {
    test('not versioned', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              config: {
                tableName: 'myTable',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
                region: 'us-east-1',
              },
            },
          ],
        },
      );
  
      const result = plugin.getDataSourceResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('with default TTL values', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              config: {
                tableName: 'myTable',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
                region: 'us-east-1',
                versioned: true,
                deltaSyncConfig: {
                  deltaSyncTableName: 'myDeltaSynTable',
                },
              },
            },
          ],
        },
      );
  
      const result = plugin.getDataSourceResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('custom config works', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              config: {
                tableName: 'myTable',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
                region: 'us-east-1',
                versioned: true,
                deltaSyncConfig: {
                  deltaSyncTableName: 'myDeltaSynTable',
                  baseTableTTL: 10,
                  deltaSyncTableTTL: 30,
                },
              },
            },
          ],
        },
      );
  
      const result = plugin.getDataSourceResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('missing tableName throws an error', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              config: {
                tableName: 'myTable',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
                region: 'us-east-1',
                versioned: true,
                deltaSyncConfig: {
                },
              },
            },
          ],
        },
      );
  
      expect(() => {
        plugin.getDataSourceResources(config);
      }).toThrowErrorMatchingSnapshot();
    });
  
    test('missing config throws an error', () => {
      Object.assign(
        config,
        {
          dataSources: [
            {
              type: 'AMAZON_DYNAMODB',
              name: 'DynamoDbSource',
              config: {
                tableName: 'myTable',
                serviceRoleArn: 'arn:aws:iam::123456789012:role/service-role/myDynamoDbRole',
                region: 'us-east-1',
                versioned: true,
              },
            },
          ],
        },
      );
  
      expect(() => {
        plugin.getDataSourceResources(config);
      }).toThrowErrorMatchingSnapshot();
    });
  });
  
  describe('Templates', () => {
    test('Should use defaultMappingTemplates', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: 'request.default.vtl',
          response: 'response.default.vtl',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Should use no templates when defaultMappingTemplates are false', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: false,
          response: false,
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('ResponseMappingTemplate');
    });
  
    test('Should use specified template when defaultMappingTemplates are false', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: false,
          response: false,
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: 'my.request.template.tpl',
            response: 'my.response.template.tpl',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Should use specified templates', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: 'request.default.vtl',
          response: 'response.default.vtl',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: 'my.request.template.tpl',
            response: 'my.response.template.tpl',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Should use automatic template when no default and no custom', () => {
      const apiConfig = {
        ...config,
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Should use automatic template when null, even if there is a default', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: 'request.default.vtl',
          response: 'response.default.vtl',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: null,
            response: null,
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources).toMatchSnapshot();
    });
  
    test('Should use No template when false, even if there is a default', () => {
      const apiConfig = {
        ...config,
        defaultMappingTemplates: {
          request: 'request.default.vtl',
          response: 'response.default.vtl',
        },
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: false,
            response: false,
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('ResponseMappingTemplate');
    });
  
    test('Resolver with template', () => {
      const apiConfig = {
        ...config,
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: 'request.vtl',
            response: 'response.vtl',
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .toHaveProperty('ResponseMappingTemplate');
    });
  
    test('Resolver without template', () => {
      const apiConfig = {
        ...config,
        mappingTemplates: [
          {
            dataSource: 'ds',
            type: 'Query',
            field: 'field',
            request: false,
            response: false,
          },
        ],
      };
  
      const apiResources = plugin.getResolverResources(apiConfig);
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlResolverQueryfield.Properties)
        .not.toHaveProperty('ResponseMappingTemplate');
    });
  
    test('Pileline Resolver with template', () => {
      const apiConfig = {
        ...config,
        functionConfigurationsLocation: 'mapping-templates',
        functionConfigurations: [
          {
            dataSource: 'ds',
            name: 'pipeline',
            request: 'request.vtl',
            response: 'response.vtl',
          },
        ],
      };
  
      const apiResources = plugin.getFunctionConfigurationResources(apiConfig);
      expect(apiResources.GraphQlFunctionConfigurationpipeline.Properties)
        .toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlFunctionConfigurationpipeline.Properties)
        .toHaveProperty('ResponseMappingTemplate');
    });
  
    test('Pileline Resolver without template', () => {
      const apiConfig = {
        ...config,
        functionConfigurationsLocation: 'mapping-templates',
        functionConfigurations: [
          {
            dataSource: 'ds',
            name: 'pipeline',
            request: false,
            response: false,
          },
        ],
      };
      const apiResources = plugin.getFunctionConfigurationResources(apiConfig);
      expect(apiResources.GraphQlFunctionConfigurationpipeline.Properties)
        .not.toHaveProperty('RequestMappingTemplate');
      expect(apiResources.GraphQlFunctionConfigurationpipeline.Properties)
        .not.toHaveProperty('ResponseMappingTemplate');
    });
  });
  
  
  describe('SyncConfig', () => {
    test('Uses no sync config', () => {
      Object.assign(
        config,
        {
          mappingTemplates: [
            {
              dataSource: 'DynamoDbSource',
              type: 'Query',
              field: 'field',
              caching: true,
            },
          ],
        },
      );
  
      const result = plugin.getResolverResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('Uses default', () => {
      Object.assign(
        config,
        {
          mappingTemplates: [
            {
              dataSource: 'DynamoDbSource',
              type: 'Query',
              field: 'field',
              caching: true,
              sync: true,
            },
          ],
        },
      );
  
      const result = plugin.getResolverResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('Uses advanced config', () => {
      Object.assign(
        config,
        {
          mappingTemplates: [
            {
              dataSource: 'DynamoDbSource',
              type: 'Query',
              field: 'field',
              caching: true,
              sync: {
                conflictDetection: 'VERSION',
                conflictHandler: 'AUTOMERGE',
              },
            },
          ],
        },
      );
  
      const result = plugin.getResolverResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('Uses lambda config', () => {
      Object.assign(
        config,
        {
          mappingTemplates: [
            {
              dataSource: 'DynamoDbSource',
              type: 'Query',
              field: 'field',
              caching: true,
              sync: {
                conflictDetection: 'VERSION',
                conflictHandler: 'LAMBDA',
                functionName: 'syncLambda',
              },
  
            },
          ],
        },
      );
  
      const result = plugin.getResolverResources(config);
      expect(result).toMatchSnapshot();
    });
  
    test('Uses lambda config with ARN', () => {
      Object.assign(
        config,
        {
          mappingTemplates: [
            {
              dataSource: 'DynamoDbSource',
              type: 'Query',
              field: 'field',
              caching: true,
              sync: {
                conflictDetection: 'VERSION',
                conflictHandler: 'LAMBDA',
                lambdaFunctionArn: 'arn:aws:lambda:us-east-1:123456789:syncLambda',
              },
            },
          ],
        },
      );
  
      const result = plugin.getResolverResources(config);
      expect(result).toMatchSnapshot();
    });
  });
  
  describe('api keys', () => {
    it('should generate several keys', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'Default',
            description: 'Default Key',
            expiresAfter: '30d',
          },
          {
            description: 'Unnamed Key1',
            expiresAfter: '1d',
          },
          {
            description: 'Unnamed Key2',
            apiKeyId: 'da2-7hfy4mjkdmh64lp0une7yht765',
          },
          {
            name: 'John',
            description: 'John\'s key',
            expiresAt: '2021-03-09T16:00:00+00:00',
          },
          {
            name: 'Jane',
            expiresAfter: '1y',
          },
          'InlineKey',
        ],
      };
      const keyResources = plugin.getApiKeyResources(apiConfig);
      const outputs = plugin.getApiKeyOutputs(apiConfig);
  
      expect(keyResources).toMatchSnapshot();
      expect(outputs).toMatchSnapshot();
    });
  
    it('should fail with invalid duration', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'MyKey',
            expiresAfter: 'foobar',
          },
        ],
      };
      expect(() => plugin.getApiKeyResources(apiConfig)).toThrowErrorMatchingSnapshot();
    });
  
    it('should fail with too long duration', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'MyKey',
            expiresAfter: '2y',
          },
        ],
      };
      expect(() => plugin.getApiKeyResources(apiConfig)).toThrowErrorMatchingSnapshot();
    });
  
    it('should fail with too short duration', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'MyKey',
            expiresAfter: '1h',
          },
        ],
      };
      expect(() => plugin.getApiKeyResources(apiConfig)).toThrowErrorMatchingSnapshot();
    });
  
    it('should fail with a date > 1 year', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'MyKey',
            expiresAt: '2021-12-09T17:00:00+00:00',
          },
        ],
      };
      expect(() => plugin.getApiKeyResources(apiConfig)).toThrowErrorMatchingSnapshot();
    });
  
    it('should fail with with a date < 1 day', () => {
      const apiConfig = {
        ...config,
        authenticationType: 'API_KEY',
        apiKeys: [
          {
            name: 'MyKey',
            expiresAt: '2020-12-10T16:00:00+00:00',
          },
        ],
      };
      expect(() => plugin.getApiKeyResources(apiConfig)).toThrowErrorMatchingSnapshot();
    });
  });
  
  describe('WAF', () => {
    it('should generate WAF config', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: [
            {
              action: 'Allow',
              name: 'UsOnly',
              priority: 0,
              statement: {
                notStatement: {
                  statement: {
                    geoMatchStatement: {
                      countryCodes: ['US'],
                    },
                  },
                },
              },
              visibilityConfig: {
                cloudWatchMetricsEnabled: true,
                metricName: 'usOnlyWafRule',
                sampledRequestsEnabled: true,
              },
            },
          ],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should disable WAF', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          enabled: false,
          rules: [
            {
              action: 'Allow',
              name: 'MyRule',
              statement: {},
            },
          ],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toEqual({});
    });
  
    it('should generate API key WAF config', () => {
      const apiConfig = {
        ...config,
        wafConfig: { enabled: true },
        apiKeys: [
          {
            name: 'GeoLimitedKey',
            wafRules: [
              {
                action: 'Block',
                name: 'UsOnly',
                priority: 0,
                statement: {
                  NotStatement: {
                    Statement: {
                      GeoMatchStatement: {
                        CountryCodes: ['US'],
                      },
                    },
                  },
                },
              },
            ],
          },
          {
            name: 'ThrottledKey',
            wafRules: [
              {
                action: 'Block',
                name: 'Throttle',
                priority: 1,
                statement: {
                  RateBasedStatement: {
                    AggregateKeyType: 'IP',
                    Limit: 500,
                  },
                },
              },
            ],
          },
          {
            name: 'ThrottledKeyWithScope',
            wafRules: [
              {
                action: 'Block',
                name: 'Throttle',
                priority: 1,
                statement: {
                  RateBasedStatement: {
                    AggregateKeyType: 'IP',
                    Limit: 500,
                    ScopeDownStatement: {
                      NotStatement: {
                        Statement: {
                          GeoMatchStatement: {
                            CountryCodes: ['US'],
                          },
                        },
                      },
                    },
                  },
                },
              },
            ],
          },
        ],
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF with default no-introspect rule', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: ['disableIntrospection'],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF with no-introspect rule and priority', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: [
            {
              disableIntrospection: {
                priority: 20,
              },
            },
          ],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF default throttle rule', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: ['throttle'],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF throttle rule with limit', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          enabled: true,
          rules: [{ throttle: 200 }],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF throttle rule with FORWARDED_IP', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: [
            {
              throttle: {
                limit: 300,
                aggregateKeyType: 'FORWARDED_IP',
              },
            },
          ],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate a WAF throttle rule with FORWARDED_IP and config', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: [
            {
              throttle: {
                limit: 400,
                aggregateKeyType: 'FORWARDED_IP',
                forwardedIPConfig: {
                  headerName: 'X-Reenviado-Para',
                  fallbackBehavior: 'NO_NOMATCH',
                },
              },
            },
          ],
        },
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate API key WAF with default preset rules', () => {
      const apiConfig = {
        ...config,
        wafConfig: { enabled: true },
        apiKeys: [
          {
            name: 'MyKey',
            wafRules: [
              'throttle',
              'disableIntrospection',
            ],
          },
        ],
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate match-all API key WAF rules', () => {
      const apiConfig = {
        ...config,
        wafConfig: { enabled: true },
        apiKeys: [
          {
            name: 'MyKey',
            wafRules: [
              {
                name: 'MatchAllRule',
              },
            ],
          },
        ],
      };
      expect(plugin.getWafResources(apiConfig)).toMatchSnapshot();
    });
  
    it('should generate consecutive but different priorities', () => {
      const apiConfig = {
        ...config,
        wafConfig: {
          rules: [
            { name: 'Dummy1' },
            { name: 'Dummy2' },
            {
              name: 'Dummy3',
              priority: 5,
            },
            { name: 'Dummy4' },
            {
              name: 'Dummy5',
              priority: 9,
            },
          ],
        },
        apiKeys: [
          {
            name: 'MyKey',
            wafRules: [
              {
                throttle: {
                  limit: 100,
                  priority: 15,
                },
              },
              {
                disableIntrospection: {
                  priority: 16,
                },
              },
              {
                name: 'Dummy6',
                statement: {},
              },
            ],
          },
        ],
      };
      const waf = plugin.getWafResources(apiConfig);
      const priorities = waf.GraphQlWaf.Properties.Rules.map(r => [r.Name, r.Priority]);
      expect(priorities).toEqual([
        ['Dummy1', 100],
        ['Dummy2', 101],
        ['Dummy3', 5],
        ['Dummy4', 102],
        ['Dummy5', 9],
        ['MyKeyThrottle', 15],
        ['MyKeyDisableIntrospection', 16],
        ['Dummy6', 103],
      ]);
    });
  });
  