regal 0.1.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.
- checksums.yaml +7 -0
- data/.yardopts +4 -0
- data/lib/regal.rb +6 -0
- data/lib/regal/app.rb +236 -0
- data/lib/regal/request.rb +61 -0
- data/lib/regal/response.rb +61 -0
- data/lib/regal/version.rb +3 -0
- data/spec/regal/app_spec.rb +924 -0
- data/spec/regal/request_spec.rb +93 -0
- data/spec/regal/response_spec.rb +64 -0
- data/spec/spec_helper.rb +2 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ddd9f8426b202a6cd710732b5d90121c1be6de3d
|
4
|
+
data.tar.gz: 923b6298aeaa7086e43e3856e4a3a1cfc840037b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 875dc13b486d7d0629ecce89564c3f26b2084a0c72fb6a98cca6ef3316527be8482d9dbfbe7b3216326afdf85e930bc7ab33bf5a672ddeb24a4077ce20efda01
|
7
|
+
data.tar.gz: 47ef101e32275bacc1019a74d7a54ab040b020fc31931b92c5fd8ddee4dd9108524efa74003a167655b04a39d6e12f9ad5e06458c92ff9066da33c8cfa297f99
|
data/.yardopts
ADDED
data/lib/regal.rb
ADDED
data/lib/regal/app.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Regal
|
4
|
+
module App
|
5
|
+
def self.create(*args, &block)
|
6
|
+
Class.new(Route).create(nil, &block)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.new(*args, &block)
|
10
|
+
create(&block).new(*args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module RouterDsl
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
def create(name=nil, &block)
|
18
|
+
@mounted_apps = []
|
19
|
+
@static_routes = {}
|
20
|
+
@dynamic_route = nil
|
21
|
+
@handlers = {}
|
22
|
+
@befores = []
|
23
|
+
@afters = []
|
24
|
+
@setups = []
|
25
|
+
@middlewares = []
|
26
|
+
@rescuers = []
|
27
|
+
@name = name
|
28
|
+
class_exec(&block)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def setups
|
33
|
+
if superclass.respond_to?(:setups) && (setups = superclass.setups)
|
34
|
+
setups + @setups
|
35
|
+
else
|
36
|
+
@setups && @setups.dup
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def befores
|
41
|
+
if superclass.respond_to?(:befores) && (befores = superclass.befores)
|
42
|
+
befores + @befores
|
43
|
+
else
|
44
|
+
@befores && @befores.dup
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def afters
|
49
|
+
if superclass.respond_to?(:afters) && (afters = superclass.afters)
|
50
|
+
afters + @afters
|
51
|
+
else
|
52
|
+
@afters && @afters.dup
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def middlewares
|
57
|
+
if superclass.respond_to?(:middlewares) && (middlewares = superclass.middlewares)
|
58
|
+
middlewares + @middlewares
|
59
|
+
else
|
60
|
+
@middlewares && @middlewares.dup
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def rescuers
|
65
|
+
if superclass.respond_to?(:rescuers) && (rescuers = superclass.rescuers)
|
66
|
+
rescuers + @rescuers
|
67
|
+
else
|
68
|
+
@rescuers && @rescuers.dup
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_routes(args)
|
73
|
+
routes = {}
|
74
|
+
if @dynamic_route
|
75
|
+
routes.default = @dynamic_route.new(*args)
|
76
|
+
end
|
77
|
+
@mounted_apps.each do |app|
|
78
|
+
routes.merge!(app.create_routes(args))
|
79
|
+
end
|
80
|
+
@static_routes.each do |path, cls|
|
81
|
+
routes[path] = cls.new(*args)
|
82
|
+
end
|
83
|
+
routes
|
84
|
+
end
|
85
|
+
|
86
|
+
def handlers
|
87
|
+
@handlers.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
def route(s, &block)
|
91
|
+
r = Class.new(self).create(s, &block)
|
92
|
+
if s.is_a?(Symbol)
|
93
|
+
@dynamic_route = r
|
94
|
+
else
|
95
|
+
@static_routes[s] = r
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def mount(app)
|
100
|
+
@mounted_apps << app
|
101
|
+
end
|
102
|
+
|
103
|
+
def use(middleware, *args, &block)
|
104
|
+
@middlewares << [middleware, args, block]
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup(&block)
|
108
|
+
@setups << block
|
109
|
+
end
|
110
|
+
|
111
|
+
def before(&block)
|
112
|
+
@befores << block
|
113
|
+
end
|
114
|
+
|
115
|
+
def after(&block)
|
116
|
+
@afters << block
|
117
|
+
end
|
118
|
+
|
119
|
+
def rescue_from(type, &block)
|
120
|
+
@rescuers << [type, block]
|
121
|
+
end
|
122
|
+
|
123
|
+
[:get, :head, :options, :delete, :post, :put, :patch].each do |name|
|
124
|
+
upcased_name = name.to_s.upcase
|
125
|
+
define_method(name) do |&block|
|
126
|
+
@handlers[upcased_name] = block
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def any(&block)
|
131
|
+
@handlers.default = block
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Route
|
136
|
+
extend RouterDsl
|
137
|
+
|
138
|
+
SLASH = '/'.freeze
|
139
|
+
PATH_CAPTURES_KEY = 'regal.path_captures'.freeze
|
140
|
+
PATH_COMPONENTS_KEY = 'regal.path_components'.freeze
|
141
|
+
PATH_INFO_KEY = 'PATH_INFO'.freeze
|
142
|
+
REQUEST_METHOD_KEY = 'REQUEST_METHOD'.freeze
|
143
|
+
METHOD_NOT_ALLOWED_RESPONSE = [405, {}.freeze, [].freeze].freeze
|
144
|
+
NOT_FOUND_RESPONSE = [404, {}.freeze, [].freeze].freeze
|
145
|
+
EMPTY_BODY = ''.freeze
|
146
|
+
|
147
|
+
attr_reader :name
|
148
|
+
|
149
|
+
def initialize(*args)
|
150
|
+
@actual = self.dup
|
151
|
+
self.class.setups.each do |setup|
|
152
|
+
@actual.instance_exec(*args, &setup)
|
153
|
+
end
|
154
|
+
@befores = self.class.befores
|
155
|
+
@afters = self.class.afters.reverse
|
156
|
+
@rescuers = self.class.rescuers
|
157
|
+
@routes = self.class.create_routes(args)
|
158
|
+
@handlers = self.class.handlers
|
159
|
+
@name = self.class.name
|
160
|
+
if !self.class.middlewares.empty?
|
161
|
+
@app = self.class.middlewares.reduce(method(:handle)) do |app, (middleware, args, block)|
|
162
|
+
middleware.new(app, *args, &block)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
freeze
|
166
|
+
end
|
167
|
+
|
168
|
+
def call(env)
|
169
|
+
path_components = env[PATH_COMPONENTS_KEY] ||= env[PATH_INFO_KEY].split(SLASH).drop(1)
|
170
|
+
path_component = path_components.shift
|
171
|
+
if path_component && (app = @routes[path_component])
|
172
|
+
dynamic_route = !@routes.key?(path_component)
|
173
|
+
if dynamic_route
|
174
|
+
env[PATH_CAPTURES_KEY] ||= {}
|
175
|
+
env[PATH_CAPTURES_KEY][app.name] = path_component
|
176
|
+
end
|
177
|
+
app.call(env)
|
178
|
+
elsif path_component.nil?
|
179
|
+
if @app
|
180
|
+
@app.call(env)
|
181
|
+
else
|
182
|
+
handle(env)
|
183
|
+
end
|
184
|
+
else
|
185
|
+
NOT_FOUND_RESPONSE
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
def handle(env)
|
192
|
+
if (handler = @handlers[env[REQUEST_METHOD_KEY]])
|
193
|
+
request = Request.new(env)
|
194
|
+
response = Response.new
|
195
|
+
begin
|
196
|
+
@befores.each do |before|
|
197
|
+
break if response.finished?
|
198
|
+
@actual.instance_exec(request, response, &before)
|
199
|
+
end
|
200
|
+
unless response.finished?
|
201
|
+
result = @actual.instance_exec(request, response, &handler)
|
202
|
+
if request.head? || response.status < 200 || response.status == 204 || response.status == 205 || response.status == 304
|
203
|
+
response.no_body
|
204
|
+
elsif !response.finished?
|
205
|
+
response.body = result
|
206
|
+
end
|
207
|
+
end
|
208
|
+
rescue => e
|
209
|
+
handle_error(e, request, response)
|
210
|
+
end
|
211
|
+
@afters.each do |after|
|
212
|
+
begin
|
213
|
+
@actual.instance_exec(request, response, &after)
|
214
|
+
rescue => e
|
215
|
+
handle_error(e, request, response)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
response
|
219
|
+
else
|
220
|
+
METHOD_NOT_ALLOWED_RESPONSE
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def handle_error(e, request, response)
|
225
|
+
handled = false
|
226
|
+
@rescuers.each do |type, handler|
|
227
|
+
if type === e
|
228
|
+
handler.call(e, request, response)
|
229
|
+
handled = true
|
230
|
+
break
|
231
|
+
end
|
232
|
+
end
|
233
|
+
raise unless handled
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Regal
|
2
|
+
class Request
|
3
|
+
attr_reader :env, :attributes
|
4
|
+
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
@attributes = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def request_method
|
11
|
+
@env[REQUEST_METHOD_KEY]
|
12
|
+
end
|
13
|
+
|
14
|
+
def head?
|
15
|
+
request_method == HEAD_METHOD
|
16
|
+
end
|
17
|
+
|
18
|
+
def parameters
|
19
|
+
@parameters ||= begin
|
20
|
+
path_captures = @env[Route::PATH_CAPTURES_KEY]
|
21
|
+
query = Rack::Utils.parse_query(@env[QUERY_STRING_KEY])
|
22
|
+
query.merge!(path_captures) if path_captures
|
23
|
+
query.freeze
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def headers
|
28
|
+
@headers ||= begin
|
29
|
+
headers = @env.each_with_object({}) do |(key, value), headers|
|
30
|
+
if key.start_with?(HEADER_PREFIX)
|
31
|
+
normalized_key = key[HEADER_PREFIX.length, key.length - HEADER_PREFIX.length]
|
32
|
+
normalized_key.gsub!(/(?<=^.|_.)[^_]+/) { |str| str.downcase }
|
33
|
+
normalized_key.gsub!('_', '-')
|
34
|
+
elsif key == CONTENT_LENGTH_KEY
|
35
|
+
normalized_key = CONTENT_LENGTH_HEADER
|
36
|
+
elsif key == CONTENT_TYPE_KEY
|
37
|
+
normalized_key = CONTENT_TYPE_HEADER
|
38
|
+
end
|
39
|
+
if normalized_key
|
40
|
+
headers[normalized_key] = value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
headers.freeze
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def body
|
48
|
+
@env[RACK_INPUT_KEY]
|
49
|
+
end
|
50
|
+
|
51
|
+
HEADER_PREFIX = 'HTTP_'.freeze
|
52
|
+
QUERY_STRING_KEY = 'QUERY_STRING'.freeze
|
53
|
+
CONTENT_LENGTH_KEY = 'CONTENT_LENGTH'.freeze
|
54
|
+
CONTENT_LENGTH_HEADER = 'Content-Length'.freeze
|
55
|
+
CONTENT_TYPE_KEY = 'CONTENT_TYPE'.freeze
|
56
|
+
CONTENT_TYPE_HEADER = 'Content-Type'.freeze
|
57
|
+
RACK_INPUT_KEY = 'rack.input'.freeze
|
58
|
+
REQUEST_METHOD_KEY = 'REQUEST_METHOD'.freeze
|
59
|
+
HEAD_METHOD = 'HEAD'.freeze
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Regal
|
2
|
+
class Response
|
3
|
+
attr_accessor :status, :body, :raw_body
|
4
|
+
attr_reader :headers
|
5
|
+
|
6
|
+
EMPTY_BODY = [].freeze
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@status = 200
|
10
|
+
@headers = {}
|
11
|
+
@body = nil
|
12
|
+
@raw_body = nil
|
13
|
+
@finished = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def finish
|
17
|
+
@finished = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def finished?
|
21
|
+
@finished
|
22
|
+
end
|
23
|
+
|
24
|
+
def no_body
|
25
|
+
@raw_body = EMPTY_BODY
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](n)
|
29
|
+
case n
|
30
|
+
when 0 then @status
|
31
|
+
when 1 then @headers
|
32
|
+
when 2 then rack_body
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(n, v)
|
37
|
+
case n
|
38
|
+
when 0 then @status = v
|
39
|
+
when 1 then @headers = v
|
40
|
+
when 2 then @raw_body = v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_ary
|
45
|
+
[@status, @headers, rack_body]
|
46
|
+
end
|
47
|
+
alias_method :to_a, :to_ary
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def rack_body
|
52
|
+
if @raw_body
|
53
|
+
@raw_body
|
54
|
+
elsif @body.is_a?(String)
|
55
|
+
[@body]
|
56
|
+
else
|
57
|
+
@body
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,924 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Regal
|
5
|
+
describe App do
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
context 'a basic app' do
|
9
|
+
let :app do
|
10
|
+
App.new do
|
11
|
+
get do
|
12
|
+
'root'
|
13
|
+
end
|
14
|
+
|
15
|
+
route 'hello' do
|
16
|
+
get do
|
17
|
+
'hello'
|
18
|
+
end
|
19
|
+
|
20
|
+
route 'world' do
|
21
|
+
get do
|
22
|
+
'hello world'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'routes a request' do
|
30
|
+
get '/hello'
|
31
|
+
expect(last_response.status).to eq(200)
|
32
|
+
expect(last_response.body).to eq('hello')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'routes a request to the root' do
|
36
|
+
get '/'
|
37
|
+
expect(last_response.status).to eq(200)
|
38
|
+
expect(last_response.body).to eq('root')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'routes a request with more than one path component' do
|
42
|
+
get '/hello/world'
|
43
|
+
expect(last_response.status).to eq(200)
|
44
|
+
expect(last_response.body).to eq('hello world')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'responds with 404 when the path does not match any route' do
|
48
|
+
get '/hello/fnord'
|
49
|
+
expect(last_response.status).to eq(404)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'responds with 405 when the path matches a route but there is no handler for the HTTP method' do
|
53
|
+
delete '/hello/world'
|
54
|
+
expect(last_response.status).to eq(405)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'a simple interactive app' do
|
59
|
+
let :app do
|
60
|
+
App.new do
|
61
|
+
route 'echo' do
|
62
|
+
get do |request|
|
63
|
+
request.parameters['s']
|
64
|
+
end
|
65
|
+
|
66
|
+
post do |request|
|
67
|
+
request.body.read
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
route 'international-hello' do
|
72
|
+
get do |request|
|
73
|
+
case request.headers['Accept-Language']
|
74
|
+
when 'sv_SE'
|
75
|
+
'hej'
|
76
|
+
when 'fr_FR'
|
77
|
+
'bonjour'
|
78
|
+
else
|
79
|
+
'?'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'can access the query parameters' do
|
87
|
+
get '/echo?s=hallo'
|
88
|
+
expect(last_response.status).to eq(200)
|
89
|
+
expect(last_response.body).to eq('hallo')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'can access the request headers' do
|
93
|
+
get '/international-hello', nil, {'HTTP_ACCEPT_LANGUAGE' => 'sv_SE'}
|
94
|
+
expect(last_response.status).to eq(200)
|
95
|
+
expect(last_response.body).to eq('hej')
|
96
|
+
get '/international-hello', nil, {'HTTP_ACCEPT_LANGUAGE' => 'fr_FR'}
|
97
|
+
expect(last_response.status).to eq(200)
|
98
|
+
expect(last_response.body).to eq('bonjour')
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'can access the request body' do
|
102
|
+
post '/echo', 'blobblobblob'
|
103
|
+
expect(last_response.status).to eq(200)
|
104
|
+
expect(last_response.body).to eq('blobblobblob')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'an app that does more than just respond with a body' do
|
109
|
+
let :app do
|
110
|
+
App.new do
|
111
|
+
route 'redirect' do
|
112
|
+
get do |_, response|
|
113
|
+
response.status = 307
|
114
|
+
response.headers['Location'] = 'somewhere/else'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'can change the response code' do
|
121
|
+
get '/redirect'
|
122
|
+
expect(last_response.status).to eq(307)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'can set response headers' do
|
126
|
+
get '/redirect'
|
127
|
+
expect(last_response.headers).to include('Location' => 'somewhere/else')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'an app doing work before route handlers' do
|
132
|
+
let :app do
|
133
|
+
App.new do
|
134
|
+
before do |request|
|
135
|
+
request.attributes[:some_key] = [1]
|
136
|
+
end
|
137
|
+
|
138
|
+
get do |request|
|
139
|
+
request.attributes[:some_key].join(',')
|
140
|
+
end
|
141
|
+
|
142
|
+
route 'one-before' do
|
143
|
+
before do |request|
|
144
|
+
request.attributes[:some_key] << 2
|
145
|
+
end
|
146
|
+
|
147
|
+
get do |request|
|
148
|
+
request.attributes[:some_key].join(',')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
route 'two-before' do
|
153
|
+
before do |request|
|
154
|
+
request.attributes[:some_key] << 2
|
155
|
+
end
|
156
|
+
|
157
|
+
before do |request|
|
158
|
+
request.attributes[:some_key] << 3
|
159
|
+
end
|
160
|
+
|
161
|
+
get do |request|
|
162
|
+
request.attributes[:some_key].join(',')
|
163
|
+
end
|
164
|
+
|
165
|
+
route 'another-before' do
|
166
|
+
before do |request|
|
167
|
+
request.attributes[:some_key] << 4
|
168
|
+
end
|
169
|
+
|
170
|
+
get do |request|
|
171
|
+
request.attributes[:some_key].join(',')
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
route 'redirect-before' do
|
177
|
+
before do |_, response|
|
178
|
+
response.headers['Location'] = 'somewhere/else'
|
179
|
+
response.status = 307
|
180
|
+
response.body = 'Go somewhere else'
|
181
|
+
response.finish
|
182
|
+
end
|
183
|
+
|
184
|
+
before do |_, response|
|
185
|
+
response.body = 'whoopiedoo'
|
186
|
+
end
|
187
|
+
|
188
|
+
get do
|
189
|
+
"I'm not called!"
|
190
|
+
end
|
191
|
+
|
192
|
+
after do |_, response|
|
193
|
+
response.headers['WasAfterCalled'] = 'yes'
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'calls the before block before the request handler' do
|
200
|
+
get '/'
|
201
|
+
expect(last_response.body).to eq('1')
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'calls the before blocks of all routes before the request handler' do
|
205
|
+
get '/one-before'
|
206
|
+
expect(last_response.body).to eq('1,2')
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'calls the before blocks of a route in order' do
|
210
|
+
get '/two-before'
|
211
|
+
expect(last_response.body).to eq('1,2,3')
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'calls all before blocks of a route before the request handler' do
|
215
|
+
get '/two-before/another-before'
|
216
|
+
expect(last_response.body).to eq('1,2,3,4')
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'gives the before blocks access to the response' do
|
220
|
+
get '/redirect-before'
|
221
|
+
expect(last_response.status).to eq(307)
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'when the response is marked as finished' do
|
225
|
+
before do
|
226
|
+
get '/redirect-before'
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'does not call further handlers or before blocks when the response is marked as finished' do
|
230
|
+
expect(last_response.body).to eq('Go somewhere else')
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'calls after blocks' do
|
234
|
+
expect(last_response.headers).to include('WasAfterCalled' => 'yes')
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'an app doing work after route handlers' do
|
240
|
+
let :app do
|
241
|
+
App.new do
|
242
|
+
after do |_, response|
|
243
|
+
response.headers['Content-Type'] = 'application/json'
|
244
|
+
response.body = JSON.dump(response.body)
|
245
|
+
end
|
246
|
+
|
247
|
+
get do |request|
|
248
|
+
{'root' => true}
|
249
|
+
end
|
250
|
+
|
251
|
+
route 'one-after' do
|
252
|
+
after do |_, response|
|
253
|
+
response.body['list'] << 1
|
254
|
+
end
|
255
|
+
|
256
|
+
get do |request|
|
257
|
+
{'list' => []}
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
route 'two-after' do
|
262
|
+
after do |request, response|
|
263
|
+
response.body['list'] << 1
|
264
|
+
end
|
265
|
+
|
266
|
+
after do |request, response|
|
267
|
+
response.body['list'] << 2
|
268
|
+
end
|
269
|
+
|
270
|
+
get do |request|
|
271
|
+
{'list' => []}
|
272
|
+
end
|
273
|
+
|
274
|
+
route 'another-after' do
|
275
|
+
after do |request, response|
|
276
|
+
response.body['list'] << 3
|
277
|
+
end
|
278
|
+
|
279
|
+
get do |request|
|
280
|
+
{'list' => []}
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'calls the after block after the request handler' do
|
288
|
+
get '/'
|
289
|
+
expect(last_response.body).to eq('{"root":true}')
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'calls the after blocks of all routes after the request handler' do
|
293
|
+
get '/one-after'
|
294
|
+
expect(last_response.body).to eq('{"list":[1]}')
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'calls all after blocks of a route in order' do
|
298
|
+
get '/two-after'
|
299
|
+
expect(last_response.body).to eq('{"list":[2,1]}')
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'calls all after blocks of a route after the request handler' do
|
303
|
+
get '/two-after/another-after'
|
304
|
+
expect(last_response.body).to eq('{"list":[3,2,1]}')
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
context 'an app that has capturing routes' do
|
309
|
+
let :app do
|
310
|
+
App.new do
|
311
|
+
route 'foo' do
|
312
|
+
route :bar do
|
313
|
+
get do
|
314
|
+
'whatever'
|
315
|
+
end
|
316
|
+
|
317
|
+
route 'echo' do
|
318
|
+
get do |request|
|
319
|
+
request.parameters[:bar]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
route 'bar' do
|
325
|
+
get do
|
326
|
+
'bar'
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'matches anything for the capture route' do
|
334
|
+
get '/foo/something'
|
335
|
+
expect(last_response.status).to eq(200)
|
336
|
+
expect(last_response.body).to eq('whatever')
|
337
|
+
get '/foo/something-else'
|
338
|
+
expect(last_response.status).to eq(200)
|
339
|
+
expect(last_response.body).to eq('whatever')
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'picks static routes first' do
|
343
|
+
get '/foo/bar'
|
344
|
+
expect(last_response.status).to eq(200)
|
345
|
+
expect(last_response.body).to eq('bar')
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'captures the path component as a parameter using a symbol as key' do
|
349
|
+
get '/foo/zzz/echo'
|
350
|
+
expect(last_response.status).to eq(200)
|
351
|
+
expect(last_response.body).to eq('zzz')
|
352
|
+
get '/foo/q/echo'
|
353
|
+
expect(last_response.status).to eq(200)
|
354
|
+
expect(last_response.body).to eq('q')
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'an app that mounts another app' do
|
359
|
+
GoodbyeApp = App.create do
|
360
|
+
route 'goodbye' do
|
361
|
+
get do
|
362
|
+
'goodbye'
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
HelloApp = App.create do
|
368
|
+
route 'hello' do
|
369
|
+
get do
|
370
|
+
'hello'
|
371
|
+
end
|
372
|
+
|
373
|
+
route 'you' do
|
374
|
+
route 'say' do
|
375
|
+
mount GoodbyeApp
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
let :app do
|
382
|
+
App.new do
|
383
|
+
route 'i' do
|
384
|
+
route 'say' do
|
385
|
+
mount HelloApp
|
386
|
+
mount GoodbyeApp
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
route 'oh' do
|
391
|
+
mount HelloApp
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'routes a request into the other app' do
|
397
|
+
get '/i/say/hello'
|
398
|
+
expect(last_response.status).to eq(200)
|
399
|
+
expect(last_response.body).to eq('hello')
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'can mount multiple apps' do
|
403
|
+
get '/i/say/goodbye'
|
404
|
+
expect(last_response.status).to eq(200)
|
405
|
+
expect(last_response.body).to eq('goodbye')
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'routes a request into apps that mount yet more apps' do
|
409
|
+
get '/i/say/hello/you/say/goodbye'
|
410
|
+
expect(last_response.status).to eq(200)
|
411
|
+
expect(last_response.body).to eq('goodbye')
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'can mount the same app multiple times' do
|
415
|
+
get '/oh/hello'
|
416
|
+
expect(last_response.status).to eq(200)
|
417
|
+
expect(last_response.body).to eq('hello')
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
context 'an app that supports all HTTP methods' do
|
422
|
+
let :app do
|
423
|
+
App.new do
|
424
|
+
get do |request|
|
425
|
+
request.request_method
|
426
|
+
end
|
427
|
+
|
428
|
+
head do |request|
|
429
|
+
request.request_method
|
430
|
+
end
|
431
|
+
|
432
|
+
options do |request|
|
433
|
+
request.request_method
|
434
|
+
end
|
435
|
+
|
436
|
+
delete do |request|
|
437
|
+
request.request_method
|
438
|
+
end
|
439
|
+
|
440
|
+
post do |request|
|
441
|
+
request.request_method
|
442
|
+
end
|
443
|
+
|
444
|
+
put do |request|
|
445
|
+
request.request_method
|
446
|
+
end
|
447
|
+
|
448
|
+
patch do |request|
|
449
|
+
request.request_method
|
450
|
+
end
|
451
|
+
|
452
|
+
route 'anything' do
|
453
|
+
any do |request|
|
454
|
+
request.request_method
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'routes GET requests' do
|
461
|
+
get '/'
|
462
|
+
expect(last_response.status).to eq(200)
|
463
|
+
expect(last_response.body).to eq('GET')
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'routes HEAD requests, but does not respond with any body' do
|
467
|
+
head '/'
|
468
|
+
expect(last_response.status).to eq(200)
|
469
|
+
expect(last_response.body).to be_empty
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'routes OPTIONS requests' do
|
473
|
+
options '/'
|
474
|
+
expect(last_response.status).to eq(200)
|
475
|
+
expect(last_response.body).to eq('OPTIONS')
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'routes DELETE requests' do
|
479
|
+
delete '/'
|
480
|
+
expect(last_response.status).to eq(200)
|
481
|
+
expect(last_response.body).to eq('DELETE')
|
482
|
+
end
|
483
|
+
|
484
|
+
it 'routes POST requests' do
|
485
|
+
post '/'
|
486
|
+
expect(last_response.status).to eq(200)
|
487
|
+
expect(last_response.body).to eq('POST')
|
488
|
+
end
|
489
|
+
|
490
|
+
it 'routes PUT requests' do
|
491
|
+
put '/'
|
492
|
+
expect(last_response.status).to eq(200)
|
493
|
+
expect(last_response.body).to eq('PUT')
|
494
|
+
end
|
495
|
+
|
496
|
+
it 'routes PATCH requests' do
|
497
|
+
patch '/'
|
498
|
+
expect(last_response.status).to eq(200)
|
499
|
+
expect(last_response.body).to eq('PATCH')
|
500
|
+
end
|
501
|
+
|
502
|
+
it 'routes all HTTP requests when there is an any handler' do
|
503
|
+
get '/anything'
|
504
|
+
expect(last_response.status).to eq(200)
|
505
|
+
expect(last_response.body).to eq('GET')
|
506
|
+
delete '/anything'
|
507
|
+
expect(last_response.status).to eq(200)
|
508
|
+
expect(last_response.body).to eq('DELETE')
|
509
|
+
head '/anything'
|
510
|
+
expect(last_response.status).to eq(200)
|
511
|
+
expect(last_response.body).to be_empty
|
512
|
+
put '/anything'
|
513
|
+
expect(last_response.status).to eq(200)
|
514
|
+
expect(last_response.body).to eq('PUT')
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
context 'an app with helper methods' do
|
519
|
+
let :app do
|
520
|
+
App.new do
|
521
|
+
def top_level_helper
|
522
|
+
'top_level_helper'
|
523
|
+
end
|
524
|
+
|
525
|
+
route 'one' do
|
526
|
+
def first_level_helper
|
527
|
+
'first_level_helper'
|
528
|
+
end
|
529
|
+
|
530
|
+
get do
|
531
|
+
first_level_helper
|
532
|
+
end
|
533
|
+
|
534
|
+
route 'two' do
|
535
|
+
def second_level_helper
|
536
|
+
'second_level_helper'
|
537
|
+
end
|
538
|
+
|
539
|
+
before do |_, response|
|
540
|
+
response.body = 'before:' << [top_level_helper, first_level_helper, second_level_helper].join(',')
|
541
|
+
end
|
542
|
+
|
543
|
+
after do |_, response|
|
544
|
+
response.body += '|after:' << [top_level_helper, first_level_helper, second_level_helper].join(',')
|
545
|
+
end
|
546
|
+
|
547
|
+
get do |_, response|
|
548
|
+
response.body + '|handler:' << [top_level_helper, first_level_helper, second_level_helper].join(',')
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'can use the helper methods defined on the same route as the handler' do
|
556
|
+
get '/one'
|
557
|
+
expect(last_response.status).to eq(200)
|
558
|
+
expect(last_response.body).to eq('first_level_helper')
|
559
|
+
end
|
560
|
+
|
561
|
+
it 'can use the helper methods defined on all routes above a handler' do
|
562
|
+
get '/one/two'
|
563
|
+
expect(last_response.status).to eq(200)
|
564
|
+
expect(last_response.body).to include('handler:top_level_helper,first_level_helper,second_level_helper')
|
565
|
+
end
|
566
|
+
|
567
|
+
it 'can use the helper methods in before blocks' do
|
568
|
+
get '/one/two'
|
569
|
+
expect(last_response.status).to eq(200)
|
570
|
+
expect(last_response.body).to include('before:top_level_helper,first_level_helper,second_level_helper')
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'can use the helper methods in after blocks' do
|
574
|
+
get '/one/two'
|
575
|
+
expect(last_response.status).to eq(200)
|
576
|
+
expect(last_response.body).to include('after:top_level_helper,first_level_helper,second_level_helper')
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
context 'an app that receives configuration when created' do
|
581
|
+
let :app do
|
582
|
+
App.new(this_thing, that_other_thing) do
|
583
|
+
setup do |*args|
|
584
|
+
@args = args
|
585
|
+
end
|
586
|
+
|
587
|
+
get do
|
588
|
+
@args.join(',')
|
589
|
+
end
|
590
|
+
|
591
|
+
route 'one' do
|
592
|
+
setup do |thing1, thing2|
|
593
|
+
@thing1 = thing1
|
594
|
+
@thing2 = thing2
|
595
|
+
end
|
596
|
+
|
597
|
+
get do
|
598
|
+
[*@args, @thing1, @thing2].join(',')
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
route 'two' do
|
603
|
+
setup do |thing1, thing2|
|
604
|
+
@thing1 = thing1
|
605
|
+
@thing2 = thing2
|
606
|
+
end
|
607
|
+
|
608
|
+
setup do |_, thing_two|
|
609
|
+
@thing_two = thing_two
|
610
|
+
end
|
611
|
+
|
612
|
+
get do
|
613
|
+
[*@args, @thing1, @thing2, @thing_two].join(',')
|
614
|
+
end
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
let :this_thing do
|
620
|
+
double(:this_thing, to_s: 'this_thing')
|
621
|
+
end
|
622
|
+
|
623
|
+
let :that_other_thing do
|
624
|
+
double(:that_other_thing, to_s: 'that_other_thing')
|
625
|
+
end
|
626
|
+
|
627
|
+
it 'calls its setup methods with the configuration' do
|
628
|
+
get '/'
|
629
|
+
expect(last_response.status).to eq(200)
|
630
|
+
expect(last_response.body).to eq('this_thing,that_other_thing')
|
631
|
+
end
|
632
|
+
|
633
|
+
it 'calls the setup methods of all routes' do
|
634
|
+
get '/one'
|
635
|
+
expect(last_response.status).to eq(200)
|
636
|
+
expect(last_response.body).to eq('this_thing,that_other_thing,this_thing,that_other_thing')
|
637
|
+
end
|
638
|
+
|
639
|
+
it 'calls all setup methods' do
|
640
|
+
get '/two'
|
641
|
+
expect(last_response.status).to eq(200)
|
642
|
+
expect(last_response.body).to eq('this_thing,that_other_thing,this_thing,that_other_thing,that_other_thing')
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
context 'an app that uses Rack middleware' do
|
647
|
+
class Reverser
|
648
|
+
def initialize(app)
|
649
|
+
@app = app
|
650
|
+
end
|
651
|
+
|
652
|
+
def call(env)
|
653
|
+
response = @app.call(env)
|
654
|
+
body = response[2][0]
|
655
|
+
body && body.reverse!
|
656
|
+
response
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
class Uppercaser
|
661
|
+
def initialize(app)
|
662
|
+
@app = app
|
663
|
+
end
|
664
|
+
|
665
|
+
def call(env)
|
666
|
+
response = @app.call(env)
|
667
|
+
body = response[2][0]
|
668
|
+
body && body.upcase!
|
669
|
+
response
|
670
|
+
end
|
671
|
+
end
|
672
|
+
|
673
|
+
class Mutator
|
674
|
+
def initialize(app, &block)
|
675
|
+
@app = app
|
676
|
+
@block = block
|
677
|
+
end
|
678
|
+
|
679
|
+
def call(env)
|
680
|
+
@app.call(@block.call(env))
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
let :app do
|
685
|
+
App.new do
|
686
|
+
use Reverser
|
687
|
+
|
688
|
+
get do
|
689
|
+
'lorem ipsum'
|
690
|
+
end
|
691
|
+
|
692
|
+
route 'more' do
|
693
|
+
use Uppercaser
|
694
|
+
|
695
|
+
get do
|
696
|
+
'dolor sit'
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
route 'hello' do
|
701
|
+
use Rack::Runtime, 'Regal'
|
702
|
+
use Mutator do |env|
|
703
|
+
env['app.greeting'] = 'Bonjour'
|
704
|
+
env
|
705
|
+
end
|
706
|
+
|
707
|
+
get do |request|
|
708
|
+
request.env['app.greeting'] + ', ' + request.parameters['name']
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'calls the middleware when processing the request' do
|
715
|
+
get '/'
|
716
|
+
expect(last_response.status).to eq(200)
|
717
|
+
expect(last_response.body).to eq('muspi merol')
|
718
|
+
end
|
719
|
+
|
720
|
+
it 'calls the middleware of all routes' do
|
721
|
+
get '/more'
|
722
|
+
expect(last_response.status).to eq(200)
|
723
|
+
expect(last_response.body).to eq('TIS ROLOD')
|
724
|
+
end
|
725
|
+
|
726
|
+
it 'passes arguments when instantiating the middleware' do
|
727
|
+
get '/hello?name=Eve'
|
728
|
+
expect(last_response.status).to eq(200)
|
729
|
+
expect(last_response.headers).to have_key('X-Runtime-Regal')
|
730
|
+
end
|
731
|
+
|
732
|
+
it 'passes blocks when instantiating the middleware' do
|
733
|
+
get '/hello?name=Eve'
|
734
|
+
expect(last_response.status).to eq(200)
|
735
|
+
expect(last_response.body).to eq('Bonjour, Eve'.reverse)
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
context 'an app which needs more control over the response body' do
|
740
|
+
let :app do
|
741
|
+
App.new do
|
742
|
+
route 'no-overwrite' do
|
743
|
+
get do |_, response|
|
744
|
+
response.body = 'foobar'
|
745
|
+
response.finish
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
route 'raw-body' do
|
750
|
+
get do |_, response|
|
751
|
+
response.raw_body = 'a'..'z'
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
route 'no-body' do
|
756
|
+
before do |_, response|
|
757
|
+
response.no_body
|
758
|
+
end
|
759
|
+
|
760
|
+
get do
|
761
|
+
'I will not be used'
|
762
|
+
end
|
763
|
+
end
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
it 'can finish the response so that the result of the handler will not be used as body' do
|
768
|
+
get '/no-overwrite'
|
769
|
+
expect(last_response.body).to eq('foobar')
|
770
|
+
end
|
771
|
+
|
772
|
+
it 'can set the raw body of the response' do
|
773
|
+
get '/raw-body'
|
774
|
+
expect(last_response.body).to eq('abcdefghijklmnopqrstuvwxyz')
|
775
|
+
end
|
776
|
+
|
777
|
+
it 'can disable the response body completely' do
|
778
|
+
get '/no-body'
|
779
|
+
expect(last_response.body).to be_empty
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
context 'an app that responds with no-body response codes' do
|
784
|
+
let :app do
|
785
|
+
App.new do
|
786
|
+
[111, 204, 205, 304].each do |status|
|
787
|
+
route status.to_s do
|
788
|
+
get do |_, response|
|
789
|
+
response.status = status
|
790
|
+
'this will not be returned'
|
791
|
+
end
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
797
|
+
it 'ignore the response body' do
|
798
|
+
[111, 204, 205, 304].each do |status|
|
799
|
+
get "/#{status}"
|
800
|
+
expect(last_response.status).to eq(status)
|
801
|
+
expect(last_response.body).to be_empty
|
802
|
+
end
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
context 'an app that raises exceptions' do
|
807
|
+
class SomeNastyError < StandardError; end
|
808
|
+
class AppError < StandardError; end
|
809
|
+
class SpecificError < AppError; end
|
810
|
+
|
811
|
+
let :app do
|
812
|
+
App.new do
|
813
|
+
route 'unhandled' do
|
814
|
+
get do
|
815
|
+
raise 'Bork!'
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
route 'handled' do
|
820
|
+
rescue_from AppError do |error, request, response|
|
821
|
+
response.body = error.message
|
822
|
+
end
|
823
|
+
|
824
|
+
after do |_, response|
|
825
|
+
response.headers['WasAfterCalled'] = 'yes'
|
826
|
+
end
|
827
|
+
|
828
|
+
get do
|
829
|
+
raise SpecificError, 'Boom!'
|
830
|
+
end
|
831
|
+
|
832
|
+
route 'handled' do
|
833
|
+
get do
|
834
|
+
raise AppError, 'Crash!'
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
route 'unhandled' do
|
839
|
+
get do
|
840
|
+
raise SomeNastyError
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
route 'from-before' do
|
845
|
+
before do
|
846
|
+
raise SpecificError, 'Bang!'
|
847
|
+
end
|
848
|
+
|
849
|
+
get do
|
850
|
+
end
|
851
|
+
end
|
852
|
+
|
853
|
+
route 'from-after' do
|
854
|
+
after do
|
855
|
+
raise SpecificError, 'Kazam!'
|
856
|
+
end
|
857
|
+
|
858
|
+
after do |_, response|
|
859
|
+
response.headers['NextAfterWasCalled'] = 'yes'
|
860
|
+
end
|
861
|
+
|
862
|
+
get do
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
route 'handled-locally' do
|
867
|
+
rescue_from SpecificError do |error, request, response|
|
868
|
+
end
|
869
|
+
|
870
|
+
get do
|
871
|
+
raise SpecificError, 'Bam!'
|
872
|
+
end
|
873
|
+
end
|
874
|
+
end
|
875
|
+
end
|
876
|
+
end
|
877
|
+
|
878
|
+
context 'from handlers' do
|
879
|
+
it 'does not catch them' do
|
880
|
+
expect { get '/unhandled' }.to raise_error('Bork!')
|
881
|
+
end
|
882
|
+
|
883
|
+
it 'delegates them to matching error handlers' do
|
884
|
+
get '/handled'
|
885
|
+
expect(last_response.body).to eq('Boom!')
|
886
|
+
end
|
887
|
+
|
888
|
+
it 'calls after blocks when errors are handled' do
|
889
|
+
get '/handled'
|
890
|
+
expect(last_response.headers['WasAfterCalled']).to eq('yes')
|
891
|
+
end
|
892
|
+
|
893
|
+
it 'lets them bubble all the way up when there are no matching error handlers' do
|
894
|
+
expect { get '/handled/unhandled' }.to raise_error(SomeNastyError)
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
context 'from before blocks' do
|
899
|
+
it 'delegates them to matching error handlers' do
|
900
|
+
get '/handled/from-before'
|
901
|
+
expect(last_response.body).to eq('Bang!')
|
902
|
+
end
|
903
|
+
|
904
|
+
it 'calls after blocks when errors are handled' do
|
905
|
+
get '/handled/from-before'
|
906
|
+
expect(last_response.headers['WasAfterCalled']).to eq('yes')
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
context 'from after blocks' do
|
911
|
+
it 'delegates them to matching error handlers' do
|
912
|
+
get '/handled/from-after'
|
913
|
+
expect(last_response.body).to eq('Kazam!')
|
914
|
+
end
|
915
|
+
|
916
|
+
it 'calls the rest of the after blocks when errors are handled' do
|
917
|
+
get '/handled/from-after'
|
918
|
+
expect(last_response.headers['NextAfterWasCalled']).to eq('yes')
|
919
|
+
expect(last_response.headers['WasAfterCalled']).to eq('yes')
|
920
|
+
end
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
end
|