rest-core 2.0.4 → 2.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.
- checksums.yaml +4 -4
- data/CHANGES.md +33 -0
- data/Gemfile +1 -1
- data/README.md +1 -1
- data/Rakefile +13 -0
- data/lib/rest-core.rb +2 -0
- data/lib/rest-core/client.rb +22 -13
- data/lib/rest-core/engine/em-http-request.rb +37 -8
- data/lib/rest-core/engine/rest-client.rb +6 -4
- data/lib/rest-core/middleware.rb +24 -3
- data/lib/rest-core/middleware/auth_basic.rb +1 -1
- data/lib/rest-core/middleware/cache.rb +2 -2
- data/lib/rest-core/middleware/default_headers.rb +1 -1
- data/lib/rest-core/middleware/default_payload.rb +1 -1
- data/lib/rest-core/middleware/default_query.rb +1 -1
- data/lib/rest-core/middleware/oauth1_header.rb +3 -13
- data/lib/rest-core/middleware/oauth2_header.rb +1 -1
- data/lib/rest-core/middleware/oauth2_query.rb +1 -1
- data/lib/rest-core/test.rb +48 -0
- data/lib/rest-core/util/payload.rb +162 -0
- data/lib/rest-core/version.rb +1 -1
- data/rest-core.gemspec +14 -7
- data/task/gemgem.rb +7 -6
- data/test/test_auth_basic.rb +7 -5
- data/test/test_cache.rb +6 -0
- data/test/test_client_oauth1.rb +22 -22
- data/test/test_default_payload.rb +38 -0
- data/test/test_default_query.rb +10 -8
- data/test/{test_em_http_request.rb → test_em-http-request.rb} +40 -0
- data/test/test_follow_redirect.rb +9 -12
- data/test/test_json_response.rb +8 -12
- data/test/test_oauth1_header.rb +53 -53
- data/test/test_payload.rb +189 -22
- data/test/test_rest-client.rb +40 -0
- data/test/test_timeout.rb +8 -10
- metadata +25 -9
@@ -18,6 +18,46 @@ describe RC::EmHttpRequest do
|
|
18
18
|
}.resume}
|
19
19
|
end
|
20
20
|
|
21
|
+
describe 'POST Payload' do
|
22
|
+
after do
|
23
|
+
WebMock.reset!
|
24
|
+
end
|
25
|
+
|
26
|
+
client = RC::Builder.client
|
27
|
+
client.builder.run(RC::EmHttpRequest)
|
28
|
+
path = 'http://example.com'
|
29
|
+
ok = 'OK'
|
30
|
+
c = client.new
|
31
|
+
|
32
|
+
post = lambda do |payload, body|
|
33
|
+
stub_request(:post, path).with(:body => body).to_return(:body => ok)
|
34
|
+
EM.error_handler{ |e| e.should.kind_of?(NilClass); EM.stop }
|
35
|
+
EM.run{ EM.defer{
|
36
|
+
c.post(path, payload).should.eq ok
|
37
|
+
EM.next_tick{ EM.stop }
|
38
|
+
}}
|
39
|
+
end
|
40
|
+
|
41
|
+
should 'post with string' do
|
42
|
+
post['string', 'string']
|
43
|
+
end
|
44
|
+
|
45
|
+
should 'post with file' do
|
46
|
+
File.open(__FILE__) do |f|
|
47
|
+
b = f.read
|
48
|
+
f.rewind
|
49
|
+
post[f, b]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
should 'post with socket' do
|
54
|
+
rd, wr = IO.pipe
|
55
|
+
wr.write('socket')
|
56
|
+
wr.close
|
57
|
+
post[rd, 'socket']
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
21
61
|
# ----------------------------------------------------------------------
|
22
62
|
|
23
63
|
describe RC::Simple do
|
@@ -2,21 +2,18 @@
|
|
2
2
|
require 'rest-core/test'
|
3
3
|
|
4
4
|
describe RC::FollowRedirect do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
5
|
+
dry = Class.new do
|
6
|
+
attr_accessor :status
|
7
|
+
def call env
|
8
|
+
yield(env.merge(RC::RESPONSE_STATUS => status,
|
9
|
+
RC::RESPONSE_HEADERS => {'LOCATION' => 'location'}))
|
10
|
+
end
|
11
|
+
end.new
|
12
|
+
app = RC::FollowRedirect.new(dry, 1)
|
13
|
+
|
15
14
|
after do
|
16
15
|
RR.verify
|
17
16
|
end
|
18
|
-
def dry; @dry; end
|
19
|
-
def app; @app; end
|
20
17
|
|
21
18
|
[301, 302, 303, 307].each do |status|
|
22
19
|
should "not follow redirect if reached max_redirects: #{status}" do
|
data/test/test_json_response.rb
CHANGED
@@ -3,9 +3,7 @@ require 'rest-core/test'
|
|
3
3
|
|
4
4
|
describe RC::JsonResponse do
|
5
5
|
describe 'app' do
|
6
|
-
|
7
|
-
@app ||= RC::JsonResponse.new(RC::Dry.new, true)
|
8
|
-
end
|
6
|
+
app = RC::JsonResponse.new(RC::Dry.new, true)
|
9
7
|
|
10
8
|
should 'do nothing' do
|
11
9
|
expected = {RC::RESPONSE_BODY => nil,
|
@@ -23,15 +21,13 @@ describe RC::JsonResponse do
|
|
23
21
|
end
|
24
22
|
|
25
23
|
describe 'client' do
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
}
|
34
|
-
end
|
24
|
+
client = RC::Builder.client do
|
25
|
+
use RC::JsonResponse, true
|
26
|
+
run Class.new{
|
27
|
+
def call env
|
28
|
+
yield(env.merge(RC::RESPONSE_BODY => '{}'))
|
29
|
+
end
|
30
|
+
}
|
35
31
|
end
|
36
32
|
|
37
33
|
should 'do nothing' do
|
data/test/test_oauth1_header.rb
CHANGED
@@ -2,92 +2,92 @@
|
|
2
2
|
require 'rest-core/test'
|
3
3
|
|
4
4
|
describe RC::Oauth1Header do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
env = {RC::REQUEST_METHOD => :post,
|
6
|
+
RC::REQUEST_PATH =>
|
7
|
+
'https://api.twitter.com/oauth/request_token',
|
8
|
+
RC::REQUEST_QUERY => {},
|
9
|
+
RC::REQUEST_PAYLOAD => {}}
|
9
10
|
|
10
|
-
|
11
|
+
callback =
|
11
12
|
'http://localhost:3005/the_dance/process_callback?service_provider_id=11'
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
14
|
+
oauth_params =
|
15
|
+
{'oauth_callback' => callback ,
|
16
|
+
'oauth_consumer_key' => 'GDdmIQH6jhtmLUypg82g' ,
|
17
|
+
'oauth_nonce' => 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk',
|
18
|
+
'oauth_timestamp' => '1272323042' ,
|
19
|
+
'oauth_version' => '1.0' ,
|
20
|
+
'oauth_signature_method' => 'HMAC-SHA1'}
|
21
|
+
|
22
|
+
auth = RC::Oauth1Header.new(RC::Dry.new,
|
23
|
+
nil, nil, nil,
|
24
|
+
'GDdmIQH6jhtmLUypg82g',
|
25
|
+
'MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98')
|
26
26
|
|
27
27
|
should 'have correct signature' do
|
28
|
-
|
28
|
+
auth.signature(env, oauth_params).should.eq(
|
29
29
|
'8wUi7m5HFQy76nowoCThusfgB+Q=')
|
30
30
|
end
|
31
31
|
|
32
32
|
describe 'base_string' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
def check
|
45
|
-
@auth.base_string(@env, @oauth_params).should.eq @base_string
|
33
|
+
base_string =
|
34
|
+
'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&' \
|
35
|
+
'oauth_callback%3Dhttp%253A%252F%252Flocalhost%253A3005%252F' \
|
36
|
+
'the_dance%252Fprocess_callback%253Fservice_provider_id%253D' \
|
37
|
+
'11%26oauth_consumer_key%3DGDdmIQH6jhtmLUypg82g%26oauth_nonc' \
|
38
|
+
'e%3DQP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk%26oauth_sig' \
|
39
|
+
'nature_method%3DHMAC-SHA1%26oauth_timestamp%3D1272323042%26' \
|
40
|
+
'oauth_version%3D1.0'
|
41
|
+
|
42
|
+
check = lambda do |e, b|
|
43
|
+
auth.base_string(e, oauth_params).should.eq b
|
46
44
|
end
|
47
45
|
|
48
46
|
should 'have correct base_string' do
|
49
|
-
check
|
47
|
+
check[env, base_string]
|
50
48
|
end
|
51
49
|
|
52
50
|
should 'not use payload in multipart request for base_string' do
|
53
|
-
|
54
|
-
|
51
|
+
File.open(__FILE__) do |f|
|
52
|
+
check[env.merge(RC::REQUEST_PAYLOAD => {'file' => f}), base_string]
|
53
|
+
end
|
55
54
|
end
|
56
55
|
|
57
56
|
should 'not use payload if it contains binary' do
|
58
|
-
|
59
|
-
|
57
|
+
File.open(__FILE__) do |f|
|
58
|
+
check[env.merge(RC::REQUEST_PAYLOAD => f), base_string]
|
59
|
+
end
|
60
60
|
end
|
61
61
|
|
62
62
|
should 'not use payload if it contains [binary]' do
|
63
|
-
|
64
|
-
|
63
|
+
File.open(__FILE__) do |f|
|
64
|
+
check[env.merge(RC::REQUEST_PAYLOAD => [f]), base_string]
|
65
|
+
end
|
65
66
|
end
|
66
67
|
|
67
68
|
should 'not use payload if Content-Type is not x-www-form-urlencoded' do
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
check[
|
70
|
+
env.merge(RC::REQUEST_PAYLOAD => {'pay' => 'load'},
|
71
|
+
RC::REQUEST_HEADERS => {'Content-Type' => 'text/plain'}),
|
72
|
+
base_string]
|
71
73
|
end
|
72
74
|
|
73
75
|
should 'use payload if Content-Type is x-www-form-urlencoded' do
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
76
|
+
check[env.merge(
|
77
|
+
RC::REQUEST_PAYLOAD => {'pay' => 'load'},
|
78
|
+
RC::REQUEST_HEADERS =>
|
79
|
+
{'Content-Type' => 'application/x-www-form-urlencoded'}),
|
80
|
+
base_string + '%26pay%3Dload']
|
79
81
|
end
|
80
82
|
|
81
83
|
should 'use payload if there is no binary data' do
|
82
|
-
|
83
|
-
|
84
|
-
check
|
84
|
+
check[env.merge(RC::REQUEST_PAYLOAD => {'pay' => 'load'}),
|
85
|
+
base_string + '%26pay%3Dload']
|
85
86
|
end
|
86
87
|
|
87
88
|
should 'not escape ~' do
|
88
|
-
|
89
|
-
|
90
|
-
check
|
89
|
+
check[env.merge(RC::REQUEST_PAYLOAD => {'tilde' => '~'}),
|
90
|
+
base_string + '%26tilde%3D~']
|
91
91
|
end
|
92
92
|
end
|
93
93
|
end
|
data/test/test_payload.rb
CHANGED
@@ -1,37 +1,204 @@
|
|
1
1
|
|
2
2
|
require 'rest-core/test'
|
3
3
|
|
4
|
-
describe RC::
|
5
|
-
|
4
|
+
describe RC::Payload do
|
5
|
+
describe 'A regular Payload' do
|
6
|
+
should 'use standard enctype as default content-type' do
|
7
|
+
RC::Payload::UrlEncoded.new({}).headers['Content-Type'].
|
8
|
+
should.eq 'application/x-www-form-urlencoded'
|
9
|
+
end
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
should 'form properly encoded params' do
|
12
|
+
RC::Payload::UrlEncoded.new(:foo => 'bar').read.
|
13
|
+
should.eq 'foo=bar'
|
14
|
+
RC::Payload::UrlEncoded.new(:foo => 'bar', :baz => 'qux').read.
|
15
|
+
should.eq 'baz=qux&foo=bar'
|
16
|
+
end
|
17
|
+
|
18
|
+
should 'escape parameters' do
|
19
|
+
RC::Payload::UrlEncoded.new('foo ' => 'bar').read.
|
20
|
+
should.eq 'foo%20=bar'
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'properly handle arrays as repeated parameters' do
|
24
|
+
RC::Payload::UrlEncoded.new(:foo => ['bar']).read.
|
25
|
+
should.eq 'foo=bar'
|
26
|
+
RC::Payload::UrlEncoded.new(:foo => ['bar', 'baz']).read.
|
27
|
+
should.eq 'foo=bar&foo=baz'
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'not close if stream already closed' do
|
31
|
+
p = RC::Payload::UrlEncoded.new('foo ' => 'bar')
|
32
|
+
p.close
|
33
|
+
2.times{ p.close.should.eq nil }
|
34
|
+
end
|
9
35
|
end
|
10
36
|
|
11
|
-
|
12
|
-
|
37
|
+
describe 'A multipart Payload' do
|
38
|
+
should 'use standard enctype as default content-type' do
|
39
|
+
p = RC::Payload::Multipart.new({})
|
40
|
+
stub(p).boundary{123}
|
41
|
+
p.headers['Content-Type'].should.eq 'multipart/form-data; boundary=123'
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'not error on close if stream already closed' do
|
45
|
+
p = RC::Payload::Multipart.new(:file => File.open(__FILE__))
|
46
|
+
p.close
|
47
|
+
2.times{ p.close.should.eq nil }
|
48
|
+
end
|
13
49
|
|
14
|
-
|
15
|
-
|
50
|
+
should 'form properly separated multipart data' do
|
51
|
+
p = RC::Payload::Multipart.new(:bar => 'baz', :foo => 'bar')
|
52
|
+
p.read.should.eq <<-EOS
|
53
|
+
--#{p.boundary}\r
|
54
|
+
Content-Disposition: form-data; name="bar"\r
|
55
|
+
\r
|
56
|
+
baz\r
|
57
|
+
--#{p.boundary}\r
|
58
|
+
Content-Disposition: form-data; name="foo"\r
|
59
|
+
\r
|
60
|
+
bar\r
|
61
|
+
--#{p.boundary}--\r
|
62
|
+
EOS
|
63
|
+
end
|
16
64
|
|
17
|
-
|
18
|
-
|
65
|
+
should 'form multiple files with the same name' do
|
66
|
+
with_img do |f, n|
|
67
|
+
with_img do |ff, nn|
|
68
|
+
p = RC::Payload::Multipart.new(:foo => [f, ff])
|
69
|
+
p.read.should.eq <<-EOS
|
70
|
+
--#{p.boundary}\r
|
71
|
+
Content-Disposition: form-data; name="foo"; filename="#{n}"\r
|
72
|
+
Content-Type: image/jpeg\r
|
73
|
+
\r
|
74
|
+
#{'a'*10}\r
|
75
|
+
--#{p.boundary}\r
|
76
|
+
Content-Disposition: form-data; name="foo"; filename="#{nn}"\r
|
77
|
+
Content-Type: image/jpeg\r
|
78
|
+
\r
|
79
|
+
#{'a'*10}\r
|
80
|
+
--#{p.boundary}--\r
|
81
|
+
EOS
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
19
85
|
|
20
|
-
|
21
|
-
|
86
|
+
should 'not escape parameters names' do
|
87
|
+
p = RC::Payload::Multipart.new('bar ' => 'baz')
|
88
|
+
p.read.should.eq <<-EOS
|
89
|
+
--#{p.boundary}\r
|
90
|
+
Content-Disposition: form-data; name="bar "\r
|
91
|
+
\r
|
92
|
+
baz\r
|
93
|
+
--#{p.boundary}--\r
|
94
|
+
EOS
|
95
|
+
end
|
96
|
+
|
97
|
+
should 'form properly separated multipart data' do
|
98
|
+
with_img do |f, n|
|
99
|
+
p = RC::Payload::Multipart.new(:foo => f)
|
100
|
+
p.read.should.eq <<-EOS
|
101
|
+
--#{p.boundary}\r
|
102
|
+
Content-Disposition: form-data; name="foo"; filename="#{n}"\r
|
103
|
+
Content-Type: image/jpeg\r
|
104
|
+
\r
|
105
|
+
#{File.read(f.path)}\r
|
106
|
+
--#{p.boundary}--\r
|
107
|
+
EOS
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
should "ignore the name attribute when it's not set" do
|
112
|
+
with_img do |f, n|
|
113
|
+
p = RC::Payload::Multipart.new(nil => f)
|
114
|
+
p.read.should.eq <<-EOS
|
115
|
+
--#{p.boundary}\r
|
116
|
+
Content-Disposition: form-data; filename="#{n}"\r
|
117
|
+
Content-Type: image/jpeg\r
|
118
|
+
\r
|
119
|
+
#{File.read(f.path)}\r
|
120
|
+
--#{p.boundary}--\r
|
121
|
+
EOS
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
should 'detect optional (original) content type and filename' do
|
126
|
+
File.open(__FILE__) do |f|
|
127
|
+
def f.content_type ; 'image/jpeg'; end
|
128
|
+
def f.original_filename; 'foo.txt' ; end
|
129
|
+
p = RC::Payload::Multipart.new(:foo => f)
|
130
|
+
p.read.should.eq <<-EOS
|
131
|
+
--#{p.boundary}\r
|
132
|
+
Content-Disposition: form-data; name="foo"; filename="foo.txt"\r
|
133
|
+
Content-Type: image/jpeg\r
|
134
|
+
\r
|
135
|
+
#{File.read(f.path)}\r
|
136
|
+
--#{p.boundary}--\r
|
137
|
+
EOS
|
138
|
+
end
|
139
|
+
end
|
22
140
|
end
|
23
141
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
142
|
+
describe 'streamed payloads' do
|
143
|
+
should 'properly determine the size of file payloads' do
|
144
|
+
File.open(__FILE__) do |f|
|
145
|
+
p = RC::Payload.generate(f)
|
146
|
+
p.size.should.eq f.stat.size
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
should 'properly determine the size of other kinds of payloads' do
|
151
|
+
s = StringIO.new('foo')
|
152
|
+
p = RC::Payload.generate(s)
|
153
|
+
p.size.should.eq 3
|
154
|
+
|
155
|
+
begin
|
156
|
+
f = Tempfile.new('rest-core')
|
157
|
+
f.write('foo bar')
|
158
|
+
f.rewind
|
159
|
+
|
160
|
+
p = RC::Payload.generate(f)
|
161
|
+
p.size.should.eq 7
|
162
|
+
ensure
|
163
|
+
f.close!
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'Payload generation' do
|
169
|
+
should 'recognize standard urlencoded params' do
|
170
|
+
RC::Payload.generate('foo' => 'bar').should.
|
171
|
+
kind_of?(RC::Payload::UrlEncoded)
|
172
|
+
end
|
173
|
+
|
174
|
+
should 'recognize multipart params' do
|
175
|
+
File.open(__FILE__) do |f|
|
176
|
+
RC::Payload.generate('foo' => f).should.
|
177
|
+
kind_of?(RC::Payload::Multipart)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
should 'return data if none of the above' do
|
182
|
+
RC::Payload.generate('data').should.
|
183
|
+
kind_of?(RC::Payload::StreamedString)
|
184
|
+
end
|
185
|
+
|
186
|
+
should 'recognize nested multipart payloads in arrays' do
|
187
|
+
File.open(__FILE__) do |f|
|
188
|
+
RC::Payload.generate('foo' => [f]).should.
|
189
|
+
kind_of?(RC::Payload::Multipart)
|
190
|
+
end
|
191
|
+
end
|
28
192
|
|
29
|
-
|
30
|
-
|
31
|
-
|
193
|
+
should 'recognize file payloads that can be streamed' do
|
194
|
+
File.open(__FILE__) do |f|
|
195
|
+
RC::Payload.generate(f).should.kind_of?(RC::Payload::Streamed)
|
196
|
+
end
|
197
|
+
end
|
32
198
|
|
33
|
-
|
34
|
-
|
35
|
-
|
199
|
+
should 'recognize other payloads that can be streamed' do
|
200
|
+
RC::Payload.generate(StringIO.new('foo')).should.
|
201
|
+
kind_of?(RC::Payload::Streamed)
|
202
|
+
end
|
36
203
|
end
|
37
204
|
end
|