rubycut-sinatra-contrib 1.4.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/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
|