hatetepe 0.0.4 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/README.md +21 -5
- data/Rakefile +3 -10
- data/bin/hatetepe +4 -0
- data/hatetepe.gemspec +10 -3
- data/lib/hatetepe.rb +5 -0
- data/lib/hatetepe/app.rb +44 -0
- data/lib/hatetepe/body.rb +79 -0
- data/lib/hatetepe/builder.rb +19 -3
- data/lib/hatetepe/cli.rb +50 -0
- data/lib/hatetepe/client.rb +95 -0
- data/lib/hatetepe/events.rb +35 -0
- data/lib/hatetepe/message.rb +13 -0
- data/lib/hatetepe/parser.rb +41 -71
- data/lib/hatetepe/prefork.rb +11 -0
- data/lib/hatetepe/proxy.rb +58 -0
- data/lib/hatetepe/request.rb +31 -0
- data/lib/hatetepe/response.rb +20 -0
- data/lib/hatetepe/server.rb +98 -0
- data/lib/hatetepe/thread_pool.rb +4 -0
- data/lib/hatetepe/version.rb +1 -1
- data/lib/rack/handler/hatetepe.rb +33 -0
- data/spec/integration/cli/start_spec.rb +169 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/unit/app_spec.rb +108 -0
- data/spec/unit/body_spec.rb +198 -0
- data/spec/unit/client_spec.rb +270 -0
- data/spec/unit/events_spec.rb +96 -0
- data/spec/unit/parser_spec.rb +215 -0
- data/spec/unit/rack_handler_spec.rb +70 -0
- data/spec/unit/server_spec.rb +255 -0
- metadata +141 -56
- data/example.rb +0 -29
- data/test/builder_test.rb +0 -7
- data/test/parser_test.rb +0 -7
- data/test/test_helper.rb +0 -7
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
begin
|
2
|
+
require "awesome_print"
|
3
|
+
rescue LoadError; end
|
4
|
+
|
5
|
+
require "em-synchrony"
|
6
|
+
require "em-synchrony/em-http"
|
7
|
+
require "fakefs/safe"
|
8
|
+
|
9
|
+
RSpec.configure {|config|
|
10
|
+
config.before {
|
11
|
+
EM.class_eval {
|
12
|
+
@spec_hooks = []
|
13
|
+
class << self
|
14
|
+
attr_reader :spec_hooks
|
15
|
+
def synchrony_with_hooks(blk = nil, tail = nil, &block)
|
16
|
+
synchrony_without_hooks do
|
17
|
+
(blk || block).call
|
18
|
+
@spec_hooks.each {|sh| sh.call }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias_method :synchrony_without_hooks, :synchrony
|
22
|
+
alias_method :synchrony, :synchrony_with_hooks
|
23
|
+
end
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
config.after {
|
28
|
+
EM.class_eval {
|
29
|
+
@spec_hooks = nil
|
30
|
+
class << self
|
31
|
+
remove_method :spec_hooks
|
32
|
+
alias_method :synchrony, :synchrony_without_hooks
|
33
|
+
remove_method :synchrony_with_hooks
|
34
|
+
end
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/app"
|
3
|
+
|
4
|
+
describe Hatetepe::App do
|
5
|
+
let(:inner_app) { stub "inner app", :call => response }
|
6
|
+
let(:app) { Hatetepe::App.new inner_app }
|
7
|
+
let(:env) {
|
8
|
+
{
|
9
|
+
"stream.start" => stub("stream.start", :call => nil),
|
10
|
+
"stream.send" => stub("stream.send", :call => nil),
|
11
|
+
"stream.close" => stub("stream.close", :call => nil)
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
let(:status) { 123 }
|
16
|
+
let(:headers) { stub "headers" }
|
17
|
+
let(:body) { [stub("chunk#1"), stub("chunk#2")] }
|
18
|
+
let(:response) { [status, headers, body] }
|
19
|
+
|
20
|
+
context "#initialize(inner_app)" do
|
21
|
+
it "keeps the inner app" do
|
22
|
+
Hatetepe::App.new(inner_app).app.should equal(inner_app)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "#call(env)" do
|
27
|
+
it "sets env[async.callback] before #call'ing inner_app" do
|
28
|
+
app.call env
|
29
|
+
|
30
|
+
app.should_receive(:postprocess) {|e, res|
|
31
|
+
e.should equal(env)
|
32
|
+
res.should equal(response)
|
33
|
+
}
|
34
|
+
env["async.callback"].call response
|
35
|
+
end
|
36
|
+
|
37
|
+
it "calls #postprocess with the return of inner_app#call(env)" do
|
38
|
+
inner_app.stub :call => response
|
39
|
+
app.should_receive(:postprocess) {|e, res|
|
40
|
+
e.should equal(env)
|
41
|
+
res.should equal(response)
|
42
|
+
}
|
43
|
+
|
44
|
+
app.call env
|
45
|
+
end
|
46
|
+
|
47
|
+
let(:error_response) {
|
48
|
+
[500, {"Content-Type" => "text/html"}, ["Internal Server Error"]]
|
49
|
+
}
|
50
|
+
|
51
|
+
it "responds with 500 when catching an exception" do
|
52
|
+
inner_app.stub(:call) { raise }
|
53
|
+
app.should_receive(:postprocess) {|e, res|
|
54
|
+
res.should == error_response
|
55
|
+
}
|
56
|
+
|
57
|
+
app.call env
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:async_response) { [-1, {}, []] }
|
61
|
+
|
62
|
+
it "catches :async for Thin compatibility" do
|
63
|
+
inner_app.stub(:call) { throw :async }
|
64
|
+
app.should_receive(:postprocess) {|e, res|
|
65
|
+
res.should == async_response
|
66
|
+
}
|
67
|
+
|
68
|
+
app.call env
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "#postprocess(env, response)" do
|
73
|
+
it "does nothing if the response status is lighter than 0" do
|
74
|
+
env["stream.start"].should_not_receive :call
|
75
|
+
app.postprocess env, [-1]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "starts the response stream" do
|
79
|
+
env["stream.start"].should_receive(:call).with([status, headers])
|
80
|
+
app.postprocess env, [status, headers, []]
|
81
|
+
end
|
82
|
+
|
83
|
+
it "streams the body" do
|
84
|
+
env["stream.send"].should_receive(:call).with(body[0])
|
85
|
+
env["stream.send"].should_receive(:call).with(body[1])
|
86
|
+
app.postprocess env, [status, headers, body]
|
87
|
+
end
|
88
|
+
|
89
|
+
it "doesn't stream the body if it equals Rack::STREAMING" do
|
90
|
+
env["stream.send"].should_not_receive :call
|
91
|
+
app.postprocess env, [status, headers, Rack::STREAMING]
|
92
|
+
end
|
93
|
+
|
94
|
+
it "closes the response stream after streaming the body" do
|
95
|
+
env["stream.close"].should_receive :call
|
96
|
+
app.postprocess env, [status, headers, body]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "closes the response even if streaming the body fails" do
|
100
|
+
body.should_receive(:each).and_raise
|
101
|
+
env["stream.close"].should_receive :call
|
102
|
+
|
103
|
+
proc {
|
104
|
+
app.postprocess env, [status, headers, body]
|
105
|
+
}.should raise_error
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/body"
|
3
|
+
|
4
|
+
describe Hatetepe::Body do
|
5
|
+
let(:body) { Hatetepe::Body.new }
|
6
|
+
|
7
|
+
it "is deferrable" do
|
8
|
+
body.should respond_to(:callback)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "#initialize" do
|
12
|
+
it "leaves the IO stream empty" do
|
13
|
+
body.io.length.should be_zero
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#initialize(string)" do
|
18
|
+
let(:body) { Hatetepe::Body.new "herp derp" }
|
19
|
+
|
20
|
+
it "writes the passed string" do
|
21
|
+
body.length.should equal(9)
|
22
|
+
body.io.read.should == "herp derp"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "#sync" do
|
27
|
+
let(:conn) { stub "conn", :paused? => true }
|
28
|
+
|
29
|
+
it "resumes the source connection if any" do
|
30
|
+
body.source = conn
|
31
|
+
|
32
|
+
conn.should_receive :resume
|
33
|
+
Fiber.new { body.succeed }.resume
|
34
|
+
body.sync
|
35
|
+
end
|
36
|
+
|
37
|
+
it "forwards to EM::Synchrony.sync(body)" do
|
38
|
+
EM::Synchrony.should_receive(:sync).with(body)
|
39
|
+
body.sync
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#length" do
|
44
|
+
let(:length) { stub "length" }
|
45
|
+
|
46
|
+
it "forwards to io#length" do
|
47
|
+
body.io.stub :length => length
|
48
|
+
body.length.should equal(length)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#empty?" do
|
53
|
+
it "returns true if length is zero" do
|
54
|
+
body.stub :length => 0
|
55
|
+
body.empty?.should be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns false if length is non-zero" do
|
59
|
+
body.stub :length => 42
|
60
|
+
body.empty?.should be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "#pos" do
|
65
|
+
let(:pos) { stub "pos" }
|
66
|
+
|
67
|
+
it "forwards to io#pos" do
|
68
|
+
body.io.stub :pos => pos
|
69
|
+
body.pos.should equal(pos)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "#rewind" do
|
74
|
+
let(:rewind) { stub "rewind" }
|
75
|
+
|
76
|
+
it "forwards to io#rewind" do
|
77
|
+
body.io.stub :rewind => rewind
|
78
|
+
body.rewind.should equal(rewind)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "#close_write" do
|
83
|
+
it "forwards to io#close_write" do
|
84
|
+
ret = stub("return")
|
85
|
+
body.io.should_receive(:close_write) { ret }
|
86
|
+
|
87
|
+
body.close_write.should equal(ret)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "succeeds the body" do
|
91
|
+
body.should_receive :succeed
|
92
|
+
body.close_write
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "#closed_write?" do
|
97
|
+
it "forwards to io#closed_write?" do
|
98
|
+
ret = stub("return")
|
99
|
+
body.io.should_receive(:closed_write?) { ret }
|
100
|
+
|
101
|
+
body.closed_write?.should equal(ret)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "#each {|chunk| ... }" do
|
106
|
+
it "yields each written chunk until the body succeeds" do
|
107
|
+
chunks = ["111", "222"]
|
108
|
+
received, succeeded = [], false
|
109
|
+
|
110
|
+
body << chunks[0]
|
111
|
+
Fiber.new {
|
112
|
+
body.each {|chunk| received << chunk }
|
113
|
+
succeeded = true
|
114
|
+
}.resume
|
115
|
+
received.should == chunks.values_at(0)
|
116
|
+
succeeded.should be_false
|
117
|
+
|
118
|
+
body << chunks[1]
|
119
|
+
received.should == chunks
|
120
|
+
succeeded.should be_false
|
121
|
+
|
122
|
+
body.succeed
|
123
|
+
succeeded.should be_true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "#read(length, buffer)" do
|
128
|
+
it "waits for the body to succeed" do
|
129
|
+
ret, read = nil, false
|
130
|
+
|
131
|
+
Fiber.new { body.read; read = true }.resume
|
132
|
+
read.should be_false
|
133
|
+
|
134
|
+
body.succeed
|
135
|
+
read.should be_true
|
136
|
+
end
|
137
|
+
|
138
|
+
it "rewinds the body" do
|
139
|
+
body.succeed
|
140
|
+
|
141
|
+
body.should_receive :rewind
|
142
|
+
body.read
|
143
|
+
end
|
144
|
+
|
145
|
+
it "forwards to io#read" do
|
146
|
+
body.succeed
|
147
|
+
args, ret = [stub("arg#1"), stub("arg#2")], stub("ret")
|
148
|
+
|
149
|
+
body.io.should_receive(:read).with(*args) { ret }
|
150
|
+
body.read(*args).should equal(ret)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "#gets" do
|
155
|
+
it "waits for the body to succeed" do
|
156
|
+
ret, read = nil, false
|
157
|
+
|
158
|
+
Fiber.new { body.gets; read = true }.resume
|
159
|
+
read.should be_false
|
160
|
+
|
161
|
+
body.succeed
|
162
|
+
read.should be_true
|
163
|
+
end
|
164
|
+
|
165
|
+
it "rewinds the body" do
|
166
|
+
body.succeed
|
167
|
+
|
168
|
+
body.should_receive :rewind
|
169
|
+
body.gets
|
170
|
+
end
|
171
|
+
|
172
|
+
it "forwards to io#gets" do
|
173
|
+
body.succeed
|
174
|
+
ret = stub("ret")
|
175
|
+
|
176
|
+
body.io.should_receive(:gets) { ret }
|
177
|
+
body.gets.should equal(ret)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
context "#write(chunk)" do
|
182
|
+
it "forwards to io#write" do
|
183
|
+
arg, ret = stub("arg"), stub("ret")
|
184
|
+
body.io.should_receive(:write).with(arg) { ret }
|
185
|
+
|
186
|
+
body.write(arg).should equal(ret)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context "#<<(chunk)" do
|
191
|
+
it "forwards to io#<<" do
|
192
|
+
arg, ret = stub("arg"), stub("ret")
|
193
|
+
body.io.should_receive(:<<).with(arg) { ret }
|
194
|
+
|
195
|
+
body.<<(arg).should equal(ret)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/client"
|
3
|
+
|
4
|
+
describe Hatetepe::Client do
|
5
|
+
let(:client) {
|
6
|
+
Hatetepe::Client.allocate.tap {|c|
|
7
|
+
c.send :initialize, config
|
8
|
+
c.stub :send_data
|
9
|
+
c.post_init
|
10
|
+
}
|
11
|
+
}
|
12
|
+
let(:config) {
|
13
|
+
{
|
14
|
+
:host => stub("host", :to_s => "foohost"),
|
15
|
+
:port => stub("port", :to_s => "12345")
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
context ".start(config)" do
|
20
|
+
it "attaches a socket to the EventMachine reactor" do
|
21
|
+
EM.should_receive(:connect) {|host, port, klass, cfg|
|
22
|
+
host.should equal(cfg[:host])
|
23
|
+
port.should equal(cfg[:port])
|
24
|
+
klass.should equal(Hatetepe::Client)
|
25
|
+
cfg.should equal(config)
|
26
|
+
client
|
27
|
+
}
|
28
|
+
Hatetepe::Client.start(config).should equal(client)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context ".request(verb, uri, headers, body)" do
|
33
|
+
let(:verb) { stub "verb" }
|
34
|
+
let(:uri) { "http://foo.bar/baz?key=value" }
|
35
|
+
let(:headers) { stub "headers", :[]= => nil, :[] => nil }
|
36
|
+
let(:body) { stub "body" }
|
37
|
+
|
38
|
+
before {
|
39
|
+
Hatetepe::Client.stub(:start) { client }
|
40
|
+
}
|
41
|
+
|
42
|
+
it "starts a client" do
|
43
|
+
Fiber.new {
|
44
|
+
Hatetepe::Client.should_receive(:start) {|config|
|
45
|
+
config[:port].should == URI.parse(uri).port
|
46
|
+
config[:host].should == URI.parse(uri).host
|
47
|
+
client
|
48
|
+
}
|
49
|
+
Hatetepe::Client.request "GET", uri
|
50
|
+
}.resume
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:user_agent) { stub "user agent" }
|
54
|
+
|
55
|
+
it "sets an appropriate User-Agent header if there is none" do
|
56
|
+
Fiber.new {
|
57
|
+
client.should_receive(:<<) {|request|
|
58
|
+
request.headers["User-Agent"].should == "hatetepe/#{Hatetepe::VERSION}"
|
59
|
+
}
|
60
|
+
Hatetepe::Client.request "GET", uri
|
61
|
+
|
62
|
+
client.should_receive(:<<) {|request|
|
63
|
+
request.headers["User-Agent"].should equal(user_agent)
|
64
|
+
}
|
65
|
+
Hatetepe::Client.request "GET", uri, "User-Agent" => user_agent
|
66
|
+
}.resume
|
67
|
+
end
|
68
|
+
|
69
|
+
it "uses an empty, write-closed Body as default" do
|
70
|
+
Fiber.new {
|
71
|
+
client.should_receive(:<<) {|request|
|
72
|
+
request.body.closed_write?.should be_true
|
73
|
+
request.body.should be_empty
|
74
|
+
}
|
75
|
+
Hatetepe::Client.request verb, uri
|
76
|
+
}.resume
|
77
|
+
end
|
78
|
+
|
79
|
+
it "sends the request" do
|
80
|
+
Fiber.new {
|
81
|
+
client.should_receive(:<<) {|request|
|
82
|
+
request.verb.should equal(verb)
|
83
|
+
request.uri.should == URI.parse(uri).request_uri
|
84
|
+
request.headers.should equal(headers)
|
85
|
+
request.body.should equal(body)
|
86
|
+
}
|
87
|
+
Hatetepe::Client.request verb, uri, headers, body
|
88
|
+
}.resume
|
89
|
+
end
|
90
|
+
|
91
|
+
it "waits for the request to succeed" do
|
92
|
+
request, succeeded = nil, false
|
93
|
+
Fiber.new {
|
94
|
+
client.should_receive(:<<) {|req| request = req }
|
95
|
+
Hatetepe::Client.request verb, uri
|
96
|
+
succeeded = true
|
97
|
+
}.resume
|
98
|
+
|
99
|
+
succeeded.should be_false
|
100
|
+
request.succeed
|
101
|
+
succeeded.should be_true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
[:get, :head].each {|verb|
|
106
|
+
context ".#{verb}(uri, headers)" do
|
107
|
+
let(:uri) { stub "uri" }
|
108
|
+
let(:headers) { stub "headers" }
|
109
|
+
let(:response) { stub "response" }
|
110
|
+
|
111
|
+
it "forwards to .request('#{verb.to_s.upcase}')" do
|
112
|
+
Hatetepe::Client.should_receive(:request) {|verb, urii, hedders|
|
113
|
+
verb.should == verb.to_s.upcase
|
114
|
+
urii.should equal(uri)
|
115
|
+
hedders.should equal(headers)
|
116
|
+
response
|
117
|
+
}
|
118
|
+
Hatetepe::Client.send(verb, uri, headers).should equal(response)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
}
|
122
|
+
|
123
|
+
[:options, :post, :put, :delete, :trace, :connect].each {|verb|
|
124
|
+
context ".#{verb}(uri, headers, body)" do
|
125
|
+
let(:uri) { stub "uri" }
|
126
|
+
let(:headers) { stub "headers" }
|
127
|
+
let(:response) { stub "response" }
|
128
|
+
let(:body) { stub "body" }
|
129
|
+
|
130
|
+
it "forwards to .request('#{verb.to_s.upcase}')" do
|
131
|
+
Hatetepe::Client.should_receive(:request) {|verb, urii, hedders, bodeh|
|
132
|
+
verb.should == verb.to_s.upcase
|
133
|
+
urii.should equal(uri)
|
134
|
+
hedders.should equal(headers)
|
135
|
+
bodeh.should equal(body)
|
136
|
+
response
|
137
|
+
}
|
138
|
+
Hatetepe::Client.send(verb, uri, headers, body).should equal(response)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
}
|
142
|
+
|
143
|
+
context "#initialize(config)" do
|
144
|
+
let(:client) { Hatetepe::Client.allocate }
|
145
|
+
|
146
|
+
it "sets the config" do
|
147
|
+
client.send :initialize, config
|
148
|
+
client.config.should equal(config)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "#post_init" do
|
153
|
+
let(:client) {
|
154
|
+
Hatetepe::Client.allocate.tap {|c|
|
155
|
+
c.send :initialize, config
|
156
|
+
}
|
157
|
+
}
|
158
|
+
let(:requests) {
|
159
|
+
[true, nil, nil].map {|response|
|
160
|
+
Hatetepe::Request.new("GET", "/").tap {|request|
|
161
|
+
request.response = response
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
let(:response) { stub "response" }
|
166
|
+
|
167
|
+
before {
|
168
|
+
client.post_init
|
169
|
+
client.requests.push *requests
|
170
|
+
}
|
171
|
+
|
172
|
+
context "'s on_response handler" do
|
173
|
+
it "associates the response with a request" do
|
174
|
+
client.parser.on_response[0].call response
|
175
|
+
requests[1].response.should equal(response)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "'s on_headers handler" do
|
180
|
+
it "succeeds the response's request" do
|
181
|
+
requests[1].response = response
|
182
|
+
requests[1].should_receive(:succeed).with response
|
183
|
+
client.parser.on_headers[0].call
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "'s on_write handler" do
|
188
|
+
it "forwards to EM's send_data" do
|
189
|
+
client.builder.on_write[0].should == client.method(:send_data)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "#<<(request)" do
|
195
|
+
let(:request) { Hatetepe::Request.new "GET", "/" }
|
196
|
+
let(:builder) { client.builder }
|
197
|
+
|
198
|
+
it "forces a new Host header" do
|
199
|
+
builder.should_receive(:header) {|key, value|
|
200
|
+
value.should == "foohost:12345"
|
201
|
+
}
|
202
|
+
client << request
|
203
|
+
end
|
204
|
+
|
205
|
+
it "adds the request to #requests" do
|
206
|
+
client << request
|
207
|
+
client.requests.last.should equal(request)
|
208
|
+
end
|
209
|
+
|
210
|
+
it "feeds the builder" do
|
211
|
+
request.body.write "asdf"
|
212
|
+
|
213
|
+
builder.should_receive(:request).with request.verb, request.uri
|
214
|
+
builder.should_receive(:headers).with request.headers
|
215
|
+
builder.should_receive(:body).with request.body
|
216
|
+
builder.should_receive(:complete)
|
217
|
+
|
218
|
+
client << request
|
219
|
+
end
|
220
|
+
|
221
|
+
it "wraps the builder feeding within a Fiber" do
|
222
|
+
outer, inner = Fiber.current, nil
|
223
|
+
builder.should_receive(:request) {
|
224
|
+
inner = Fiber.current
|
225
|
+
}
|
226
|
+
|
227
|
+
builder.stub :headers
|
228
|
+
builder.stub :body
|
229
|
+
builder.should_receive(:complete) {
|
230
|
+
inner.should equal(Fiber.current)
|
231
|
+
}
|
232
|
+
|
233
|
+
client << request
|
234
|
+
outer.should_not equal(inner)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
context "#receive_data(data)" do
|
239
|
+
let(:chunk) { stub "chunk" }
|
240
|
+
|
241
|
+
it "feeds the parser" do
|
242
|
+
client.parser.should_receive(:<<).with chunk
|
243
|
+
client.receive_data chunk
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "#stop" do
|
248
|
+
it "closes the connection" do
|
249
|
+
client.should_receive :close_connection_after_writing
|
250
|
+
client.stop
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context "#responses" do
|
255
|
+
let(:requests) {
|
256
|
+
4.times.map {|i| stub "request##{i}", :response => responses[i] }
|
257
|
+
}
|
258
|
+
let(:responses) {
|
259
|
+
2.times.map {|i| stub "response##{i}" }
|
260
|
+
}
|
261
|
+
|
262
|
+
before {
|
263
|
+
client.stub :requests => requests
|
264
|
+
}
|
265
|
+
|
266
|
+
it "returns all requests' responses" do
|
267
|
+
client.responses.should == responses
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|