require 'spec_helper' require 'rollbar/middleware/js' describe Rollbar::Middleware::Js do subject { described_class.new(app, config) } let(:env) { {} } let(:config) { {} } let(:app) do proc do |_| [status, headers, body] end end let(:html) do <<-END <html> <head> <link rel="stylesheet" href="url" type="text/css" media="screen" /> <script type="text/javascript" src="foo"></script> </head> <body> <h1>Testing the middleware</h1> </body> </html> END end let(:minified_html) do <<-END <html><head><link rel="stylesheet" href="url" type="text/css" media="screen" /><script type="text/javascript" src="foo"></script></head><body><h1>Testing the middleware</h1></body></html> END end let(:snippet) { 'THIS IS THE SNIPPET' } let(:content_type) { 'text/html' } before do reconfigure_notifier allow(subject).to receive(:js_snippet).and_return(snippet) end shared_examples "doesn't add the snippet or config", :add_js => false do it "doesn't add the snippet or config" do res_status, res_headers, response = subject.call(env) new_body = response.join expect(new_body).not_to include(snippet) expect(new_body).not_to include(config[:options].to_json) expect(new_body).to be_eql(body.join) expect(res_status).to be_eql(status) expect(res_headers['Content-Type']).to be_eql(content_type) end end describe '#call' do context 'with enabled config' do let(:config) do { :enabled => true, :options => { :foo => :bar } } end context 'having a html 200 response' do let(:body) { [html] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end it 'adds the config and the snippet to the response' do res_status, res_headers, response = subject.call(env) new_body = response.body.join expect(new_body).to_not include('>>') expect(new_body).to include(snippet) expect(new_body).to include(config[:options].to_json) expect(res_status).to be_eql(status) expect(res_headers['Content-Type']).to be_eql(content_type) end end context 'having a html 200 response with minified body' do let(:body) { [minified_html] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end it 'adds the config and the snippet to the response' do res_status, res_headers, response = subject.call(env) new_body = response.body.join expect(new_body).to_not include('>>') expect(new_body).to include(snippet) expect(new_body).to include(config[:options].to_json) expect(res_status).to be_eql(status) expect(res_headers['Content-Type']).to be_eql(content_type) end end context 'having a html 200 response and SecureHeaders >= 3.0.0 defined' do let(:body) { [html] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end before do Object.const_set('SecureHeaders', Module.new) SecureHeaders.const_set('VERSION', '3.0.0') SecureHeaders.const_set('Configuration', Module.new { def self.get end }) allow(SecureHeaders).to receive(:content_security_policy_script_nonce) { 'lorem-ipsum-nonce' } end after do Object.send(:remove_const, 'SecureHeaders') end it 'renders the snippet and config in the response with nonce in script tag when SecureHeaders installed' do secure_headers_config = double(:configuration, :current_csp => {}) allow(SecureHeaders::Configuration).to receive(:get).and_return(secure_headers_config) res_status, res_headers, response = subject.call(env) new_body = response.body.join expect(new_body).to include('<script type="text/javascript" nonce="lorem-ipsum-nonce">') expect(new_body).to include("var _rollbarConfig = #{config[:options].to_json};") expect(new_body).to include(snippet) end it 'renders the snippet in the response without nonce if SecureHeaders script_src includes \'unsafe-inline\'' do secure_headers_config = double(:configuration, :current_csp => { :script_src => %w('unsafe-inline') }) allow(SecureHeaders::Configuration).to receive(:get).and_return(secure_headers_config) res_status, res_headers, response = subject.call(env) new_body = response.body.join expect(new_body).to include('<script type="text/javascript">') expect(new_body).to include("var _rollbarConfig = #{config[:options].to_json};") expect(new_body).to include(snippet) SecureHeaders.send(:remove_const, 'Configuration') end end context 'having a html 200 response and SecureHeaders < 3.0.0 defined' do let(:body) { [html] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end before do Object.const_set('SecureHeaders', Module.new) SecureHeaders.const_set('VERSION', '2.4.0') end after do Object.send(:remove_const, 'SecureHeaders') end it 'renders the snippet and config in the response without nonce in script tag when too old SecureHeaders installed' do res_status, res_headers, response = subject.call(env) new_body = response.body.join expect(new_body).to include('<script type="text/javascript">') expect(new_body).to include("var _rollbarConfig = #{config[:options].to_json};") expect(new_body).to include(snippet) end end context 'having a html 200 response without head', :add_js => false do let(:body) { ['foobar'] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end end context 'having a html 200 response without head but with an header tag', :add_js => false do let(:body) { ['<header>foobar</header>'] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end end context 'having a html 302 response', :add_js => false do let(:body) { ['foobar'] } let(:status) { 302 } let(:headers) do { 'Content-Type' => content_type } end end context 'having the js already injected key in env', :add_js => false do let(:body) { ['foobar'] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end let(:env) do { described_class::JS_IS_INJECTED_KEY => true } end end context 'having an attachment', :add_js => false do let(:content_type) { 'text/plain' } let(:body) { ['foobar'] } let(:status) { 200 } let(:headers) do { 'Content-Disposition' => 'attachment', 'Content-Type' => content_type } end end context 'with an exception raised while adding the js', :add_js => false do let(:body) { [html] } let(:status) { 200 } let(:headers) do { 'Content-Type' => content_type } end before do allow(subject).to receive(:add_js).and_raise(StandardError.new) end end end context 'having the config disabled', :add_js => false do let(:body) { ['foobar'] } let(:status) { 302 } let(:headers) do { 'Content-Type' => content_type } end let(:config) do { :enabled => false, :options => { :foo => :bar } } end end context 'if the app raises' do let(:exception) { StandardError.new } let(:app) do proc do |_| raise exception end end it 'propagates the exception' do expect do app.call(env) end.to raise_exception(exception) end end end end