require 'test_helper'

class ActionTest < MiniTest::Should::TestCase
  context "An Action" do
    setup do
      @svc = BananaPhone::Service.new('localhost', 9941)
      @req = @svc.call
    end

    should "be created with a Service, module name, fun name, and args" do
      assert BananaPhone::Action.new(@svc, @req, :mymod, :myfun, [1, 2]).is_a?(BananaPhone::Action)
    end
  end

  context "An Action instance" do
    setup do
      @svc = BananaPhone::Service.new('localhost', 9941)
      @req = @svc.call
      @enc = Enc.new
    end

    should "call with single-arity" do
      req = @enc.encode_ruby_request([:call, :mymod, :myfun, [1]])
      res = @enc.encode_ruby_request([:reply, 2])
      call = BananaPhone::Action.new(@svc, @req, :mymod, :myfun, [1])
      call.expects(:transaction).with(req).returns(res)
      assert_equal 2, call.execute
    end

    should "call with single-arity array" do
      req = @enc.encode_ruby_request([:call, :mymod, :myfun, [[1, 2, 3]]])
      res = @enc.encode_ruby_request([:reply, [4, 5, 6]])
      call = BananaPhone::Action.new(@svc, @req, :mymod, :myfun, [[1, 2, 3]])
      call.expects(:transaction).with(req).returns(res)
      assert_equal [4, 5, 6], call.execute
    end

    should "call with multi-arity" do
      req = @enc.encode_ruby_request([:call, :mymod, :myfun, [1, 2, 3]])
      res = @enc.encode_ruby_request([:reply, [4, 5, 6]])
      call = BananaPhone::Action.new(@svc, @req, :mymod, :myfun, [1, 2, 3])
      call.expects(:transaction).with(req).returns(res)
      assert_equal [4, 5, 6], call.execute
    end

    context "sync_request" do
      setup do
        @svc = BananaPhone::Service.new('localhost', 9941)
        @req = @svc.call
        @call = BananaPhone::Action.new(@svc, @req, :mymod, :myfun, [])
      end

      should "read and write BananaPack from the socket" do
        io = stub()
        io.expects(:write).with("\000\000\000\003")
        io.expects(:write).with("foo")
        @call.expects(:read).with(io, 4, nil).returns("\000\000\000\003")
        @call.expects(:read).with(io, 3, nil).returns("bar")
        io.expects(:close)
        @call.expects(:connect_to).returns(io)
        assert_equal "bar", @call.transaction("foo")
      end

      should "raise a ProtocolError when the length is invalid" do
        io = stub()
        io.expects(:write).with("\000\000\000\003")
        io.expects(:write).with("foo")
        @call.expects(:read).with(io, 4, nil).returns(nil)
        @call.expects(:connect_to).returns(io)
        begin
          @call.transaction("foo")
          fail "Should have thrown an error"
        rescue BananaPhone::ProtocolError => e
          assert_equal 0, e.code
        end
      end

      should "raise a ProtocolError when the data is invalid" do
        io = stub()
        io.expects(:write).with("\000\000\000\003")
        io.expects(:write).with("foo")
        @call.expects(:read).with(io, 4, nil).returns("\000\000\000\003")
        @call.expects(:read).with(io, 3, nil).returns(nil)
        @call.expects(:connect_to).returns(io)
         begin
          @call.transaction("foo")
          fail "Should have thrown an error"
        rescue BananaPhone::ProtocolError => e
          assert_equal 1, e.code
        end
      end

      should "raise a ReadTimeoutError when the connection times out" do
        io = stub()
        io.expects(:write).with("\000\000\000\003")
        io.expects(:write).with("foo")
        @call.expects(:read).with(io, 4, nil).raises(Errno::EAGAIN)
        @call.expects(:connect_to).returns(io)
        begin
          @call.transaction("foo")
          fail "Should have thrown an error"
        rescue BananaPhone::ReadTimeoutError => e
          assert_equal 0, e.code
          assert_equal 'localhost', e.host
          assert_equal 9941, e.port
        end
      end

      should "raise a ReadError when the socket becomes unreadable" do
        io = stub()
        io.expects(:write).with("\000\000\000\003")
        io.expects(:write).with("foo")
        @call.expects(:read).with(io, 4, nil).raises(Errno::ECONNRESET)
        @call.expects(:connect_to).returns(io)
        begin
          @call.transaction("foo")
          fail "Should have thrown an error"
        rescue BananaPhone::ReadError => e
          assert_equal 0, e.code
          assert_equal 'localhost', e.host
          assert_equal 9941, e.port
        end
      end
    end
  end
end
