rest-builder 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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