1 # frozen_string_literal: true
3 require_relative 'helper'
6 require_relative '../lib/rack/builder'
7 require_relative '../lib/rack/lint'
8 require_relative '../lib/rack/mock_request'
9 require_relative '../lib/rack/content_length'
10 require_relative '../lib/rack/show_exceptions'
11 require_relative '../lib/rack/auth/basic'
14 class NothingMiddleware
15 def initialize(app, **)
20 response = @app.call(env)
28 describe Rack::Builder do
30 Rack::Lint.new Rack::Builder.new(&block)
33 def builder_to_app(&block)
34 Rack::Lint.new Rack::Builder.new(&block).to_app
37 it "can provide options" do
38 builder = Rack::Builder.new(foo: :bar)
39 builder.options[:foo].must_equal :bar
42 it "supports run with block" do
43 app = builder_to_app do
44 run {|env| [200, { "content-type" => "text/plain" }, ["OK"]]}
46 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'
49 it "supports mapping" do
50 app = builder_to_app do
51 map '/' do |outer_env|
52 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] }
55 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] }
58 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root'
59 Rack::MockRequest.new(app).get("/sub").body.to_s.must_equal 'sub'
62 it "supports use when mapping" do
63 app = builder_to_app do
65 use Rack::ContentLength
66 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] }
68 use Rack::ContentLength
69 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] }
71 Rack::MockRequest.new(app).get("/").headers['content-length'].must_equal '4'
72 Rack::MockRequest.new(app).get("/sub").headers['content-length'].must_equal '3'
75 it "doesn't dupe env even when mapping" do
76 app = builder_to_app do
77 use NothingMiddleware, noop: :noop
78 map '/' do |outer_env|
79 run lambda { |inner_env|
80 inner_env['new_key'] = 'new_value'
81 [200, { "content-type" => "text/plain" }, ['root']]
85 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root'
86 NothingMiddleware.env['new_key'].must_equal 'new_value'
89 it "dupe #to_app when mapping so Rack::Reloader can reload the application on each request" do
91 map '/' do |outer_env|
92 run lambda { |env| [200, { "content-type" => "text/plain" }, [object_id.to_s]] }
96 builder_app1_id = Rack::MockRequest.new(app).get("/").body.to_s
97 builder_app2_id = Rack::MockRequest.new(app).get("/").body.to_s
99 builder_app2_id.wont_equal builder_app1_id
102 it "chains apps by default" do
103 app = builder_to_app do
104 use Rack::ShowExceptions
105 run lambda { |env| raise "bzzzt" }
108 Rack::MockRequest.new(app).get("/").must_be :server_error?
109 Rack::MockRequest.new(app).get("/").must_be :server_error?
110 Rack::MockRequest.new(app).get("/").must_be :server_error?
113 it "has implicit #to_app" do
115 use Rack::ShowExceptions
116 run lambda { |env| raise "bzzzt" }
119 Rack::MockRequest.new(app).get("/").must_be :server_error?
120 Rack::MockRequest.new(app).get("/").must_be :server_error?
121 Rack::MockRequest.new(app).get("/").must_be :server_error?
124 it "supports blocks on use" do
126 use Rack::ShowExceptions
127 use Rack::Auth::Basic do |username, password|
131 run lambda { |env| [200, { "content-type" => "text/plain" }, ['Hi Boss']] }
134 response = Rack::MockRequest.new(app).get("/")
135 response.must_be :client_error?
136 response.status.must_equal 401
139 response = Rack::MockRequest.new(app).get("/",
140 'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
141 response.status.must_equal 200
142 response.body.to_s.must_equal 'Hi Boss'
145 it "has explicit #to_app" do
147 use Rack::ShowExceptions
148 run lambda { |env| raise "bzzzt" }
151 Rack::MockRequest.new(app).get("/").must_be :server_error?
152 Rack::MockRequest.new(app).get("/").must_be :server_error?
153 Rack::MockRequest.new(app).get("/").must_be :server_error?
156 it "can mix map and run for endpoints" do
159 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['sub']] }
161 run lambda { |inner_env| [200, { "content-type" => "text/plain" }, ['root']] }
164 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'root'
165 Rack::MockRequest.new(app).get("/sub").body.to_s.must_equal 'sub'
168 it "accepts middleware-only map blocks" do
170 map('/foo') { use Rack::ShowExceptions }
171 run lambda { |env| raise "bzzzt" }
174 proc { Rack::MockRequest.new(app).get("/") }.must_raise(RuntimeError)
175 Rack::MockRequest.new(app).get("/foo").must_be :server_error?
178 it "yields the generated app to a block for warmup" do
181 app = Rack::Builder.new do
182 warmup { |a| warmed_up_app = a }
183 run lambda { |env| [200, {}, []] }
186 warmed_up_app.must_equal app
189 it "initialize apps once" do
196 raise "bzzzt" if @called > 0
198 [200, { 'content-type' => 'text/plain' }, ['OK']]
202 use Rack::ShowExceptions
206 Rack::MockRequest.new(app).get("/").status.must_equal 200
207 Rack::MockRequest.new(app).get("/").must_be :server_error?
210 it "allows use after run" do
212 run lambda { |env| raise "bzzzt" }
213 use Rack::ShowExceptions
216 Rack::MockRequest.new(app).get("/").must_be :server_error?
217 Rack::MockRequest.new(app).get("/").must_be :server_error?
218 Rack::MockRequest.new(app).get("/").must_be :server_error?
221 it "supports #freeze_app for freezing app and middleware" do
224 use Rack::ShowExceptions
226 def initialize(app) @app = app end
227 def call(env) @a = 1 if env['PATH_INFO'] == '/a'; @app.call(env) end
231 @a = 1 if env['PATH_INFO'] == '/b';
237 Rack::MockRequest.new(app).get("/a").must_be :server_error?
238 Rack::MockRequest.new(app).get("/b").must_be :server_error?
239 Rack::MockRequest.new(app).get("/c").status.must_equal 200
242 it 'complains about a missing run' do
244 Rack::Lint.new Rack::Builder.app { use Rack::ShowExceptions }
245 end.must_raise(RuntimeError)
248 describe "parse_file" do
249 def config_file(name)
250 File.join(File.dirname(__FILE__), 'builder', name)
253 it "raises if parses commented options" do
255 Rack::Builder.parse_file config_file('options.ru')
256 end.must_raise(RuntimeError).
257 message.must_include('Parsing options from the first comment line is no longer supported')
260 it "removes __END__ before evaluating app" do
261 app, _ = Rack::Builder.parse_file config_file('end.ru')
262 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'
265 it "supports multi-line comments" do
266 app = Rack::Builder.parse_file(config_file('comment.ru'))
267 app.must_be_kind_of(Proc)
270 it 'requires an_underscore_app not ending in .ru' do
271 $: << File.dirname(__FILE__)
272 app, * = Rack::Builder.parse_file 'builder/an_underscore_app'
273 Rack::MockRequest.new(app).get('/').body.to_s.must_equal 'OK'
277 it "sets __LINE__ correctly" do
278 app, _ = Rack::Builder.parse_file config_file('line.ru')
279 Rack::MockRequest.new(app).get("/").body.to_s.must_equal '3'
282 it "strips leading unicode byte order mark when present" do
283 enc = Encoding.default_external
285 verbose, $VERBOSE = $VERBOSE, nil
286 Encoding.default_external = 'UTF-8'
287 app, _ = Rack::Builder.parse_file config_file('bom.ru')
288 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'
290 Encoding.default_external = enc
295 it "respects the frozen_string_literal magic comment" do
296 app, _ = Rack::Builder.parse_file(config_file('frozen.ru'))
297 response = Rack::MockRequest.new(app).get('/')
298 response.body.must_equal 'frozen'
299 body = response.instance_variable_get(:@body)
300 body.must_equal(['frozen'])
301 body[0].frozen?.must_equal true
305 describe 'new_from_string' do
306 it "builds a rack app from string" do
307 app, = Rack::Builder.new_from_string "run lambda{|env| [200, {'content-type' => 'text/plane'}, ['OK']] }"
308 Rack::MockRequest.new(app).get("/").body.to_s.must_equal 'OK'