require 'spec_helper'
require 'callback_examples'

require 'ffi/http/parser/instance'

describe Instance do
  describe "#initialize" do
    context "when initialized from a pointer" do
      it "should not call http_parser_init" do
        ptr = described_class.new.to_ptr

        FFI::HTTP::Parser.should_not_receive(:http_parser_init)

        described_class.new(ptr)
      end
    end

    context "when given a block" do
      it "should yield the new Instance" do
        expected = nil

        described_class.new { |parser| expected = parser }

        expected.should be_kind_of(described_class)
      end

      it "should allow changing the parser type" do
        parser = described_class.new do |parser|
          parser.type = :both
        end

        parser.type.should == :both
      end
    end
  end

  describe "#type" do
    it "should default to :request" do
      subject.type.should == :request
    end

    it "should convert the type to a Symbol" do
      subject[:type_flags] = TYPES[:both]

      subject.type.should == :both
    end

    it "should extract the type from the type_flags field" do
      subject[:type_flags] = ((0xff & ~0x3) | TYPES[:both])

      subject.type.should == :both
    end
  end

  describe "#type=" do
    it "should set the type" do
      subject.type = :both

      subject.type.should == :both
    end

    it "should not change flags" do
      flags = (0xff & ~0x3)
      subject[:type_flags] = flags

      subject.type = :both

      subject[:type_flags].should == (flags | TYPES[:both])
    end
  end

  describe "#<<" do
    it "should call http_parser_execute" do
      FFI::HTTP::Parser.should_receive(:http_parser_execute)

      subject << "GET / HTTP/1.1\r\n"
    end
  end

  describe "callbacks" do
    describe "on_message_begin" do
      include_examples "callback", {:on_message_begin => :on_path}

      subject do
        described_class.new do |parser|
          parser.on_message_begin { @begun = true }
        end
      end

      it "should trigger on a new request" do
        subject << "GET / HTTP/1.1"

        @begun.should be_true
      end
    end

    describe "on_path" do
      include_examples "callback", {:on_path => :on_query_string}

      let(:expected) { '/foo' }

      subject do
        described_class.new do |parser|
          parser.on_path { |data| @path = data }
        end
      end

      it "should pass the recognized path" do
        subject << "GET "

        @path.should be_nil

        subject << "#{expected} HTTP/1.1"

        @path.should == expected
      end
    end

    describe "on_query_string" do
      include_examples "callback", {:on_query_string => :on_fragment}

      let(:expected) { 'x=1&y=2' }

      subject do
        described_class.new do |parser|
          parser.on_query_string { |data| @query_string = data }
        end
      end

      it "should pass the recognized query_string" do
        subject << "GET /foo"

        @query_string.should be_nil

        subject << "?#{expected} HTTP/1.1"

        @query_string.should == expected
      end
    end

    describe "on_fragment" do
      include_examples "callback", {:on_fragment => :on_header_field}

      let(:expected) { 'bar' }

      subject do
        described_class.new do |parser|
          parser.on_fragment { |data| @fragment = data }
        end
      end

      it "should pass the recognized fragment" do
        subject << "GET /foo"

        @fragment.should be_nil

        subject << "##{expected} HTTP/1.1"

        @fragment.should == expected
      end
    end

    describe "on_url" do
      include_examples "callback", {:on_url => :on_header_field}

      let(:expected) { '/foo?q=1' }

      subject do
        described_class.new do |parser|
          parser.on_url { |data| @url = data }
        end
      end

      it "should pass the recognized url" do
        subject << "GET "

        @url.should be_nil

        subject << "#{expected} HTTP/1.1"

        @url.should == expected
      end
    end

    describe "on_header_field" do
      include_examples "callback", {:on_header_field => :on_header_value}

      let(:expected) { 'Host' }

      subject do
        described_class.new do |parser|
          parser.on_header_field { |data| @header_field = data }
        end
      end

      it "should pass the recognized header-name" do
        subject << "GET /foo HTTP/1.1\r\n"

        @header_field.should be_nil

        subject << "#{expected}: example.com\r\n"

        @header_field.should == expected
      end
    end

    describe "on_header_value" do
      include_examples "callback", {:on_header_value => :on_body}

      let(:expected) { 'example.com' }

      subject do
        described_class.new do |parser|
          parser.on_header_value { |data| @header_value = data }
        end
      end

      it "should pass the recognized header-value" do
        subject << "GET /foo HTTP/1.1\r\n"

        @header_value.should be_nil

        subject << "Host: #{expected}\r\n"

        @header_value.should == expected
      end
    end

    describe "on_headers_complete" do
      include_examples "callback", {:on_headers_complete => :on_body}

      subject do
        described_class.new do |parser|
          parser.on_headers_complete { @header_complete = true }
        end
      end

      it "should trigger on the last header" do
        subject << "GET / HTTP/1.1\r\n"
        subject << "Host: example.com\r\n"

        @header_complete.should be_nil

        subject << "\r\n"

        @header_complete.should be_true
      end

      context "when the callback returns :stop" do
        subject do
          described_class.new do |parser|
            parser.on_headers_complete { :stop }

            parser.on_body { |data| @body = data }
          end
        end

        it "should indicate there is no request body to parse" do
          subject << "GET / HTTP/1.1\r\n"
          subject << "Host: example.com\r\n"
          subject << "\r\n"
          subject << "Body"

          @body.should be_nil
        end
      end
    end

    describe "on_body" do
      include_examples "callback", {:on_body => :on_message_complete}

      let(:expected) { "Body" }

      subject do
        described_class.new do |parser|
          parser.on_body { |data| @body = data }
        end
      end

      it "should trigger on the body" do
        subject << "POST / HTTP/1.1\r\n"
        subject << "Transfer-Encoding: chunked\r\n"
        subject << "\r\n"

        @body.should be_nil

        subject << "#{"%x" % expected.length}\r\n"
        subject << expected

        @body.should == expected
      end
    end

    describe "on_message_complete" do
      subject do
        described_class.new do |parser|
          parser.on_message_complete { @message_complete = true }
        end
      end

      it "should trigger at the end of the message" do
        subject << "GET / HTTP/1.1\r\n"

        @message_complete.should be_nil

        subject << "Host: example.com\r\n\r\n"

        @message_complete.should be_true
      end
    end
  end

  describe "#reset!" do
    it "should call http_parser_init" do
      parser = described_class.new

      FFI::HTTP::Parser.should_receive(:http_parser_init)

      parser.reset!
    end

    it "should not change the type" do
      parser = described_class.new do |parser|
        parser.type = :both
      end

      parser.reset!

      parser.type.should == :both
    end
  end

  describe "#http_method" do
    let(:expected) { :POST }

    it "should set the http_method field" do
      subject << "#{expected} / HTTP/1.1\r\n"

      subject.http_method.should == expected
    end
  end

  describe "#http_major" do
    let(:expected) { 1 }

    context "when parsing requests" do
      it "should set the http_major field" do
        subject << "GET / HTTP/#{expected}."

        subject.http_major.should == expected
      end
    end

    context "when parsing responses" do
      subject do
        described_class.new do |parser|
          parser.type = :response
        end
      end

      it "should set the http_major field" do
        subject << "HTTP/#{expected}."

        subject.http_major.should == expected
      end
    end
  end

  describe "#http_minor" do
    let(:expected) { 2 }

    context "when parsing requests" do
      it "should set the http_minor field" do
        subject << "GET / HTTP/1.#{expected}\r\n"

        subject.http_minor.should == expected
      end
    end

    context "when parsing responses" do
      subject do
        described_class.new do |parser|
          parser.type = :response
        end
      end

      it "should set the http_major field" do
        subject << "HTTP/1.#{expected} "

        subject.http_minor.should == expected
      end
    end
  end

  describe "#http_version" do
    let(:expected) { '1.1' }

    before do
      subject << "GET / HTTP/#{expected}\r\n"
    end

    it "should combine #http_major and #http_minor" do
      subject.http_version.should == expected
    end
  end

  describe "#http_status" do
    context "when parsing requests" do
      before do
        subject << "GET / HTTP/1.1\r\n"
        subject << "Host: example.com\r\n"
        subject << "\r\n"
      end

      it "should not be set" do
        subject.http_status.should be_zero
      end
    end

    context "when parsing responses" do
      let(:expected) { 200 }

      subject do
        described_class.new do |parser|
          parser.type = :response
        end
      end

      before do
        subject << "HTTP/1.1 #{expected} OK\r\n"
        subject << "Location: http://example.com/\r\n"
        subject << "\r\n"
      end

      it "should set the http_status field" do
        subject.http_status.should == expected
      end
    end
  end

  describe "#upgrade?" do
    let(:upgrade) { 'WebSocket' }

    before do
      subject << "GET /demo HTTP/1.1\r\n"
      subject << "Upgrade: #{upgrade}\r\n"
      subject << "Connection: Upgrade\r\n"
      subject << "Host: example.com\r\n"
      subject << "Origin: http://example.com\r\n"
      subject << "WebSocket-Protocol: sample\r\n"
      subject << "\r\n"
    end

    it "should return true if the Upgrade header was set" do
      subject.upgrade?.should be_true
    end
  end
end