# frozen_string_literal: true

require 'spec_helper'
require 'shared/versioning_examples'

describe Grape::API do
  subject(:a_remounted_api) { Class.new(described_class) }

  let(:root_api) { Class.new(described_class) }

  def app
    root_api
  end

  describe 'remounting an API' do
    context 'with a defined route' do
      before do
        a_remounted_api.get '/votes' do
          '10 votes'
        end
      end

      context 'when mounting one instance' do
        before do
          root_api.mount a_remounted_api
        end

        it 'can access the endpoint' do
          get '/votes'
          expect(last_response.body).to eql '10 votes'
        end
      end

      context 'when mounting twice' do
        before do
          root_api.mount a_remounted_api => '/posts'
          root_api.mount a_remounted_api => '/comments'
        end

        it 'can access the votes in both places' do
          get '/posts/votes'
          expect(last_response.body).to eql '10 votes'
          get '/comments/votes'
          expect(last_response.body).to eql '10 votes'
        end
      end

      context 'when mounting on namespace' do
        before do
          stub_const('StaticRefToAPI', a_remounted_api)
          root_api.namespace 'posts' do
            mount StaticRefToAPI
          end

          root_api.namespace 'comments' do
            mount StaticRefToAPI
          end
        end

        it 'can access the votes in both places' do
          get '/posts/votes'
          expect(last_response.body).to eql '10 votes'
          get '/comments/votes'
          expect(last_response.body).to eql '10 votes'
        end
      end
    end

    describe 'with dynamic configuration' do
      context 'when mounting an endpoint conditional on a configuration' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            get 'always' do
              'success'
            end

            given configuration[:mount_sometimes] do
              get 'sometimes' do
                'sometimes'
              end
            end
          end
        end

        it 'mounts the endpoints only when configured to do so' do
          root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true })
          root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false })

          get '/with_conditional/always'
          expect(last_response.body).to eq 'success'

          get '/with_conditional/sometimes'
          expect(last_response.body).to eq 'sometimes'

          get '/without_conditional/always'
          expect(last_response.body).to eq 'success'

          get '/without_conditional/sometimes'
          expect(last_response.status).to eq 404
        end
      end

      context 'when using an expression derived from a configuration' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            get(mounted { "api_name_#{configuration[:api_name]}" }) do
              'success'
            end
          end
        end

        before do
          root_api.mount a_remounted_api, with: {
            api_name: 'a_name'
          }
        end

        it 'mounts the endpoint with the name' do
          get 'api_name_a_name'
          expect(last_response.body).to eq 'success'
        end

        it 'does not mount the endpoint with a null name' do
          get 'api_name_'
          expect(last_response.body).not_to eq 'success'
        end

        context 'when the expression lives in a namespace' do
          subject(:a_remounted_api) do
            Class.new(described_class) do
              namespace :base do
                get(mounted { "api_name_#{configuration[:api_name]}" }) do
                  'success'
                end
              end
            end
          end

          it 'mounts the endpoint with the name' do
            get 'base/api_name_a_name'
            expect(last_response.body).to eq 'success'
          end

          it 'does not mount the endpoint with a null name' do
            get 'base/api_name_'
            expect(last_response.body).not_to eq 'success'
          end
        end
      end

      context 'when executing a standard block within a `mounted` block with all dynamic params' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            mounted do
              desc configuration[:description] do
                headers configuration[:headers]
              end
              get configuration[:endpoint] do
                configuration[:response]
              end
            end
          end
        end

        let(:api_endpoint) { 'custom_endpoint' }
        let(:api_response) { 'custom response' }
        let(:endpoint_description) { 'this is a custom API' }
        let(:headers) do
          {
            'XAuthToken' => {
              'description' => 'Validates your identity',
              'required' => true
            }
          }
        end

        it 'mounts the API and obtains the description and headers definition' do
          root_api.mount a_remounted_api, with: {
            description: endpoint_description,
            headers: headers,
            endpoint: api_endpoint,
            response: api_response
          }
          get api_endpoint
          expect(last_response.body).to eq api_response
          expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:description])
            .to eq endpoint_description
          expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:headers])
            .to eq headers
        end
      end

      context 'when executing a custom block on mount' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            get 'always' do
              'success'
            end

            mounted do
              configuration[:endpoints].each do |endpoint_name, endpoint_response|
                get endpoint_name do
                  endpoint_response
                end
              end
            end
          end
        end

        it 'mounts the endpoints only when configured to do so' do
          root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } }
          get 'api_name'
          expect(last_response.body).to eq 'api_response'
        end
      end

      context 'when the configuration is part of the arguments of a method' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            get configuration[:endpoint_name] do
              'success'
            end
          end
        end

        it 'mounts the endpoint in the location it is configured' do
          root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' }
          get '/some_location'
          expect(last_response.body).to eq 'success'

          get '/different_location'
          expect(last_response.status).to eq 404

          root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' }
          get '/new_location'
          expect(last_response.body).to eq 'success'
        end

        context 'when the configuration is the value in a key-arg pair' do
          subject(:a_remounted_api) do
            Class.new(described_class) do
              version 'v1', using: :param, parameter: configuration[:version_param]
              get 'endpoint' do
                'version 1'
              end

              version 'v2', using: :param, parameter: configuration[:version_param]
              get 'endpoint' do
                'version 2'
              end
            end
          end

          it 'takes the param from the configuration' do
            root_api.mount a_remounted_api, with: { version_param: 'param_name' }

            get '/endpoint?param_name=v1'
            expect(last_response.body).to eq 'version 1'

            get '/endpoint?param_name=v2'
            expect(last_response.body).to eq 'version 2'

            get '/endpoint?wrong_param_name=v2'
            expect(last_response.body).to eq 'version 1'
          end
        end
      end

      context 'on the DescSCope' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            desc 'The description of this' do
              tags ['not_configurable_tag', configuration[:a_configurable_tag]]
            end
            get 'location' do
              'success'
            end
          end
        end

        it 'mounts the endpoint with the appropiate tags' do
          root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' })
        end
      end

      context 'on the ParamScope' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            params do
              requires configuration[:required_param], type: configuration[:required_type]
            end

            get 'location' do
              'success'
            end
          end
        end

        it 'mounts the endpoint in the location it is configured' do
          root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String })
          root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer })

          get '/string/location', param_key: 'a'
          expect(last_response.body).to eq 'success'

          get '/string/location', param_integer: 1
          expect(last_response.status).to eq 400

          get '/integer/location', param_integer: 1
          expect(last_response.body).to eq 'success'

          get '/integer/location', param_integer: 'a'
          expect(last_response.status).to eq 400
        end

        context 'on dynamic checks' do
          subject(:a_remounted_api) do
            Class.new(described_class) do
              params do
                optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] }
              end

              get 'location' do
                'success'
              end
            end
          end

          it 'can read the configuration on lambdas' do
            root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' }
            get '/location', restricted_values: 'always'
            expect(last_response.body).to eq 'success'
            get '/location', restricted_values: 'sometimes'
            expect(last_response.body).to eq 'success'
            get '/location', restricted_values: 'never'
            expect(last_response.status).to eq 400
          end
        end
      end

      context 'when the configuration is read within a namespace' do
        before do
          a_remounted_api.namespace 'api' do
            params do
              requires configuration[:required_param]
            end
            get "/#{configuration[:path]}" do
              '10 votes'
            end
          end
          root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' }
          root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' }
        end

        it 'will use the dynamic configuration on all routes' do
          get 'api/votes', param_key: 'a'
          expect(last_response.body).to eql '10 votes'
          get 'api/scores', param_key: 'a'
          expect(last_response.body).to eql '10 votes'
          get 'api/votes'
          expect(last_response.status).to eq 400
        end
      end

      context 'a very complex configuration example' do
        before do
          top_level_api = Class.new(described_class) do
            remounted_api = Class.new(Grape::API) do
              get configuration[:endpoint_name] do
                configuration[:response]
              end
            end

            expression_namespace = mounted { configuration[:namespace].to_s * 2 }
            given(mounted { configuration[:should_mount_expressed] != false }) do
              namespace expression_namespace do
                mount remounted_api, with: { endpoint_name: configuration[:endpoint_name], response: configuration[:endpoint_response] }
              end
            end
          end
          root_api.mount top_level_api, with: configuration_options
        end

        context 'when the namespace should be mounted' do
          let(:configuration_options) do
            {
              should_mount_expressed: true,
              namespace: 'bang',
              endpoint_name: 'james',
              endpoint_response: 'bond'
            }
          end

          it 'gets a response' do
            get 'bangbang/james'
            expect(last_response.body).to eq 'bond'
          end
        end

        context 'when should be mounted is nil' do
          let(:configuration_options) do
            {
              should_mount_expressed: nil,
              namespace: 'bang',
              endpoint_name: 'james',
              endpoint_response: 'bond'
            }
          end

          it 'gets a response' do
            get 'bangbang/james'
            expect(last_response.body).to eq 'bond'
          end
        end

        context 'when it should not be mounted' do
          let(:configuration_options) do
            {
              should_mount_expressed: false,
              namespace: 'bang',
              endpoint_name: 'james',
              endpoint_response: 'bond'
            }
          end

          it 'gets a response' do
            get 'bangbang/james'
            expect(last_response.body).not_to eq 'bond'
          end
        end
      end

      context 'when the configuration is read in a helper' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            helpers do
              def printed_response
                configuration[:some_value]
              end
            end

            get 'location' do
              printed_response
            end
          end
        end

        it 'will use the dynamic configuration on all routes' do
          root_api.mount(a_remounted_api, with: { some_value: 'response value' })

          get '/location'
          expect(last_response.body).to eq 'response value'
        end
      end

      context 'when the configuration is read within the response block' do
        subject(:a_remounted_api) do
          Class.new(described_class) do
            get 'location' do
              configuration[:some_value]
            end
          end
        end

        it 'will use the dynamic configuration on all routes' do
          root_api.mount(a_remounted_api, with: { some_value: 'response value' })

          get '/location'
          expect(last_response.body).to eq 'response value'
        end
      end
    end
  end
end
