rubycut-sinatra-contrib 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +136 -0
- data/Rakefile +75 -0
- data/ideas.md +29 -0
- data/lib/sinatra/capture.rb +124 -0
- data/lib/sinatra/config_file.rb +167 -0
- data/lib/sinatra/content_for.rb +125 -0
- data/lib/sinatra/contrib.rb +39 -0
- data/lib/sinatra/contrib/all.rb +2 -0
- data/lib/sinatra/contrib/setup.rb +53 -0
- data/lib/sinatra/contrib/version.rb +17 -0
- data/lib/sinatra/cookies.rb +331 -0
- data/lib/sinatra/decompile.rb +120 -0
- data/lib/sinatra/engine_tracking.rb +96 -0
- data/lib/sinatra/extension.rb +95 -0
- data/lib/sinatra/json.rb +130 -0
- data/lib/sinatra/link_header.rb +132 -0
- data/lib/sinatra/multi_route.rb +87 -0
- data/lib/sinatra/namespace.rb +284 -0
- data/lib/sinatra/reloader.rb +394 -0
- data/lib/sinatra/respond_with.rb +249 -0
- data/lib/sinatra/streaming.rb +267 -0
- data/lib/sinatra/test_helpers.rb +87 -0
- data/sinatra-contrib.gemspec +127 -0
- data/spec/capture_spec.rb +93 -0
- data/spec/config_file/key_value.yml +6 -0
- data/spec/config_file/key_value.yml.erb +6 -0
- data/spec/config_file/key_value_override.yml +2 -0
- data/spec/config_file/missing_env.yml +4 -0
- data/spec/config_file/with_envs.yml +7 -0
- data/spec/config_file/with_nested_envs.yml +11 -0
- data/spec/config_file_spec.rb +63 -0
- data/spec/content_for/different_key.erb +1 -0
- data/spec/content_for/different_key.erubis +1 -0
- data/spec/content_for/different_key.haml +2 -0
- data/spec/content_for/different_key.slim +2 -0
- data/spec/content_for/layout.erb +1 -0
- data/spec/content_for/layout.erubis +1 -0
- data/spec/content_for/layout.haml +1 -0
- data/spec/content_for/layout.slim +1 -0
- data/spec/content_for/multiple_blocks.erb +4 -0
- data/spec/content_for/multiple_blocks.erubis +4 -0
- data/spec/content_for/multiple_blocks.haml +8 -0
- data/spec/content_for/multiple_blocks.slim +8 -0
- data/spec/content_for/multiple_yields.erb +3 -0
- data/spec/content_for/multiple_yields.erubis +3 -0
- data/spec/content_for/multiple_yields.haml +3 -0
- data/spec/content_for/multiple_yields.slim +3 -0
- data/spec/content_for/passes_values.erb +1 -0
- data/spec/content_for/passes_values.erubis +1 -0
- data/spec/content_for/passes_values.haml +1 -0
- data/spec/content_for/passes_values.slim +1 -0
- data/spec/content_for/same_key.erb +1 -0
- data/spec/content_for/same_key.erubis +1 -0
- data/spec/content_for/same_key.haml +2 -0
- data/spec/content_for/same_key.slim +2 -0
- data/spec/content_for/takes_values.erb +1 -0
- data/spec/content_for/takes_values.erubis +1 -0
- data/spec/content_for/takes_values.haml +3 -0
- data/spec/content_for/takes_values.slim +3 -0
- data/spec/content_for_spec.rb +213 -0
- data/spec/cookies_spec.rb +802 -0
- data/spec/decompile_spec.rb +44 -0
- data/spec/extension_spec.rb +33 -0
- data/spec/json_spec.rb +117 -0
- data/spec/link_header_spec.rb +100 -0
- data/spec/multi_route_spec.rb +60 -0
- data/spec/namespace/foo.erb +1 -0
- data/spec/namespace/nested/foo.erb +1 -0
- data/spec/namespace_spec.rb +676 -0
- data/spec/okjson.rb +581 -0
- data/spec/reloader/app.rb.erb +40 -0
- data/spec/reloader_spec.rb +441 -0
- data/spec/respond_with/bar.erb +1 -0
- data/spec/respond_with/bar.json.erb +1 -0
- data/spec/respond_with/foo.html.erb +1 -0
- data/spec/respond_with/not_html.sass +2 -0
- data/spec/respond_with_spec.rb +297 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/streaming_spec.rb +436 -0
- metadata +313 -0
@@ -0,0 +1 @@
|
|
1
|
+
Girl! I wanna take you to a ... bar!
|
@@ -0,0 +1 @@
|
|
1
|
+
json!
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello <%= name %>!
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'backports'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
require_relative 'spec_helper'
|
5
|
+
require_relative 'okjson'
|
6
|
+
|
7
|
+
describe Sinatra::RespondWith do
|
8
|
+
def provides(*args)
|
9
|
+
@provides = args
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_app(&block)
|
13
|
+
types = @provides
|
14
|
+
mock_app do
|
15
|
+
set :app_file, __FILE__
|
16
|
+
set :views, root + '/respond_with'
|
17
|
+
register Sinatra::RespondWith
|
18
|
+
respond_to(*types) if types
|
19
|
+
class_eval(&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond_to(*args, &block)
|
24
|
+
respond_app { get('/') { respond_to(*args, &block) } }
|
25
|
+
end
|
26
|
+
|
27
|
+
def respond_with(*args, &block)
|
28
|
+
respond_app { get('/') { respond_with(*args, &block) } }
|
29
|
+
end
|
30
|
+
|
31
|
+
def req(*types)
|
32
|
+
p = types.shift if types.first.is_a? String and types.first.start_with? '/'
|
33
|
+
accept = types.map { |t| Sinatra::Base.mime_type(t).to_s }.join ','
|
34
|
+
get (p || '/'), {}, 'HTTP_ACCEPT' => accept
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "Helpers#respond_to" do
|
38
|
+
it 'allows defining handlers by file extensions' do
|
39
|
+
respond_to do |format|
|
40
|
+
format.html { "html!" }
|
41
|
+
format.json { "json!" }
|
42
|
+
end
|
43
|
+
|
44
|
+
req(:html).body.should == "html!"
|
45
|
+
req(:json).body.should == "json!"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'respects quality' do
|
49
|
+
respond_to do |format|
|
50
|
+
format.html { "html!" }
|
51
|
+
format.json { "json!" }
|
52
|
+
end
|
53
|
+
|
54
|
+
req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
|
55
|
+
req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'allows using mime types' do
|
59
|
+
respond_to do |format|
|
60
|
+
format.on('text/html') { "html!" }
|
61
|
+
format.json { "json!" }
|
62
|
+
end
|
63
|
+
|
64
|
+
req(:html).body.should == "html!"
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'allows using wildcards in format matchers' do
|
68
|
+
respond_to do |format|
|
69
|
+
format.on('text/*') { "text!" }
|
70
|
+
format.json { "json!" }
|
71
|
+
end
|
72
|
+
|
73
|
+
req(:html).body.should == "text!"
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'allows using catch all wildcards in format matchers' do
|
77
|
+
respond_to do |format|
|
78
|
+
format.on('*/*') { "anything!" }
|
79
|
+
format.json { "json!" }
|
80
|
+
end
|
81
|
+
|
82
|
+
req(:html).body.should == "anything!"
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'prefers concret over generic' do
|
86
|
+
respond_to do |format|
|
87
|
+
format.on('text/*') { "text!" }
|
88
|
+
format.on('*/*') { "anything!" }
|
89
|
+
format.json { "json!" }
|
90
|
+
end
|
91
|
+
|
92
|
+
req(:json).body.should == "json!"
|
93
|
+
req(:html).body.should == "text!"
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'does not set up default handlers' do
|
97
|
+
respond_to
|
98
|
+
req.should_not be_ok
|
99
|
+
status.should == 406
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "Helpers#respond_with" do
|
104
|
+
describe "matching" do
|
105
|
+
it 'allows defining handlers by file extensions' do
|
106
|
+
respond_with(:ignore) do |format|
|
107
|
+
format.html { "html!" }
|
108
|
+
format.json { "json!" }
|
109
|
+
end
|
110
|
+
|
111
|
+
req(:html).body.should == "html!"
|
112
|
+
req(:json).body.should == "json!"
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'respects quality' do
|
116
|
+
respond_with(:ignore) do |format|
|
117
|
+
format.html { "html!" }
|
118
|
+
format.json { "json!" }
|
119
|
+
end
|
120
|
+
|
121
|
+
req("text/html;q=0.7, application/json;q=0.3").body.should == "html!"
|
122
|
+
req("text/html;q=0.3, application/json;q=0.7").body.should == "json!"
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'allows using mime types' do
|
126
|
+
respond_with(:ignore) do |format|
|
127
|
+
format.on('text/html') { "html!" }
|
128
|
+
format.json { "json!" }
|
129
|
+
end
|
130
|
+
|
131
|
+
req(:html).body.should == "html!"
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'allows using wildcards in format matchers' do
|
135
|
+
respond_with(:ignore) do |format|
|
136
|
+
format.on('text/*') { "text!" }
|
137
|
+
format.json { "json!" }
|
138
|
+
end
|
139
|
+
|
140
|
+
req(:html).body.should == "text!"
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'allows using catch all wildcards in format matchers' do
|
144
|
+
respond_with(:ignore) do |format|
|
145
|
+
format.on('*/*') { "anything!" }
|
146
|
+
format.json { "json!" }
|
147
|
+
end
|
148
|
+
|
149
|
+
req(:html).body.should == "anything!"
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'prefers concret over generic' do
|
153
|
+
respond_with(:ignore) do |format|
|
154
|
+
format.on('text/*') { "text!" }
|
155
|
+
format.on('*/*') { "anything!" }
|
156
|
+
format.json { "json!" }
|
157
|
+
end
|
158
|
+
|
159
|
+
req(:json).body.should == "json!"
|
160
|
+
req(:html).body.should == "text!"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "default behavior" do
|
165
|
+
it 'converts objects to json out of the box' do
|
166
|
+
respond_with 'a' => 'b'
|
167
|
+
OkJson.decode(req(:json).body).should == {'a' => 'b'}
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'handles multiple routes correctly' do
|
171
|
+
respond_app do
|
172
|
+
get('/') { respond_with 'a' => 'b' }
|
173
|
+
get('/:name') { respond_with 'a' => params[:name] }
|
174
|
+
end
|
175
|
+
OkJson.decode(req('/', :json).body).should == {'a' => 'b'}
|
176
|
+
OkJson.decode(req('/b', :json).body).should == {'a' => 'b'}
|
177
|
+
OkJson.decode(req('/c', :json).body).should == {'a' => 'c'}
|
178
|
+
end
|
179
|
+
|
180
|
+
it "calls to_EXT if available" do
|
181
|
+
respond_with Struct.new(:to_pdf).new("hello")
|
182
|
+
req(:pdf).body.should == "hello"
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'results in a 406 if format cannot be produced' do
|
186
|
+
respond_with({})
|
187
|
+
req(:html).should_not be_ok
|
188
|
+
status.should == 406
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe 'templates' do
|
193
|
+
it 'looks for templates with name.target.engine' do
|
194
|
+
respond_with :foo, :name => 'World'
|
195
|
+
req(:html).should be_ok
|
196
|
+
body.should == "Hello World!"
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'looks for templates with name.engine for specific engines' do
|
200
|
+
respond_with :bar
|
201
|
+
req(:html).should be_ok
|
202
|
+
body.should == "Girl! I wanna take you to a ... bar!"
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'does not use name.engine for engines producing other formats' do
|
206
|
+
respond_with :not_html
|
207
|
+
req(:html).should_not be_ok
|
208
|
+
status.should == 406
|
209
|
+
body.should be_empty
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'falls back to #json if no template is found' do
|
213
|
+
respond_with :foo, :name => 'World'
|
214
|
+
req(:json).should be_ok
|
215
|
+
OkJson.decode(body).should == {'name' => 'World'}
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'favors templates over #json' do
|
219
|
+
respond_with :bar, :name => 'World'
|
220
|
+
req(:json).should be_ok
|
221
|
+
body.should == 'json!'
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'falls back to to_EXT if no template is found' do
|
225
|
+
object = {:name => 'World'}
|
226
|
+
def object.to_pdf; "hi" end
|
227
|
+
respond_with :foo, object
|
228
|
+
req(:pdf).should be_ok
|
229
|
+
body.should == "hi"
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'uses yajl for json' do
|
233
|
+
respond_with :baz
|
234
|
+
req(:json).should be_ok
|
235
|
+
body.should == "\"yajl!\""
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe 'customizing' do
|
240
|
+
it 'allows customizing' do
|
241
|
+
respond_with(:foo, :name => 'World') { |f| f.html { 'html!' }}
|
242
|
+
req(:html).should be_ok
|
243
|
+
body.should == "html!"
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'falls back to default behavior if none matches' do
|
247
|
+
respond_with(:foo, :name => 'World') { |f| f.json { 'json!' }}
|
248
|
+
req(:html).should be_ok
|
249
|
+
body.should == "Hello World!"
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'favors generic rule over default behavior' do
|
253
|
+
respond_with(:foo, :name => 'World') { |f| f.on('*/*') { 'generic!' }}
|
254
|
+
req(:html).should be_ok
|
255
|
+
body.should == "generic!"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe :respond_to do
|
261
|
+
it 'acts as global provides condition' do
|
262
|
+
respond_app do
|
263
|
+
respond_to :json, :html
|
264
|
+
get('/a') { 'ok' }
|
265
|
+
get('/b') { 'ok' }
|
266
|
+
end
|
267
|
+
|
268
|
+
req('/b', :xml).should_not be_ok
|
269
|
+
req('/b', :html).should be_ok
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'still allows provides' do
|
273
|
+
respond_app do
|
274
|
+
respond_to :json, :html
|
275
|
+
get('/a') { 'ok' }
|
276
|
+
get('/b', :provides => :json) { 'ok' }
|
277
|
+
end
|
278
|
+
|
279
|
+
req('/b', :html).should_not be_ok
|
280
|
+
req('/b', :json).should be_ok
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'plays well with namespaces' do
|
284
|
+
respond_app do
|
285
|
+
register Sinatra::Namespace
|
286
|
+
namespace '/a' do
|
287
|
+
respond_to :json
|
288
|
+
get { 'json' }
|
289
|
+
end
|
290
|
+
get('/b') { 'anything' }
|
291
|
+
end
|
292
|
+
|
293
|
+
req('/a', :html).should_not be_ok
|
294
|
+
req('/b', :html).should be_ok
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'backports'
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe Sinatra::Streaming do
|
5
|
+
def stream(&block)
|
6
|
+
rack_middleware = @use
|
7
|
+
out = nil
|
8
|
+
mock_app do
|
9
|
+
rack_middleware.each { |args| use(*args) }
|
10
|
+
helpers Sinatra::Streaming
|
11
|
+
get('/') { out = stream(&block) }
|
12
|
+
end
|
13
|
+
get('/')
|
14
|
+
out
|
15
|
+
end
|
16
|
+
|
17
|
+
def use(*args)
|
18
|
+
@use << args
|
19
|
+
end
|
20
|
+
|
21
|
+
before do
|
22
|
+
@use = []
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'stream test helper' do
|
26
|
+
it 'runs the given block' do
|
27
|
+
ran = false
|
28
|
+
stream { ran = true }
|
29
|
+
ran.should be_true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns the stream object' do
|
33
|
+
out = stream { }
|
34
|
+
out.should be_a(Sinatra::Helpers::Stream)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'fires a request against that stream' do
|
38
|
+
stream { |out| out << "Hello World!" }
|
39
|
+
last_response.should be_ok
|
40
|
+
body.should be == "Hello World!"
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'passes the stream object to the block' do
|
44
|
+
passed = nil
|
45
|
+
returned = stream { |out| passed = out }
|
46
|
+
passed.should be == returned
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context Sinatra::Streaming::Stream do
|
51
|
+
it 'should extend the stream object' do
|
52
|
+
out = stream { }
|
53
|
+
out.should be_a(Sinatra::Streaming::Stream)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should not extend stream objects of other apps' do
|
57
|
+
out = nil
|
58
|
+
mock_app { get('/') { out = stream { }}}
|
59
|
+
get('/')
|
60
|
+
out.should be_a(Sinatra::Helpers::Stream)
|
61
|
+
out.should_not be_a(Sinatra::Streaming::Stream)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context EventMachine::Deferrable do
|
66
|
+
it 'allows attaching more than one callback' do
|
67
|
+
a = b = false
|
68
|
+
stream do |out|
|
69
|
+
out.callback { a = true }
|
70
|
+
out.callback { b = true }
|
71
|
+
end
|
72
|
+
a.should be_true
|
73
|
+
b.should be_true
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'triggers callbacks after streaming' do
|
77
|
+
triggered = false
|
78
|
+
stream do |out|
|
79
|
+
out.callback { triggered = true }
|
80
|
+
triggered.should be_false
|
81
|
+
end
|
82
|
+
triggered.should be_true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'app' do
|
87
|
+
it 'is the app instance the stream was created from' do
|
88
|
+
out = stream { }
|
89
|
+
out.app.should be_a(Sinatra::Base)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'lineno' do
|
94
|
+
it 'defaults to 0' do
|
95
|
+
stream { }.lineno.should be == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'does not increase on write' do
|
99
|
+
stream do |out|
|
100
|
+
out << "many\nlines\n"
|
101
|
+
out.lineno.should be == 0
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'is writable' do
|
106
|
+
out = stream { }
|
107
|
+
out.lineno = 10
|
108
|
+
out.lineno.should be == 10
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'pos' do
|
113
|
+
it 'defaults to 0' do
|
114
|
+
stream { }.pos.should be == 0
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'increases when writing data' do
|
118
|
+
stream do |out|
|
119
|
+
out.pos.should be == 0
|
120
|
+
out << 'hi'
|
121
|
+
out.pos.should be == 2
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'is writable' do
|
126
|
+
out = stream { }
|
127
|
+
out.pos = 10
|
128
|
+
out.pos.should be == 10
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'aliased to #tell' do
|
132
|
+
out = stream { }
|
133
|
+
out.tell.should be == 0
|
134
|
+
out.pos = 10
|
135
|
+
out.tell.should be == 10
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'closed' do
|
140
|
+
it 'returns false while streaming' do
|
141
|
+
stream { |out| out.should_not be_closed }
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'returns true after streaming' do
|
145
|
+
stream {}.should be_closed
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'map!' do
|
150
|
+
it 'applies transformations later' do
|
151
|
+
stream do |out|
|
152
|
+
out.map! { |s| s.upcase }
|
153
|
+
out << 'ok'
|
154
|
+
end
|
155
|
+
body.should be == "OK"
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'is chainable' do
|
159
|
+
stream do |out|
|
160
|
+
out.map! { |s| s.upcase }
|
161
|
+
out.map! { |s| s.reverse }
|
162
|
+
out << 'ok'
|
163
|
+
end
|
164
|
+
body.should be == "KO"
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'works with middleware' do
|
168
|
+
middleware = Class.new do
|
169
|
+
def initialize(app) @app = app end
|
170
|
+
def call(env)
|
171
|
+
status, headers, body = @app.call(env)
|
172
|
+
body.map! { |s| s.upcase }
|
173
|
+
[status, headers, body]
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
use middleware
|
178
|
+
stream { |out| out << "ok" }
|
179
|
+
body.should be == "OK"
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'modifies each value separately' do
|
183
|
+
stream do |out|
|
184
|
+
out.map! { |s| s.reverse }
|
185
|
+
out << "ab" << "cd"
|
186
|
+
end
|
187
|
+
body.should be == "badc"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'map' do
|
192
|
+
it 'works with middleware' do
|
193
|
+
middleware = Class.new do
|
194
|
+
def initialize(app) @app = app end
|
195
|
+
def call(env)
|
196
|
+
status, headers, body = @app.call(env)
|
197
|
+
[status, headers, body.map(&:upcase)]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
use middleware
|
202
|
+
stream { |out| out << "ok" }
|
203
|
+
body.should be == "OK"
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'is chainable' do
|
207
|
+
middleware = Class.new do
|
208
|
+
def initialize(app) @app = app end
|
209
|
+
def call(env)
|
210
|
+
status, headers, body = @app.call(env)
|
211
|
+
[status, headers, body.map(&:upcase).map(&:reverse)]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
use middleware
|
216
|
+
stream { |out| out << "ok" }
|
217
|
+
body.should be == "KO"
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'can be written as each.map' do
|
221
|
+
middleware = Class.new do
|
222
|
+
def initialize(app) @app = app end
|
223
|
+
def call(env)
|
224
|
+
status, headers, body = @app.call(env)
|
225
|
+
[status, headers, body.each.map(&:upcase)]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
use middleware
|
230
|
+
stream { |out| out << "ok" }
|
231
|
+
body.should be == "OK"
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'does not modify the original body' do
|
235
|
+
stream do |out|
|
236
|
+
out.map { |s| s.reverse }
|
237
|
+
out << 'ok'
|
238
|
+
end
|
239
|
+
body.should be == 'ok'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'write' do
|
244
|
+
it 'writes to the stream' do
|
245
|
+
stream { |out| out.write 'hi' }
|
246
|
+
body.should be == 'hi'
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'returns the number of bytes' do
|
250
|
+
stream do |out|
|
251
|
+
out.write('hi').should be == 2
|
252
|
+
out.write('hello').should be == 5
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'accepts non-string objects' do
|
257
|
+
stream do |out|
|
258
|
+
out.write(12).should be == 2
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should be aliased to syswrite' do
|
263
|
+
stream { |out| out.syswrite('hi').should be == 2 }
|
264
|
+
body.should be == 'hi'
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'should be aliased to write_nonblock' do
|
268
|
+
stream { |out| out.write_nonblock('hi').should be == 2 }
|
269
|
+
body.should be == 'hi'
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context 'print' do
|
274
|
+
it 'writes to the stream' do
|
275
|
+
stream { |out| out.print('hi') }
|
276
|
+
body.should be == 'hi'
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'accepts multiple arguments' do
|
280
|
+
stream { |out| out.print(1, 2, 3, 4) }
|
281
|
+
body.should be == '1234'
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'returns nil' do
|
285
|
+
stream { |out| out.print('hi').should be_nil }
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context 'printf' do
|
290
|
+
it 'writes to the stream' do
|
291
|
+
stream { |out| out.printf('hi') }
|
292
|
+
body.should be == 'hi'
|
293
|
+
end
|
294
|
+
|
295
|
+
it 'interpolates the format string' do
|
296
|
+
stream { |out| out.printf("%s: %d", "answer", 42) }
|
297
|
+
body.should be == 'answer: 42'
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'returns nil' do
|
301
|
+
stream { |out| out.printf('hi').should be_nil }
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
context 'putc' do
|
306
|
+
it 'writes the first character of a string' do
|
307
|
+
stream { |out| out.putc('hi') }
|
308
|
+
body.should be == 'h'
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'writes the character corresponding to an integer' do
|
312
|
+
stream { |out| out.putc(42) }
|
313
|
+
body.should be == '*'
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'returns nil' do
|
317
|
+
stream { |out| out.putc('hi').should be_nil }
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
context 'puts' do
|
322
|
+
it 'writes to the stream' do
|
323
|
+
stream { |out| out.puts('hi') }
|
324
|
+
body.should be == "hi\n"
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'accepts multiple arguments' do
|
328
|
+
stream { |out| out.puts(1, 2, 3, 4) }
|
329
|
+
body.should be == "1\n2\n3\n4\n"
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'returns nil' do
|
333
|
+
stream { |out| out.puts('hi').should be_nil }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context 'close' do
|
338
|
+
it 'sets #closed? to true' do
|
339
|
+
stream do |out|
|
340
|
+
out.close
|
341
|
+
out.should be_closed
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'sets #closed_write? to true' do
|
346
|
+
stream do |out|
|
347
|
+
out.should_not be_closed_write
|
348
|
+
out.close
|
349
|
+
out.should be_closed_write
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'fires callbacks' do
|
354
|
+
stream do |out|
|
355
|
+
fired = false
|
356
|
+
out.callback { fired = true }
|
357
|
+
out.close
|
358
|
+
fired.should be_true
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
it 'prevents from further writing' do
|
363
|
+
stream do |out|
|
364
|
+
out.close
|
365
|
+
expect { out << 'hi' }.to raise_error(IOError, 'not opened for writing')
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
context 'close_read' do
|
371
|
+
it 'raises the appropriate exception' do
|
372
|
+
expect { stream { |out| out.close_read }}.
|
373
|
+
to raise_error(IOError, "closing non-duplex IO for reading")
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context 'closed_read?' do
|
378
|
+
it('returns true') { stream { |out| out.should be_closed_read }}
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'rewind' do
|
382
|
+
it 'resets pos' do
|
383
|
+
stream do |out|
|
384
|
+
out << 'hi'
|
385
|
+
out.rewind
|
386
|
+
out.pos.should be == 0
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'resets lineno' do
|
391
|
+
stream do |out|
|
392
|
+
out.lineno = 10
|
393
|
+
out.rewind
|
394
|
+
out.lineno.should be == 0
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
raises = %w[
|
400
|
+
bytes eof? eof getbyte getc gets read read_nonblock readbyte readchar
|
401
|
+
readline readlines readpartial sysread ungetbyte ungetc
|
402
|
+
]
|
403
|
+
|
404
|
+
enum = %w[chars each_line each_byte each_char lines]
|
405
|
+
dummies = %w[flush fsync internal_encoding pid]
|
406
|
+
|
407
|
+
raises.each do |method|
|
408
|
+
context method do
|
409
|
+
it 'raises the appropriate exception' do
|
410
|
+
expect { stream { |out| out.public_send(method) }}.
|
411
|
+
to raise_error(IOError, "not opened for reading")
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
enum.each do |method|
|
417
|
+
context method do
|
418
|
+
it 'creates an Enumerator' do
|
419
|
+
stream { |out| out.public_send(method).should be_a(Enumerator) }
|
420
|
+
end
|
421
|
+
|
422
|
+
it 'calling each raises the appropriate exception' do
|
423
|
+
expect { stream { |out| out.public_send(method).each { }}}.
|
424
|
+
to raise_error(IOError, "not opened for reading")
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
dummies.each do |method|
|
430
|
+
context method do
|
431
|
+
it 'returns nil' do
|
432
|
+
stream { |out| out.public_send(method).should be_nil }
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|