diff options
| -rw-r--r-- | activeresource/CHANGELOG | 2 | ||||
| -rw-r--r-- | activeresource/lib/active_resource/connection.rb | 24 | ||||
| -rw-r--r-- | activeresource/test/authorization_test.rb | 82 | ||||
| -rw-r--r-- | activeresource/test/base_errors_test.rb | 2 | ||||
| -rw-r--r-- | activeresource/test/base_test.rb | 46 | ||||
| -rw-r--r-- | activeresource/test/connection_test.rb | 40 | ||||
| -rw-r--r-- | activeresource/test/http_mock.rb | 33 | 
7 files changed, 190 insertions, 39 deletions
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 6906c17ac7..e24462a750 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,5 +1,7 @@  *SVN* +* Add Basic HTTP Authentication to ActiveResource (closes #6305). [jonathan] +  * Extracted #id_from_response as an entry point for customizing how a created resource gets its own ID.    By default, it extracts from the Location response header. diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 920e5a15b0..57471957e2 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -25,7 +25,7 @@ module ActiveResource    class Connection -    attr_accessor :site +    attr_reader :site      class << self        def requests @@ -39,23 +39,27 @@ module ActiveResource      end      def initialize(site) -      @site = site +      self.site = site.is_a?(URI) ? site : URI.parse(site) +    end +     +    def site=(site) +      @site = site.is_a?(URI) ? site : URI.parse(site)      end      def get(path) -      Hash.from_xml(request(:get, path).body) +      Hash.from_xml(request(:get, path, build_request_headers).body)      end      def delete(path) -      request(:delete, path, self.class.default_header) +      request(:delete, path, build_request_headers)      end      def put(path, body = '') -      request(:put, path, body, self.class.default_header) +      request(:put, path, body, build_request_headers)      end      def post(path, body = '') -      request(:post, path, body, self.class.default_header) +      request(:post, path, body, build_request_headers)      end      private @@ -91,5 +95,13 @@ module ActiveResource          @http        end +       +      def build_request_headers +        authorization_header.update(self.class.default_header) +      end +       +      def authorization_header +        (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {}) +      end    end  end diff --git a/activeresource/test/authorization_test.rb b/activeresource/test/authorization_test.rb new file mode 100644 index 0000000000..bfe51ce1e3 --- /dev/null +++ b/activeresource/test/authorization_test.rb @@ -0,0 +1,82 @@ +require "#{File.dirname(__FILE__)}/abstract_unit" +require 'base64' + +class AuthorizationTest < Test::Unit::TestCase +  Response = Struct.new(:code) + +  def setup +    @conn = ActiveResource::Connection.new('http://localhost') +    @matz  = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') +    @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') +    @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") +    @authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } +     +    ActiveResource::HttpMock.respond_to do |mock| +      mock.get    "/people/2.xml",           @authorization_request_header, @david +      mock.put    "/people/2.xml",           @authorization_request_header, nil, 204 +      mock.delete "/people/2.xml",           @authorization_request_header, nil, 200 +      mock.post   "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' +    end +  end + +  def test_authorization_header +    authorization_header = @authenticated_conn.send(:authorization_header) +    assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] +    authorization = authorization_header["Authorization"].to_s.split +     +    assert_equal "Basic", authorization[0] +    assert_equal ["david", "test123"], Base64.decode64(authorization[1]).split(":")[0..1] +  end +   +  def test_authorization_header_with_username_but_no_password +    @conn = ActiveResource::Connection.new("http://david:@localhost") +    authorization_header = @conn.send(:authorization_header) +    authorization = authorization_header["Authorization"].to_s.split +     +    assert_equal "Basic", authorization[0] +    assert_equal ["david"], Base64.decode64(authorization[1]).split(":")[0..1] +  end +   +  def test_authorization_header_with_password_but_no_username +    @conn = ActiveResource::Connection.new("http://:test123@localhost") +    authorization_header = @conn.send(:authorization_header) +    authorization = authorization_header["Authorization"].to_s.split +     +    assert_equal "Basic", authorization[0] +    assert_equal ["", "test123"], Base64.decode64(authorization[1]).split(":")[0..1] +  end +   +  def test_get +    david = @authenticated_conn.get("/people/2.xml") +    assert_equal "David", david["person"]["name"] +  end +   +  def test_post +    response = @authenticated_conn.post("/people/2/addresses.xml") +    assert_equal "/people/1/addresses/5", response["Location"] +  end +   +  def test_put +    response = @authenticated_conn.put("/people/2.xml") +    assert_equal 204, response.code +  end +   +  def test_delete +    response = @authenticated_conn.delete("/people/2.xml") +    assert_equal 200, response.code +  end + +  def test_raises_invalid_request_on_unauthorized_requests +    assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } +    assert_raises(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } +    assert_raises(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } +    assert_raises(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } +  end + +  protected +    def assert_response_raises(klass, code) +      assert_raise(klass, "Expected response code #{code} to raise #{klass}") do +        @conn.send(:handle_response, Response.new(code)) +      end +    end +end diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb index 5125adf218..1aa677b1ba 100644 --- a/activeresource/test/base_errors_test.rb +++ b/activeresource/test/base_errors_test.rb @@ -4,7 +4,7 @@ require "fixtures/person"  class BaseErrorsTest < Test::Unit::TestCase    def setup      ActiveResource::HttpMock.respond_to do |mock| -      mock.post "/people.xml", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400 +      mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 400      end      @exception = nil      @person    = Person.new(:name => '', :age => '') diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 2a3091ecba..f3fd3b43e2 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -7,25 +7,27 @@ class BaseTest < Test::Unit::TestCase      @matz  = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')      @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')      @addy  = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address') +    @default_request_headers = { 'Content-Type' => 'application/xml' } +          ActiveResource::HttpMock.respond_to do |mock| -      mock.get    "/people/1.xml",             @matz -      mock.get    "/people/2.xml",             @david -      mock.put    "/people/1.xml",             nil, 204 -      mock.delete "/people/1.xml",             nil, 200 -      mock.delete "/people/2.xml",             nil, 400 -      mock.post   "/people.xml",               nil, 201, 'Location' => '/people/5.xml' -      mock.get    "/people/99.xml",            nil, 404 -      mock.get    "/people.xml",               "<people>#{@matz}#{@david}</people>" -      mock.get    "/people/1/addresses.xml",   "<addresses>#{@addy}</addresses>" -      mock.get    "/people/1/addresses/1.xml", @addy -      mock.put    "/people/1/addresses/1.xml", nil, 204 -      mock.delete "/people/1/addresses/1.xml", nil, 200 -      mock.post   "/people/1/addresses.xml",   nil, 201, 'Location' => '/people/1/addresses/5' -      mock.get    "/people//addresses.xml",    nil, 404 -      mock.get    "/people//addresses/1.xml",  nil, 404 -      mock.put    "/people//addresses/1.xml",  nil, 404 -      mock.delete "/people//addresses/1.xml",  nil, 404 -      mock.post   "/people//addresses.xml",    nil, 404 +      mock.get    "/people/1.xml",             {}, @matz +      mock.get    "/people/2.xml",             {}, @david +      mock.put    "/people/1.xml",             {}, nil, 204 +      mock.delete "/people/1.xml",             {}, nil, 200 +      mock.delete "/people/2.xml",             {}, nil, 400 +      mock.post   "/people.xml",               {}, nil, 201, 'Location' => '/people/5.xml' +      mock.get    "/people/99.xml",            {}, nil, 404 +      mock.get    "/people.xml",               {}, "<people>#{@matz}#{@david}</people>" +      mock.get    "/people/1/addresses.xml",   {}, "<addresses>#{@addy}</addresses>" +      mock.get    "/people/1/addresses/1.xml", {}, @addy +      mock.put    "/people/1/addresses/1.xml", {}, nil, 204 +      mock.delete "/people/1/addresses/1.xml", {}, nil, 200 +      mock.post   "/people/1/addresses.xml",   {}, nil, 201, 'Location' => '/people/1/addresses/5' +      mock.get    "/people//addresses.xml",    {}, nil, 404 +      mock.get    "/people//addresses/1.xml",  {}, nil, 404 +      mock.put    "/people//addresses/1.xml",  {}, nil, 404 +      mock.delete "/people//addresses/1.xml",  {}, nil, 404 +      mock.post   "/people//addresses.xml",    {}, nil, 404      end    end @@ -144,8 +146,8 @@ class BaseTest < Test::Unit::TestCase    def test_update_conflict      ActiveResource::HttpMock.respond_to do |mock| -      mock.get "/people/2.xml", @david -      mock.put "/people/2.xml", nil, 409 +      mock.get "/people/2.xml", {}, @david +      mock.put "/people/2.xml", @default_request_headers, nil, 409      end      assert_raises(ActiveResource::ResourceConflict) { Person.find(2).save }    end @@ -153,7 +155,7 @@ class BaseTest < Test::Unit::TestCase    def test_destroy      assert Person.find(1).destroy      ActiveResource::HttpMock.respond_to do |mock| -      mock.get "/people/1.xml", nil, 404 +      mock.get "/people/1.xml", {}, nil, 404      end      assert_raises(ActiveResource::ResourceNotFound) { Person.find(1).destroy }    end @@ -161,7 +163,7 @@ class BaseTest < Test::Unit::TestCase    def test_destroy_with_custom_prefix      assert StreetAddress.find(1, :person_id => 1).destroy      ActiveResource::HttpMock.respond_to do |mock| -      mock.get "/people/1/addresses/1.xml", nil, 404 +      mock.get "/people/1/addresses/1.xml", {}, nil, 404      end      assert_raises(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :person_id => 1).destroy }    end diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 0744e327e6..efde099c90 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -1,10 +1,20 @@  require "#{File.dirname(__FILE__)}/abstract_unit" +require 'base64'  class ConnectionTest < Test::Unit::TestCase    Response = Struct.new(:code)    def setup      @conn = ActiveResource::Connection.new('http://localhost') +    @matz  = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') +    @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') +    @default_request_headers = { 'Content-Type' => 'application/xml' } +    ActiveResource::HttpMock.respond_to do |mock| +      mock.get    "/people/1.xml", {}, @matz +      mock.put    "/people/1.xml", {}, nil, 204 +      mock.delete "/people/1.xml", {}, nil, 200 +      mock.post   "/people.xml",   {}, nil, 201, 'Location' => '/people/5.xml' +    end    end    def test_handle_response @@ -38,7 +48,37 @@ class ConnectionTest < Test::Unit::TestCase        assert_response_raises ActiveResource::ConnectionError, code      end    end +   +  def test_site_accessor_accepts_uri_or_string_argument +    site = URI.parse("http://localhost") + +    assert_nothing_raised { @conn.site = "http://localhost" } +    assert_equal site,  @conn.site +    assert_nothing_raised { @conn.site = site } +    assert_equal site, @conn.site +  end +   +  def test_get +    matz = @conn.get("/people/1.xml") +    assert_equal "Matz", matz["person"]["name"] +  end +   +  def test_post +    response = @conn.post("/people.xml") +    assert_equal "/people/5.xml", response["Location"] +  end +   +  def test_put +    response = @conn.put("/people/1.xml") +    assert_equal 204, response.code +  end +   +  def test_delete +    response = @conn.delete("/people/1.xml") +    assert_equal 200, response.code +  end +      protected      def assert_response_raises(klass, code)        assert_raise(klass, "Expected response code #{code} to raise #{klass}") do diff --git a/activeresource/test/http_mock.rb b/activeresource/test/http_mock.rb index 40921f6b6b..66351501d9 100644 --- a/activeresource/test/http_mock.rb +++ b/activeresource/test/http_mock.rb @@ -1,6 +1,8 @@  require 'active_resource/connection'  module ActiveResource +  class InvalidRequestError < StandardError; end +        class HttpMock      class Responder        def initialize(responses) @@ -9,8 +11,8 @@ module ActiveResource        for method in [ :post, :put, :get, :delete ]          module_eval <<-EOE -          def #{method}(path, body = nil, status = 200, headers = {}) -            @responses[Request.new(:#{method}, path, nil)] = Response.new(body || {}, status, headers) +          def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {}) +            @responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || {}, status, response_headers)            end          EOE        end @@ -39,12 +41,22 @@ module ActiveResource        end      end -    for method in [ :post, :put, :get, :delete ] +    for method in [ :post, :put ] +      module_eval <<-EOE +        def #{method}(path, body, headers) +          request = ActiveResource::Request.new(:#{method}, path, body, headers) +          self.class.requests << request +          self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}")) +        end +      EOE +    end +     +    for method in [ :get, :delete ]        module_eval <<-EOE -        def #{method}(*arguments) -          request = ActiveResource::Request.new(:#{method}, *arguments) +        def #{method}(path, headers) +          request = ActiveResource::Request.new(:#{method}, path, nil, headers)            self.class.requests << request -          self.class.responses[request] || raise("No response recorded for: \#{request}") +          self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for: \#{request}"))          end        EOE      end @@ -55,10 +67,11 @@ module ActiveResource    end    class Request -    attr_accessor :path, :method, :body +    attr_accessor :path, :method, :body, :headers      def initialize(method, path, body = nil, headers = nil) -      @method, @path, @body = method, path, body +      @method, @path, @body, @headers = method, path, body, headers +      @headers.update('Content-Type' => 'application/xml')      end      def ==(other_request) @@ -70,11 +83,11 @@ module ActiveResource      end      def to_s -      "<#{method.to_s.upcase}: #{path} (#{body})>" +      "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"      end      def hash -      "#{path}#{method}".hash +      "#{path}#{method}#{headers}".hash      end    end  | 
