require File.expand_path('helper', __dir__)

class DelegatorTest < Minitest::Test
  class Mirror
    attr_reader :last_call
    def method_missing(*a, &b)
      @last_call = [*a.map(&:to_s)]
      @last_call << b if b
    end
  end

  def self.delegates(name)
    it "delegates #{name}" do
      m = mirror { send name }
      assert_equal [name.to_s], m.last_call
    end

    it "delegates #{name} with arguments" do
      m = mirror { send name, "foo", "bar" }
      assert_equal [name.to_s, "foo", "bar"], m.last_call
    end

    it "delegates #{name} with block" do
      block = proc { }
      m = mirror { send(name, &block) }
      assert_equal [name.to_s, block], m.last_call
    end
  end

  setup do
    @target_was = Sinatra::Delegator.target
  end

  def teardown
    Sinatra::Delegator.target = @target_was
  end

  def delegation_app(&block)
    mock_app { Sinatra::Delegator.target = self }
    delegate(&block)
  end

  def mirror(&block)
    mirror = Mirror.new
    Sinatra::Delegator.target = mirror
    delegate(&block)
  end

  def delegate(&block)
    assert Sinatra::Delegator.target != Sinatra::Application
    Object.new.extend(Sinatra::Delegator).instance_eval(&block) if block
    Sinatra::Delegator.target
  end

  def target
    Sinatra::Delegator.target
  end

  it 'defaults to Sinatra::Application as target' do
    assert_equal Sinatra::Application, Sinatra::Delegator.target
  end

  %w[get put post delete options patch link unlink].each do |verb|
    it "delegates #{verb} correctly" do
      delegation_app do
        send(verb, '/hello') { 'Hello World' }
      end

      request = Rack::MockRequest.new(@app)
      response = request.request(verb.upcase, '/hello', {})
      assert response.ok?
      assert_equal 'Hello World', response.body
    end
  end

  it "delegates head correctly" do
    delegation_app do
      head '/hello' do
        response['X-Hello'] = 'World!'
        'remove me'
      end
    end

    request = Rack::MockRequest.new(@app)
    response = request.request('HEAD', '/hello', {})
    assert response.ok?
    assert_equal 'World!', response['X-Hello']
    assert_equal '', response.body
  end

  it "delegates before with keyword arguments correctly" do
    delegation_app do
      set(:foo) do |something|
        something
      end
      before(foo: 'bar') do
        :nothing
      end
    end
  end

  it "registers extensions with the delegation target" do
    app, mixin = mirror, Module.new
    Sinatra.register mixin
    assert_equal ["register", mixin.to_s], app.last_call
  end

  it "registers helpers with the delegation target" do
    app, mixin = mirror, Module.new
    Sinatra.helpers mixin
    assert_equal ["helpers", mixin.to_s], app.last_call
  end

  it "registers middleware with the delegation target" do
    app, mixin = mirror, Module.new
    Sinatra.use mixin
    assert_equal ["use", mixin.to_s], app.last_call
  end

  it "should work with method_missing proxies for options" do
    mixin = Module.new do
      def respond_to?(method, *)
        method.to_sym == :options or super
      end

      def method_missing(method, *args, &block)
        return super unless method.to_sym == :options
        {:some => :option}
      end
    end

    value = nil
    mirror do
      extend mixin
      value = options
    end

    assert_equal({:some => :option}, value)
  end

  it "delegates crazy method names" do
    Sinatra::Delegator.delegate "foo:bar:"
    method = mirror { send "foo:bar:" }.last_call.first
    assert_equal "foo:bar:", method
  end

  delegates 'get'
  delegates 'patch'
  delegates 'put'
  delegates 'post'
  delegates 'delete'
  delegates 'head'
  delegates 'options'
  delegates 'template'
  delegates 'layout'
  delegates 'before'
  delegates 'after'
  delegates 'error'
  delegates 'not_found'
  delegates 'configure'
  delegates 'set'
  delegates 'mime_type'
  delegates 'enable'
  delegates 'disable'
  delegates 'use'
  delegates 'development?'
  delegates 'test?'
  delegates 'production?'
  delegates 'helpers'
  delegates 'settings'
end
