├── .gitignore ├── .rspec ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── curb-fu.gemspec ├── lib ├── curb-fu.rb └── curb-fu │ ├── authentication.rb │ ├── core_ext.rb │ ├── request.rb │ ├── request │ ├── base.rb │ ├── common.rb │ ├── parameter.rb │ └── test.rb │ ├── response.rb │ ├── test.rb │ ├── test │ ├── request_logger.rb │ └── server.rb │ └── version.rb └── spec ├── fixtures └── foo.txt ├── lib ├── curb-fu │ ├── core_ext_spec.rb │ ├── request │ │ ├── base_spec.rb │ │ ├── parameter_spec.rb │ │ └── test_spec.rb │ └── response_spec.rb └── curb_fu_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | *.kpf 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format nested 3 | --order random -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use --create 1.9.3@curb-fu 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | curb-fu (0.6.2) 5 | curb (>= 0.5.4.0) 6 | rack-test (>= 0.2.0) 7 | 8 | GEM 9 | remote: http://rubygems.org/ 10 | specs: 11 | curb (0.8.2) 12 | diff-lcs (1.1.3) 13 | htmlentities (4.3.1) 14 | rack (1.4.1) 15 | rack-test (0.6.2) 16 | rack (>= 1.0) 17 | rake (0.9.2.2) 18 | rspec (2.11.0) 19 | rspec-core (~> 2.11.0) 20 | rspec-expectations (~> 2.11.0) 21 | rspec-mocks (~> 2.11.0) 22 | rspec-core (2.11.1) 23 | rspec-expectations (2.11.3) 24 | diff-lcs (~> 1.1.3) 25 | rspec-mocks (2.11.3) 26 | 27 | PLATFORMS 28 | ruby 29 | 30 | DEPENDENCIES 31 | curb-fu! 32 | htmlentities 33 | rake 34 | rspec 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006 Ross Bamford (rosco AT roscopeco DOT co DOT uk). 2 | Curb is free software licensed under the following terms: 3 | 4 | 1. You may make and give away verbatim copies of the source form of the 5 | software without restriction, provided that you duplicate all of the 6 | original copyright notices and associated disclaimers. 7 | 8 | 2. You may modify your copy of the software in any way, provided that 9 | you do at least ONE of the following: 10 | 11 | a) place your modifications in the Public Domain or otherwise 12 | make them Freely Available, such as by posting said 13 | modifications to Usenet or an equivalent medium, or by allowing 14 | the author to include your modifications in the software. 15 | 16 | b) use the modified software only within your corporation or 17 | organization. 18 | 19 | c) give non-standard binaries non-standard names, with 20 | instructions on where to get the original software distribution. 21 | 22 | d) make other distribution arrangements with the author. 23 | 24 | 3. You may distribute the software in object code or binary form, 25 | provided that you do at least ONE of the following: 26 | 27 | a) distribute the binaries and library files of the software, 28 | together with instructions (in the manual page or equivalent) 29 | on where to get the original distribution. 30 | 31 | b) accompany the distribution with the machine-readable source of 32 | the software. 33 | 34 | c) give non-standard binaries non-standard names, with 35 | instructions on where to get the original software distribution. 36 | 37 | d) make other distribution arrangements with the author. 38 | 39 | 4. You may modify and include the part of the software into any other 40 | software (possibly commercial). 41 | 42 | 5. The scripts and library files supplied as input to or produced as 43 | output from the software do not automatically fall under the 44 | copyright of the software, but belong to whomever generated them, 45 | and may be sold commercially, and may be aggregated with this 46 | software. 47 | 48 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 49 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 50 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 51 | PURPOSE. 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # curb-fu - easy-to-use wrapper around curb - the ruby wrapper around libcurl 2 | 3 | * https://github.com/gdi/curb-fu 4 | 5 | Curb can be found at http://github.com/taf2/curb 6 | 7 | ## License 8 | 9 | This gem is released under the terms of the Ruby license. See the LICENSE file for details. 10 | 11 | ## Troubleshooting 12 | 13 | If you are POSTing data and curb seems to be locking up, try posting it with an explicit 'Expect: 100-continue' header. 14 | 15 | You can set this per-request, e.g. 16 | 17 | CurbFu.post({:host => 'example.com', :headers => { "Expect" => "100-continue" }}, { "data" => "here" }) 18 | 19 | or you can configure it as a global header, e.g. 20 | 21 | CurbFu.global_headers = { "Expect" => "100-continue" } 22 | # ... then make your requests as normal 23 | 24 | however you feel best. 25 | 26 | ## Prerequisites 27 | 28 | * Ruby (tested on 1.8.7, 1.9.x) 29 | * The Curb gem (and its libcurl dependency) 30 | * http://github.com/taf2/curb 31 | 32 | ## Installation 33 | 34 | $ gem install curb-fu --source http://gems.github.com 35 | 36 | Or, if you have the source: 37 | 38 | $ cd 39 | $ rake gem 40 | $ gem install pkg/ 41 | 42 | ## Examples 43 | 44 | Urls can be requested using hashes of options or strings. The GET, POST, PUT, and DELETE methods are supported 45 | through their respective methods on CurbFu and CurbFu::Request. 46 | 47 | ### String Examples 48 | 49 | response = CurbFu.get('http://slashdot.org') 50 | puts response.body 51 | 52 | response = CurbFu.post('http://example.com/some/resource', { :color => 'red', :shape => 'sphere' }) 53 | puts response.body unless response.success? 54 | 55 | If you need to pass custom headers, you can still pass a string URL with :url : 56 | 57 | response = CurbFu.post(:url => 'http://example.com/some/resource', :headers => {'Content-Type' => 'application/xml'}) 58 | 59 | 60 | ### Hash Examples 61 | 62 | response = CurbFu.get(:host => 'github.com', :path => '/gdi/curb-fu') 63 | puts response.body 64 | 65 | response = CurbFu.post({:host => 'example.com', :path => '/some/resource'}, { :color => 'red', :shape => 'sphere' }) 66 | puts response.body unless response.success? 67 | 68 | if you need https: 69 | 70 | response = CurbFu.post({:host => 'example.com', :path => '/some/resource', :protocol => "https"}, { :color => 'red', :shape => 'sphere' }) 71 | 72 | ### Cookies; changes as of 0.6.1 73 | 74 | if you want to send a cookie, previous to 0.6.1 you have to pass a block to the HTTP verb method like so: 75 | 76 | response = CurbFu.get("http://myhost.com") do |curb| 77 | curb.cookies = "SekretToken=123234234235;" 78 | end 79 | 80 | As of 0.6.1 one can set the cookies either as an optional final parameter or via a hash, e.g.: 81 | 82 | response = CurbFu.get("http://myhost.com", { :param => "value" }, "SekretToken=123234;") 83 | # or with a hash: 84 | response = CurbFu.get({ :host => "http://myhost", :cookies => "SekretToken=1234;" }) 85 | 86 | etc. 87 | 88 | ### Following Redirects, and other advanced topics 89 | 90 | you can pass a block to the CurbFu methods, we will yield the Curl::Easy object to this block to allow you to reach as far into the Curb guts as you want. This is particularly useful if you want to follow redirects: 91 | 92 | ```ruby 93 | resp = CurbFu.get('http://google.com') { |curb| curb.follow_location = true } 94 | ``` 95 | 96 | **n.b.** we would happily entertain pull requests to add some sugar to common configuration items. 97 | 98 | Have fun! 99 | 100 | ## Contributing 101 | 102 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet 103 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it 104 | * Fork the project 105 | * Start a feature/bugfix branch 106 | * Commit and push until you are happy with your contribution 107 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 108 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 109 | * Send a pull request! 110 | 111 | ## Contributors 112 | 113 | * thickpaddy (https://github.com/thickpaddy) 114 | * hoverlover (https://github.com/hoverlover) 115 | * whatcould (https://github.com/whatcould) 116 | 117 | ## Original Authorship 118 | 119 | * dkastner (https://github.com/dkastner) 120 | * hypomodern (https://github.com/hypomodern) 121 | * Greenview Data, Inc. (http://greenviewdata.com) 122 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rake' 3 | require 'rspec/core/rake_task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) do |spec| 6 | spec.pattern = 'spec/**/*_spec.rb' 7 | spec.rspec_opts = ['--backtrace'] 8 | # spec.ruby_opts = ['-w'] 9 | end 10 | 11 | task :default => :spec 12 | 13 | -------------------------------------------------------------------------------- /curb-fu.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require 'curb-fu/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.platform = Gem::Platform::RUBY 7 | s.name = "curb-fu" 8 | s.version = CurbFu::VERSION 9 | s.author = "Derek Kastner, Matt Wilson" 10 | s.email = "development@greenviewdata.com" 11 | s.summary = "Friendly wrapper for curb" 12 | s.has_rdoc = false 13 | 14 | s.files = `git ls-files`.split("\n") 15 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 16 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 17 | s.require_paths = ["lib"] 18 | 19 | s.add_dependency('curb', '>= 0.5.4.0') 20 | s.add_dependency('rack-test', '>= 0.2.0') 21 | 22 | s.add_development_dependency('rake') 23 | s.add_development_dependency('rspec') 24 | s.add_development_dependency('htmlentities') 25 | end 26 | -------------------------------------------------------------------------------- /lib/curb-fu.rb: -------------------------------------------------------------------------------- 1 | dir = File.dirname(__FILE__) 2 | $:.unshift(dir) unless $:.include?(dir) 3 | require 'curb-fu/response' 4 | require 'curb-fu/request' 5 | require 'curb-fu/authentication' 6 | require 'curb-fu/core_ext' 7 | 8 | module CurbFu 9 | class << self 10 | def get(*args, &block) 11 | CurbFu::Request.get(*args, &block) 12 | end 13 | 14 | def post(*args, &block) 15 | CurbFu::Request.post(*args, &block) 16 | end 17 | 18 | def put(*args, &block) 19 | CurbFu::Request.put(*args, &block) 20 | end 21 | 22 | def delete(*args, &block) 23 | CurbFu::Request.delete(*args, &block) 24 | end 25 | 26 | attr_accessor :stubs 27 | 28 | def stubs=(val) 29 | if val 30 | @stubs = {} 31 | val.each do |hostname, rack_app| 32 | stub(hostname, rack_app) 33 | end 34 | 35 | unless CurbFu::Request.include?(CurbFu::Request::Test) 36 | CurbFu::Request.send(:include, CurbFu::Request::Test) 37 | end 38 | else 39 | @stubs = nil 40 | end 41 | end 42 | 43 | def stub(hostname, rack_app) 44 | raise "You must use CurbFu.stubs= to define initial stubs before using stub()" if @stubs.nil? 45 | @stubs[hostname] = CurbFu::Request::Test::Interface.new(rack_app, hostname) 46 | end 47 | 48 | def stubs 49 | @stubs 50 | end 51 | 52 | def debug=(val) 53 | @debug = val ? true : false 54 | end 55 | 56 | def debug? 57 | @debug 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/curb-fu/authentication.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | module Authentication 3 | BASIC = Curl::CURLAUTH_BASIC 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/curb-fu/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | ## 4 | # ActiveSupport look alike for to_param_pair. Very useful. 5 | module CurbFu 6 | module HashExtensions 7 | def self.included(base) 8 | base.send(:include, InstanceMethods) 9 | #base.extend(ClassMethods) 10 | end 11 | 12 | module InstanceMethods 13 | def to_param_pair(prefix) 14 | collect do |k, v| 15 | key_prefix = prefix ? "#{prefix}[#{k}]" : k 16 | v.to_param_pair(key_prefix) 17 | end.join("&") 18 | end 19 | end 20 | end 21 | 22 | module ObjectExtensions 23 | def self.included(base) 24 | base.send(:include, InstanceMethods) 25 | #base.extend(ClassMethods) 26 | end 27 | 28 | module InstanceMethods 29 | def to_param_pair(prefix = self.class) 30 | value = CGI::escape(to_s) 31 | "#{prefix}=#{value}" 32 | end 33 | end 34 | end 35 | 36 | module ArrayExtensions 37 | def self.included(base) 38 | base.send(:include, InstanceMethods) 39 | #base.extend(ClassMethods) 40 | end 41 | 42 | module InstanceMethods 43 | def to_param_pair(prefix) 44 | prefix = "#{prefix}[]" 45 | collect { |item| "#{item.to_param_pair(prefix)}" }.join('&') 46 | end 47 | end 48 | end 49 | end 50 | 51 | class Hash 52 | include CurbFu::HashExtensions 53 | end 54 | class Array 55 | include CurbFu::ArrayExtensions 56 | end 57 | class String 58 | include CurbFu::ObjectExtensions 59 | end 60 | class Fixnum 61 | include CurbFu::ObjectExtensions 62 | end 63 | -------------------------------------------------------------------------------- /lib/curb-fu/request.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'curb' 3 | require 'curb-fu/request/common' 4 | require 'curb-fu/request/base' 5 | require 'curb-fu/request/parameter' 6 | require 'curb-fu/request/test' 7 | 8 | module CurbFu 9 | class Request 10 | extend Base 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/curb-fu/request/base.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | class Request 3 | module Base 4 | include Common 5 | 6 | def build(url_params, query_params = {}, cookies = nil, &block) 7 | curb = Curl::Easy.new(build_url(url_params, query_params)) 8 | 9 | headers = global_headers 10 | 11 | unless url_params.is_a?(String) 12 | curb.userpwd = "#{url_params[:username]}:#{url_params[:password]}" if url_params[:username] 13 | if url_params[:authtype] 14 | curb.http_auth_types = url_params[:authtype] 15 | elsif url_params[:username] 16 | curb.http_auth_types = CurbFu::Authentication::BASIC 17 | end 18 | 19 | cookies ||= url_params[:cookies] 20 | 21 | headers = headers.merge(url_params[:headers]) unless url_params[:headers].nil? 22 | headers["Expect"] = '' unless url_params[:headers] && url_params[:headers]["Expect"] 23 | end 24 | 25 | curb.cookies = cookies if cookies 26 | curb.headers = headers 27 | curb.timeout = @timeout 28 | 29 | yield curb if block_given? 30 | 31 | curb 32 | end 33 | 34 | # Set headers to be used for every request 35 | # * headers: hash of header names and values 36 | def global_headers=(headers) 37 | @global_headers = headers 38 | end 39 | 40 | # Headers to be used for every request 41 | # Returns: hash of header names and values 42 | def global_headers 43 | @global_headers ||= {} 44 | end 45 | 46 | def get(url, params = {}, cookies = nil, &block) 47 | curb = self.build(url, params, cookies, &block) 48 | curb.http_get 49 | CurbFu::Response::Base.from_curb_response(curb) 50 | end 51 | 52 | def put(url, params = {}, cookies = nil, &block) 53 | fields = create_post_fields(params) 54 | fields = [fields] if fields.is_a?(String) 55 | 56 | curb = self.build(url, {}, cookies, &block) 57 | curb.http_put(*fields) 58 | CurbFu::Response::Base.from_curb_response(curb) 59 | end 60 | 61 | def post(url, params = {}, cookies = nil, &block) 62 | fields = create_post_fields(params) 63 | fields = [fields] if fields.is_a?(String) 64 | 65 | curb = self.build(url, {}, cookies, &block) 66 | curb.http_post(*fields) 67 | response = CurbFu::Response::Base.from_curb_response(curb) 68 | if CurbFu.debug? 69 | puts "Response from server was" 70 | puts "Status: #{response.status}" 71 | puts "Headers: #{response.headers.inspect}" 72 | puts "Body: #{response.body.inspect}" 73 | end 74 | response 75 | end 76 | 77 | def post_file(url, params = {}, filez = {}, cookies = nil, &block) 78 | fields = create_post_fields(params) 79 | fields += create_file_fields(filez) 80 | 81 | curb = self.build(url, {}, cookies, &block) 82 | curb.multipart_form_post = true 83 | 84 | begin 85 | curb.http_post(*fields) 86 | rescue Curl::Err::InvalidPostFieldError => e 87 | field_list = (params.merge(filez)).inject([]) { |list, (name, value)| list << "#{name} => #{value.to_s[0..49].inspect}"; list } 88 | raise e, "There was an attempt to post invalid fields. The fields were:\n#{field_list.join("\n")}" 89 | end 90 | CurbFu::Response::Base.from_curb_response(curb) 91 | end 92 | 93 | def delete(url, cookies = nil, &block) 94 | curb = self.build(url, {}, cookies, &block) 95 | curb.http_delete 96 | CurbFu::Response::Base.from_curb_response(curb) 97 | end 98 | 99 | private 100 | def create_post_fields(params) 101 | return params if params.is_a? String 102 | 103 | fields = [] 104 | params.each do |name, value| 105 | value_string = value if value.is_a?(String) 106 | value_string = value.join(',') if value.is_a?(Array) 107 | value_string ||= value.to_s 108 | 109 | fields << Curl::PostField.content(name.to_s,value_string) 110 | end 111 | return fields 112 | end 113 | 114 | def create_put_fields(params) 115 | return params if params.is_a? String 116 | 117 | params.inject([]) do |list, (k,v)| 118 | v = v.is_a?(Array) ? v.join(',') : v 119 | list << "#{k}=#{v}" 120 | list 121 | end.join('&') 122 | end 123 | 124 | def create_file_fields(filez) 125 | fields = [] 126 | filez.each do |name, path| 127 | fields << Curl::PostField.file(name, path) 128 | end 129 | fields 130 | end 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /lib/curb-fu/request/common.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | class Request 3 | module Common 4 | def timeout=(val) 5 | @timeout = val 6 | end 7 | 8 | def timeout 9 | @timeout.nil? ? 10 : @timeout 10 | end 11 | 12 | def build_url(url_params, query_params = {}) 13 | if url_params.is_a? String 14 | built_url = url_params 15 | elsif url_params[:url] 16 | built_url = url_params[:url] 17 | else 18 | protocol = url_params[:protocol] || "http" 19 | built_url = "#{protocol}://#{url_params[:host]}" 20 | built_url += ":" + url_params[:port].to_s if url_params[:port] 21 | built_url += url_params[:path] if url_params[:path] 22 | end 23 | 24 | # TODO: update for use with CurbFu::Entity 25 | if query_params.is_a? String 26 | built_url += query_params 27 | elsif !query_params.empty? 28 | built_url += "?" 29 | built_url += query_params.collect do |name, value| 30 | CurbFu::Request::Parameter.new(name, value).to_uri_param 31 | end.join('&') 32 | end 33 | built_url 34 | end 35 | end 36 | end 37 | end -------------------------------------------------------------------------------- /lib/curb-fu/request/parameter.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | class Request 3 | class Parameter 4 | attr_accessor :name, :value 5 | 6 | def initialize(name, value) 7 | self.name = name 8 | self.value = value 9 | end 10 | 11 | def self.build_uri_params(param_hash) 12 | param_hash.to_param_pair 13 | end 14 | 15 | def self.build_post_fields(param_hash) 16 | param_hash.to_post_fields 17 | end 18 | 19 | def to_uri_param 20 | value.to_param_pair(name) 21 | end 22 | 23 | def to_curl_post_field 24 | field_string = value.to_param_pair(name) 25 | fields = field_string.split('&').collect do |field_value_pair| 26 | field_name, field_value = field_value_pair.split('=') 27 | Curl::PostField.content(field_name, CGI::unescape(field_value)) 28 | end 29 | fields.length == 1 ? fields[0] : fields 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/curb-fu/request/test.rb: -------------------------------------------------------------------------------- 1 | require 'rack/test' 2 | 3 | module CurbFu 4 | class Request 5 | module Test 6 | include Common 7 | 8 | def self.included(target) 9 | target.extend(ClassMethods) 10 | end 11 | 12 | module ClassMethods 13 | def get(url, params = {}) 14 | request_options = build_request_options(url) 15 | params = hashify_params(params) if params.is_a?(String) 16 | respond(request_options[:interface], :get, request_options[:url], 17 | params, request_options[:headers], request_options[:username], request_options[:password]) 18 | end 19 | 20 | def post(url, params = {}) 21 | request_options = build_request_options(url) 22 | respond(request_options[:interface], :post, request_options[:url], 23 | params, request_options[:headers], request_options[:username], request_options[:password]) 24 | end 25 | 26 | def post_file(url, params = {}, filez = {}) 27 | request_options = build_request_options(url) 28 | uploaded_files = filez.inject({}) do |hsh, (name, path)| 29 | hsh[name] = Rack::Test::UploadedFile.new(path) 30 | hsh 31 | end 32 | respond(request_options[:interface], :post, request_options[:url], 33 | params.merge(uploaded_files), request_options[:headers], request_options[:username], request_options[:password]) 34 | end 35 | 36 | def put(url, params = {}) 37 | request_options = build_request_options(url) 38 | respond(request_options[:interface], :put, request_options[:url], 39 | params, request_options[:headers], request_options[:username], request_options[:password]) 40 | end 41 | 42 | def delete(url, params = {}) 43 | request_options = build_request_options(url) 44 | params = hashify_params(params) if params.is_a?(String) 45 | respond(request_options[:interface], :delete, request_options[:url], 46 | params, request_options[:headers], request_options[:username], request_options[:password]) 47 | end 48 | 49 | def hashify_params(param_string) 50 | param_string.sub!(/^\?/,'') 51 | param_string.split('&').inject({}) do |hsh, pair| 52 | key, value = pair.split('=') 53 | 54 | if key.match(/(.+)\[\]$/) 55 | key = $1 56 | hsh[key] ||= [] 57 | hsh[key] << value 58 | elsif key.match(/([^\[]+)\[(.+)\]$/) 59 | key = $1 60 | subkey = $2 61 | hsh[key] ||= {} 62 | hsh[key].update( subkey => value ) 63 | else 64 | hsh[key] = value 65 | end 66 | 67 | hsh 68 | end 69 | end 70 | 71 | def build_request_options(url) 72 | options = {} 73 | options[:headers] = (url.is_a?(String)) ? nil : url.delete(:headers) 74 | options[:url] = build_url(url) 75 | options[:username], options[:password] = get_auth(url) 76 | options[:interface] = get_interface(url) 77 | options 78 | end 79 | 80 | def respond(interface, operation, url, params, headers, username = nil, password = nil) 81 | if interface.nil? 82 | raise Curl::Err::ConnectionFailedError 83 | else 84 | unless headers.nil? 85 | process_headers(headers).each do |name, value| 86 | interface.header(name, value) 87 | end 88 | end 89 | interface.authorize(username, password) unless username.nil? 90 | puts "sending #{operation} to #{url} with params #{params.inspect} using interface #{interface.inspect}" if CurbFu.debug? 91 | begin 92 | response = interface.send(operation, url, params, 'SERVER_NAME' => interface.hostname) 93 | rescue => e 94 | puts "Caught error: #{e}, #{e.backtrace.join("\n")}" if CurbFu.debug? 95 | raise e 96 | end 97 | CurbFu::Response::Base.from_rack_response(response) 98 | end 99 | end 100 | 101 | def process_headers(headers) 102 | headers.inject({}) do |accum, (header_name, value)| 103 | key = header_name.gsub("-", "_").upcase 104 | key = "HTTP_" + key unless key =~ /^HTTP_/ 105 | accum[key] = value 106 | accum 107 | end 108 | end 109 | 110 | def get_interface(url) 111 | if url.is_a?(Hash) 112 | host = url[:host] || parse_hostname(url[:url]) 113 | else 114 | host = parse_hostname(url) 115 | end 116 | match_host(host) 117 | end 118 | 119 | def parse_hostname(uri) 120 | parsed_hostname = URI.parse(uri) 121 | parsed_hostname.host || parsed_hostname.path 122 | end 123 | 124 | def match_host(host) 125 | match = CurbFu.stubs.find do |(hostname, interface)| 126 | hostname == host 127 | end 128 | match.last unless match.nil? 129 | end 130 | 131 | def get_auth(url) 132 | username = password = nil 133 | if url.is_a?(Hash) && url[:username] 134 | username = url[:username] 135 | password = url[:password] 136 | end 137 | [username, password] 138 | end 139 | end 140 | 141 | class Interface 142 | include Rack::Test::Methods 143 | 144 | attr_accessor :app, :hostname 145 | 146 | def initialize(app, hostname = 'example.org') 147 | self.app = app 148 | self.hostname = hostname 149 | end 150 | end 151 | end 152 | end 153 | end -------------------------------------------------------------------------------- /lib/curb-fu/response.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | module Response 3 | class Base 4 | attr_accessor :status, :body, :headers 5 | 6 | def initialize(status, headers, body) 7 | @status = status 8 | set_response_type(status) 9 | @body = body 10 | @headers = headers.is_a?(String) ? parse_headers(headers) : headers 11 | end 12 | 13 | def success? 14 | self.is_a?(CurbFu::Response::Success) 15 | end 16 | 17 | def redirect? 18 | self.is_a?(CurbFu::Response::Redirection) 19 | end 20 | 21 | def failure? 22 | !(success? || redirect?) 23 | end 24 | 25 | def server_fail? 26 | self.is_a?(CurbFu::Response::ServerError) 27 | end 28 | 29 | def client_fail? 30 | self.is_a?(CurbFu::Response::ClientError) 31 | end 32 | 33 | def parse_headers(header_string) 34 | header_lines = header_string.split($/) 35 | header_lines.shift 36 | header_lines.inject({}) do |hsh, line| 37 | whole_enchillada, key, value = /^(.*?):\s*(.*)$/.match(line.chomp).to_a 38 | unless whole_enchillada.nil? 39 | # note: headers with multiple instances should have multiple values in the headers hash 40 | hsh[key] = hsh[key] ? [hsh[key]].flatten << value : value 41 | end 42 | hsh 43 | end 44 | end 45 | 46 | def to_hash 47 | { :status => status, :body => body, :headers => headers } 48 | end 49 | 50 | def content_length 51 | if ( header_value = self['Content-Length'] ) 52 | header_value.to_i 53 | end 54 | end 55 | 56 | def content_type 57 | if ( header_value = self['Content-Type'] ) 58 | header_value.split(';').first 59 | end 60 | end 61 | 62 | def get_fields(key) 63 | if ( match = @headers.find{|k,v| k.downcase == key.downcase} ) 64 | [match.last].flatten 65 | else 66 | [] 67 | end 68 | end 69 | 70 | def [](key) 71 | get_fields(key).last 72 | end 73 | 74 | def set_response_type(status) 75 | case status 76 | when 100..199 then 77 | self.extend CurbFu::Response::Information 78 | case self.status 79 | when 101 then self.extend CurbFu::Response::Continue 80 | when 102 then self.extend CurbFu::Response::SwitchProtocl 81 | end 82 | when 200..299 then 83 | self.extend CurbFu::Response::Success 84 | case self.status 85 | when 200 then self.extend CurbFu::Response::OK 86 | when 201 then self.extend CurbFu::Response::Created 87 | when 202 then self.extend CurbFu::Response::Accepted 88 | when 203 then self.extend CurbFu::Response::NonAuthoritativeInformation 89 | when 204 then self.extend CurbFu::Response::NoContent 90 | when 205 then self.extend CurbFu::Response::ResetContent 91 | when 206 then self.extend CurbFu::Response::PartialContent 92 | end 93 | when 300..399 then 94 | self.extend CurbFu::Response::Redirection 95 | case self.status 96 | when 300 then self.extend CurbFu::Response::MultipleChoice 97 | when 301 then self.extend CurbFu::Response::MovedPermanently 98 | when 302 then self.extend CurbFu::Response::Found 99 | when 303 then self.extend CurbFu::Response::SeeOther 100 | when 304 then self.extend CurbFu::Response::NotModified 101 | when 305 then self.extend CurbFu::Response::UseProxy 102 | when 307 then self.extend CurbFu::Response::TemporaryRedirect 103 | end 104 | when 400..499 then 105 | self.extend CurbFu::Response::ClientError 106 | case self.status 107 | when 400 then self.extend CurbFu::Response::BadRequest 108 | when 401 then self.extend CurbFu::Response::Unauthorized 109 | when 402 then self.extend CurbFu::Response::PaymentRequired 110 | when 403 then self.extend CurbFu::Response::Forbidden 111 | when 404 then self.extend CurbFu::Response::NotFound 112 | when 405 then self.extend CurbFu::Response::MethodNotAllowed 113 | when 406 then self.extend CurbFu::Response::NotAcceptable 114 | when 407 then self.extend CurbFu::Response::ProxyAuthenticationRequired 115 | when 408 then self.extend CurbFu::Response::RequestTimeOut 116 | when 409 then self.extend CurbFu::Response::Conflict 117 | when 410 then self.extend CurbFu::Response::Gone 118 | when 411 then self.extend CurbFu::Response::LengthRequired 119 | when 412 then self.extend CurbFu::Response::PreconditionFailed 120 | when 413 then self.extend CurbFu::Response::RequestEntityTooLarge 121 | when 414 then self.extend CurbFu::Response::RequestURITooLong 122 | when 415 then self.extend CurbFu::Response::UnsupportedMediaType 123 | when 416 then self.extend CurbFu::Response::UnsupportedMediaType 124 | when 417 then self.extend CurbFu::Response::ExpectationFailed 125 | end 126 | when 500..599 then 127 | self.extend CurbFu::Response::ServerError 128 | case self.status 129 | when 500 then self.extend CurbFu::Response::InternalServerError 130 | when 501 then self.extend CurbFu::Response::NotImplemented 131 | when 502 then self.extend CurbFu::Response::BadGateway 132 | when 503 then self.extend CurbFu::Response::ServiceUnavailable 133 | when 504 then self.extend CurbFu::Response::GatewayTimeOut 134 | when 505 then self.extend CurbFu::Response::VersionNotSupported 135 | end 136 | else 137 | self.extend CurbFu::Response::UnknownResponse 138 | end 139 | end 140 | 141 | class << self 142 | def from_rack_response(rack) 143 | raise ArgumentError.new("Rack response may not be nil") if rack.nil? 144 | response = self.new(rack.status, rack.headers, rack.body) 145 | end 146 | 147 | def from_curb_response(curb) 148 | response = self.new(curb.response_code, curb.header_str, curb.body_str) 149 | response 150 | end 151 | 152 | def from_hash(hash) 153 | return nil if hash.nil? 154 | self.new(hash[:status], hash[:headers], hash[:body]) 155 | end 156 | end 157 | end 158 | 159 | module Information; end 160 | module Continue; def self.to_i; 100; end; def message; "Continue"; end; end 161 | module SwitchProtocol; def self.to_i; 101; end; def message; "Switch Protocol"; end; end 162 | module Success; def self.to_i; 200; end; def message; "Success"; end; end 163 | module OK; def self.to_i; 200; end; def message; "OK"; end; end 164 | module Created; def self.to_i; 201; end; def message; "Created"; end; end 165 | module Accepted; def self.to_i; 202; end; def message; "Accepted"; end; end 166 | module NonAuthoritativeInformation; def self.to_i; 203; end; def message; "Non Authoritative Information"; end; end 167 | module NoContent; def self.to_i; 204; end; def message; "No Content"; end; end 168 | module ResetContent; def self.to_i; 205; end; def message; "Reset Content"; end; end 169 | module PartialContent; def self.to_i; 206; end; def message; "Partial Content"; end; end 170 | module Redirection; def self.to_i; 300; end; def message; "Redirection"; end; end 171 | module MultipleChoice; def self.to_i; 300; end; def message; "Multiple Choice"; end; end 172 | module MovedPermanently; def self.to_i; 301; end; def message; "Moved Permanently"; end; end 173 | module Found; def self.to_i; 302; end; def message; "Found"; end; end 174 | module SeeOther; def self.to_i; 303; end; def message; "See Other"; end; end 175 | module NotModified; def self.to_i; 304; end; def message; "Not Modified"; end; end 176 | module UseProxy; def self.to_i; 305; end; def message; "Use Proxy"; end; end 177 | module TemporaryRedirect; def self.to_i; 307; end; def message; "Temporary Redirect"; end; end 178 | module ClientError; def self.to_i; 400; end; def message; "Client Error"; end; end 179 | module BadRequest; def self.to_i; 400; end; def message; "Bad Request"; end; end 180 | module Unauthorized; def self.to_i; 401; end; def message; "Unauthorized"; end; end 181 | module PaymentRequired; def self.to_i; 402; end; def message; "Payment Required"; end; end 182 | module Forbidden; def self.to_i; 403; end; def message; "Forbidden"; end; end 183 | module NotFound; def self.to_i; 404; end; def message; "Not Found"; end; end 184 | module MethodNotAllowed; def self.to_i; 405; end; def message; "Method Not Allowed"; end; end 185 | module NotAcceptable; def self.to_i; 406; end; def message; "Not Acceptable"; end; end 186 | module ProxyAuthenticationRequired; def self.to_i; 407; end; def message; "Proxy Authentication Required"; end; end 187 | module RequestTimeOut; def self.to_i; 408; end; def message; "Request Time Out"; end; end 188 | module Conflict; def self.to_i; 409; end; def message; "Conflict"; end; end 189 | module Gone; def self.to_i; 410; end; def message; "Gone"; end; end 190 | module LengthRequired; def self.to_i; 411; end; def message; "Length Required"; end; end 191 | module PreconditionFailed; def self.to_i; 412; end; def message; "Precondition Failed"; end; end 192 | module RequestEntityTooLarge; def self.to_i; 413; end; def message; "Request Entity Too Large"; end; end 193 | module RequestURITooLong; def self.to_i; 414; end; def message; "Request URI Too Long"; end; end 194 | module UnsupportedMediaType; def self.to_i; 415; end; def message; "Unsupported Media Type"; end; end 195 | module RequestedRangeNotSatisfiable; def self.to_i; 416; end; def message; "Requested Range Not Satisfiable"; end; end 196 | module ExpectationFailed; def self.to_i; 417; end; def message; "Expectation Failed"; end; end 197 | module ServerError; def self.to_i; 500; end; def message; "Server Error"; end; end 198 | module InternalServerError; def self.to_i; 500; end; def message; "Internal Server Error"; end; end 199 | module NotImplemented; def self.to_i; 501; end; def message; "Not Implemented"; end; end 200 | module BadGateway; def self.to_i; 502; end; def message; "Bad Gateway"; end; end 201 | module ServiceUnavailable; def self.to_i; 503; end; def message; "Service Unavailable"; end; end 202 | module GatewayTimeOut; def self.to_i; 504; end; def message; "Gateway Time Out"; end; end 203 | module VersionNotSupported; def self.to_i; 505; end; def message; "Version Not Supported"; end; end 204 | module UnknownResponse; def self.to_i; 0; end; def message; "Unknown Response"; end; end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /lib/curb-fu/test.rb: -------------------------------------------------------------------------------- 1 | require 'curb-fu/test/server' 2 | require 'curb-fu/test/request_logger' 3 | 4 | module CurbFu 5 | module Test; end 6 | end -------------------------------------------------------------------------------- /lib/curb-fu/test/request_logger.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | module Test 3 | class RequestLogger 4 | class << self 5 | def entries(host) 6 | @entries ||= {} 7 | @entries[host] ||= [] 8 | end 9 | 10 | def log(env) 11 | req = Rack::Request.new(env) 12 | url = env['PATH_INFO'] 13 | post_params = req.POST 14 | host = env['HTTP_HOST'] || env['SERVER_NAME'] 15 | entries(host) << { :url => url, :params => post_params } 16 | end 17 | def requested?(host, url, params = nil) 18 | url_found = (url.is_a?(String)) ? 19 | !entries(host).find { |entry| entry[:url] == url }.nil? : 20 | !entries(host).find { |entry| entry[:url] =~ url }.nil? 21 | if params.nil? 22 | return url_found 23 | else 24 | params_found = !entries(host).find { |entry| entry[:params] == params }.nil? 25 | url_found && params_found 26 | end 27 | end 28 | end 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/curb-fu/test/server.rb: -------------------------------------------------------------------------------- 1 | require 'rack' 2 | 3 | module CurbFu 4 | module Test 5 | class Server 6 | def self.serve(&blk) 7 | Rack::Builder.app do 8 | run lambda { |env| 9 | CurbFu::Test::RequestLogger.log(env) 10 | yield(env) 11 | } 12 | end 13 | end 14 | 15 | def self.error!(message) 16 | puts message 17 | raise StandardError, message 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /lib/curb-fu/version.rb: -------------------------------------------------------------------------------- 1 | module CurbFu 2 | VERSION = "0.6.2" 3 | end -------------------------------------------------------------------------------- /spec/fixtures/foo.txt: -------------------------------------------------------------------------------- 1 | asdf -------------------------------------------------------------------------------- /spec/lib/curb-fu/core_ext_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') 2 | require 'curb-fu/core_ext' 3 | 4 | describe "module inclusion" do 5 | it "should include appropriate InstanceMethods" do 6 | class Tester 7 | include CurbFu::ObjectExtensions 8 | end 9 | 10 | Tester.new.should respond_to(:to_param_pair) 11 | end 12 | end 13 | 14 | describe String do 15 | it "should respond_to #to_param_pair" do 16 | "".should respond_to(:to_param_pair) 17 | end 18 | describe "to_param_pair" do 19 | it "should return itself as the value for the passed-in name" do 20 | "foo".to_param_pair("quux").should == "quux=foo" 21 | end 22 | it "should be CGI escaped" do 23 | "Whee, some 'unsafe' uri things".to_param_pair("safe").should == "safe=Whee%2C+some+%27unsafe%27+uri+things" 24 | end 25 | end 26 | end 27 | 28 | describe Hash do 29 | it "should respond to #to_param_pair" do 30 | {}.should respond_to(:to_param_pair) 31 | end 32 | describe "to_param_pair" do 33 | it "should collect its keys and values into parameter pairs, prepending the provided prefix" do 34 | { 35 | "kraplach" => "messy", 36 | "zebot" => 2003 37 | }.to_param_pair("things").should == "things[kraplach]=messy&things[zebot]=2003" 38 | end 39 | it "should handle having an array as one of its parameters" do 40 | result = { 41 | "vielleicht" => "perhaps", 42 | "ratings" => [5, 3, 5, 2, 4] 43 | }.to_param_pair("things") 44 | result.split('&').size.should == 6 45 | result.should =~ /things\[vielleicht\]=perhaps/ 46 | result.should =~ /things\[ratings\]\[\]=5/ 47 | result.should =~ /things\[ratings\]\[\]=3/ 48 | result.should =~ /things\[ratings\]\[\]=5/ 49 | result.should =~ /things\[ratings\]\[\]=2/ 50 | result.should =~ /things\[ratings\]\[\]=4/ 51 | end 52 | end 53 | end 54 | 55 | describe Array do 56 | it "should respond_to #to_param_pair" do 57 | [].should respond_to(:to_param_pair) 58 | end 59 | describe "to_param_pair" do 60 | it "should join each element, prepending a provided key prefix" do 61 | [1, 23, 5].to_param_pair("magic_numbers").should == ["magic_numbers[]=1", "magic_numbers[]=23", "magic_numbers[]=5"].join("&") 62 | end 63 | it "should call to_param_pair on each element, too" do 64 | [1, 23, {"barkley" => 5}].to_param_pair("magic_numbers").should == "magic_numbers[]=1&magic_numbers[]=23&magic_numbers[][barkley]=5" 65 | end 66 | end 67 | end 68 | 69 | describe Integer do 70 | it "should respond_to #to_param_pair" do 71 | 1.should respond_to(:to_param_pair) 72 | end 73 | describe "to_param_pair" do 74 | it "should return a stringified version of itself, using the provided key" do 75 | 5.to_param_pair("fixnum").should == "fixnum=5" 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /spec/lib/curb-fu/request/base_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../../spec_helper' 2 | require 'curb' 3 | 4 | def regex_for_url_with_params(url, *params) 5 | regex = '^' + url.gsub('/','\/').gsub('.','\.') 6 | regex += '\?' unless params.empty? 7 | 8 | unless params.empty? 9 | param_possibilities = params.join('|') 10 | regex += params.inject([]) { |list, param| list << "(#{param_possibilities})" }.join('&') 11 | end 12 | regex += '$' 13 | Regexp.new(regex) 14 | end 15 | 16 | class TestHarness 17 | extend CurbFu::Request::Base 18 | 19 | def self.create_post_fields(*args) # testing interface to private method #create_post_fields 20 | super(*args) 21 | end 22 | 23 | def self.create_put_fields(*args) # testing interface to private method #create_put_fields 24 | super(*args) 25 | end 26 | end 27 | 28 | describe CurbFu::Request::Base do 29 | describe "build_url" do 30 | it "should return a string if a string parameter is given" do 31 | TestHarness.build_url("http://www.cliffsofinsanity.com").should == "http://www.cliffsofinsanity.com" 32 | end 33 | it "should return a string if a :url paramter is given" do 34 | TestHarness.build_url(:url => "http://www.cliffsofinsanity.com", :headers => { 'Content-Type' => 'cash/dollars' }).should == "http://www.cliffsofinsanity.com" 35 | end 36 | it "should return a built url with just a hostname if only the hostname is given" do 37 | TestHarness.build_url(:host => "poisonedwine.com").should == "http://poisonedwine.com" 38 | end 39 | it "should return a built url with hostname and port if port is also given" do 40 | TestHarness.build_url(:host => "www2.giantthrowingrocks.com", :port => 8080). 41 | should == "http://www2.giantthrowingrocks.com:8080" 42 | end 43 | it "should return a built url with hostname, port, and path if all are given" do 44 | TestHarness.build_url(:host => "spookygiantburningmonk.org", :port => 3000, :path => '/standing/in/a/wheelbarrow.aspx'). 45 | should == "http://spookygiantburningmonk.org:3000/standing/in/a/wheelbarrow.aspx" 46 | end 47 | it 'should append a query string if a query params hash is given' do 48 | TestHarness.build_url('http://navyseals.mil', :swim_speed => '2knots'). 49 | should == 'http://navyseals.mil?swim_speed=2knots' 50 | end 51 | it 'should append a query string if a query string is given' do 52 | TestHarness.build_url('http://chocolatecheese.com','?nuts=true'). 53 | should == 'http://chocolatecheese.com?nuts=true' 54 | end 55 | it "should accept a 'protocol' parameter" do 56 | TestHarness.build_url(:host => "mybank.com", :protocol => "https").should == "https://mybank.com" 57 | end 58 | end 59 | 60 | describe "get" do 61 | it "should get the google" do 62 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 63 | Curl::Easy.should_receive(:new).with('http://www.google.com').and_return(@mock_curb) 64 | 65 | TestHarness.get("http://www.google.com") 66 | end 67 | it "should return a 404 code correctly" do 68 | mock_curb = mock(Object, :http_get => nil) 69 | TestHarness.stub!(:build).and_return(mock_curb) 70 | mock_response = mock(CurbFu::Response::NotFound, :status => 404) 71 | CurbFu::Response::Base.stub!(:from_curb_response).and_return(mock_response) 72 | 73 | TestHarness.get("http://www.google.com/ponies_and_pirates").should == mock_response 74 | end 75 | it "should append query parameters" do 76 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 77 | Curl::Easy.should_receive(:new).with(regex_for_url_with_params('http://www.google.com', 'search=MSU\+vs\+UNC', 'limit=200')).and_return(@mock_curb) 78 | TestHarness.get('http://www.google.com', { :search => 'MSU vs UNC', :limit => 200 }) 79 | end 80 | it "should set cookies" do 81 | the_cookies = "SekretAuth=123134234" 82 | 83 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 84 | Curl::Easy.should_receive(:new).and_return(@mock_curb) 85 | @mock_curb.should_receive(:cookies=).with(the_cookies) 86 | 87 | TestHarness.get("http://google.com", {}, the_cookies) 88 | end 89 | 90 | describe "with_hash" do 91 | it "should get google from {:host => \"www.google.com\", :port => 80}" do 92 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 93 | Curl::Easy.should_receive(:new).with('http://www.google.com:80').and_return(@mock_curb) 94 | 95 | TestHarness.get({:host => "www.google.com", :port => 80}) 96 | end 97 | it "should set authorization username and password if provided" do 98 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil, :http_auth_types= => nil) 99 | Curl::Easy.stub!(:new).and_return(@mock_curb) 100 | @mock_curb.should_receive(:userpwd=).with("agent:donttellanyone") 101 | 102 | TestHarness.get({:host => "secret.domain.com", :port => 80, :username => "agent", :password => "donttellanyone"}) 103 | end 104 | it "should append parameters to the url" do 105 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 106 | Curl::Easy.should_receive(:new).with(regex_for_url_with_params('http://www.google.com', 'search=MSU\+vs\+UNC', 'limit=200')).and_return(@mock_curb) 107 | TestHarness.get({ :host => 'www.google.com' }, { :search => 'MSU vs UNC', :limit => 200 }) 108 | end 109 | it "should set cookies" do 110 | the_cookies = "SekretAuth=123134234" 111 | 112 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil, :http_get => nil) 113 | Curl::Easy.should_receive(:new).and_return(@mock_curb) 114 | @mock_curb.should_receive(:cookies=).with(the_cookies) 115 | 116 | TestHarness.get({ 117 | :host => "google.com", 118 | :port => 80, 119 | :cookies => the_cookies 120 | }) 121 | end 122 | end 123 | end 124 | 125 | describe "post" do 126 | before(:each) do 127 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil) 128 | Curl::Easy.stub!(:new).and_return(@mock_curb) 129 | end 130 | 131 | it "should send each parameter to Curb#http_post" do 132 | @mock_q = Curl::PostField.content('q','derek') 133 | @mock_r = Curl::PostField.content('r','matt') 134 | TestHarness.stub!(:create_post_fields).and_return([@mock_q,@mock_r]) 135 | 136 | @mock_curb.should_receive(:http_post).with(@mock_q,@mock_r) 137 | 138 | response = TestHarness.post( 139 | {:host => "google.com", :port => 80, :path => "/search"}, 140 | { 'q' => 'derek', 'r' => 'matt' }) 141 | end 142 | end 143 | 144 | describe "post_file" do 145 | it "should set encoding to multipart/form-data" do 146 | @cc = mock(Curl::PostField) 147 | Curl::PostField.stub!(:file).and_return(@cc) 148 | mock_curl = mock(Object, :http_post => nil) 149 | mock_curl.should_receive(:multipart_form_post=).with(true) 150 | TestHarness.stub!(:build).and_return(mock_curl) 151 | CurbFu::Response::Base.stub!(:from_curb_response) 152 | 153 | TestHarness.post_file('http://example.com', {'gelato' => 'peanut butter'}, 'cc_pic' => '/images/credit_card.jpg') 154 | end 155 | it "should post with file fields" do 156 | @cc = mock(Curl::PostField) 157 | Curl::PostField.should_receive(:file).and_return(@cc) 158 | mock_curl = mock(Object, :multipart_form_post= => nil, :http_post => nil) 159 | TestHarness.stub!(:build).and_return(mock_curl) 160 | CurbFu::Response::Base.stub!(:from_curb_response) 161 | 162 | TestHarness.post_file('http://example.com', {'gelato' => 'peanut butter'}, 'cc_pic' => '/images/credit_card.jpg') 163 | end 164 | it "should offer more debug information about CurlErrInvalidPostField errors" do 165 | @cc = mock(Curl::PostField) 166 | Curl::PostField.should_receive(:file).and_return(@cc) 167 | mock_curl = mock(Object, :multipart_form_post= => nil) 168 | mock_curl.stub!(:http_post).and_raise(Curl::Err::InvalidPostFieldError) 169 | TestHarness.stub!(:build).and_return(mock_curl) 170 | CurbFu::Response::Base.stub!(:from_curb_response) 171 | 172 | lambda { TestHarness.post_file('http://example.com', {'gelato' => 'peanut butter'}, 'cc_pic' => '/images/credit_card.jpg') }. 173 | should raise_error(Curl::Err::InvalidPostFieldError) 174 | end 175 | end 176 | 177 | describe "put" do 178 | before(:each) do 179 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil) 180 | Curl::Easy.stub!(:new).and_return(@mock_curb) 181 | end 182 | 183 | it "should send each parameter to Curb#http_put" do 184 | @mock_q = Curl::PostField.content('q','derek') 185 | @mock_r = Curl::PostField.content('r','matt') 186 | TestHarness.stub!(:create_post_fields).and_return([@mock_q,@mock_r]) 187 | 188 | @mock_curb.should_receive(:http_put).with(@mock_q,@mock_r) 189 | 190 | response = TestHarness.put( 191 | {:host => "google.com", :port => 80, :path => "/search"}, 192 | { 'q' => 'derek', 'r' => 'matt' }) 193 | end 194 | end 195 | 196 | describe "delete" do 197 | before(:each) do 198 | @resource_link = 'http://example.com/resource/1' 199 | @mock_curb = mock(Curl::Easy, :headers= => nil, :headers => {}, :header_str => "", :response_code => 200, :body_str => 'yeeeah', :timeout= => nil) 200 | Curl::Easy.stub!(:new).and_return(@mock_curb) 201 | end 202 | 203 | it "should send each parameter to Curb#http_delete" do 204 | Curl::Easy.should_receive(:new).with(@resource_link).and_return(@mock_curb) 205 | @mock_curb.should_receive(:http_delete) 206 | 207 | response = TestHarness.delete(@resource_link) 208 | end 209 | end 210 | 211 | 212 | describe "create_post_fields" do 213 | it "should return the params if params is a string" do 214 | TestHarness.create_post_fields("my awesome data that I'm sending to you"). 215 | should == "my awesome data that I'm sending to you" 216 | end 217 | it "should convert hash items into Curl::PostFields" do 218 | Curl::PostField.should_receive(:content).with('us','obama') 219 | Curl::PostField.should_receive(:content).with('de','merkel') 220 | TestHarness.create_post_fields(:us => 'obama', :de => 'merkel') 221 | end 222 | it "should handle params that contain arrays" do 223 | Curl::PostField.should_receive(:content).with('q','derek,matt') 224 | 225 | TestHarness.create_post_fields('q' => ['derek','matt']) 226 | end 227 | it "should handle params that contain any non-Array or non-String data" do 228 | Curl::PostField.should_receive(:content).with('q','1') 229 | 230 | TestHarness.create_post_fields('q' => 1) 231 | end 232 | it "should return an array of Curl::PostFields" do 233 | TestHarness.create_post_fields(:ice_cream => 'chocolate', :beverage => 'water').each do |field| 234 | field.should be_a_kind_of(Curl::PostField) 235 | end 236 | end 237 | end 238 | 239 | describe "create_put_fields" do 240 | it "should return the params if params is a string" do 241 | TestHarness.create_put_fields("my awesome data that I'm sending to you"). 242 | should == "my awesome data that I'm sending to you" 243 | end 244 | 245 | it 'should handle multiple parameters' do 246 | TestHarness.create_put_fields(:rock => 'beatles', :rap => '2pac').split("&"). 247 | should include("rock=beatles","rap=2pac") 248 | end 249 | 250 | it "should handle params that contain arrays" do 251 | TestHarness.create_put_fields('q' => ['derek','matt']). 252 | should == "q=derek,matt" 253 | end 254 | 255 | it "should handle params that contain any non-Array or non-String data" do 256 | TestHarness.create_put_fields('q' => 1).should == "q=1" 257 | end 258 | end 259 | 260 | describe "global_headers" do 261 | it "should use any global headers for every request" do 262 | TestHarness.global_headers = { 263 | 'X-Http-Modern-Parlance' => 'Transmogrify' 264 | } 265 | 266 | mock_curl = mock(Object, :timeout= => 'sure', :http_get => 'uhuh', :response_code => 200, :header_str => 'yep: sure', :body_str => 'ok') 267 | Curl::Easy.stub!(:new).and_return(mock_curl) 268 | mock_curl.should_receive(:headers=).with('X-Http-Modern-Parlance' => 'Transmogrify') 269 | TestHarness.get('http://example.com') 270 | end 271 | it "should not keep temporary headers from previous requests" do 272 | TestHarness.global_headers = { 273 | 'X-Http-Political-Party' => 'republican' 274 | } 275 | 276 | mock_curl = mock(Object, :timeout= => 'sure', :http_get => 'uhuh', :response_code => 200, :header_str => 'yep: sure', :body_str => 'ok') 277 | Curl::Easy.stub!(:new).and_return(mock_curl) 278 | mock_curl.stub!(:headers=) 279 | 280 | TestHarness.get(:host => 'example.com', :headers => { 'Content-Type' => 'cash/dollars' }) 281 | 282 | mock_curl.should_not_receive(:headers=).with(hash_including('Content-Type' => 'cash/dollars')) 283 | TestHarness.get('http://example.com') 284 | TestHarness.global_headers.should_not include('Content-Type' => 'cash/dollars') # leave no trace! 285 | end 286 | end 287 | end 288 | -------------------------------------------------------------------------------- /spec/lib/curb-fu/request/parameter_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../../spec_helper') 2 | require 'curb-fu/core_ext' 3 | 4 | describe CurbFu::Request::Parameter do 5 | describe "initialize" do 6 | it "should accept a key and value pair" do 7 | param = CurbFu::Request::Parameter.new("simple", "value") 8 | param.name.should == "simple" 9 | param.value.should == "value" 10 | end 11 | it "should accept a hash" do 12 | lambda { CurbFu::Request::Parameter.new("policy", 13 | { "archive_length_units" => "eons", "to" => "cthulhu@goo.org" }) }.should_not raise_error 14 | end 15 | end 16 | 17 | describe "to_uri_param" do 18 | it "should serialize the key and value into an acceptable uri format" do 19 | param = CurbFu::Request::Parameter.new("simple", "value") 20 | param.to_uri_param.should == "simple=value" 21 | end 22 | describe "complex cases" do 23 | it "should convert a hash parameter into the appropriate set of name-value pairs" do 24 | params = CurbFu::Request::Parameter.new("policy", { "archive_length_units" => "eons", "to" => "cthulhu@goo.org" }) 25 | params.to_uri_param.should =~ /policy\[archive_length_units\]=eons/ 26 | params.to_uri_param.should =~ /policy\[to\]=cthulhu\%40goo\.org/ 27 | params.to_uri_param.should =~ /.+&.+/ 28 | end 29 | it "should even handle cases where one of the hash parameters is an array" do 30 | params = CurbFu::Request::Parameter.new("messages", { "failed" => [2134, 123, 4325], "policy_id" => 45 }) 31 | params.to_uri_param.should =~ /messages\[failed\]\[\]=2134/ 32 | params.to_uri_param.should =~ /messages\[failed\]\[\]=123/ 33 | params.to_uri_param.should =~ /messages\[failed\]\[\]=4325/ 34 | params.to_uri_param.should =~ /messages\[policy_id\]=45/ 35 | params.to_uri_param.should =~ /.+&.+&.+&.+/ 36 | end 37 | end 38 | end 39 | 40 | describe "to_curl_post_field" do 41 | it "should serialize the key and value into an acceptable uri format" do 42 | param = CurbFu::Request::Parameter.new("simple", "value") 43 | field = param.to_curl_post_field 44 | field.name.should == 'simple' 45 | field.content.should == 'value' 46 | end 47 | describe "complex cases" do 48 | it "should convert a hash parameter into the appropriate set of name-value pairs" do 49 | params = CurbFu::Request::Parameter.new("policy", 50 | { "archive_length_units" => "eons", "to" => "cthulhu@goo.org" }) 51 | fields = params.to_curl_post_field 52 | fields.find { |f| f.name == 'policy[archive_length_units]' && f.content == 'eons'}.should_not be_nil 53 | fields.find { |f| f.name == 'policy[to]' && f.content == 'cthulhu@goo.org'}.should_not be_nil 54 | end 55 | it "should even handle cases where one of the hash parameters is an array" do 56 | params = CurbFu::Request::Parameter.new("messages", { "failed" => [2134, 123, 4325], "policy_id" => 45 }). 57 | to_curl_post_field 58 | params.find { |p| p.name == 'messages[failed][]' && p.content == '2134' }.should_not be_nil 59 | params.find { |p| p.name == 'messages[failed][]' && p.content == '123' }.should_not be_nil 60 | params.find { |p| p.name == 'messages[failed][]' && p.content == '4325' }.should_not be_nil 61 | params.find { |p| p.name == 'messages[policy_id]' && p.content == '45' }.should_not be_nil 62 | end 63 | it "should not send a CGI-escaped value to Curl::PostField" do 64 | field = CurbFu::Request::Parameter.new("messages", "uh-oh! We've failed!"). 65 | to_curl_post_field 66 | 67 | field.content.should == "uh-oh! We've failed!" 68 | end 69 | it "should not CGI-escape @ symbols" do 70 | field = CurbFu::Request::Parameter.new("messages", "bob@apple.com"). 71 | to_curl_post_field 72 | 73 | field.content.should == "bob@apple.com" 74 | end 75 | end 76 | end 77 | end -------------------------------------------------------------------------------- /spec/lib/curb-fu/request/test_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../../spec_helper' 2 | 3 | def test_file_path 4 | File.dirname(__FILE__) + "/../../../fixtures/foo.txt" 5 | end 6 | 7 | describe CurbFu::Request::Test do 8 | before :each do 9 | @a_server = mock(Object, :call => [200, { 'Content-Type' => 'spec/testcase' }, ["A is for Archer, an excellent typeface."]]) 10 | @b_server = mock(Object, :call => [200, {},["B is for Ballyhoo, like what happened when Twitter switched to Scala"]]) 11 | @c_server = mock(Object, :call => [200, {}, ["C is for Continuous, as in Integration"]]) 12 | 13 | CurbFu.stubs = { 14 | 'a.example.com' => @a_server, 15 | 'b.example.com' => @b_server, 16 | 'c.example.com' => @c_server 17 | } 18 | 19 | @mock_rack_response = mock(Rack::MockResponse, :status => 200, :headers => {}, :body => "C is for Continuous, as in Integration") 20 | end 21 | 22 | describe "module inclusion" do 23 | it "should define a 'get' method" do 24 | class Test 25 | include CurbFu::Request::Test 26 | end 27 | Test.should respond_to(:get) 28 | end 29 | end 30 | 31 | describe "parse_hostname" do 32 | it "should return just the hostname from a full URL" do 33 | CurbFu::Request.parse_hostname('http://a.example.com/foo/bar?transaxle=true'). 34 | should == 'a.example.com' 35 | end 36 | it 'should return the hostname if just a hostname is given' do 37 | CurbFu::Request.parse_hostname('b.example.com'). 38 | should == 'b.example.com' 39 | end 40 | end 41 | 42 | describe "process_headers" do 43 | it "should convert http headers into their upcased, HTTP_ prepended form for the Rack environment" do 44 | CurbFu::Request.process_headers({'X-Mirror-Request' => 'true'}).should == {"HTTP_X_MIRROR_REQUEST" => "true"} 45 | end 46 | it "should handle a whole hashful of headers" do 47 | CurbFu::Request.process_headers({ 48 | 'X-Mirror-Request' => 'true', 49 | 'Accept-Encoding' => '*/*', 50 | 'X-Forwarded-For' => 'greenviewdata.com' 51 | }).should == { 52 | "HTTP_X_MIRROR_REQUEST" => "true", 53 | "HTTP_ACCEPT_ENCODING" => "*/*", 54 | "HTTP_X_FORWARDED_FOR" => "greenviewdata.com" 55 | } 56 | end 57 | end 58 | 59 | describe "match_host" do 60 | it "should return the appropriate Rack::Test instance to delegate the request to" do 61 | CurbFu::Request.match_host("a.example.com").app.should == @a_server 62 | end 63 | it "should return nil if no match is made" do 64 | CurbFu::Request.match_host("m.google.com").should be_nil 65 | end 66 | end 67 | 68 | describe "build_request_options" do 69 | it "should parse headers" do 70 | CurbFu::Request.build_request_options({:host => 'd.example.com', :path => '/big/white/dog', :headers => { 'Accept' => 'beer/pilsner' }}). 71 | should include(:headers => { 'Accept' => 'beer/pilsner' }) 72 | end 73 | it "should parse url" do 74 | CurbFu::Request.build_request_options({:host => 'd.example.com', :path => '/big/white/dog'}). 75 | should include(:url => 'http://d.example.com/big/white/dog') 76 | end 77 | it "should parse username and password" do 78 | CurbFu::Request.build_request_options({:host => 'd.example.com', :path => '/big/white/dog', :username => 'bill', :password => 's3cr3t' }). 79 | should include(:username => 'bill', :password => 's3cr3t') 80 | end 81 | it "should get an interface" do 82 | CurbFu::Request.build_request_options({:host => 'c.example.com', :path => '/big/white/dog'}). 83 | should include(:interface => CurbFu.stubs['c.example.com']) 84 | end 85 | end 86 | 87 | describe "get_interface" do 88 | it "should parse a string" do 89 | CurbFu::Request.get_interface('http://a.example.com').app.should == @a_server 90 | end 91 | it "should parse a hash" do 92 | CurbFu::Request.get_interface({:host => 'a.example.com'}).app.should == @a_server 93 | end 94 | end 95 | 96 | describe "respond" do 97 | it "should convert headers to uppercase, underscorized" do 98 | CurbFu::Response::Base.stub!(:from_rack_response) 99 | mock_interface = mock(Object, :send => mock(Object, :status => 200), :hostname= => nil, :hostname => 'a.example.com') 100 | mock_interface.should_receive(:header).with('HTTP_X_MONARCHY','false') 101 | mock_interface.should_receive(:header).with('HTTP_X_ANARCHO_SYNDICALIST_COMMUNE','true') 102 | 103 | CurbFu::Request.respond(mock_interface, :get, 'http://a.example.com/', {}, 104 | {'X-Anarcho-Syndicalist-Commune' => 'true', 'X-Monarchy' => 'false'}, nil, nil) 105 | end 106 | end 107 | 108 | describe "hashify_params" do 109 | it "should turn a URL-formatted query string into a hash of parameters" do 110 | hash = CurbFu::Request.hashify_params("color=red&shape=round") 111 | hash.should include('color' => 'red') 112 | hash.should include('shape' => 'round') 113 | end 114 | it "should convert array-formatted params into a hash of arrays" do 115 | hash = CurbFu::Request.hashify_params("make[]=Chevrolet&make[]=Pontiac&make[]=GMC") 116 | hash.should == {'make' => ['Chevrolet','Pontiac','GMC']} 117 | end 118 | it "should convert hash parameters into a hash of hashes" do 119 | hash = CurbFu::Request.hashify_params("car[make]=Chevrolet&car[color]=red&car[wheel_shape]=round") 120 | hash.should == {'car' => { 121 | 'make' => 'Chevrolet', 122 | 'color' => 'red', 123 | 'wheel_shape' => 'round' 124 | }} 125 | end 126 | it 'should remove any leading ?s' do 127 | hash = CurbFu::Request.hashify_params("?q=134&dave=astronaut") 128 | hash.keys.should_not include('?q') 129 | end 130 | end 131 | 132 | describe "get" do 133 | it 'should delegate the get request to the Rack::Test instance' do 134 | CurbFu.stubs['a.example.com'].should_receive(:get).with('http://a.example.com/gimme/html', anything, anything).and_return(@mock_rack_response) 135 | @a_server.should respond_to(:call) 136 | CurbFu::Request.get('http://a.example.com/gimme/html') 137 | end 138 | it 'should raise Curl::Err::ConnectionFailedError if hostname is not defined in stub list' do 139 | lambda { CurbFu::Request.get('http://m.google.com/gimme/html') }.should raise_error(Curl::Err::ConnectionFailedError) 140 | end 141 | it 'should return a CurbFu::Response object' do 142 | response = CurbFu::Request.get('http://a.example.com/gimme/html') 143 | response.should be_a_kind_of(CurbFu::Response::Base) 144 | response.status.should == 200 145 | response.headers.should == { 'Content-Type' => 'spec/testcase', "Content-Length"=>"39" } 146 | response.body.should == "A is for Archer, an excellent typeface." 147 | end 148 | end 149 | 150 | describe "post" do 151 | it 'should delegate the post request to the Rack::Test instance' do 152 | CurbFu.stubs['b.example.com'].should_receive(:post). 153 | with('http://b.example.com/html/backatcha', {'html' => 'CSRF in da house! '}, anything). 154 | and_return(@mock_rack_response) 155 | CurbFu::Request.post('http://b.example.com/html/backatcha', 156 | {'html' => 'CSRF in da house! '}) 157 | end 158 | it 'should raise Curl::Err::ConnectionFailedError if hostname is not defined in stub list' do 159 | lambda { CurbFu::Request.post('http://m.google.com/gimme/html') }.should raise_error(Curl::Err::ConnectionFailedError) 160 | end 161 | it 'should return a CurbFu::Response object' do 162 | response = CurbFu::Request.post('http://a.example.com/gimme/html') 163 | response.should be_a_kind_of(CurbFu::Response::Base) 164 | response.status.should == 200 165 | response.headers.should == { 'Content-Type' => 'spec/testcase', "Content-Length"=>"39" } 166 | response.body.should == "A is for Archer, an excellent typeface." 167 | end 168 | end 169 | 170 | describe "post_file" do 171 | it 'should delegate the post request to the Rack::Test instance' do 172 | CurbFu.stubs['b.example.com'].should_receive(:post). 173 | with('http://b.example.com/html/backatcha', hash_including("foo.txt"=>anything, "filename"=>"asdf ftw"), anything). 174 | and_return(@mock_rack_response) 175 | CurbFu::Request.post_file('http://b.example.com/html/backatcha', {'filename' => 'asdf ftw'}, {'foo.txt' => test_file_path }) 176 | end 177 | it 'should raise Curl::Err::ConnectionFailedError if hostname is not defined in stub list' do 178 | lambda { CurbFu::Request.post_file('http://m.google.com/gimme/html') }.should raise_error(Curl::Err::ConnectionFailedError) 179 | end 180 | it 'should return a CurbFu::Response object' do 181 | response = CurbFu::Request.post_file('http://a.example.com/gimme/html') 182 | response.should be_a_kind_of(CurbFu::Response::Base) 183 | response.status.should == 200 184 | response.headers.should == { 'Content-Type' => 'spec/testcase', "Content-Length"=>"39" } 185 | response.body.should == "A is for Archer, an excellent typeface." 186 | end 187 | end 188 | 189 | describe "put" do 190 | it 'should delegate the put request to the Rack::Test instance' do 191 | CurbFu.stubs['a.example.com'].should_receive(:put).with('http://a.example.com/gimme/html', anything, anything).and_return(@mock_rack_response) 192 | CurbFu::Request.put('http://a.example.com/gimme/html') 193 | end 194 | it 'should raise Curl::Err::ConnectionFailedError if hostname is not defined in stub list' do 195 | lambda { CurbFu::Request.put('http://m.google.com/gimme/html') }.should raise_error(Curl::Err::ConnectionFailedError) 196 | end 197 | it 'should return a CurbFu::Response object' do 198 | response = CurbFu::Request.put('http://a.example.com/gimme/html') 199 | response.should be_a_kind_of(CurbFu::Response::Base) 200 | response.status.should == 200 201 | response.headers.should == { 'Content-Type' => 'spec/testcase', "Content-Length"=>"39" } 202 | response.body.should == "A is for Archer, an excellent typeface." 203 | end 204 | end 205 | 206 | describe "delete" do 207 | it 'should delegate the delete request to the Rack::Test instance' do 208 | CurbFu.stubs['a.example.com'].should_receive(:delete).with('http://a.example.com/gimme/html', anything, anything).and_return(@mock_rack_response) 209 | @a_server.should respond_to(:call) 210 | CurbFu::Request.delete('http://a.example.com/gimme/html') 211 | end 212 | it 'should raise Curl::Err::ConnectionFailedError if hostname is not defined in stub list' do 213 | lambda { CurbFu::Request.delete('http://m.google.com/gimme/html') }.should raise_error(Curl::Err::ConnectionFailedError) 214 | end 215 | it 'should return a CurbFu::Response object' do 216 | response = CurbFu::Request.delete('http://a.example.com/gimme/html') 217 | response.should be_a_kind_of(CurbFu::Response::Base) 218 | response.status.should == 200 219 | response.headers.should == { 'Content-Type' => 'spec/testcase', "Content-Length"=>"39" } 220 | response.body.should == "A is for Archer, an excellent typeface." 221 | end 222 | end 223 | end 224 | -------------------------------------------------------------------------------- /spec/lib/curb-fu/response_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../../spec_helper' 2 | require 'htmlentities' 3 | 4 | describe CurbFu::Response::Base do 5 | describe "from_rack_response" do 6 | it "should create a new CurbFu::Response object out of a rack response (array)" do 7 | rack = mock(Object, :status => 200, :headers => { 'Expires' => '05-12-2034' }, :body => "This will never go out of style") 8 | response = CurbFu::Response::Base.from_rack_response(rack) 9 | response.should be_a_kind_of(CurbFu::Response::OK) 10 | response.body.should == "This will never go out of style" 11 | response.headers.should include('Expires' => '05-12-2034') 12 | end 13 | end 14 | 15 | describe "from_curb_response" do 16 | it "should create a new CurbFu::Response object out of a curb response object" do 17 | curb = mock(Curl::Easy, :body_str => "Miscellaneous Facts About Curb", :header_str => "HTTP/1.1 200 OK\r\nCache-Control: private, max-age=0\r\nDate: Tue, 17 Mar 2009 17:34:08 GMT\r\nExpires: -1\r\n", :response_code => 200 ) 18 | response = CurbFu::Response::Base.from_curb_response(curb) 19 | response.should be_a_kind_of(CurbFu::Response::OK) 20 | response.body.should == "Miscellaneous Facts About Curb" 21 | response.headers.should include("Expires" => '-1', "Cache-Control" => 'private, max-age=0') 22 | response.status.should == 200 23 | end 24 | end 25 | 26 | describe "successes" do 27 | it "should create a success (200) response" do 28 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => "") 29 | r = CurbFu::Response::Base.from_curb_response(mock_curb) 30 | r.should be_a_kind_of(CurbFu::Response::Base) 31 | r.should be_a_kind_of(CurbFu::Response::Success) 32 | r.should be_a_kind_of(CurbFu::Response::OK) 33 | r.should_not be_a_kind_of(CurbFu::Response::Created) 34 | end 35 | it "should create a success (201) response" do 36 | mock_curb = mock(Object, :response_code => 201, :body_str => 'OK', :header_str => "") 37 | r = CurbFu::Response::Base.from_curb_response(mock_curb) 38 | r.should be_a_kind_of(CurbFu::Response::Base) 39 | r.should be_a_kind_of(CurbFu::Response::Success) 40 | r.should be_a_kind_of(CurbFu::Response::Created) 41 | end 42 | end 43 | it "should create a 400 response" do 44 | mock_curb = mock(Object, :response_code => 404, :body_str => 'OK', :header_str => "", :timeout= => nil) 45 | r = CurbFu::Response::Base.from_curb_response(mock_curb) 46 | r.should be_a_kind_of(CurbFu::Response::Base) 47 | r.should be_a_kind_of(CurbFu::Response::ClientError) 48 | end 49 | it "should create a 500 response" do 50 | mock_curb = mock(Object, :response_code => 503, :body_str => 'OK', :header_str => "", :timeout= => nil) 51 | r = CurbFu::Response::Base.from_curb_response(mock_curb) 52 | r.should be_a_kind_of(CurbFu::Response::Base) 53 | r.should be_a_kind_of(CurbFu::Response::ServerError) 54 | end 55 | 56 | describe "response modules" do 57 | describe ".to_i" do 58 | it "should return the status code represented by the module" do 59 | CurbFu::Response::OK.to_i.should == 200 60 | CurbFu::Response::NotFound.to_i.should == 404 61 | end 62 | end 63 | describe "#message" do 64 | it "should return a string indicating the english translation of the status code" do 65 | r = CurbFu::Response::Base.new(200, {}, "text") 66 | r.message.should == "OK" 67 | r = CurbFu::Response::Base.new(404, {}, "text") 68 | r.message.should == "Not Found" 69 | r = CurbFu::Response::Base.new(302, {}, "text") 70 | r.message.should == "Found" 71 | r = CurbFu::Response::Base.new(505, {}, "text") 72 | r.message.should == "Version Not Supported" 73 | end 74 | end 75 | end 76 | 77 | describe "parse_headers" do 78 | before(:each) do 79 | end 80 | 81 | describe "test data" do 82 | it "should parse all of Google's headers" do 83 | headers = "HTTP/1.1 200 OK\r\nCache-Control: private, max-age=0\r\nDate: Tue, 17 Mar 2009 17:34:08 GMT\r\nExpires: -1\r\nContent-Type: text/html; charset=ISO-8859-1\r\nSet-Cookie: PREF=ID=16472704f58eb437:TM=1237311248:LM=1237311248:S=KrWlq33vvam8d_De; expires=Thu, 17-Mar-2011 17:34:08 GMT; path=/; domain=.google.com\r\nServer: gws\r\nTransfer-Encoding: chunked\r\n\r\n" 84 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 85 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 86 | 87 | @cf.headers['Cache-Control'].should == 'private, max-age=0' 88 | @cf.headers['Date'].should == 'Tue, 17 Mar 2009 17:34:08 GMT' 89 | @cf.headers['Expires'].should == '-1' 90 | @cf.headers['Content-Type'].should == 'text/html; charset=ISO-8859-1' 91 | @cf.headers['Set-Cookie'].should == 'PREF=ID=16472704f58eb437:TM=1237311248:LM=1237311248:S=KrWlq33vvam8d_De; expires=Thu, 17-Mar-2011 17:34:08 GMT; path=/; domain=.google.com' 92 | @cf.headers['Server'].should == 'gws' 93 | @cf.headers['Transfer-Encoding'].should == 'chunked' 94 | @cf.headers.keys.length.should == 7 95 | end 96 | 97 | it "should parse our json headers from the data_store" do 98 | headers = "HTTP/1.1 200 OK\r\nServer: nginx/0.6.34\r\nDate: Tue, 17 Mar 2009 05:40:32 GMT\r\nContent-Type: text/json\r\nConnection: close\r\nContent-Length: 18\r\n\r\n" 99 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 100 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 101 | 102 | @cf.headers['Server'].should == 'nginx/0.6.34' 103 | @cf.headers['Date'].should == 'Tue, 17 Mar 2009 05:40:32 GMT' 104 | @cf.headers['Content-Type'].should == 'text/json' 105 | @cf.headers['Connection'].should == 'close' 106 | @cf.headers['Content-Length'].should == '18' 107 | @cf.headers.keys.length.should == 5 108 | end 109 | 110 | it "should use an array to store values for headers fields with multiple instances" do 111 | headers = "HTTP/1.1 200 OK\r\nSet-Cookie: first cookie value\r\nSet-Cookie: second cookie value\r\n\r\n" 112 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 113 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 114 | @cf.headers['Set-Cookie'].should == ["first cookie value", "second cookie value"] 115 | end 116 | end 117 | end 118 | 119 | describe "get_fields" do 120 | 121 | before(:each) do 122 | headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: 18\r\nSet-Cookie: first cookie value\r\nServer: gws\r\nTransfer-Encoding: chunked\r\nSet-Cookie: second cookie value\r\n\r\n" 123 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 124 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 125 | end 126 | 127 | it "should return an array containing all matching field values" do 128 | @cf.get_fields("Set-Cookie").should == ["first cookie value", "second cookie value"] 129 | @cf.get_fields("Content-Length").should == ["18"] 130 | end 131 | 132 | it "should do a case-insensitive match of the key to header fields" do 133 | @cf.get_fields("content-length").should == ["18"] 134 | end 135 | 136 | it "should return empty array when key matches no header field" do 137 | @cf.get_fields("non-existent").should == [] 138 | end 139 | 140 | end 141 | 142 | describe "[]" do 143 | 144 | before(:each) do 145 | headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: 18\r\nSet-Cookie: first cookie value\r\nServer: gws\r\nTransfer-Encoding: chunked\r\nSet-Cookie: second cookie value\r\n\r\n" 146 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 147 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 148 | end 149 | 150 | it "should return the last matching field value" do 151 | @cf["Set-Cookie"].should == "second cookie value" 152 | end 153 | 154 | it "should return the entire header value" do 155 | @cf["Content-Type"].should == "text/html; charset=ISO-8859-1" 156 | end 157 | 158 | it "should return the header value as a string" do 159 | @cf["Content-Length"].should == "18" 160 | end 161 | 162 | it "should do a case-insensitive match of the key to header fields" do 163 | @cf["content-length"].should == "18" 164 | end 165 | 166 | it "should return nil when key matches no header field" do 167 | @cf["non-existent"].should == nil 168 | end 169 | 170 | end 171 | 172 | describe "content_type" do 173 | 174 | it "should return the content-type as a mime type, disgarding charset or other info found in the content-type header" do 175 | headers = "HTTP/1.1 200 OK\r\nContent-Length: 18\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" 176 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 177 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 178 | @cf.content_type.should == "text/html" 179 | end 180 | 181 | it "should return the content-type from the last header field value" do 182 | headers = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=ISO-8859-1\r\nContent-Length: 18\r\nContent-Type: application/xhtml+xml; charset=UTF-8\r\n\r\n" 183 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 184 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 185 | @cf.content_type.should == "application/xhtml+xml" 186 | end 187 | 188 | it "should return nil when the response doesn't contain a content-type header" do 189 | headers = "HTTP/1.1 200 OK\r\nr\nContent-Length: 18r\n\r\n" 190 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 191 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 192 | @cf.content_type.should == nil 193 | end 194 | 195 | end 196 | 197 | describe "content_length" do 198 | 199 | it "should return the last content-length header field value, as an integer" do 200 | headers = "HTTP/1.1 200 OK\r\nContent-Length: 100\r\nContent-Length: 18\r\n\r\n" 201 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 202 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 203 | @cf.content_length.should == 18 204 | end 205 | 206 | it "should return nil when the response doesn't contain a content-length header" do 207 | headers = "HTTP/1.1 200 OK\r\nr\nContent-Type: text/htmlr\n\r\n" 208 | mock_curb = mock(Object, :response_code => 200, :body_str => 'OK', :header_str => headers, :timeout= => nil) 209 | @cf = CurbFu::Response::Base.from_curb_response(mock_curb) 210 | @cf.content_length.should == nil 211 | end 212 | 213 | end 214 | 215 | end 216 | -------------------------------------------------------------------------------- /spec/lib/curb_fu_spec.rb: -------------------------------------------------------------------------------- 1 | require File.dirname(__FILE__) + '/../spec_helper' 2 | 3 | describe CurbFu do 4 | describe "stubs=" do 5 | it 'should insert the CurbFu::Request::Test module into CurbFu::Request' do 6 | CurbFu.stubs = { 'example.com' => mock(Object, :call => [200, {}, "Hello, World"] ) } 7 | CurbFu::Request.should include(CurbFu::Request::Test) 8 | end 9 | it 'should not insert the CurbFu::StubbedRequest module into CurbFu::Request if it is already there' do 10 | CurbFu::Request.stub!(:include?).and_return(false, true) 11 | CurbFu::Request.should_receive(:include).once 12 | CurbFu.stubs = { 'example.com' => mock(Object, :call => [200, {}, "Hello, World"] ) } 13 | CurbFu.stubs = { 'example.net' => mock(Object, :call => [404, {}, "not found"] ) } 14 | end 15 | it 'should not insert the CurbFu::StubbedRequest module if the method is given nil instead of a hash' do 16 | CurbFu::Request.should_not_receive(:include) 17 | CurbFu.stubs = nil 18 | end 19 | end 20 | 21 | describe 'stub' do 22 | it 'should create a stub interface for the given hostname using the supplied object' do 23 | CurbFu.stubs = { 'localhost' => Object } 24 | my_rack_app = mock(Object) 25 | CurbFu.stub('webserver.com',my_rack_app) 26 | CurbFu.stubs['webserver.com']. 27 | should be_an_instance_of(CurbFu::Request::Test::Interface) 28 | CurbFu.stubs = nil 29 | end 30 | end 31 | 32 | describe 'stubs' do 33 | it 'should return nil by default' do 34 | CurbFu.stubs = nil # no way to guarantee that CurbFu.stubs hasn't already been set by another spec 35 | CurbFu.stubs.should be_nil 36 | end 37 | it 'should return a hash of hostnames pointing to CurbFu::StubbedRequest::TestInterfaces' do 38 | CurbFu.stubs = { 'example.com' => mock(Object, :call => [200, {}, "Hello, World"] ) } 39 | CurbFu.stubs['example.com'].should be_a_kind_of(CurbFu::Request::Test::Interface) 40 | end 41 | it 'should set the hostname on each interface' do 42 | CurbFu.stubs = { 43 | 'ysthevanishedomens.com' => mock(Object) 44 | } 45 | 46 | CurbFu.stubs['ysthevanishedomens.com'].hostname.should == 'ysthevanishedomens.com' 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'curb-fu' 3 | require 'htmlentities' 4 | 5 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 6 | 7 | RSpec.configure do |config| 8 | end 9 | --------------------------------------------------------------------------------