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 +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +9 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/lib/partigirb/client.rb +169 -0
- data/lib/partigirb/core_ext.rb +19 -0
- data/lib/partigirb/transport.rb +160 -0
- data/lib/partigirb.rb +17 -0
- data/partigirb.gemspec +57 -0
- data/test/client_test.rb +179 -0
- data/test/mocks/net_http_mock.rb +12 -0
- data/test/mocks/response_mock.rb +12 -0
- data/test/mocks/transport_mock.rb +15 -0
- data/test/test_helper.rb +32 -0
- data/test/transport_test.rb +8 -0
- metadata +77 -0
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
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
|
data/test/client_test.rb
ADDED
@@ -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
|
+
#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
|
data/test/test_helper.rb
ADDED
@@ -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
|
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
|