@@ -4,9 +4,14 @@ module BetterErrors
4
4
describe Middleware do
5
5
let ( :app ) { Middleware . new ( -> env { ":)" } ) }
6
6
let ( :exception ) { RuntimeError . new ( "oh no :(" ) }
7
+ let ( :status ) { response_env [ 0 ] }
8
+ let ( :headers ) { response_env [ 1 ] }
9
+ let ( :body ) { response_env [ 2 ] . join }
7
10
8
- it "passes non-error responses through" do
9
- expect ( app . call ( { } ) ) . to eq ( ":)" )
11
+ context 'when the application raises no exception' do
12
+ it "passes non-error responses through" do
13
+ expect ( app . call ( { } ) ) . to eq ( ":)" )
14
+ end
10
15
end
11
16
12
17
it "calls the internal methods" do
@@ -24,11 +29,6 @@ module BetterErrors
24
29
app . call ( "PATH_INFO" => "/__better_errors/" )
25
30
end
26
31
27
- it "shows the error page on any subfolder path" do
28
- expect ( app ) . to receive :show_error_page
29
- app . call ( "PATH_INFO" => "/any_sub/folder/path/__better_errors/" )
30
- end
31
-
32
32
it "doesn't show the error page to a non-local address" do
33
33
expect ( app ) . not_to receive :better_errors_call
34
34
app . call ( "REMOTE_ADDR" => "1.2.3.4" )
@@ -62,34 +62,71 @@ module BetterErrors
62
62
expect { app . call ( "REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) } . to_not raise_error
63
63
end
64
64
65
- context "when requesting the /__better_errors manually " do
66
- let ( :app ) { Middleware . new ( -> env { ":)" } ) }
65
+ context "when /__better_errors is requested directly " do
66
+ let ( :response_env ) { app . call ( "PATH_INFO" => "/__better_errors" ) }
67
67
68
- it "shows that no errors have been recorded" do
69
- status , headers , body = app . call ( "PATH_INFO" => "/__better_errors" )
70
- expect ( body . join ) . to match /No errors have been recorded yet./
71
- end
68
+ context "when no error has been recorded since startup " do
69
+ it "shows that no errors have been recorded" do
70
+ expect ( body ) . to match /No errors have been recorded yet./
71
+ end
72
72
73
- it 'does not attempt to use ActionDispatch::ExceptionWrapper with a nil exception' do
74
- ad_ew = double ( "ActionDispatch::ExceptionWrapper" )
75
- stub_const ( 'ActionDispatch::ExceptionWrapper' , ad_ew )
76
- expect ( ad_ew ) . to_not receive :new
73
+ it 'does not attempt to use ActionDispatch::ExceptionWrapper on the nil exception' do
74
+ ad_ew = double ( "ActionDispatch::ExceptionWrapper" )
75
+ stub_const ( 'ActionDispatch::ExceptionWrapper' , ad_ew )
76
+ expect ( ad_ew ) . to_not receive :new
77
+
78
+ response_env
79
+ end
80
+
81
+ context 'when requested inside a subfolder path' do
82
+ let ( :response_env ) { app . call ( "PATH_INFO" => "/any_sub/folder/__better_errors" ) }
77
83
78
- status , headers , body = app . call ( "PATH_INFO" => "/__better_errors" )
84
+ it "shows that no errors have been recorded" do
85
+ expect ( body ) . to match /No errors have been recorded yet./
86
+ end
87
+ end
79
88
end
80
89
81
- it "shows that no errors have been recorded on any subfolder path" do
82
- status , headers , body = app . call ( "PATH_INFO" => "/any_sub/folder/path/__better_errors" )
83
- expect ( body . join ) . to match /No errors have been recorded yet./
90
+ context 'when an error has been recorded' do
91
+ let ( :app ) {
92
+ Middleware . new ( -> env do
93
+ # Only raise on the first request
94
+ raise exception unless @already_raised
95
+ @already_raised = true
96
+ end )
97
+ }
98
+ before do
99
+ app . call ( { } )
100
+ end
101
+
102
+ it 'returns the information of the most recent error' do
103
+ expect ( body ) . to include ( "oh no :(" )
104
+ end
105
+
106
+ it 'does not attempt to use ActionDispatch::ExceptionWrapper' do
107
+ ad_ew = double ( "ActionDispatch::ExceptionWrapper" )
108
+ stub_const ( 'ActionDispatch::ExceptionWrapper' , ad_ew )
109
+ expect ( ad_ew ) . to_not receive :new
110
+
111
+ response_env
112
+ end
113
+
114
+ context 'when inside a subfolder path' do
115
+ let ( :response_env ) { app . call ( "PATH_INFO" => "/any_sub/folder/__better_errors" ) }
116
+
117
+ it "shows the error page on any subfolder path" do
118
+ expect ( app ) . to receive :show_error_page
119
+ app . call ( "PATH_INFO" => "/any_sub/folder/path/__better_errors/" )
120
+ end
121
+ end
84
122
end
85
123
end
86
124
87
125
context "when handling an error" do
88
126
let ( :app ) { Middleware . new ( -> env { raise exception } ) }
127
+ let ( :response_env ) { app . call ( { } ) }
89
128
90
129
it "returns status 500" do
91
- status , headers , body = app . call ( { } )
92
-
93
130
expect ( status ) . to eq ( 500 )
94
131
end
95
132
@@ -109,11 +146,9 @@ module BetterErrors
109
146
}
110
147
111
148
it "shows the exception as-is" do
112
- status , _ , body = app . call ( { } )
113
-
114
149
expect ( status ) . to eq ( 500 )
115
- expect ( body . join ) . to match ( /\n > Second Exception\n / )
116
- expect ( body . join ) . not_to match ( /\n > First Exception\n / )
150
+ expect ( body ) . to match ( /\n > Second Exception\n / )
151
+ expect ( body ) . not_to match ( /\n > First Exception\n / )
117
152
end
118
153
end
119
154
@@ -135,11 +170,9 @@ def initialize(message, original_exception = nil)
135
170
}
136
171
137
172
it "shows the original exception instead of the last-raised one" do
138
- status , _ , body = app . call ( { } )
139
-
140
173
expect ( status ) . to eq ( 500 )
141
- expect ( body . join ) . not_to match ( /Second Exception/ )
142
- expect ( body . join ) . to match ( /First Exception/ )
174
+ expect ( body ) . not_to match ( /Second Exception/ )
175
+ expect ( body ) . to match ( /First Exception/ )
143
176
end
144
177
end
145
178
@@ -151,10 +184,8 @@ def initialize(message, original_exception = nil)
151
184
}
152
185
153
186
it "shows the exception as-is" do
154
- status , _ , body = app . call ( { } )
155
-
156
187
expect ( status ) . to eq ( 500 )
157
- expect ( body . join ) . to match ( /The Exception/ )
188
+ expect ( body ) . to match ( /The Exception/ )
158
189
end
159
190
end
160
191
end
@@ -164,28 +195,57 @@ def initialize(message, original_exception = nil)
164
195
allow ( ad_ew ) . to receive ( 'new' ) . with ( anything , exception ) { double ( "ExceptionWrapper" , status_code : 404 ) }
165
196
stub_const ( 'ActionDispatch::ExceptionWrapper' , ad_ew )
166
197
167
- status , headers , body = app . call ( { } )
168
-
169
198
expect ( status ) . to eq ( 404 )
170
199
end
171
200
172
201
it "returns UTF-8 error pages" do
173
- status , headers , body = app . call ( { } )
174
-
175
202
expect ( headers [ "Content-Type" ] ) . to match /charset=utf-8/
176
203
end
177
204
178
- it "returns text pages by default" do
179
- status , headers , body = app . call ( { } )
180
-
205
+ it "returns text content by default" do
181
206
expect ( headers [ "Content-Type" ] ) . to match /text\/ plain/
182
207
end
183
208
184
- it "returns HTML pages by default" do
185
- # Chrome's 'Accept' header looks similar this.
186
- status , headers , body = app . call ( "HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*" )
209
+ context 'when a CSRF token cookie is not specified' do
210
+ it 'includes a newly-generated CSRF token cookie' do
211
+ expect ( headers ) . to include (
212
+ 'Set-Cookie' => /BetterErrors-CSRF-Token=[-a-z0-9]+; HttpOnly; SameSite=Strict/
213
+ )
214
+ end
215
+ end
216
+
217
+ context 'when a CSRF token cookie is specified' do
218
+ let ( :response_env ) { app . call ( { 'HTTP_COOKIE' => 'BetterErrors-CSRF-Token=abc123' } ) }
219
+
220
+ it 'does not set a new CSRF token cookie' do
221
+ expect ( headers ) . not_to include ( 'Set-Cookie' )
222
+ end
223
+ end
224
+
225
+ context 'when the Accept header specifies HTML first' do
226
+ let ( :response_env ) { app . call ( "HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*" ) }
227
+
228
+ it "returns HTML content" do
229
+ expect ( headers [ "Content-Type" ] ) . to match /text\/ html/
230
+ end
231
+
232
+ it 'includes the newly-generated CSRF token in the body of the page' do
233
+ matches = headers [ 'Set-Cookie' ] . match ( /BetterErrors-CSRF-Token=(?<tok>[-a-z0-9]+); HttpOnly; SameSite=Strict/ )
234
+ expect ( body ) . to include ( matches [ :tok ] )
235
+ end
187
236
188
- expect ( headers [ "Content-Type" ] ) . to match /text\/ html/
237
+ context 'when a CSRF token cookie is specified' do
238
+ let ( :response_env ) {
239
+ app . call ( {
240
+ 'HTTP_COOKIE' => 'BetterErrors-CSRF-Token=csrfTokenGHI' ,
241
+ "HTTP_ACCEPT" => "text/html,application/xhtml+xml;q=0.9,*/*" ,
242
+ } )
243
+ }
244
+
245
+ it 'includes that CSRF token in the body of the page' do
246
+ expect ( body ) . to include ( 'csrfTokenGHI' )
247
+ end
248
+ end
189
249
end
190
250
191
251
context 'the logger' do
@@ -196,7 +256,7 @@ def initialize(message, original_exception = nil)
196
256
197
257
it "receives the exception as a fatal message" do
198
258
expect ( logger ) . to receive ( :fatal ) . with ( /RuntimeError/ )
199
- app . call ( { } )
259
+ response_env
200
260
end
201
261
202
262
context 'when Rails is being used' do
@@ -208,7 +268,7 @@ def initialize(message, original_exception = nil)
208
268
expect ( logger ) . to receive ( :fatal ) do |message |
209
269
expect ( message ) . to_not match ( /rspec-core/ )
210
270
end
211
- app . call ( { } )
271
+ response_env
212
272
end
213
273
end
214
274
context 'when Rails is not being used' do
@@ -220,24 +280,21 @@ def initialize(message, original_exception = nil)
220
280
expect ( logger ) . to receive ( :fatal ) do |message |
221
281
expect ( message ) . to match ( /rspec-core/ )
222
282
end
223
- app . call ( { } )
283
+ response_env
224
284
end
225
285
end
226
286
end
227
287
end
228
288
229
289
context "requesting the variables for a specific frame" do
230
290
let ( :env ) { { } }
231
- let ( :result ) {
291
+ let ( :response_env ) {
232
292
app . call ( request_env )
233
293
}
234
294
let ( :request_env ) {
235
295
Rack ::MockRequest . env_for ( "/__better_errors/#{ id } /variables" , input : StringIO . new ( JSON . dump ( request_body_data ) ) )
236
296
}
237
297
let ( :request_body_data ) { { "index" : 0 } }
238
- let ( :status ) { result [ 0 ] }
239
- let ( :headers ) { result [ 1 ] }
240
- let ( :body ) { result [ 2 ] . join }
241
298
let ( :json_body ) { JSON . parse ( body ) }
242
299
let ( :id ) { 'abcdefg' }
243
300
0 commit comments