# frozen_string_literal: true

require 'spec_helper'

describe Grape::Validations::Validators::DefaultValidator do
  let_it_be(:app) do
    Class.new(Grape::API) do
      default_format :json

      params do
        optional :id
        optional :type, default: 'default-type'
      end
      get '/' do
        { id: params[:id], type: params[:type] }
      end

      params do
        optional :type1, default: 'default-type1'
        optional :type2, default: 'default-type2'
      end
      get '/user' do
        { type1: params[:type1], type2: params[:type2] }
      end

      params do
        requires :id
        optional :type1, default: 'default-type1'
        optional :type2, default: 'default-type2'
      end

      get '/message' do
        { id: params[:id], type1: params[:type1], type2: params[:type2] }
      end

      params do
        optional :random, default: -> { Random.rand }
        optional :not_random, default: Random.rand
      end
      get '/numbers' do
        { random_number: params[:random], non_random_number: params[:non_random_number] }
      end

      params do
        optional :array, type: Array do
          requires :name
          optional :with_default, default: 'default'
        end
      end
      get '/array' do
        { array: params[:array] }
      end

      params do
        requires :thing1
        optional :more_things, type: Array do
          requires :nested_thing
          requires :other_thing, default: 1
        end
      end
      get '/optional_array' do
        { thing1: params[:thing1] }
      end

      params do
        requires :root, type: Hash do
          optional :some_things, type: Array do
            requires :foo
            optional :options, type: Array do
              requires :name, type: String
              requires :value, type: String
            end
          end
        end
      end
      get '/nested_optional_array' do
        { root: params[:root] }
      end

      params do
        requires :root, type: Hash do
          optional :some_things, type: Array do
            requires :foo
            optional :options, type: Array do
              optional :name, type: String
              optional :value, type: String
            end
          end
        end
      end
      get '/another_nested_optional_array' do
        { root: params[:root] }
      end
    end
  end

  it 'lets you leave required values nested inside an optional blank' do
    get '/optional_array', thing1: 'stuff'
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ thing1: 'stuff' }.to_json)
  end

  it 'allows optional arrays to be omitted' do
    params = { some_things:
                [{ foo: 'one', options: [{ name: 'wat', value: 'nope' }] },
                 { foo: 'two' },
                 { foo: 'three', options: [{ name: 'wooop', value: 'yap' }] }] }
    get '/nested_optional_array', root: params
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ root: params }.to_json)
  end

  it 'does not allows faulty optional arrays' do
    params = { some_things:
                 [
                   { foo: 'one', options: [{ name: 'wat', value: 'nope' }] },
                   { foo: 'two', options: [{ name: 'wat' }] },
                   { foo: 'three' }
                 ] }
    error = { error: 'root[some_things][1][options][0][value] is missing' }
    get '/nested_optional_array', root: params
    expect(last_response.status).to eq(400)
    expect(last_response.body).to eq(error.to_json)
  end

  it 'allows optional arrays with optional params' do
    params = { some_things:
                 [
                   { foo: 'one', options: [{ value: 'nope' }] },
                   { foo: 'two', options: [{ name: 'wat' }] },
                   { foo: 'three' }
                 ] }
    get '/another_nested_optional_array', root: params
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ root: params }.to_json)
  end

  it 'set default value for optional param' do
    get('/')
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ id: nil, type: 'default-type' }.to_json)
  end

  it 'set default values for optional params' do
    get('/user')
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ type1: 'default-type1', type2: 'default-type2' }.to_json)
  end

  it 'set default values for missing params in the request' do
    get('/user?type2=value2')
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ type1: 'default-type1', type2: 'value2' }.to_json)
  end

  it 'set default values for optional params and allow to use required fields in the same time' do
    get('/message?id=1')
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ id: '1', type1: 'default-type1', type2: 'default-type2' }.to_json)
  end

  it 'sets lambda based defaults at the time of call' do
    get('/numbers')
    expect(last_response.status).to eq(200)
    before = JSON.parse(last_response.body)
    get('/numbers')
    expect(last_response.status).to eq(200)
    after = JSON.parse(last_response.body)

    expect(before['non_random_number']).to eq(after['non_random_number'])
    expect(before['random_number']).not_to eq(after['random_number'])
  end

  it 'sets default values for grouped arrays' do
    get('/array?array[][name]=name&array[][name]=name2&array[][with_default]=bar2')
    expect(last_response.status).to eq(200)
    expect(last_response.body).to eq({ array: [{ name: 'name', with_default: 'default' }, { name: 'name2', with_default: 'bar2' }] }.to_json)
  end

  context 'optional group with defaults' do
    subject do
      Class.new(Grape::API) do
        default_format :json
      end
    end

    def app
      subject
    end

    context 'optional array without default value includes optional param with default value' do
      before do
        subject.params do
          optional :optional_array, type: Array do
            optional :foo_in_optional_array, default: 'bar'
          end
        end
        subject.post '/optional_array' do
          { optional_array: params[:optional_array] }
        end
      end

      it 'returns nil for optional array if param is not provided' do
        post '/optional_array'
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ optional_array: nil }.to_json)
      end
    end

    context 'optional array with default value includes optional param with default value' do
      before do
        subject.params do
          optional :optional_array_with_default, type: Array, default: [] do
            optional :foo_in_optional_array, default: 'bar'
          end
        end
        subject.post '/optional_array_with_default' do
          { optional_array_with_default: params[:optional_array_with_default] }
        end
      end

      it 'sets default value for optional array if param is not provided' do
        post '/optional_array_with_default'
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ optional_array_with_default: [] }.to_json)
      end
    end

    context 'optional hash without default value includes optional param with default value' do
      before do
        subject.params do
          optional :optional_hash_without_default, type: Hash do
            optional :foo_in_optional_hash, default: 'bar'
          end
        end
        subject.post '/optional_hash_without_default' do
          { optional_hash_without_default: params[:optional_hash_without_default] }
        end
      end

      it 'returns nil for optional hash if param is not provided' do
        post '/optional_hash_without_default'
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ optional_hash_without_default: nil }.to_json)
      end

      it 'does not fail even if invalid params is passed to default validator' do
        expect { post '/optional_hash_without_default', optional_hash_without_default: '5678' }.not_to raise_error
        expect(last_response.status).to eq(400)
        expect(last_response.body).to eq({ error: 'optional_hash_without_default is invalid' }.to_json)
      end
    end

    context 'optional hash with default value includes optional param with default value' do
      before do
        subject.params do
          optional :optional_hash_with_default, type: Hash, default: {} do
            optional :foo_in_optional_hash, default: 'bar'
          end
        end
        subject.post '/optional_hash_with_default_empty_hash' do
          { optional_hash_with_default: params[:optional_hash_with_default] }
        end

        subject.params do
          optional :optional_hash_with_default, type: Hash, default: { foo_in_optional_hash: 'parent_default' } do
            optional :some_param
            optional :foo_in_optional_hash, default: 'own_default'
          end
        end
        subject.post '/optional_hash_with_default_inner_params' do
          { foo_in_optional_hash: params[:optional_hash_with_default][:foo_in_optional_hash] }
        end
      end

      it 'sets default value for optional hash if param is not provided' do
        post '/optional_hash_with_default_empty_hash'
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ optional_hash_with_default: {} }.to_json)
      end

      it 'sets default value from parent defaults for inner param if parent param is not provided' do
        post '/optional_hash_with_default_inner_params'
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ foo_in_optional_hash: 'parent_default' }.to_json)
      end

      it 'sets own default value for inner param if parent param is provided' do
        post '/optional_hash_with_default_inner_params', optional_hash_with_default: { some_param: 'param' }
        expect(last_response.status).to eq(201)
        expect(last_response.body).to eq({ foo_in_optional_hash: 'own_default' }.to_json)
      end
    end
  end

  context 'optional with nil as value' do
    subject do
      Class.new(Grape::API) do
        default_format :json
      end
    end

    def app
      subject
    end

    context 'primitive types' do
      [
        [Integer, 0],
        [Integer, 42],
        [Float, 0.0],
        [Float, 4.2],
        [BigDecimal, 0.0],
        [BigDecimal, 4.2],
        [Numeric, 0],
        [Numeric, 42],
        [Date, Date.today],
        [DateTime, DateTime.now],
        [Time, Time.now],
        [Time, Time.at(0)],
        [Grape::API::Boolean, false],
        [String, ''],
        [String, 'non-empty-string'],
        [Symbol, :symbol],
        [TrueClass, true],
        [FalseClass, false]
      ].each do |type, default|
        it 'respects the default value' do
          subject.params do
            optional :param, type: type, default: default
          end
          subject.get '/default_value' do
            params[:param]
          end

          get '/default_value', param: nil
          expect(last_response.status).to eq(200)
          expect(last_response.body).to eq(default.to_json)
        end
      end
    end

    context 'structures types' do
      [
        [Hash, {}],
        [Hash, { test: 'non-empty' }],
        [Array, []],
        [Array, ['non-empty']],
        [Array[Integer], []],
        [Set, []],
        [Set, [1]]
      ].each do |type, default|
        it 'respects the default value' do
          subject.params do
            optional :param, type: type, default: default
          end
          subject.get '/default_value' do
            params[:param]
          end

          get '/default_value', param: nil
          expect(last_response.status).to eq(200)
          expect(last_response.body).to eq(default.to_json)
        end
      end
    end

    context 'special types' do
      [
        [JSON, ''],
        [JSON, { test: 'non-empty-string' }.to_json],
        [Array[JSON], []],
        [Array[JSON], [{ test: 'non-empty-string' }.to_json]],
        [::File, ''],
        [::File, { test: 'non-empty-string' }.to_json],
        [Rack::Multipart::UploadedFile, ''],
        [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]
      ].each do |type, default|
        it 'respects the default value' do
          subject.params do
            optional :param, type: type, default: default
          end
          subject.get '/default_value' do
            params[:param]
          end

          get '/default_value', param: nil
          expect(last_response.status).to eq(200)
          expect(last_response.body).to eq(default.to_json)
        end
      end
    end

    context 'variant-member-type collections' do
      [
        [Array[Integer, String], [0, '']],
        [Array[Integer, String], [42, 'non-empty-string']],
        [[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
        [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
      ].each do |type, default|
        it 'respects the default value' do
          subject.params do
            optional :param, type: type, default: default
          end
          subject.get '/default_value' do
            params[:param]
          end

          get '/default_value', param: nil
          expect(last_response.status).to eq(200)
          expect(last_response.body).to eq(default.to_json)
        end
      end
    end
  end

  context 'array with default values and given conditions' do
    subject do
      Class.new(Grape::API) do
        default_format :json
      end
    end

    def app
      subject
    end

    it 'applies the default values only if the conditions are met' do
      subject.params do
        requires :ary, type: Array do
          requires :has_value, type: Grape::API::Boolean
          given has_value: ->(has_value) { has_value } do
            optional :type, type: String, values: %w[str int], default: 'str'
            given type: ->(type) { type == 'str' } do
              optional :str, type: String, default: 'a'
            end
            given type: ->(type) { type == 'int' } do
              optional :int, type: Integer, default: 1
            end
          end
        end
      end
      subject.post('/nested_given_and_default') { declared(self.params) }

      params = {
        ary: [
          { has_value: false },
          { has_value: true, type: 'int', int: 123 },
          { has_value: true, type: 'str', str: 'b' }
        ]
      }
      expected = {
        'ary' => [
          { 'has_value' => false, 'type' => nil,   'int' => nil, 'str' => nil },
          { 'has_value' => true,  'type' => 'int', 'int' => 123, 'str' => nil },
          { 'has_value' => true,  'type' => 'str', 'int' => nil, 'str' => 'b' }
        ]
      }

      post '/nested_given_and_default', params
      expect(last_response.status).to eq(201)
      expect(JSON.parse(last_response.body)).to eq(expected)
    end
  end
end
