# frozen_string_literal: true

require_relative '../spec_helper'

module Aws
  describe 'Client' do
    describe 'response stubbing' do

      let(:options) {{
        stub_responses: true,
        region: 'us-east-1',
        access_key_id: 'akid',
        secret_access_key: 'secret',
      }}

      let(:client_class) { ApiHelper.sample_client }

      let(:client) { client_class.new(options) }

      it 'requires a region' do
        expect {
          client_class.new(access_key_id:'akid', secret_access_key: 'secret')
        }.to raise_error(Aws::Errors::MissingRegionError)
      end

      context 'when requests are signed' do

        let(:sample_service) { ApiHelper.sample_service(metadata: { 'signatureVersion' => 'v4' }) }
        let(:client_class) { ApiHelper.sample_client(service: sample_service) }

        it 'raises an error when credentials are nil' do
          creds = Credentials.new(nil, nil)
          client = client_class.new(credentials: creds, region: 'us-east-1', stub_responses: true)
          expect do
            client.example_operation
          end.to raise_error(Aws::Errors::MissingCredentialsError)
        end


        it 'raises an error when credentials are empty' do
          creds = Credentials.new('', '')
          client = client_class.new(credentials: creds, region: 'us-east-1', stub_responses: true)
          expect do
            client.example_operation
          end.to raise_error(Aws::Errors::MissingCredentialsError)
        end
      end

      it 'raises a helpful error on possible incorrect regions' do

        # simulate an error from connecting to an unknown endpoint
        stub_request(:any, /.*/).
          to_raise(SocketError.new("Failed to open TCP connection to s3.us-west-2a.amazonaws.com:443 (getaddrinfo: nodename nor servname provided, or not known"))

        client = client_class.new(
          region: 'us-west-2a',
          credentials: Credentials.new('akid', 'secret')
        )

        error = nil
        begin
          client.example_operation
        rescue => e
          error = e
        end

        expect(e).to be_kind_of(Errors::NoSuchEndpointError)
        expect(e.context.retries).to be(3) # updated to retry based on customer request
        expect(e.message).to include('us-east-1')
        expect(e.message).to include('us-west-1')
        expect(e.message).to include('cn-north-1')
        expect(e.message).to include(<<-MSG)
Encountered a `SocketError` while attempting to connect to:

  https://svc.us-west-2a.amazonaws.com

This is typically the result of an invalid `:region` option or a
poorly formatted `:endpoint` option.

* Avoid configuring the `:endpoint` option directly. Endpoints are constructed
  from the `:region`. The `:endpoint` option is reserved for certain services
  or for connecting to non-standard test endpoints.

* Not every service is available in every region.

* Never suffix region names with availability zones.
  Use "us-east-1", not "us-east-1a"

Known AWS regions include (not specific to this service):

        MSG
      end


      it 'skips the credential provider chain' do
        expect(Aws::CredentialProviderChain).not_to receive(:new)
        creds = client_class.new(stub_responses:true).config.credentials
        expect(creds).to be_kind_of(Credentials)
        expect(creds.access_key_id).to eq('stubbed-akid')
        expect(creds.secret_access_key).to eq('stubbed-secret')
      end

      it 'raises an error if stubbed_responses is not enabled' do
        client = client_class.new(options.merge(stub_responses: false))
        expect {
          client.stub_responses(:example_operation, {})
        }.to raise_error(/enable stubbing .* `:stub_responses => true`/)
      end

      it 'returns stubbed responses without making a request' do
        client.stub_responses(:example_operation, string:'value')
        resp = client.example_operation
        expect(resp.string).to eq('value')
      end

      it 'accepts a list of stubs' do
        client.stub_responses(:example_operation, [
          { string: 'value-1' },
          { string: 'value-2' },
        ])
        expect(client.example_operation.string).to eq('value-1')
        expect(client.example_operation.string).to eq('value-2')
      end

      it 'returns the same stub multiple times' do
        client.stub_responses(:example_operation, string: 'fixed-value')
        expect(client.example_operation.string).to eq('fixed-value')
        expect(client.example_operation.string).to eq('fixed-value')
      end

      it 'returns the last stub after mulitple times' do
        client.stub_responses(:example_operation, [
          { string: 'value-1' },
          { string: 'value-2' },
        ])
        expect(client.example_operation.string).to eq('value-1')
        expect(client.example_operation.string).to eq('value-2')
        expect(client.example_operation.string).to eq('value-2')
      end

      it 'can stub errors' do
        client.stub_responses(:example_operation, [
          'NotFound',
          StandardError,
          RuntimeError.new('oops')
        ])

        expect {
          client.example_operation
        }.to raise_error(client_class.errors_module::NotFound, 'stubbed-response-error-message')

        expect {
          client.example_operation
        }.to raise_error(StandardError)

        expect {
          client.example_operation
        }.to raise_error(RuntimeError, 'oops')
      end

      it 'can stub errors and data' do
        client.stub_responses(:example_operation, ['NotFound', {}])
        expect {
          client.example_operation
        }.to raise_error(client_class.errors_module::NotFound)
        expect(client.example_operation.data.to_h).to eq({})
      end

      it 'accepts stubs given to the constructor' do
        client = client_class.new(stub_responses: {
          example_operation: { string: 'value' }
        })
        expect(client.example_operation.string).to eq('value')
      end

      context 'api requests' do
        it 'allows api requests to be logged when stubbed' do
          client_class =
            ApiHelper.sample_client(
              service: ApiHelper.sample_service(module_name: 'ApiRequestsToBeLogged')
            )
          client = client_class.new(options.merge(validate_params: false))
          expect(client.api_requests.empty?).to be(true)
          client.example_operation(foo: 'bar')
          expect(client.api_requests.length).to eq(1)

          log_obj = client.api_requests[0]
          expect(log_obj[:operation_name]).to eq(:example_operation)
          expect(log_obj[:params]).to eq(foo: 'bar')
          expect(log_obj[:context].metadata).to include(
            {
              gem_name: 'aws-sdk-apirequeststobelogged',
              gem_version: '1.0.0',
              response_target: nil,
              original_params: { foo: 'bar' },
              request_id: 'stubbed-request-id',
              http_checksum: {}
            }
          )
        end

        it 'raises an error when accessing api requests of a non stubbed client' do
          client = client_class.new(options.merge(stub_responses: false))
          expect {
            client.api_requests
          }.to raise_error(NotImplementedError)
        end

      end

    end
  end
end
