require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

unless defined? SAMPLE_HEADERS
  SAMPLE_HEADERS = { "Content-Length" => "8888", "Accept" => "application/json" }
  ESCAPED_PARAMS = "x=ab%2Bc&z=%27Stop%21%27%20said%20Fred"
  NOT_ESCAPED_PARAMS = "z='Stop!' said Fred&x=ab c"
end

describe "WebMock", :shared => true do
  before(:each) do
    WebMock.disable_net_connect!
    RequestRegistry.instance.reset_webmock
  end

  describe "when web connect" do

    describe "is allowed" do
      before(:each) do
        WebMock.allow_net_connect!
      end

      it "should make a real web request if request is not stubbed" do
        setup_expectations_for_real_example_com_request
        http_request(:get, "http://www.example.com/").
          body.should =~ /.*Google fake response.*/
      end

      it "should make a real https request if request is not stubbed" do
        setup_expectations_for_real_example_com_request(:port => 443)
        http_request(:get, "https://www.example.com/").
          body.should =~ /.*Google fake response.*/
      end

      it "should return stubbed response if request was stubbed" do
        stub_http_request(:get, "www.example.com").to_return(:body => "abc")
        http_request(:get, "http://www.example.com/").body.should == "abc"
      end
    end

    describe "is not allowed" do
      before(:each) do
        WebMock.disable_net_connect!
      end

      it "should return stubbed response if request was stubbed" do
        stub_http_request(:get, "www.example.com").to_return(:body => "abc")
        http_request(:get, "http://www.example.com/").body.should == "abc"
      end

      it "should raise exception if request was not stubbed" do
        lambda {
          http_request(:get, "http://www.example.com/")
        }.should fail_with("Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/")
      end
    end

  end

  describe "when matching requests" do

    describe "on uri" do

      it "should match the request by uri with non escaped params if request have escaped parameters" do
        stub_http_request(:get, "www.example.com/?#{NOT_ESCAPED_PARAMS}").to_return(:body => "abc")
        http_request(:get, "http://www.example.com/?#{ESCAPED_PARAMS}").body.should == "abc"
      end

      it "should match the request by uri with escaped parameters even if request has non escaped params" do
        stub_http_request(:get, "www.example.com/?#{ESCAPED_PARAMS}").to_return(:body => "abc")
        http_request(:get, "http://www.example.com/?#{NOT_ESCAPED_PARAMS}").body.should == "abc"
      end

      it "should match the request by regexp matching non escaped params uri if request params are escaped" do
        stub_http_request(:get, /.*x=ab c.*/).to_return(:body => "abc")
        http_request(:get, "http://www.example.com/?#{ESCAPED_PARAMS}").body.should == "abc"
      end

    end

    describe "on method" do

      it "should match the request by method if registered" do
        stub_http_request(:get, "www.example.com")
        http_request(:get, "http://www.example.com/").status.should == "200"
      end

      it "should not match requests if method is different" do
        stub_http_request(:get, "www.example.com")
        http_request(:get, "http://www.example.com/").status.should == "200"
        lambda {
          http_request(:post, "http://www.example.com/")
        }.should fail_with("Real HTTP connections are disabled. Unregistered request: POST http://www.example.com/"
        )
      end

    end

    describe "on body" do

      it "should match requests if body is the same" do
        stub_http_request(:get, "www.example.com").with(:body => "abc")
        http_request(
          :get, "http://www.example.com/",
          :body => "abc").status.should == "200"
      end

      it "should match requests if body is not set in the stub" do
        stub_http_request(:get, "www.example.com")
        http_request(
          :get, "http://www.example.com/",
          :body => "abc").status.should == "200"
      end

      it "should not match requests if body is different" do
        stub_http_request(:get, "www.example.com").with(:body => "abc")
        lambda {
          http_request(:get, "http://www.example.com/", :body => "def")
        }.should fail_with("Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/ with body 'def'")
      end

    end

    describe "on headers" do

      it "should match requests if headers are the same" do
        stub_http_request(:get, "www.example.com").with(:headers => SAMPLE_HEADERS )
        http_request(
          :get, "http://www.example.com/",
          :headers => SAMPLE_HEADERS).status.should == "200"
      end

      it "should match requests if request headers are not stubbed" do
        stub_http_request(:get, "www.example.com")
        http_request(
          :get, "http://www.example.com/",
          :headers => SAMPLE_HEADERS).status.should == "200"
      end

      it "should not match requests if headers are different" do
        stub_http_request(:get, "www.example.com").with(:headers => SAMPLE_HEADERS)

        lambda {
          http_request(
            :get, "http://www.example.com/",
          :headers => { 'Content-Length' => '9999'})
        }.should fail_with(%q(Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/ with headers {'Content-Length'=>'9999'}))
      end
      
      it "should not match if accept header is different" do
        stub_http_request(:get, "www.example.com").
          with(:headers => { 'Accept' => 'application/json'})
        lambda {
          http_request(
            :get, "http://www.example.com/",
            :headers => { 'Accept' => 'application/xml'})
        }.should fail_with(%q(Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/ with headers {'Accept'=>'application/xml'}))  
      end
    end

    describe "with basic authentication" do

      it "should match if credentials are the same" do
        stub_http_request(:get, "user:pass@www.example.com")
        http_request(:get, "http://user:pass@www.example.com/").status.should == "200"
      end

      it "should not match if credentials are different" do
        stub_http_request(:get, "user:pass@www.example.com")
        lambda {
          http_request(:get, "http://user:pazz@www.example.com/").status.should == "200"
        }.should fail_with(%q(Real HTTP connections are disabled. Unregistered request: GET http://user:pazz@www.example.com/))
      end

      it "should not match if credentials are stubbed but not provided in the request" do
        stub_http_request(:get, "user:pass@www.example.com")
        lambda {
          http_request(:get, "http://www.example.com/").status.should == "200"
        }.should fail_with(%q(Real HTTP connections are disabled. Unregistered request: GET http://www.example.com/))
      end

      it "should not match if credentials are not stubbed but exist in the request" do
        stub_http_request(:get, "www.example.com")
        lambda {
          http_request(:get, "http://user:pazz@www.example.com/").status.should == "200"
        }.should fail_with(%q(Real HTTP connections are disabled. Unregistered request: GET http://user:pazz@www.example.com/))
      end

    end

  end

  describe "raising stubbed exceptions" do

    it "should raise exception if declared in a stubbed response" do
      class MyException < StandardError; end;
        stub_http_request(:get, "www.example.com").to_raise(MyException)
        lambda {
          http_request(:get, "http://www.example.com/")
        }.should raise_error(MyException, "Exception from WebMock")
      end

    end


    describe "returning stubbed responses" do

      it "should return declared body" do
        stub_http_request(:get, "www.example.com").to_return(:body => "abc")
        http_request(:get, "http://www.example.com/").body.should == "abc"
      end

      it "should return declared headers" do
        stub_http_request(:get, "www.example.com").to_return(:headers => SAMPLE_HEADERS)
        response = http_request(:get, "http://www.example.com/")
        response.headers["Content-Length"].should == "8888"
      end

      it "should return declared status" do
        stub_http_request(:get, "www.example.com").to_return(:status => 500)
        http_request(:get, "http://www.example.com/").status.should == "500"
      end

      it "should return body declared as IO" do
        stub_http_request(:get, "www.example.com").to_return(:body => File.new(__FILE__))
        http_request(:get, "http://www.example.com/").body.should == File.new(__FILE__).read
      end

      it "should return body declared as IO if requested many times" do
        stub_http_request(:get, "www.example.com").to_return(:body => File.new(__FILE__))
        2.times do
          http_request(:get, "http://www.example.com/").body.should == File.new(__FILE__).read
        end
      end

      describe "dynamic responses" do
        
        it "should return evaluated response body" do
          stub_http_request(:post, "www.example.com").to_return(:body => lambda { |request| request.body })
          http_request(:post, "http://www.example.com/", :body => "echo").body.should == "echo"
        end
        
        it "should return evaluated response headers" do
          stub_http_request(:post, "www.example.com").to_return(:headers => lambda { |request| request.headers })
          http_request(:post, "http://www.example.com/", :headers => {'A' => 'B'}).headers['A'].should == 'B'
        end        
        
      end

    end


    describe "precedence of stubs" do

      it "should use the last declared matching request stub" do
        stub_http_request(:get, "www.example.com").to_return(:body => "abc")
        stub_http_request(:get, "www.example.com").to_return(:body => "def")
        http_request(:get, "http://www.example.com/").body.should == "def"
      end

      it "should not be affected by the type of uri or request method" do
        stub_http_request(:get, "www.example.com").to_return(:body => "abc")
        stub_http_request(:any, /.*example.*/).to_return(:body => "def")
        http_request(:get, "http://www.example.com/").body.should == "def"
      end

    end

    describe "verification of request expectation" do

      describe "when net connect not allowed" do

        before(:each) do
          WebMock.disable_net_connect!
          stub_http_request(:any, "http://www.example.com")
          stub_http_request(:any, "https://www.example.com")
        end

        it "should pass if request was executed with the same uri and method" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made.once
          }.should_not raise_error
        end

        it "should pass if request was not expected and not executed" do
          lambda {
            request(:get, "http://www.example.com").should_not have_been_made
          }.should_not raise_error
        end

        it "should fail if request was not expected but executed" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should_not have_been_made
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 0 times but it executed 1 time")
        end


        it "should fail if request was not executed" do
          lambda {
            request(:get, "http://www.example.com").should have_been_made
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 1 time but it executed 0 times")
        end

        it "should fail if request was executed to different uri" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.org").should have_been_made
          }.should fail_with("The request GET http://www.example.org/ was expected to execute 1 time but it executed 0 times")
        end

        it "should fail if request was executed with different method" do
          lambda {
            http_request(:post, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 1 time but it executed 0 times")
        end

        it "should pass if request was executed with different form of uri" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "www.example.com").should have_been_made
          }.should_not raise_error
        end

        it "should pass if request was executed with different form of uri without port " do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "www.example.com:80").should have_been_made
          }.should_not raise_error
        end

        it "should pass if request was executed with different form of uri with port" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "www.example.com:80").should have_been_made
          }.should_not raise_error
        end

        it "should fail if request was executed with different  port" do
          lambda {
            http_request(:get, "http://www.example.com:80/")
            request(:get, "www.example.com:90").should have_been_made
          }.should fail_with("The request GET http://www.example.com:90/ was expected to execute 1 time but it executed 0 times")
        end

        it "should pass if request was executed with different form of uri with https port" do
          lambda {
            http_request(:get, "https://www.example.com/")
            request(:get, "https://www.example.com:443/").should have_been_made
          }.should_not raise_error
        end

        describe "when matching requests with escaped uris" do

          before(:each) do
            WebMock.disable_net_connect!
            stub_http_request(:any, "http://www.example.com/?#{NOT_ESCAPED_PARAMS}")
          end

          it "should pass if request was executed with escaped params" do
            lambda {
              http_request(:get, "http://www.example.com/?#{ESCAPED_PARAMS}")
              request(:get, "http://www.example.com/?#{NOT_ESCAPED_PARAMS}").should have_been_made
            }.should_not raise_error
          end

          it "should pass if request was executed with non escaped params but escaped expected" do
            lambda {
              http_request(:get, "http://www.example.com/?#{NOT_ESCAPED_PARAMS}")
              request(:get, "http://www.example.com/?#{ESCAPED_PARAMS}").should have_been_made
            }.should_not raise_error
          end

          it "should pass if request was executed with escaped params but uri matichg regexp expected" do
            lambda {
              http_request(:get, "http://www.example.com/?#{ESCAPED_PARAMS}")
              request(:get, /.*example.*/).should have_been_made
            }.should_not raise_error
          end
        end

        it "should fail if requested more times than expected" do
          lambda {
            http_request(:get, "http://www.example.com/")
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 1 time but it executed 2 times")
        end

        it "should fail if requested less times than expected" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made.twice
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 2 times but it executed 1 time")
        end

        it "should fail if requested less times than expected when 3 times expected" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made.times(3)
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 3 times but it executed 1 time")
        end

        it "should succeed if request was executed with the same body" do
          lambda {
            http_request(:get, "http://www.example.com/", :body => "abc")
            request(:get, "www.example.com").with(:body => "abc").should have_been_made
          }.should_not raise_error
        end

        it "should fail if request was executed with different body" do
          lambda {
            http_request(:get, "http://www.example.com/", :body => "abc")
            request(:get, "www.example.com").
            with(:body => "def").should have_been_made
          }.should fail_with("The request GET http://www.example.com/ with body 'def' was expected to execute 1 time but it executed 0 times")
        end

        it "should succeed if request was executed with the same headers" do
          lambda {
            http_request(:get, "http://www.example.com/", :headers => SAMPLE_HEADERS)
            request(:get, "www.example.com").
            with(:headers => SAMPLE_HEADERS).should have_been_made
          }.should_not raise_error
        end

        it "should fail if request was executed with different headers" do
          lambda {
            http_request(:get, "http://www.example.com/", :headers => SAMPLE_HEADERS)
            request(:get, "www.example.com").
            with(:headers => { 'Content-Length' => '9999'}).should have_been_made
          }.should fail_with("The request GET http://www.example.com/ with headers {'Content-Length'=>'9999'} was expected to execute 1 time but it executed 0 times")
        end

        it "should fail if request was executed with less headers" do
          lambda {
            http_request(:get, "http://www.example.com/", :headers => {'A' => 'a'})
            request(:get, "www.example.com").
            with(:headers => {'A' => 'a', 'B' => 'b'}).should have_been_made
          }.should fail_with("The request GET http://www.example.com/ with headers {'A'=>'a', 'B'=>'b'} was expected to execute 1 time but it executed 0 times")
        end

        it "should succeed if request was executed with more headers" do
          lambda {
            http_request(:get, "http://www.example.com/",
              :headers => {'A' => 'a', 'B' => 'b'}
            )
            request(:get, "www.example.com").
            with(:headers => {'A' => 'a'}).should have_been_made
          }.should_not raise_error
        end

        it "should succeed if request was executed with body and headers but they were not specified in expectantion" do
          lambda {
            http_request(:get, "http://www.example.com/",
              :body => "abc",
              :headers => SAMPLE_HEADERS
            )
            request(:get, "www.example.com").should have_been_made
          }.should_not raise_error
        end


        describe "with authentication" do
          before(:each) do
            stub_http_request(:any, "http://user:pass@www.example.com")
            stub_http_request(:any, "http://user:pazz@www.example.com")
          end

          it "should succeed if succeed if request was executed with expected credentials" do
            lambda {
              http_request(:get, "http://user:pass@www.example.com/")
              request(:get, "http://user:pass@www.example.com").should have_been_made.once
            }.should_not raise_error
          end

          it "should fail if request was executed with different credentials than expected" do
            lambda {
              http_request(:get, "http://user:pass@www.example.com/")
              request(:get, "http://user:pazz@www.example.com").should have_been_made.once
            }.should fail_with("The request GET http://user:pazz@www.example.com/ was expected to execute 1 time but it executed 0 times")
          end

          it "should fail if request was executed without credentials but credentials were expected" do
            lambda {
              http_request(:get, "http://www.example.com/")
              request(:get, "http://user:pass@www.example.com").should have_been_made.once
            }.should fail_with("The request GET http://user:pass@www.example.com/ was expected to execute 1 time but it executed 0 times")
          end

          it "should fail if request was executed with credentials but expected without" do
            lambda {
              http_request(:get, "http://user:pass@www.example.com/")
              request(:get, "http://www.example.com").should have_been_made.once
            }.should fail_with("The request GET http://www.example.com/ was expected to execute 1 time but it executed 0 times")
          end

        end

        describe "using webmock matcher" do

          it "should verify expected requests occured" do
            lambda {
              http_request(:get, "http://www.example.com/")
              WebMock.should have_requested(:get, "http://www.example.com").once
            }.should_not raise_error
          end

          it "should verify expected requests occured" do
            lambda {
              http_request(:get, "http://www.example.com/", :body => "abc", :headers => {'A' => 'a'})
              WebMock.should have_requested(:get, "http://www.example.com").with(:body => "abc", :headers => {'A' => 'a'}).once
            }.should_not raise_error
          end

          it "should verify that non expected requests didn't occur" do
            lambda {
              http_request(:get, "http://www.example.com/")
              WebMock.should_not have_requested(:get, "http://www.example.com")
            }.should fail_with("The request GET http://www.example.com/ was expected to execute 0 times but it executed 1 time")
          end
        end



        describe "using assert_requested" do

          it "should verify expected requests occured" do
            lambda {
              http_request(:get, "http://www.example.com/")
              assert_requested(:get, "http://www.example.com", :times => 1)
              assert_requested(:get, "http://www.example.com")
            }.should_not raise_error
          end

          it "should verify expected requests occured" do
            lambda {
              http_request(:get, "http://www.example.com/", :body => "abc", :headers => {'A' => 'a'})
              assert_requested(:get, "http://www.example.com", :body => "abc", :headers => {'A' => 'a'})
            }.should_not raise_error
          end

          it "should verify that non expected requests didn't occur" do
            lambda {
              http_request(:get, "http://www.example.com/")
              assert_not_requested(:get, "http://www.example.com")
            }.should fail_with("The request GET http://www.example.com/ was expected to execute 0 times but it executed 1 time")
          end
        end
      end


      describe "when net connect allowed" do
        before(:each) do
          WebMock.allow_net_connect!
        end

        it "should verify expected requests occured" do
          setup_expectations_for_real_example_com_request
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should have_been_made
          }.should_not raise_error
        end

        it "should verify that non expected requests didn't occur" do
          lambda {
            http_request(:get, "http://www.example.com/")
            request(:get, "http://www.example.com").should_not have_been_made
          }.should fail_with("The request GET http://www.example.com/ was expected to execute 0 times but it executed 1 time")
        end
      end

    end

  end