alvarobp-partigirb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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