hatetepe 0.0.4 → 0.2.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/.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
|