# frozen_string_literal: true

require './lib/param_validation.rb'
require 'minitest/autorun'

class ParamValidationTest < Minitest::Test
  def setup; end

  def test_required
    begin; ParamValidation.new({}, x: { required: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  # If a key is not required, then don't run the tests on it
  def test_not_required_and_absent_then_tests_do_not_run
    ParamValidation.new({}, x: { max: 100 })
    assert true
  end

  def test_not_blank_fail
    begin; ParamValidation.new({ x: '' }, x: { not_blank: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_not_blank_fail_nil
    begin; ParamValidation.new({ x: nil }, x: { not_blank: true, required: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert(e.data.one? { |i| i[:name] == :not_blank && i[:key] == :x })
    assert(e.data.one? { |i| i[:name] == :required && i[:key] == :x })
  end

  def test_not_blank_succeed
    ParamValidation.new({ x: 'x' }, x: { not_blank: true })
    assert true
  end

  def test_require_no_err
    begin; ParamValidation.new({ x: 1 }, x: { required: true })
    rescue ParamValidation::ValidationError => e; end
    assert e.nil?
  end

  def test_absent
    begin; ParamValidation.new({ x: 1 }, x: { absent: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_not_included_in
    begin; ParamValidation.new({ x: 1 }, x: { not_included_in: [1] })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_included_in
    begin; ParamValidation.new({ x: 1 }, x: { included_in: [2] })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_format
    begin; ParamValidation.new({ x: 'x' }, x: { format: /y/ })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_is_reference_string
    begin; ParamValidation.new({ x: '-0' }, x: { is_reference: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_is_reference_negative_integer
    begin; ParamValidation.new({ x: -1 }, x: { is_reference: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_is_reference_passes
    ParamValidation.new({ x: '0' }, x: { is_reference: true })
    ParamValidation.new({ x: 1 }, x: { is_reference: true })
    ParamValidation.new({ x: '' }, x: { is_reference: true })
    pass
  end

  def test_is_integer
    begin; ParamValidation.new({ x: 'x' }, x: { is_integer: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_is_float
    begin; ParamValidation.new({ x: 'x' }, x: { is_float: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_min_length
    begin; ParamValidation.new({ x: [] }, x: { min_length: 2 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal :x, e.data[:key]
  end

  def test_max_length
    begin; ParamValidation.new({ x: [1, 2, 3] }, x: { max_length: 2 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:key], :x
  end

  def test_length_range
    begin; ParamValidation.new({ x: [1, 2, 3, 4] }, x: { length_range: 1..3 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:key], :x
  end

  def test_length_equals
    begin; ParamValidation.new({ x: [1, 2] }, x: { length_equals: 1 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:key], :x
  end

  def test_min
    begin; ParamValidation.new({ x: 1 }, x: { min: 2 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:key], :x
  end

  def test_max
    begin; ParamValidation.new({ x: 4 }, x: { max: 2 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:name], :max
  end

  def test_in_range
    begin; ParamValidation.new({ x: 1 }, x: { in_range: 2..4 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal e.data[:val], 1
  end

  def test_equals
    begin; ParamValidation.new({ x: 1 }, x: { equals: 2 })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal 'x should equal 2', e.to_s
  end

  def test_root_array_of_hashes
    begin; ParamValidation.new({ x: 1 }, root: { array_of_hashes: { x: { required: true } } })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal 'Please pass in an array of hashes', e.to_s
  end

  def test_root_array_of_hashes_with_nesting_ok
    v = ParamValidation.new([{ 'x' => 1 }, { x: 1 }], root: { array_of_hashes: { x: { is_integer: true } } })
    assert_equal v, v # test that it does not raise
  end

  def test_root_array_of_hashes_with_nesting
    begin; ParamValidation.new([{ x: 1 }, { x: 'hi' }], root: { array_of_hashes: { x: { is_integer: true } } })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal 'x should be an integer', e.to_s
  end

  def test_is_json_with_string
    begin; ParamValidation.new({ x: '[[[[[[[' }, x: { is_json: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal 'x should be valid JSON', e.to_s
  end

  def test_is_json_without_string
    begin; ParamValidation.new({ x: {} }, x: { is_json: true })
    rescue ParamValidation::ValidationError => e; e; end
    assert_equal 'x should be valid JSON', e.to_s
  end

  def test_is_a_single
    ParamValidation.new({ x: 5.6 }, x: { is_a: Float })
    begin
      ParamValidation.new({ x: 5.6 }, x: { is_a: Integer })
    rescue ParamValidation::ValidationError => e
      e
    end
    assert_equal 'x should be of the type(s): Integer', e.to_s
  end

  def test_is_a_multiple
    ParamValidation.new({ x: 5.6 }, x: { is_a: [Integer, Float] })
    begin
      ParamValidation.new({ x: 5.6 }, x: { is_a: [Integer, Array] })
    rescue ParamValidation::ValidationError => e
      e
    end

    assert_equal 'x should be of the type(s): Integer, Array', e.to_s
  end

  def test_can_be_date
    ParamValidation.new({ x: Date.new }, x: { can_be_date: true })
    ParamValidation.new({ x: DateTime.new }, x: { can_be_date: true })
    ParamValidation.new({ x: '2017-05-15T12:00:00.000Z' }, x: { can_be_date: true })
    ParamValidation.new({ x: '2017-05-15' }, x: { can_be_date: true })

    begin
      ParamValidation.new({ x: 'not_a _date' }, x: { can_be_date: true })
    rescue ParamValidation::ValidationError => e
      e
    end

    assert_equal 'x should be a datetime or be parsable as one', e.to_s
  end

  def test_add_validator
    ParamValidation.add_validator(:dollars) { |val, _arg, _data| val =~ /^\d+(\.\d\d)?$/ }
    begin
      ParamValidation.new({ x: 'hi' }, x: { dollars: true })
    rescue ParamValidation::ValidationError => e
      e
    end
    assert_equal :dollars, e.data[:name]
  end

  def test_set_message
    ParamValidation.add_validator(:dollars) { |val, _arg, _data| val =~ /^\d+(\.\d\d)?$/ }
    ParamValidation.set_message(:dollars) { |h| "#{h[:key]} must be a dollar amount" }
    begin
      ParamValidation.new({ x: 'hi' }, x: { dollars: true })
    rescue ParamValidation::ValidationError => e
      e
    end
    assert_equal 'x must be a dollar amount', e.to_s
  end

  def test_custom_validator; end
end
