rest-builder 0.9.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.
@@ -0,0 +1,45 @@
1
+
2
+ require 'rest-builder/test'
3
+
4
+ describe RestBuilder::Builder do
5
+ would 'default client app is a kind of RestBuilder::Engine' do
6
+ RestBuilder::Builder.client.new.app.should.kind_of? RestBuilder::Engine
7
+ end
8
+
9
+ would 'default app is a kind of RestBuilder::Engine' do
10
+ RestBuilder::Builder.new.to_app.should.kind_of? RestBuilder::Engine
11
+ end
12
+
13
+ would 'switch default_engine to RestBuilder::Dry a' do
14
+ builder = Class.new(RestBuilder::Builder)
15
+ builder.default_engine = RestBuilder::Dry
16
+ builder.new.to_app.class.should.eq RestBuilder::Dry
17
+ end
18
+
19
+ would 'switch default_engine to RestBuilder::Dry b' do
20
+ builder = RestBuilder::Builder.dup
21
+ builder.default_engine = RestBuilder::Dry
22
+ builder.client.new.app.class.should.eq RestBuilder::Dry
23
+ end
24
+
25
+ would 'accept middleware without a member' do
26
+ RestBuilder::Builder.client{
27
+ use Class.new.send(:include, RestBuilder::Middleware)
28
+ }.members.should.eq [:config_engine]
29
+ end
30
+
31
+ would 'not have duplicated fields' do
32
+ middleware = Class.new do
33
+ def self.members; [:value]; end
34
+ include RestBuilder::Middleware
35
+ end
36
+ client = RestBuilder::Builder.client(:value){ use middleware }.new
37
+ client.value = 10
38
+ client.value.should.eq 10
39
+ end
40
+
41
+ would 'have the same pool for the same client' do
42
+ client = RestBuilder::Builder.client
43
+ client.thread_pool.object_id.should.eq client.thread_pool.object_id
44
+ end
45
+ end
@@ -0,0 +1,212 @@
1
+
2
+ require 'rest-builder/test'
3
+
4
+ describe RestBuilder::Client do
5
+ after do
6
+ WebMock.reset!
7
+ Muack.verify
8
+ end
9
+
10
+ url = 'http://example.com/'
11
+ simple = RestBuilder::Builder.client
12
+
13
+ would 'do simple request' do
14
+ c = simple.new
15
+ [:get, :post, :delete, :put, :patch].each do |method|
16
+ stub_request(method, url).to_return(:body => '[]')
17
+ c.send(method, url).should.eq '[]'
18
+ end
19
+
20
+ stub_request(:head , url).to_return(:headers => {'A' => 'B'})
21
+ c. head(url).should.eq('A' => 'B')
22
+
23
+ stub_request(:options, url).to_return(:headers => {'A' => 'B'})
24
+ c.options(url).should.eq('A' => 'B')
25
+ end
26
+
27
+ would 'call the callback' do
28
+ [:get, :post, :delete, :put, :patch].each do |method|
29
+ stub_request(method, url).to_return(:body => '123')
30
+ (client = simple.new).send(method, url){ |res|
31
+ res.should.eq '123' }.should.eq client
32
+ client.wait
33
+ end
34
+
35
+ stub_request(:head, url).to_return(:headers => {'A' => 'B'})
36
+ (client = simple.new).head(url){ |res|
37
+ res.should.eq({'A' => 'B'})
38
+ }.should.eq client
39
+ client.wait
40
+
41
+ stub_request(:options, url).to_return(:headers => {'A' => 'B'})
42
+ (client = simple.new).options(url){ |res|
43
+ res.should.eq('A' => 'B')
44
+ }.should.eq client
45
+ client.wait
46
+ end
47
+
48
+ would 'wait for all the requests' do
49
+ t, i, m = 5, 0, Mutex.new
50
+ stub_request(:get, url).to_return do
51
+ m.synchronize{ i += 1 }
52
+ Thread.pass
53
+ {}
54
+ end
55
+
56
+ client = RestBuilder::Builder.client
57
+ t.times{ client.new.get(url) }
58
+ client.wait
59
+ client.promises.should.empty?
60
+ i.should.eq t
61
+ end
62
+
63
+ would 'wait for callback' do
64
+ rd, wr = IO.pipe
65
+ called = false
66
+ stub_request(:get, url).to_return(:body => 'nnf')
67
+ client = simple.new.get(url) do |nnf|
68
+ wr.puts
69
+ sleep 0.001 # make sure our callback is slow enough,
70
+ # so that if `wait` is not waiting for the callback,
71
+ # it would leave before the callback is completely done.
72
+ # without sleeping, the callback is very likely to be
73
+ # done first than `wait` anyway. raising the sleeping time
74
+ # would make this test more reliable...
75
+ called = true
76
+ nil # test against returning nil, so that Promise#response is not set
77
+ end
78
+ rd.gets
79
+ client.wait
80
+ called.should.eq true
81
+ end
82
+
83
+ would 'cleanup promises' do
84
+ stub_request(:get, url).to_return(:body => 'nnf')
85
+ 5.times{ simple.new.get(url) }
86
+ Thread.pass
87
+ GC.start # can only force GC run on MRI, so we mock for jruby and rubinius
88
+ stub(any_instance_of(WeakRef)).weakref_alive?{false}
89
+ simple.new.get(url)
90
+ simple.promises.size.should.lt 6
91
+ simple.shutdown
92
+ simple.promises.should.empty?
93
+ end
94
+
95
+ would 'have correct to_i' do
96
+ stub_request(:get, url).to_return(:body => '123')
97
+ simple.new.get(url).to_i.should.eq 123
98
+ end
99
+
100
+ would 'use defaults' do
101
+ client = RestBuilder::Builder.client do
102
+ use Class.new{
103
+ def self.members; [:timeout]; end
104
+ include RestBuilder::Middleware
105
+ }, 4
106
+ end
107
+ c = client.new
108
+ c.timeout.should.eq 4 # default goes to middleware
109
+ client.extend(Module.new do
110
+ def default_timeout
111
+ 3
112
+ end
113
+ end)
114
+ c.timeout.should.eq 4 # default is cached, so it stays the same
115
+ c.timeout = nil # clear cache
116
+ c.timeout.should.eq 3 # now default goes to module default
117
+ class << client
118
+ def default_timeout # module defaults could be overriden
119
+ super - 1
120
+ end
121
+ end
122
+ c.timeout = nil
123
+ c.timeout.should.eq 2 # so it goes to class default
124
+ c.timeout = 1 # setup instance level value
125
+ c.build_env( )['timeout'].should.eq 1 # pick instance var
126
+ c.build_env({'timeout' => 0})['timeout'].should.eq 0 # per-request var
127
+ c.timeout.should.eq 1 # won't affect underlying instance var
128
+ c.timeout = nil
129
+ c.timeout.should.eq 2 # goes back to class default
130
+ c.timeout = false
131
+ c.timeout.should.eq false # false would disable default
132
+ end
133
+
134
+ would 'work for inheritance' do
135
+ stub_request(:get, url).to_return(:body => '123')
136
+ Class.new(simple).new.get(url).should.eq '123'
137
+ end
138
+
139
+ would 'not deadlock when exception was raised in the callback' do
140
+ client = Class.new(simple).new
141
+ stub_request(:get, url).to_return(:body => 'nnf')
142
+
143
+ (-1..1).each do |size|
144
+ mock(any_instance_of(RestBuilder::Promise)).warn(is_a(String)) do |msg|
145
+ msg.should.include?('nnf')
146
+ end
147
+ client.class.pool_size = size
148
+ client.get(url) do |body|
149
+ raise body
150
+ end
151
+ client.class.shutdown
152
+ end
153
+ end
154
+
155
+ would 'be able to access caller outside the callback' do
156
+ flag = false
157
+ client = simple.new
158
+ stub_request(:get, url).to_return(:body => 'nnf')
159
+ client.get(url) do
160
+ current_file = /^#{__FILE__}/
161
+ caller.grep(current_file).should.empty?
162
+ RestBuilder::Promise.backtrace.grep(current_file).should.not.empty?
163
+ client.get(url) do
164
+ RestBuilder::Promise.backtrace.last.should.not =~ /promise\.rb:\d+:in/
165
+ flag = true
166
+ end
167
+ end
168
+ client.wait
169
+ flag.should.eq true # to make sure the inner most block did run
170
+ end
171
+
172
+ would 'call error_callback' do
173
+ error = nil
174
+ error_callback = lambda{ |e| error = e }
175
+ should.raise(Errno::ECONNREFUSED) do
176
+ simple.new(:error_callback => error_callback).
177
+ get('http://localhost:1').tap{}
178
+ end
179
+ error.should.kind_of?(Errno::ECONNREFUSED)
180
+ end
181
+
182
+ path = 'http://example.com/'
183
+
184
+ would 'give RESPONSE_BODY' do
185
+ stub_request(:get, path).to_return(:body => 'OK')
186
+ simple.new.get(path).should.eq 'OK'
187
+ end
188
+
189
+ would 'give RESPONSE_HEADERS' do
190
+ stub_request(:head, path).to_return(:headers => {'A' => 'B'})
191
+ simple.new.head(path).should.eq 'A' => 'B'
192
+ end
193
+
194
+ would 'give RESPONSE_HEADERS' do
195
+ stub_request(:get, path).to_return(:status => 199)
196
+ simple.new.get(path, {},
197
+ RestBuilder::RESPONSE_KEY => RestBuilder::RESPONSE_STATUS).should.eq 199
198
+ end
199
+
200
+ would 'give RESPONSE_SOCKET' do
201
+ stub_request(:get, path).to_return(:body => 'OK')
202
+ simple.new.get(path, {}, RestBuilder::HIJACK => true).read.should.eq 'OK'
203
+ end
204
+
205
+ would 'give REQUEST_URI' do
206
+ stub_request(:get, "#{path}?a=b").to_return(:body => 'OK')
207
+ simple.new.get(path, {:a => 'b'},
208
+ RestBuilder::RESPONSE_KEY => RestBuilder::REQUEST_URI).
209
+ should.eq "#{path}?a=b"
210
+ simple.wait
211
+ end
212
+ end
@@ -0,0 +1,152 @@
1
+
2
+ require 'socket'
3
+ require 'rest-builder/test'
4
+
5
+ describe RestBuilder::EventSource do
6
+ after do
7
+ Muack.verify
8
+ WebMock.reset!
9
+ end
10
+
11
+ client = RestBuilder::Builder.client.new
12
+ server = lambda do |close=true|
13
+ serv = TCPServer.new(0)
14
+ port = serv.addr[1]
15
+ path = "http://localhost:#{port}/"
16
+ payload = <<-SSE
17
+ event: put
18
+ data: {}
19
+
20
+ event: keep-alive
21
+ data: null
22
+ SSE
23
+ m = [{'event' => 'put' , 'data' => '{}'},
24
+ {'event' => 'keep-alive', 'data' => 'null'}]
25
+
26
+ t = Thread.new do
27
+ sock = serv.accept
28
+ sock.readline("\r\n\r\n")
29
+ sock.puts("HTTP/1.1 200 OK\r")
30
+ sock.puts("Content-Type: text/event-stream\r")
31
+ sock.puts
32
+ sock.puts(payload)
33
+ sock.close if close
34
+ end
35
+
36
+ [client.event_source(path, :a => 'b'), m, t]
37
+ end
38
+
39
+ would 'work regularly' do
40
+ es, m, t = server.call
41
+ flag = 0
42
+
43
+ es.onopen do |sock|
44
+ sock.should.kind_of? IO
45
+ flag.should.eq 0
46
+ flag += 1
47
+ end.
48
+ onmessage do |event, data, sock|
49
+ {'event' => event, 'data' => data}.should.eq m.shift
50
+ sock.should.kind_of? IO
51
+ sock.should.not.closed?
52
+ flag += 1
53
+ end.
54
+ onerror do |error, sock|
55
+ error.should.kind_of? EOFError
56
+ m.should.empty?
57
+ sock.should.closed?
58
+ flag.should.eq 3
59
+ flag += 1
60
+ end.start.wait
61
+
62
+ flag.should.eq 4
63
+ t.join
64
+ end
65
+
66
+ would 'close' do
67
+ es, _, t = server.call(false)
68
+ flag = 0
69
+ es.onmessage do
70
+ es.close
71
+ flag += 1
72
+ end.start.wait
73
+
74
+ flag.should.eq 1
75
+ t.join
76
+ end
77
+
78
+ would 'reconnect' do
79
+ stub_select_for_stringio
80
+ stub_request(:get, 'https://a?b=c').to_return(:body => <<-SSE)
81
+ event: put
82
+ data: 0
83
+
84
+ event: put
85
+ data: 1
86
+ SSE
87
+ stub_request(:get, 'https://a?c=d').to_return(:body => <<-SSE)
88
+ event: put
89
+ data: 2
90
+
91
+ event: put
92
+ data: 3
93
+ SSE
94
+ es = client.event_source('https://a', :b => 'c')
95
+ m = ('0'..'3').to_a
96
+ es.onmessage do |event, data|
97
+ data.should.eq m.shift
98
+
99
+ end.onerror do |error|
100
+ error.should.kind_of? EOFError
101
+ es.query = {:c => 'd'}
102
+
103
+ end.onreconnect do |error, sock|
104
+ error.should.kind_of? EOFError
105
+ sock.should.respond_to? :read
106
+ !m.empty? # not empty to reconnect
107
+
108
+ end.start.wait
109
+ m.should.empty?
110
+ end
111
+
112
+ would 'not cache' do
113
+ stub_select_for_stringio
114
+ stub_request(:get, 'https://a?b=c').to_return(:body => <<-SSE)
115
+ event: put
116
+ data: 0
117
+
118
+ event: put
119
+ data: 1
120
+ SSE
121
+ es = client.event_source('https://a', :b => 'c')
122
+ m = %w[0 1 0 1]
123
+ es.onmessage do |event, data|
124
+ data.should.eq m.shift
125
+
126
+ end.onerror do |error|
127
+ error.should.kind_of? EOFError
128
+
129
+ end.onreconnect do |error, sock|
130
+ error.should.kind_of? EOFError
131
+ sock.should.respond_to? :read
132
+ !m.empty? # not empty to reconnect
133
+
134
+ end.start.wait
135
+ m.should.empty?
136
+ end
137
+
138
+ would 'not deadlock without ErrorHandler' do
139
+ c = RestBuilder::Builder.client.new.event_source('http://localhost:1')
140
+ c.onerror{ |e| e.should.kind_of?(Errno::ECONNREFUSED) }
141
+ c.start.wait
142
+ end
143
+
144
+ would 'not deadlock if errors in onmessage' do
145
+ err = nil
146
+ es, _, _ = server.call
147
+ es.onmessage do |event, data|
148
+ raise err = "error"
149
+ end.start.wait
150
+ err.should.eq "error"
151
+ end
152
+ end
@@ -0,0 +1,21 @@
1
+
2
+ require 'stringio'
3
+ require 'rest-builder/test'
4
+
5
+ describe RestBuilder::Promise::Future do
6
+ would 'fulfill the future' do
7
+ promise = RestBuilder::Promise.new
8
+ promise.fulfill(RestBuilder::RESPONSE_STATUS => 200,
9
+ RestBuilder::RESPONSE_HEADERS => {'A' => 'B'},
10
+ RestBuilder::RESPONSE_BODY => 'body',
11
+ RestBuilder::RESPONSE_SOCKET => StringIO.new,
12
+ RestBuilder::FAIL => [])
13
+
14
+ promise.future_body .should.eq 'body'
15
+ promise.future_status .should.eq 200
16
+ promise.future_headers .should.eq('A' => 'B')
17
+ promise.future_socket .should.kind_of?(StringIO)
18
+ promise.future_failures.should.eq []
19
+ ([] + promise.future_failures).should.eq []
20
+ end
21
+ end
@@ -0,0 +1,118 @@
1
+
2
+ require 'rest-builder/test'
3
+
4
+ require 'openssl'
5
+ require 'socket'
6
+ require 'zlib'
7
+
8
+ describe RestBuilder::HttpClient do
9
+ describe 'POST Payload' do
10
+ after do
11
+ WebMock.reset!
12
+ end
13
+
14
+ client = RestBuilder::Builder.client
15
+ path = 'http://example.com'
16
+ ok = 'OK'
17
+ c = client.new
18
+
19
+ post = lambda do |payload, body|
20
+ WebMock::API.stub_request(:post, path).
21
+ with(:body => body).to_return(:body => ok)
22
+ c.post(path, payload).should.eq ok
23
+ end
24
+
25
+ would 'post with string' do
26
+ post['string', 'string']
27
+ end
28
+
29
+ would 'post with file' do
30
+ File.open(__FILE__) do |f|
31
+ b = f.read
32
+ f.rewind
33
+ post[f, b]
34
+ end
35
+ end
36
+
37
+ would 'post with socket' do
38
+ rd, wr = IO.pipe
39
+ wr.write('socket')
40
+ wr.close
41
+ post[rd, 'socket']
42
+ end
43
+
44
+ would 'not kill the thread if error was coming from the task' do
45
+ mock(HTTPClient).new{ raise 'boom' }.with_any_args
46
+ c.request(RestBuilder::ASYNC => true).message.should.eq 'boom'
47
+ Muack.verify
48
+ end
49
+
50
+ def accept body
51
+ server = TCPServer.new(0)
52
+ t = Thread.new do
53
+ client = server.accept
54
+ client.write(<<-HTTP)
55
+ HTTP/1.0 200 OK\r
56
+ Connection: close\r
57
+ Content-Encoding: deflate\r
58
+ \r
59
+ #{body}\r
60
+ HTTP
61
+ client.close_write
62
+ end
63
+
64
+ yield("http://localhost:#{server.local_address.ip_port}")
65
+
66
+ t.join
67
+ end
68
+
69
+ would 'accept deflate' do
70
+ accept(Zlib::Deflate.deflate(ok)) do |site|
71
+ c.post(site, 'body').should.eq ok
72
+ end
73
+ end
74
+
75
+ config_engine = lambda do |engine|
76
+ engine.transparent_gzip_decompression = false
77
+ engine.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
78
+ end
79
+
80
+ define_method(:define_default_config_engine) do |d|
81
+ d.singleton_class.module_eval do
82
+ define_method(:default_config_engine) do
83
+ config_engine
84
+ end
85
+ end
86
+ end
87
+
88
+ would 'disable auto-deflate' do
89
+ accept(ok) do |site|
90
+ c.post(site, 'body', {}, :config_engine => config_engine).
91
+ chomp.should.eq ok
92
+ end
93
+ end
94
+
95
+ would 'disable auto-deflate with class default_config_engine' do
96
+ accept(ok) do |site|
97
+ d = RestBuilder::Builder.client
98
+ define_default_config_engine(d)
99
+ d.new.post(site, 'body').chomp.should.eq ok
100
+ end
101
+ end
102
+
103
+ would 'disable auto-deflate with instance default_config_engine' do
104
+ accept(ok) do |site|
105
+ d = RestBuilder::Builder.client.new
106
+ define_default_config_engine(d)
107
+ d.post(site, 'body').chomp.should.eq ok
108
+ end
109
+ end
110
+
111
+ would 'disable auto-deflate with setting config_engine' do
112
+ accept(ok) do |site|
113
+ d = RestBuilder::Builder.client.new(:config_engine => config_engine)
114
+ d.post(site, 'body').chomp.should.eq ok
115
+ end
116
+ end
117
+ end
118
+ end