@@ -4,9 +4,14 @@ module BetterErrors
44 describe Middleware do
55 let ( :app ) { Middleware . new ( -> env { ":)" } ) }
66 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 }
710
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
1015 end
1116
1217 it "calls the internal methods" do
@@ -24,11 +29,6 @@ module BetterErrors
2429 app . call ( "PATH_INFO" => "/__better_errors/" )
2530 end
2631
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-
3232 it "doesn't show the error page to a non-local address" do
3333 expect ( app ) . not_to receive :better_errors_call
3434 app . call ( "REMOTE_ADDR" => "1.2.3.4" )
@@ -62,34 +62,71 @@ module BetterErrors
6262 expect { app . call ( "REMOTE_ADDR" => "0:0:0:0:0:0:0:1%0" ) } . to_not raise_error
6363 end
6464
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" ) }
6767
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
7272
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" ) }
7783
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
7988 end
8089
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
84122 end
85123 end
86124
87125 context "when handling an error" do
88126 let ( :app ) { Middleware . new ( -> env { raise exception } ) }
127+ let ( :response_env ) { app . call ( { } ) }
89128
90129 it "returns status 500" do
91- status , headers , body = app . call ( { } )
92-
93130 expect ( status ) . to eq ( 500 )
94131 end
95132
@@ -109,11 +146,9 @@ module BetterErrors
109146 }
110147
111148 it "shows the exception as-is" do
112- status , _ , body = app . call ( { } )
113-
114149 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 / )
117152 end
118153 end
119154
@@ -135,11 +170,9 @@ def initialize(message, original_exception = nil)
135170 }
136171
137172 it "shows the original exception instead of the last-raised one" do
138- status , _ , body = app . call ( { } )
139-
140173 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/ )
143176 end
144177 end
145178
@@ -151,10 +184,8 @@ def initialize(message, original_exception = nil)
151184 }
152185
153186 it "shows the exception as-is" do
154- status , _ , body = app . call ( { } )
155-
156187 expect ( status ) . to eq ( 500 )
157- expect ( body . join ) . to match ( /The Exception/ )
188+ expect ( body ) . to match ( /The Exception/ )
158189 end
159190 end
160191 end
@@ -164,28 +195,57 @@ def initialize(message, original_exception = nil)
164195 allow ( ad_ew ) . to receive ( 'new' ) . with ( anything , exception ) { double ( "ExceptionWrapper" , status_code : 404 ) }
165196 stub_const ( 'ActionDispatch::ExceptionWrapper' , ad_ew )
166197
167- status , headers , body = app . call ( { } )
168-
169198 expect ( status ) . to eq ( 404 )
170199 end
171200
172201 it "returns UTF-8 error pages" do
173- status , headers , body = app . call ( { } )
174-
175202 expect ( headers [ "Content-Type" ] ) . to match /charset=utf-8/
176203 end
177204
178- it "returns text pages by default" do
179- status , headers , body = app . call ( { } )
180-
205+ it "returns text content by default" do
181206 expect ( headers [ "Content-Type" ] ) . to match /text\/ plain/
182207 end
183208
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
187236
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
189249 end
190250
191251 context 'the logger' do
@@ -196,7 +256,7 @@ def initialize(message, original_exception = nil)
196256
197257 it "receives the exception as a fatal message" do
198258 expect ( logger ) . to receive ( :fatal ) . with ( /RuntimeError/ )
199- app . call ( { } )
259+ response_env
200260 end
201261
202262 context 'when Rails is being used' do
@@ -208,7 +268,7 @@ def initialize(message, original_exception = nil)
208268 expect ( logger ) . to receive ( :fatal ) do |message |
209269 expect ( message ) . to_not match ( /rspec-core/ )
210270 end
211- app . call ( { } )
271+ response_env
212272 end
213273 end
214274 context 'when Rails is not being used' do
@@ -220,24 +280,21 @@ def initialize(message, original_exception = nil)
220280 expect ( logger ) . to receive ( :fatal ) do |message |
221281 expect ( message ) . to match ( /rspec-core/ )
222282 end
223- app . call ( { } )
283+ response_env
224284 end
225285 end
226286 end
227287 end
228288
229289 context "requesting the variables for a specific frame" do
230290 let ( :env ) { { } }
231- let ( :result ) {
291+ let ( :response_env ) {
232292 app . call ( request_env )
233293 }
234294 let ( :request_env ) {
235295 Rack ::MockRequest . env_for ( "/__better_errors/#{ id } /variables" , input : StringIO . new ( JSON . dump ( request_body_data ) ) )
236296 }
237297 let ( :request_body_data ) { { "index" : 0 } }
238- let ( :status ) { result [ 0 ] }
239- let ( :headers ) { result [ 1 ] }
240- let ( :body ) { result [ 2 ] . join }
241298 let ( :json_body ) { JSON . parse ( body ) }
242299 let ( :id ) { 'abcdefg' }
243300
0 commit comments