File: rack_spec.rb

package info (click to toggle)
ruby-bullet 7.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 836 kB
  • sloc: ruby: 6,133; javascript: 57; sh: 27; makefile: 4
file content (339 lines) | stat: -rw-r--r-- 15,372 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# frozen_string_literal: true

require 'spec_helper'

module Bullet
  describe Rack do
    let(:middleware) { Bullet::Rack.new app }
    let(:app) { Support::AppDouble.new }

    context '#html_request?' do
      it 'should be true if Content-Type is text/html and http body contains html tag' do
        headers = { 'Content-Type' => 'text/html' }
        response = double(body: '<html><head></head><body></body></html>')
        expect(middleware).to be_html_request(headers, response)
      end

      it 'should be true if Content-Type is text/html and http body contains html tag with attributes' do
        headers = { 'Content-Type' => 'text/html' }
        response = double(body: "<html attr='hello'><head></head><body></body></html>")
        expect(middleware).to be_html_request(headers, response)
      end

      it 'should be false if there is no Content-Type header' do
        headers = {}
        response = double(body: '<html><head></head><body></body></html>')
        expect(middleware).not_to be_html_request(headers, response)
      end

      it 'should be false if Content-Type is javascript' do
        headers = { 'Content-Type' => 'text/javascript' }
        response = double(body: '<html><head></head><body></body></html>')
        expect(middleware).not_to be_html_request(headers, response)
      end
    end

    context 'empty?' do
      it 'should be false if response is a string and not empty' do
        response = double(body: '<html><head></head><body></body></html>')
        expect(middleware).not_to be_empty(response)
      end

      it 'should be false if response is not found' do
        response = ['Not Found']
        expect(middleware).not_to be_empty(response)
      end

      it 'should be true if response body is empty' do
        response = double(body: '')
        expect(middleware).to be_empty(response)
      end

      it 'should be true if no response body' do
        response = double
        expect(middleware).to be_empty(response)
      end
    end

    context '#call' do
      context 'when Bullet is enabled' do
        it 'should return original response body' do
          expected_response = Support::ResponseDouble.new 'Actual body'
          app.response = expected_response
          _, _, response = middleware.call({})
          expect(response).to eq(expected_response)
        end

        it 'should change response body if notification is active' do
          expect(Bullet).to receive(:notification?).and_return(true)
          expect(Bullet).to receive(:console_enabled?).and_return(true)
          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
          expect(Bullet).to receive(:perform_out_of_channel_notifications)
          _, headers, response = middleware.call('Content-Type' => 'text/html')
          expect(headers['Content-Length']).to eq('56')
          expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
        end

        it 'should change response body if always_append_html_body is true' do
          expect(Bullet).to receive(:always_append_html_body).and_return(true)
          expect(Bullet).to receive(:console_enabled?).and_return(true)
          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
          expect(Bullet).to receive(:perform_out_of_channel_notifications)
          _, headers, response = middleware.call('Content-Type' => 'text/html')
          expect(headers['Content-Length']).to eq('56')
          expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
        end

        it 'should set the right Content-Length if response body contains accents' do
          response = Support::ResponseDouble.new
          response.body = '<html><head></head><body>é</body></html>'
          app.response = response
          expect(Bullet).to receive(:notification?).and_return(true)
          allow(Bullet).to receive(:console_enabled?).and_return(true)
          expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
          _, headers, response = middleware.call('Content-Type' => 'text/html')
          expect(headers['Content-Length']).to eq('58')
        end

        shared_examples 'inject notifiers' do
          before do
            allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
            allow(middleware).to receive(:xhr_script).and_return('<script></script>')
            allow(middleware).to receive(:footer_note).and_return('footer')
            expect(Bullet).to receive(:perform_out_of_channel_notifications)
          end

          it 'should change response body if add_footer is true' do
            expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
            _, headers, response = middleware.call('Content-Type' => 'text/html')

            expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
            expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
          end

          it 'should change response body for html safe string if add_footer is true' do
            expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
            app.response =
              Support::ResponseDouble.new.tap do |response|
                response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
              end
            _, headers, response = middleware.call('Content-Type' => 'text/html')

            expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
            expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
          end

          it 'should add the footer-text header for non-html requests when add_footer is true' do
            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
            allow(Bullet).to receive(:footer_info).and_return(['footer text'])
            app.headers = { 'Content-Type' => 'application/json' }
            _, headers, _response = middleware.call({})
            expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
          end

          it 'should change response body if console_enabled is true' do
            expect(Bullet).to receive(:console_enabled?).and_return(true)
            _, headers, response = middleware.call('Content-Type' => 'text/html')
            expect(headers['Content-Length']).to eq('56')
            expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
          end

          it 'should include CSP nonce in inline script if console_enabled and a CSP is applied' do
            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
            expect(Bullet).to receive(:console_enabled?).and_return(true)
            allow(middleware).to receive(:xhr_script).and_call_original

            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='
            app.headers = {
              'Content-Type' => 'text/html',
              'Content-Security-Policy' => "default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'"
            }

            _, headers, response = middleware.call('Content-Type' => 'text/html')

            size = 56 + middleware.send(:footer_note).length + middleware.send(:xhr_script, nonce).length
            expect(headers['Content-Length']).to eq(size.to_s)
          end

          it 'should include CSP nonce in inline script if console_enabled and a CSP (report only) is applied' do
            allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
            expect(Bullet).to receive(:console_enabled?).and_return(true)
            allow(middleware).to receive(:xhr_script).and_call_original

            nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='
            app.headers = {
              'Content-Type' => 'text/html',
              'Content-Security-Policy-Report-Only' =>
                "default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'"
            }

            _, headers, response = middleware.call('Content-Type' => 'text/html')

            size = 56 + middleware.send(:footer_note).length + middleware.send(:xhr_script, nonce).length
            expect(headers['Content-Length']).to eq(size.to_s)
          end

          it 'should change response body for html safe string if console_enabled is true' do
            expect(Bullet).to receive(:console_enabled?).and_return(true)
            app.response =
              Support::ResponseDouble.new.tap do |response|
                response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
              end
            _, headers, response = middleware.call('Content-Type' => 'text/html')
            expect(headers['Content-Length']).to eq('56')
            expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
          end

          it 'should add headers for non-html requests when console_enabled is true' do
            allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
            allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
            app.headers = { 'Content-Type' => 'application/json' }
            _, headers, _response = middleware.call({})
            expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
          end

          it "shouldn't change response body unnecessarily" do
            expected_response = Support::ResponseDouble.new 'Actual body'
            app.response = expected_response
            _, _, response = middleware.call({})
            expect(response).to eq(expected_response)
          end

          it "shouldn't add headers unnecessarily" do
            app.headers = { 'Content-Type' => 'application/json' }
            _, headers, _response = middleware.call({})
            expect(headers).not_to include('X-bullet-footer-text')
            expect(headers).not_to include('X-bullet-console-text')
          end

          context 'when skip_http_headers is enabled' do
            before do
              allow(Bullet).to receive(:skip_http_headers).and_return(true)
            end

            it 'should include the footer but not the xhr script tag if add_footer is true' do
              expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
              _, headers, response = middleware.call({})

              expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
              expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
            end

            it 'should not include the xhr script tag if console_enabled is true' do
              expect(Bullet).to receive(:console_enabled?).and_return(true)
              _, headers, response = middleware.call({})
              expect(headers['Content-Length']).to eq('56')
              expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
            end

            it 'should not add the footer-text header for non-html requests when add_footer is true' do
              allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
              app.headers = { 'Content-Type' => 'application/json' }
              _, headers, _response = middleware.call({})
              expect(headers).not_to include('X-bullet-footer-text')
            end

            it 'should not add headers for non-html requests when console_enabled is true' do
              allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
              app.headers = { 'Content-Type' => 'application/json' }
              _, headers, _response = middleware.call({})
              expect(headers).not_to include('X-bullet-console-text')
            end
          end
        end

        context 'with notifications present' do
          before do
            expect(Bullet).to receive(:notification?).and_return(true)
          end

          include_examples 'inject notifiers'
        end

        context 'with always_append_html_body true' do
          before do
            expect(Bullet).to receive(:always_append_html_body).and_return(true)
          end

          include_examples 'inject notifiers'
        end

        context 'when skip_html_injection is enabled' do
          it 'should not try to inject html' do
            expected_response = Support::ResponseDouble.new 'Actual body'
            app.response = expected_response
            allow(Bullet).to receive(:notification?).and_return(true)
            allow(Bullet).to receive(:skip_html_injection?).and_return(true)
            expect(Bullet).to receive(:gather_inline_notifications).never
            expect(middleware).to receive(:xhr_script).never
            expect(Bullet).to receive(:perform_out_of_channel_notifications)
            _, _, response = middleware.call('Content-Type' => 'text/html')
            expect(response).to eq(expected_response)
          end
        end
      end

      context 'when Bullet is disabled' do
        before(:each) { allow(Bullet).to receive(:enable?).and_return(false) }

        it 'should not call Bullet.start_request' do
          expect(Bullet).not_to receive(:start_request)
          middleware.call({})
        end
      end
    end

    context '#set_header' do
      it 'should truncate headers to under 8kb' do
        long_header = ['a' * 1_024] * 10
        expected_res = (['a' * 1_024] * 7).to_json
        expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
      end
    end

    describe '#response_body' do
      let(:response) { double }
      let(:body_string) { '<html><body>My Body</body></html>' }

      context 'when `response` responds to `body`' do
        before { allow(response).to receive(:body).and_return(body) }

        context 'when `body` returns an Array' do
          let(:body) { [body_string, 'random string'] }
          it 'should return the plain body string' do
            expect(middleware.response_body(response)).to eq body_string
          end
        end

        context 'when `body` does not return an Array' do
          let(:body) { body_string }
          it 'should return the plain body string' do
            expect(middleware.response_body(response)).to eq body_string
          end
        end
      end

      context 'when `response` does not respond to `body`' do
        before { allow(response).to receive(:first).and_return(body_string) }

        it 'should return the plain body string' do
          expect(middleware.response_body(response)).to eq body_string
        end
      end

      begin
        require 'rack/files'

        context 'when `response` is a Rack::Files::Iterator' do
          let(:response) { instance_double(::Rack::Files::Iterator) }
          before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }

          it 'should return nil' do
            expect(middleware.response_body(response)).to be_nil
          end
        end
      rescue LoadError
      end
    end
  end
end