alvarobp-partigirb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Alvaro Bautista & Fernando Blat
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,9 @@
1
+ = partigirb
2
+
3
+ Wrapper for the Partigi REST API.
4
+
5
+ Adapted from Hayes Davis grackle gem (http://github.com/hayesdavis/grackle/tree/master)
6
+
7
+ == Copyright
8
+
9
+ Copyright (c) 2009 Alvaro Bautista & Fernando Blat, released under MIT license
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "partigirb"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = ["alvarobp@gmail.com", "ferblape@gmail.com"]
10
+ gem.homepage = "http://github.com/alvarobp/partigirb"
11
+ gem.authors = ["Alvaro Bautista", "Fernando Blat"]
12
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
13
+ end
14
+
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/*_test.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+
40
+ task :default => :test
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ if File.exist?('VERSION.yml')
45
+ config = YAML.load(File.read('VERSION.yml'))
46
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
47
+ else
48
+ version = ""
49
+ end
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "partigirb #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
56
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,169 @@
1
+ module Partigirb
2
+ class Client
3
+ class Request #:nodoc:
4
+ attr_accessor :client, :path, :method, :api_version
5
+
6
+ def initialize(client,api_version=Partigirb::CURRENT_API_VERSION)
7
+ self.client = client
8
+ self.api_version = api_version
9
+ self.method = :get
10
+ self.path = ''
11
+ end
12
+
13
+ def <<(path)
14
+ self.path << path
15
+ end
16
+
17
+ def path?
18
+ path.length > 0
19
+ end
20
+
21
+ def url
22
+ "#{scheme}://#{host}/api/v#{self.api_version}#{path}"
23
+ end
24
+
25
+ def host
26
+ client.api_host
27
+ end
28
+
29
+ def scheme
30
+ 'http'
31
+ end
32
+ end
33
+
34
+ VALID_METHODS = [:get,:post,:put,:delete]
35
+ VALID_FORMATS = [:atom,:xml,:json]
36
+
37
+ PARTIGI_API_HOST = "www.partigi.com"
38
+ TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
39
+
40
+ attr_accessor :default_format, :headers, :api_version, :transport, :request, :api_host, :auth
41
+
42
+ def initialize(options={})
43
+ self.transport = Transport.new
44
+ self.api_host = PARTIGI_API_HOST.clone
45
+ self.api_version = options[:api_version] || Partigirb::CURRENT_API_VERSION
46
+ self.headers = {"User-Agent"=>"Partigirb/#{Partigirb::VERSION}"}.merge!(options[:headers]||{})
47
+ self.default_format = options[:default_format] || :xml
48
+
49
+ #self.handlers = {:json=>Handlers::JSONHandler.new,:xml=>Handlers::XMLHandler.new,:unknown=>Handlers::StringHandler.new}
50
+ #self.handlers.merge!(options[:handlers]||{})
51
+
52
+ # Authentication param should be a hash with keys:
53
+ # login (required)
54
+ # api_secret (required)
55
+ # nonce (optional, would be automatically generated if missing)
56
+ # timestamp (optional, current timestamp will be automatically used if missing)
57
+ self.auth = options[:auth]
58
+ end
59
+
60
+ def method_missing(name,*args)
61
+ # If method is a format name, execute using that format
62
+ if format_invocation?(name)
63
+ return call_with_format(name,*args)
64
+ end
65
+ # If method ends in ! or ? use that to determine post or get
66
+ if name.to_s =~ /^(.*)(!|\?)$/
67
+ name = $1.to_sym
68
+ # ! is a post, ? is a get
69
+ self.request.method = ($2 == '!' ? :post : :get)
70
+ if format_invocation?(name)
71
+ return call_with_format(name,*args)
72
+ else
73
+ self.request << "/#{$1}"
74
+ return call_with_format(self.default_format,*args)
75
+ end
76
+ end
77
+ # Else add to the request path
78
+ self.request << "/#{name}"
79
+ self
80
+ end
81
+
82
+ # Clears any pending request built up by chained methods but not executed
83
+ def clear
84
+ self.request = nil
85
+ end
86
+
87
+ def request
88
+ @request ||= Request.new(self,api_version)
89
+ end
90
+
91
+ protected
92
+
93
+ def call_with_format(format,params={})
94
+ request << ".#{format}"
95
+ res = send_request(params)
96
+ process_response(format,res)
97
+ ensure
98
+ clear
99
+ end
100
+
101
+ def send_request(params)
102
+ begin
103
+ set_authentication_headers
104
+
105
+ transport.request(
106
+ request.method, request.url, :headers=>headers, :params=>params
107
+ )
108
+ rescue => e
109
+ puts e
110
+ end
111
+ end
112
+
113
+ def process_response(format, res)
114
+ process_response_errors(format, res) if res.code != 200
115
+
116
+ # TODO: Use ResponseParser here depending on format to return a Response object
117
+ res
118
+ end
119
+
120
+ def process_response_errors(format, res)
121
+ # FIXME: This is totally provisional, we should use a ResponseParser to parse errors for each format
122
+ case format
123
+ when :xml
124
+ res.body =~ /<error>Partigi::(.*)<\/error>/
125
+ puts "Error: #{$1}"
126
+ end
127
+ end
128
+
129
+ def format_invocation?(name)
130
+ self.request.path? && VALID_FORMATS.include?(name)
131
+ end
132
+
133
+ # Adds the proper WSSE headers if there are the right authentication parameters
134
+ def set_authentication_headers
135
+ unless self.auth.nil? || self.auth === Hash || self.auth.empty?
136
+ auths = self.auth.stringify_keys
137
+
138
+ if auths.has_key?('login') && auths.has_key?('api_secret')
139
+ if !auths['timestamp'].nil?
140
+ timestamp = case auths['timestamp']
141
+ when Time
142
+ auths['timestamp'].strftime(TIMESTAMP_FORMAT)
143
+ when String
144
+ auths['timestamp']
145
+ end
146
+ else
147
+ timestamp = Time.now.strftime(TIMESTAMP_FORMAT) if timestamp.nil?
148
+ end
149
+
150
+ nonce = auths['nonce'] || generate_nonce
151
+ password_digest = generate_password_digest(nonce, timestamp, auths['login'], auths['api_secret'])
152
+ headers.merge!({
153
+ 'Authorization' => "WSSE realm=\"#{PARTIGI_API_HOST}\", profile=\"UsernameToken\"",
154
+ 'X-WSSE' => "UsernameToken Username=\"#{auths['login']}\", PasswordDigest=\"#{password_digest}\", Nonce=\"#{nonce}\", Created=\"#{timestamp}\""
155
+ })
156
+ end
157
+ end
158
+ end
159
+
160
+ def generate_nonce
161
+ o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten
162
+ Digest::MD5.hexdigest((0..10).map{o[rand(o.length)]}.join)
163
+ end
164
+
165
+ def generate_password_digest(nonce, timestamp, login, secret)
166
+ Base64.encode64(Digest::SHA1.hexdigest("#{nonce}#{timestamp}#{login}#{secret}")).chomp
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,19 @@
1
+ # Ruby Hash extensions from ActiveSupport
2
+
3
+ class Hash
4
+ # Return a new hash with all keys converted to strings.
5
+ def stringify_keys
6
+ inject({}) do |options, (key, value)|
7
+ options[key.to_s] = value
8
+ options
9
+ end
10
+ end
11
+
12
+ # Destructively convert all keys to strings.
13
+ def stringify_keys!
14
+ keys.each do |key|
15
+ self[key.to_s] = delete(key)
16
+ end
17
+ self
18
+ end
19
+ end
@@ -0,0 +1,160 @@
1
+ module Partigirb
2
+
3
+ class Response #:nodoc:
4
+ attr_accessor :method, :request_uri, :status, :body
5
+
6
+ def initialize(method,request_uri,status,body)
7
+ self.method = method
8
+ self.request_uri = request_uri
9
+ self.status = status
10
+ self.body = body
11
+ end
12
+ end
13
+
14
+ class Transport
15
+
16
+ attr_accessor :debug
17
+
18
+ CRLF = "\r\n"
19
+
20
+ def req_class(method)
21
+ case method
22
+ when :get then Net::HTTP::Get
23
+ when :post then Net::HTTP::Post
24
+ when :put then Net::HTTP::Put
25
+ when :delete then Net::HTTP::Delete
26
+ end
27
+ end
28
+
29
+ # Options are one of
30
+ # - :params - a hash of parameters to be sent with the request. If a File is a parameter value, \
31
+ # a multipart request will be sent. If a Time is included, .httpdate will be called on it.
32
+ # - :headers - a hash of headers to send with the request
33
+ def request(method, string_url, options={})
34
+ params = stringify_params(options[:params])
35
+ if method == :get && params
36
+ string_url << query_string(params)
37
+ end
38
+ url = URI.parse(string_url)
39
+ begin
40
+ execute_request(method,url,options)
41
+ rescue Timeout::Error
42
+ raise "Timeout while #{method}ing #{url.to_s}"
43
+ end
44
+ end
45
+
46
+ def execute_request(method,url,options={})
47
+ conn = Net::HTTP.new(url.host, url.port)
48
+ #conn.use_ssl = (url.scheme == 'https')
49
+ conn.start do |http|
50
+ req = req_class(method).new(url.request_uri)
51
+
52
+ add_headers(req,options[:headers])
53
+ if file_param?(options[:params])
54
+ add_multipart_data(req,options[:params])
55
+ else
56
+ add_form_data(req,options[:params])
57
+ end
58
+
59
+ dump_request(req) if debug
60
+ res = http.request(req)
61
+ dump_response(res) if debug
62
+ res
63
+ end
64
+ end
65
+
66
+ def query_string(params)
67
+ query = case params
68
+ when Hash then params.map{|key,value| url_encode_param(key,value) }.join("&")
69
+ else url_encode(params.to_s)
70
+ end
71
+ if !(query == nil || query.length == 0) && query[0,1] != '?'
72
+ query = "?#{query}"
73
+ end
74
+ query
75
+ end
76
+
77
+ private
78
+ def stringify_params(params)
79
+ return nil unless params
80
+ params.inject({}) do |h, pair|
81
+ key, value = pair
82
+ if value.respond_to? :httpdate
83
+ value = value.httpdate
84
+ end
85
+ h[key] = value
86
+ h
87
+ end
88
+ end
89
+
90
+ def file_param?(params)
91
+ return false unless params
92
+ params.any? {|key,value| value.respond_to? :read }
93
+ end
94
+
95
+ def url_encode(value)
96
+ require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
97
+ CGI.escape(value.to_s)
98
+ end
99
+
100
+ def url_encode_param(key,value)
101
+ "#{url_encode(key)}=#{url_encode(value)}"
102
+ end
103
+
104
+ def add_headers(req,headers)
105
+ if headers
106
+ headers.each do |header, value|
107
+ req[header] = value
108
+ end
109
+ end
110
+ end
111
+
112
+ def add_form_data(req,params)
113
+ if req.request_body_permitted? && params
114
+ req.set_form_data(params)
115
+ end
116
+ end
117
+
118
+ def add_multipart_data(req,params)
119
+ boundary = Time.now.to_i.to_s(16)
120
+ req["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
121
+ body = ""
122
+ params.each do |key,value|
123
+ esc_key = url_encode(key)
124
+ body << "--#{boundary}#{CRLF}"
125
+ if value.respond_to?(:read)
126
+ mime_type = MIME::Types.type_for(value.path)[0] || MIME::Types["application/octet-stream"][0]
127
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"; filename=\"#{File.basename(value.path)}\"#{CRLF}"
128
+ body << "Content-Type: #{mime_type.simplified}#{CRLF*2}"
129
+ body << value.read
130
+ else
131
+ body << "Content-Disposition: form-data; name=\"#{esc_key}\"#{CRLF*2}#{value}"
132
+ end
133
+ body << CRLF
134
+ end
135
+ body << "--#{boundary}--#{CRLF*2}"
136
+ req.body = body
137
+ req["Content-Length"] = req.body.size
138
+ end
139
+
140
+ private
141
+
142
+ def dump_request(req)
143
+ puts "Sending Request"
144
+ puts"#{req.method} #{req.path}"
145
+ dump_headers(req)
146
+ end
147
+
148
+ def dump_response(res)
149
+ puts "Received Response"
150
+ dump_headers(res)
151
+ puts res.body
152
+ end
153
+
154
+ def dump_headers(msg)
155
+ msg.each_header do |key, value|
156
+ puts "\t#{key}=#{value}"
157
+ end
158
+ end
159
+ end
160
+ end
data/lib/partigirb.rb ADDED
@@ -0,0 +1,17 @@
1
+ module Partigirb
2
+ VERSION='0.1.0'
3
+ CURRENT_API_VERSION=1
4
+ end
5
+
6
+ $:.unshift File.dirname(__FILE__)
7
+
8
+ require 'open-uri'
9
+ require 'net/http'
10
+ require 'base64'
11
+ require 'digest'
12
+ require 'mime/types'
13
+
14
+ require 'partigirb/core_ext'
15
+
16
+ require 'partigirb/transport'
17
+ require 'partigirb/client'
data/partigirb.gemspec ADDED
@@ -0,0 +1,57 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{partigirb}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Alvaro Bautista", "Fernando Blat"]
9
+ s.date = %q{2009-07-20}
10
+ s.email = ["alvarobp@gmail.com", "ferblape@gmail.com"]
11
+ s.extra_rdoc_files = [
12
+ "LICENSE",
13
+ "README.rdoc"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "LICENSE",
18
+ "README.rdoc",
19
+ "Rakefile",
20
+ "VERSION",
21
+ "lib/partigirb.rb",
22
+ "lib/partigirb/client.rb",
23
+ "lib/partigirb/core_ext.rb",
24
+ "lib/partigirb/transport.rb",
25
+ "partigirb.gemspec",
26
+ "test/client_test.rb",
27
+ "test/mocks/net_http_mock.rb",
28
+ "test/mocks/response_mock.rb",
29
+ "test/mocks/transport_mock.rb",
30
+ "test/test_helper.rb",
31
+ "test/transport_test.rb"
32
+ ]
33
+ s.has_rdoc = true
34
+ s.homepage = %q{http://github.com/alvarobp/partigirb}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.1}
38
+ s.summary = %q{TODO}
39
+ s.test_files = [
40
+ "test/client_test.rb",
41
+ "test/mocks/net_http_mock.rb",
42
+ "test/mocks/response_mock.rb",
43
+ "test/mocks/transport_mock.rb",
44
+ "test/test_helper.rb",
45
+ "test/transport_test.rb"
46
+ ]
47
+
48
+ if s.respond_to? :specification_version then
49
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
50
+ s.specification_version = 2
51
+
52
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
+ else
54
+ end
55
+ else
56
+ end
57
+ end
@@ -0,0 +1,179 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+ def setup
5
+ @client = new_client
6
+ end
7
+
8
+ should "use GET method by default" do
9
+ @client.users.show.xml
10
+
11
+ assert_equal(:get, @client.transport.method)
12
+ end
13
+
14
+ should "use GET method for methods ending with ?" do
15
+ @client.users.show.xml?
16
+
17
+ assert_equal(:get, @client.transport.method)
18
+ end
19
+
20
+ should "not make a request if format is missing" do
21
+ MockTransport.any_instance.expects(:request).never
22
+
23
+ @client.users.show
24
+
25
+ assert_nil @client.transport.url
26
+ end
27
+
28
+ should "use POST method for methods ending with !" do
29
+ @client.users.show.xml!
30
+
31
+ assert_equal(:post, @client.transport.method)
32
+ end
33
+
34
+ should "use POST method and default format for methods ending with !" do
35
+ @client.friendships.create!
36
+
37
+ assert_equal(:post, @client.transport.method)
38
+ assert @client.transport.url.path.include?("/friendships/create.xml")
39
+ end
40
+
41
+ should "request to the Partigi API host through HTTP protocol" do
42
+ @client.users.show.xml
43
+
44
+ assert_equal('http', @client.transport.url.scheme)
45
+ assert_equal(Partigirb::Client::PARTIGI_API_HOST, @client.transport.url.host)
46
+ end
47
+
48
+ should "use the current API version by default" do
49
+ @client.users.show.xml
50
+
51
+ assert_equal(Partigirb::CURRENT_API_VERSION, @client.api_version)
52
+ end
53
+
54
+ should "put the requested API version in the request url" do
55
+ @client = new_client(200, '', {:api_version => 3})
56
+ @client.users.show.xml
57
+
58
+ assert_equal '/api/v3/users/show.xml', @client.transport.url.path
59
+ end
60
+
61
+ should "add paremeters to url on GET" do
62
+ @client.items.index.xml :type => 'film', :page => 1, :per_page => 20
63
+
64
+ assert_equal 3, request_query.size
65
+ assert request_query.include?('type=film')
66
+ assert request_query.include?('page=1')
67
+ assert request_query.include?('per_page=20')
68
+ end
69
+
70
+ should "add parameters to request body on POST" do
71
+ @client.reviews.create! :item_id => 'star-wars', :type => 'film', :status => 2, :text => 'My favorite movie', :rating => '5'
72
+
73
+ assert_equal 5, post_data.size
74
+ assert post_data.include?('item_id=star-wars')
75
+ assert post_data.include?('type=film')
76
+ assert post_data.include?('status=2')
77
+ assert post_data.include?('text=My%20favorite%20movie')
78
+ assert post_data.include?('rating=5')
79
+ end
80
+
81
+ should "add any headers to the HTTP requests" do
82
+ @client = new_client(200, '', {:headers => {'Useless' => 'void', 'Fake' => 'header'}})
83
+ @client.user.show.xml
84
+
85
+ assert_not_nil Net::HTTP.request['Useless']
86
+ assert_equal 'void', Net::HTTP.request['Useless']
87
+ assert_not_nil Net::HTTP.request['Fake']
88
+ assert_equal 'header', Net::HTTP.request['Fake']
89
+ end
90
+
91
+ should "add authentication headers when login and secret are provided" do
92
+ @client = new_client(200, '', :auth => {:login => 'auser', :api_secret => 'his_api_secret'})
93
+
94
+ @client.friendships.update! :id => 321
95
+
96
+ assert_not_nil Net::HTTP.request['Authorization']
97
+ assert_equal "WSSE realm=\"#{Partigirb::Client::PARTIGI_API_HOST}\", profile=\"UsernameToken\"", Net::HTTP.request['Authorization']
98
+
99
+ assert_not_nil Net::HTTP.request['X-WSSE']
100
+ assert_match /UsernameToken Username="auser", PasswordDigest="[^"]+", Nonce="[^"]+", Created="[^"]+"/, Net::HTTP.request['X-WSSE']
101
+ end
102
+
103
+ should "not add authentication headers if no auth params are provided" do
104
+ @client.friendships.update! :id => 321
105
+
106
+ assert_nil Net::HTTP.request['Authorization']
107
+ assert_nil Net::HTTP.request['X-WSSE']
108
+ end
109
+
110
+ should "use given nonce for authentication" do
111
+ @client = new_client(200, '', :auth => {:login => 'auser', :api_secret => 'his_api_secret', :nonce => '123456789101112'})
112
+ @client.friendships.update! :id => 321
113
+
114
+ assert_equal "WSSE realm=\"#{Partigirb::Client::PARTIGI_API_HOST}\", profile=\"UsernameToken\"", Net::HTTP.request['Authorization']
115
+ assert_match /UsernameToken Username="auser", PasswordDigest="[^"]+", Nonce="123456789101112", Created="[^"]+"/, Net::HTTP.request['X-WSSE']
116
+ end
117
+
118
+ should "use given timestamp string for authentication" do
119
+ @client = new_client(200, '', :auth => {:login => 'auser', :api_secret => 'his_api_secret', :timestamp => '2009-07-15T14:43:07Z'})
120
+ @client.friendships.update! :id => 321
121
+
122
+ assert_equal "WSSE realm=\"#{Partigirb::Client::PARTIGI_API_HOST}\", profile=\"UsernameToken\"", Net::HTTP.request['Authorization']
123
+ assert_match /UsernameToken Username="auser", PasswordDigest="[^"]+", Nonce="[^"]+", Created="2009-07-15T14:43:07Z"/, Net::HTTP.request['X-WSSE']
124
+ end
125
+
126
+ should "use given Time object as timestamp for authentication" do
127
+ timestamp = Time.now
128
+ @client = new_client(200, '', :auth => {:login => 'auser', :api_secret => 'his_api_secret', :timestamp => timestamp})
129
+ @client.friendships.update! :id => 321
130
+
131
+ assert_equal "WSSE realm=\"#{Partigirb::Client::PARTIGI_API_HOST}\", profile=\"UsernameToken\"", Net::HTTP.request['Authorization']
132
+ assert_match /UsernameToken Username="auser", PasswordDigest="[^"]+", Nonce="[^"]+", Created="#{timestamp.strftime(Partigirb::Client::TIMESTAMP_FORMAT)}"/, Net::HTTP.request['X-WSSE']
133
+ end
134
+
135
+ should "use the PasswordDigest from given parameters" do
136
+ @client = new_client(200, '', :auth => {:login => 'auser', :api_secret => 'his_api_secret', :nonce => '123456789101112', :timestamp => '2009-07-15T14:43:07Z'})
137
+ @client.friendships.update! :id => 321
138
+
139
+ pdigest = Base64.encode64(Digest::SHA1.hexdigest("1234567891011122009-07-15T14:43:07Zauserhis_api_secret")).chomp
140
+
141
+ assert_equal "WSSE realm=\"#{Partigirb::Client::PARTIGI_API_HOST}\", profile=\"UsernameToken\"", Net::HTTP.request['Authorization']
142
+ assert_match /UsernameToken Username="auser", PasswordDigest="#{pdigest}", Nonce="123456789101112", Created="2009-07-15T14:43:07Z"/, Net::HTTP.request['X-WSSE']
143
+ end
144
+
145
+ context "generate_nonce method" do
146
+ should "generate random strings" do
147
+ @client.instance_eval do
148
+ nonces = []
149
+ 1.upto(25) do
150
+ assert !nonces.include?(generate_nonce)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # TODO: Test for responses
157
+
158
+ # Copied from Grackle
159
+ should "clear the request path on clear" do
160
+ client = new_client(200,'[{"id":1,"text":"test 1"}]')
161
+ client.some.url.that.does.not.exist
162
+ assert_equal('/some/url/that/does/not/exist',client.send(:request).path,"An unexecuted path should be build up")
163
+ client.clear
164
+ assert_equal('',client.send(:request).path,"The path shoudl be cleared")
165
+ end
166
+
167
+ should "use multipart encoding when using a file param" do
168
+ client = new_client(200,'[{"id":1,"text":"test 1"}]')
169
+ client.account.update_profile_image! :image=>File.new(__FILE__)
170
+ assert_match(/multipart\/form-data/,Net::HTTP.request['Content-Type'])
171
+ end
172
+
173
+ should "escape and encode time param" do
174
+ client = new_client(200,'[{"id":1,"text":"test 1"}]')
175
+ time = Time.now-60*60
176
+ client.statuses.public_timeline? :since=>time
177
+ assert_equal("/api/v#{Partigirb::CURRENT_API_VERSION}/statuses/public_timeline.xml?since=#{CGI::escape(time.httpdate)}", Net::HTTP.request.path)
178
+ end
179
+ end
@@ -0,0 +1,12 @@
1
+ #Used for mocking HTTP requests
2
+ class Net::HTTP
3
+ class << self
4
+ attr_accessor :response, :request, :last_instance
5
+ end
6
+
7
+ def request(req)
8
+ self.class.last_instance = self
9
+ self.class.request = req
10
+ self.class.response
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ #Mock responses that conform mostly to HTTPResponse's interface
2
+ class MockResponse
3
+ include Net::HTTPHeader
4
+ attr_accessor :code, :body
5
+ def initialize(code,body,headers={})
6
+ self.code = code
7
+ self.body = body
8
+ headers.each do |name, value|
9
+ self[name] = value
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ #Transport that collects info on requests and responses for testing purposes
2
+ class MockTransport < Partigirb::Transport
3
+ attr_accessor :status, :body, :method, :url, :options
4
+
5
+ def initialize(status,body,headers={})
6
+ Net::HTTP.response = MockResponse.new(status,body,headers)
7
+ end
8
+
9
+ def request(method, string_url, options)
10
+ self.method = method
11
+ self.url = URI.parse(string_url)
12
+ self.options = options
13
+ super(method,string_url,options)
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+ require 'ruby-debug'
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'partigirb'
10
+
11
+ # TODO: Mock requests in some better way?
12
+ Dir.glob('test/mocks/*_mock.rb').each { |e| require e }
13
+
14
+ class Test::Unit::TestCase
15
+ def new_client(status=200, response_body='', client_opts={})
16
+ client = Partigirb::Client.new(client_opts)
17
+ client.transport = MockTransport.new(status,response_body)
18
+ client
19
+ end
20
+
21
+ def request_query
22
+ if Net::HTTP.request && !Net::HTTP.request.path.nil? && !Net::HTTP.request.path.empty?
23
+ Net::HTTP.request.path.split(/\?/)[1].split('&')
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ def post_data
30
+ Net::HTTP.request.body.split('&')
31
+ end
32
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'net/http'
3
+
4
+ class RequestTest < Test::Unit::TestCase
5
+ def test_nothing
6
+ assert true
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alvarobp-partigirb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alvaro Bautista
8
+ - Fernando Blat
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-07-20 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description:
18
+ email:
19
+ - alvarobp@gmail.com
20
+ - ferblape@gmail.com
21
+ executables: []
22
+
23
+ extensions: []
24
+
25
+ extra_rdoc_files:
26
+ - LICENSE
27
+ - README.rdoc
28
+ files:
29
+ - .gitignore
30
+ - LICENSE
31
+ - README.rdoc
32
+ - Rakefile
33
+ - VERSION
34
+ - lib/partigirb.rb
35
+ - lib/partigirb/client.rb
36
+ - lib/partigirb/core_ext.rb
37
+ - lib/partigirb/transport.rb
38
+ - partigirb.gemspec
39
+ - test/client_test.rb
40
+ - test/mocks/net_http_mock.rb
41
+ - test/mocks/response_mock.rb
42
+ - test/mocks/transport_mock.rb
43
+ - test/test_helper.rb
44
+ - test/transport_test.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/alvarobp/partigirb
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: TODO
71
+ test_files:
72
+ - test/client_test.rb
73
+ - test/mocks/net_http_mock.rb
74
+ - test/mocks/response_mock.rb
75
+ - test/mocks/transport_mock.rb
76
+ - test/test_helper.rb
77
+ - test/transport_test.rb