# frozen_string_literal: true

require_relative '../../spec_helper'

module Aws
  module Plugins
    describe EndpointDiscovery, :jruby_flaky do

      EndpointDiscoveryClient = ApiHelper.sample_service(
        api: {
          'metadata' => {
            'apiVersion' => '2016-07-30',
            'protocol' => 'json',
            'jsonVersion' => '1.1',
            'endpointPrefix' => 'svc',
            "signatureVersion" => "v4"
          },
          'operations' => {
            'DescribeEndpoints' => {
              'http' => { 'method' => 'POST', 'requestUri' => '/' },
              'input' => { 'shape' => 'InputStructure' },
              'output' => { 'shape' => 'OutputStructure' },
              'endpointoperation' => true
            },
            'OperationRequired' => {
              'http' => { 'method' => 'POST', 'requestUri' => '/' },
              'input' => { 'shape' => 'OperationRequiredInput' },
              'endpointdiscovery' => {
                'required' => 'true'
              }
            },
            'OperationNotRequired' => {
              'http' => { 'method' => 'POST', 'requestUri' => '/' },
              'input' => { 'shape' => 'OperationNotRequiredInput' },
              'endpointdiscovery' => {}
            }
          },
          'shapes' => {
            'InputStructure' => {
              'type' => 'structure',
              'members' => {
                'Operation' => {'shape' => 'String'},
                'Identifiers' => {'shape' => 'Identifiers'}
              }
            },
            'String' => {'type' => 'string'},
            'Identifiers' => {
              'type' => 'map',
              'key' => {'shape' => 'String'},
              'value' => {'shape' => 'String'}
            },
            'OutputStructure' => {
              'type' => 'structure',
              'members' => {
                'Endpoints' => {'shape' => 'Endpoints'}
              }
            },
            'Endpoints' => {
              'type' => 'list',
              'member' => {'shape' => 'Endpoint'}
            },
            'Endpoint' => {
              'type' => 'structure',
              'members' => {
                'Address' => {'shape' => 'String'},
                'CachePeriodInMinutes' => {'shape' => 'Long'}
              }
            },
            'Long' => { 'type' => 'long' },
            'OperationRequiredInput' => {
              'type' => 'structure',
              'members' => {
                'ResourceId' => {
                  'shape' => 'String',
                  'endpointdiscoveryid' => true
                },
                'ResourceName' => {
                  'shape' => 'String',
                  'endpointdiscoveryid' => true
                }
              }
            },
            'OperationNotRequiredInput' => {
              'type' => 'structure',
              'members' => {
                'Foo' => {'shape' => 'String'}
              }
            }
          }
        }
      ).const_get(:Client)

      let(:creds) { Aws::Credentials.new('akid', 'secret') }
      let(:env) {{}}

      before do
        stub_const("ENV", env)
      end

      it 'enables endpoint discovery from ENV' do
        env['AWS_ENABLE_ENDPOINT_DISCOVERY'] = true
        expect(
          EndpointDiscoveryClient.new(region: 'us-east-1').config.endpoint_discovery
        ).to be_truthy

        env['AWS_ENABLE_ENDPOINT_DISCOVERY'] = "true"
        expect(
          EndpointDiscoveryClient.new(region: 'us-east-1').config.endpoint_discovery
        ).to be_truthy

        env['AWS_ENABLE_ENDPOINT_DISCOVERY'] = "false"
        expect(
          EndpointDiscoveryClient.new(region: 'us-east-1').config.endpoint_discovery
        ).to be_falsey
      end

      it 'can be set directly, overriding the ENV' do
        env['AWS_ENABLE_ENDPOINT_DISCOVERY'] = true
        expect(
          EndpointDiscoveryClient.new(endpoint_discovery: false, region: 'us-east-1').config.endpoint_discovery
        ).to be_falsey
      end

      it 'does nothing when :endpoint is specified' do
        stub_request(:post, "https://foo.com/")
          .to_return(:status => 200, :body => "", :headers => {})
        c = EndpointDiscoveryClient.new(
          credentials: creds, endpoint: 'https://foo.com', region: 'us-east-1'
        )
        resp = c.operation_required(resource_name: 'foo', resource_id: '123')
        expect(resp.context.http_request.headers['user-agent']).not_to include('endpoint-discovery')
      end

      it 'fetches endpoint from cache when available' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1', endpoint_discovery: true,
          stub_responses: true
        )
        c.config.endpoint_cache['akid_operation_not_required'] = {:address => "https://bar.com/foo", :cache_period_in_minutes => 20}
        c.config.endpoint_cache['akid_operation_required_123_foo'] = {:address => "https://bar.com/foo", :cache_period_in_minutes => 20}

        resp = c.operation_not_required(foo: 'foo')
        expect(resp.context.http_request.headers['user-agent']).to include('endpoint-discovery')
        expect(c.api_requests.size).to eq(1)

        resp = c.operation_required(resource_name: 'foo', resource_id: '123')
        expect(resp.context.http_request.headers['user-agent']).to include('endpoint-discovery')
        expect(c.api_requests.size).to eq(2)
      end

      it 'adds apiVersion header for endpoint operation' do
        stub_request(:post, "https://svc.us-east-1.amazonaws.com/")
          .to_return(:status => 200, :body => "", :headers => {})
        c = EndpointDiscoveryClient.new(credentials: creds, region: 'us-east-1')
        resp = c.describe_endpoints
        expect(resp.context.http_request.headers['x-amz-api-version']).to eq('2016-07-30')
      end

      it 'caches endpoint when request endpoint succeed' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1', stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          {
            endpoints: [
              { address: "https://foo.com/bar", cache_period_in_minutes: 60 }
            ]
          }
        )
        c.operation_required(resource_name: 'foo', resource_id: '123')
        expect(c.config.endpoint_cache['akid_operation_required_123_foo'].address).to eq('https://foo.com/bar')
        c.operation_required(resource_name: 'foo', resource_id: '123')
        expect(c.api_requests.size).to eq(3)
      end

      it 'makes requests to fetch endpoints when required and enabled' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1', endpoint_discovery: true,
          stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          {
            endpoints: [
              { address: "foo.com/bar", cache_period_in_minutes: 60 }
            ]
          }
        )
        c.operation_required(resource_name: 'foo', resource_id: '123')
        expect(c.api_requests.size).to eq(2)
      end

      it 'raises an exception when required and disabled' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1',
          endpoint_discovery: false,
          stub_responses: true
        )
        expect do
          c.operation_required(resource_name: 'foo', resource_id: '123')
        end.to raise_exception(ArgumentError)
      end

      it 'fails when fetching endpoint failed but required' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1',
          stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          'ServiceUnavaiable'
        )
        expect {
          c.operation_required(resource_name: 'foo', resource_id: '123')
        }.to raise_error(Aws::Errors::EndpointDiscoveryError)
      end

      it 'makes requests to fetch endpoints when enabled but not required' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1',endpoint_discovery: true,
          stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          {
            endpoints: [
              { address: "https://foo.com/bar", cache_period_in_minutes: 60 }
            ]
          }
        )
        c.operation_not_required(foo: 'foo')
        expect(c.api_requests.size).to eq(2)
      end

      it 'does not fail when fetching endpoint failed when enabled but not required' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1', endpoint_discovery: true,
          stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          'ServiceUnavaiable'
        )
        expect {
          c.operation_not_required(foo: 'foo')
        }.to_not raise_error
        expect(c.api_requests.size).to eq(2)
      end

      it 'does nothing when disabled and not required' do
        c = EndpointDiscoveryClient.new(
          credentials: creds, region: 'us-east-1', stub_responses: true,
          endpoint_discovery: false
        )
        c.operation_not_required(foo: 'foo')
        expect(c.api_requests.size).to eq(1)
      end

      it 'async makes requests to fetch endpoint when enabled but not required and :active_endpoint_cache is enabled' do
        c = EndpointDiscoveryClient.new(
          credentials: creds,
          region: 'us-east-1',
          endpoint_discovery: true,
          active_endpoint_cache: true,
          endpoint_cache_poll_interval: 0.01,
          stub_responses: true
        )
        c.stub_responses(
          :describe_endpoints,
          'ServiceUnavaiable',
          {
            endpoints: [
              { address: "https://foo.com/bar", cache_period_in_minutes: 60 }
            ]
          }
        )
        cache_thread_block = nil
        allow(Thread).to receive(:new) do |&block|
          cache_thread_block = block
        end
        c.operation_not_required(foo: 'foo')
        cache_thread_block.call
        expect(c.api_requests.size).to eq(3)
        c.operation_not_required(foo: 'foo')
        expect(c.api_requests.size).to eq(4)
      end

      it 'can set endpoint cache max entries limit' do
        c = EndpointDiscoveryClient.new(credentials: creds, region: 'us-east-1', endpoint_cache_max_entries: 5)
        expect(c.config.endpoint_cache.max_entries).to eq(5)
      end

      it 'can set endpoint cache max threads limit' do
        c = EndpointDiscoveryClient.new(credentials: creds, region: 'us-east-1', endpoint_cache_max_threads: 5)
        expect(c.config.endpoint_cache.max_threads).to eq(5)
      end

    end
  end
end
