exception_handling 2.5.0 → 2.7.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 +4 -4
- data/.gitignore +1 -1
- data/.jenkins/Jenkinsfile +24 -8
- data/.rspec +3 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +67 -58
- data/Rakefile +7 -6
- data/gemfiles/rails_4.gemfile +4 -4
- data/gemfiles/rails_5.gemfile +4 -4
- data/gemfiles/rails_6.gemfile +4 -4
- data/lib/exception_handling.rb +9 -3
- data/lib/exception_handling/exception_info.rb +3 -6
- data/lib/exception_handling/log_stub_error.rb +2 -1
- data/lib/exception_handling/logging_methods.rb +27 -0
- data/lib/exception_handling/methods.rb +6 -53
- data/lib/exception_handling/testing.rb +20 -10
- data/lib/exception_handling/version.rb +1 -1
- data/{test → spec}/helpers/controller_helpers.rb +0 -0
- data/{test → spec}/helpers/exception_helpers.rb +2 -2
- data/{test → spec}/rake_test_warning_false.rb +0 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +57 -42
- data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
- data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
- data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +118 -99
- data/{test/unit/exception_handling/honeybadger_callbacks_test.rb → spec/unit/exception_handling/honeybadger_callbacks_spec.rb} +20 -20
- data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
- data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
- data/{test/unit/exception_handling/mailer_test.rb → spec/unit/exception_handling/mailer_spec.rb} +17 -17
- data/spec/unit/exception_handling/methods_spec.rb +105 -0
- data/spec/unit/exception_handling/sensu_spec.rb +51 -0
- data/{test/unit/exception_handling_test.rb → spec/unit/exception_handling_spec.rb} +349 -319
- metadata +35 -31
- data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
- data/test/unit/exception_handling/exception_description_test.rb +0 -82
- data/test/unit/exception_handling/methods_test.rb +0 -84
- data/test/unit/exception_handling/sensu_test.rb +0 -52
| @@ -0,0 +1,82 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require File.expand_path('../../spec_helper',  __dir__)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ExceptionHandling
         | 
| 6 | 
            +
              describe ExceptionDescription do
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                context "Filter" do
         | 
| 9 | 
            +
                  it "allow direct matching of strings" do
         | 
| 10 | 
            +
                    @f = ExceptionDescription.new(:filter1, error: "my error message")
         | 
| 11 | 
            +
                    expect(@f.match?('error' => "my error message")).to be_truthy
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it "allow direct matching of strings on with symbol keys" do
         | 
| 15 | 
            +
                    @f = ExceptionDescription.new(:filter1, error: "my error message")
         | 
| 16 | 
            +
                    expect(@f.match?(error: "my error message")).to be_truthy
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  it "allow wildcards to cross line boundries" do
         | 
| 20 | 
            +
                    @f = ExceptionDescription.new(:filter1, error: "my error message.*with multiple lines")
         | 
| 21 | 
            +
                    expect(@f.match?(error: "my error message\nwith more than one, with multiple lines")).to be_truthy
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  it "complain when no regexps have a value" do
         | 
| 25 | 
            +
                    expect { ExceptionDescription.new(:filter1, error: nil) }.to raise_exception(ArgumentError, /has all blank regexes/)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  it "report when an invalid key is passed" do
         | 
| 29 | 
            +
                    expect { ExceptionDescription.new(:filter1, error: "my error message", not_a_parameter: false) }.to raise_exception(ArgumentError, "Unknown section: not_a_parameter")
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  it "allow send_to_honeybadger to be specified and have it disabled by default" do
         | 
| 33 | 
            +
                    expect(!ExceptionDescription.new(:filter1, error: "my error message", send_to_honeybadger: false).send_to_honeybadger).to be_truthy
         | 
| 34 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", send_to_honeybadger: true).send_to_honeybadger).to be_truthy
         | 
| 35 | 
            +
                    expect(!ExceptionDescription.new(:filter1, error: "my error message").send_to_honeybadger).to be_truthy
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  it "allow send_metric to be configured" do
         | 
| 39 | 
            +
                    expect(!ExceptionDescription.new(:filter1, error: "my error message", send_metric: false).send_metric).to be_truthy
         | 
| 40 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message").send_metric).to be_truthy
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  it "provide metric name" do
         | 
| 44 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message").metric_name).to eq("filter1")
         | 
| 45 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", metric_name: :some_other_metric_name).metric_name).to eq("some_other_metric_name")
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  it "replace spaces in metric name" do
         | 
| 49 | 
            +
                    @f = ExceptionDescription.new(:"filter has spaces", error: "my error message")
         | 
| 50 | 
            +
                    expect(@f.metric_name).to eq( "filter_has_spaces")
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  it "allow notes to be recorded" do
         | 
| 54 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message").notes).to be_nil
         | 
| 55 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", notes: "a long string").notes).to eq("a long string")
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  it "not consider config options in the filter set" do
         | 
| 59 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", send_metric: false).match?(error: "my error message")).to be_truthy
         | 
| 60 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", metric_name: "false").match?(error: "my error message")).to be_truthy
         | 
| 61 | 
            +
                    expect(ExceptionDescription.new(:filter1, error: "my error message", notes: "hey").match?(error: "my error message")).to be_truthy
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  it "provide exception details" do
         | 
| 65 | 
            +
                    exception_description = ExceptionDescription.new(:filter1, error: "my error message", notes: "hey")
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    expected = { "send_metric" => true, "metric_name" => "filter1", "notes" => "hey" }
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    expect(exception_description.exception_data).to eq( expected)
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  it "match multiple email addresses" do
         | 
| 73 | 
            +
                    mobi = "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for 'mcc@mobistreak.com'"
         | 
| 74 | 
            +
                    credit = "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for 'damon@thecreditpros.com'"
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    exception_description = ExceptionDescription.new(:filter1, error: "ExceptionHandling::Warning: LoginAttempt::IPAddressLocked: failed login for '(mcc\@mobistreak|damon\@thecreditpros).com'")
         | 
| 77 | 
            +
                    expect(exception_description.match?(error: mobi)).to be_truthy
         | 
| 78 | 
            +
                    expect(exception_description.match?(error: credit)).to be_truthy
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
            end
         | 
| @@ -1,58 +1,58 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require File.expand_path('../../ | 
| 3 | 
            +
            require File.expand_path('../../spec_helper',  __dir__)
         | 
| 4 4 | 
             
            require_test_helper 'controller_helpers'
         | 
| 5 5 | 
             
            require_test_helper 'exception_helpers'
         | 
| 6 6 |  | 
| 7 7 | 
             
            module ExceptionHandling
         | 
| 8 | 
            -
               | 
| 8 | 
            +
              describe ExceptionInfo do
         | 
| 9 9 | 
             
                include ControllerHelpers
         | 
| 10 10 | 
             
                include ExceptionHelpers
         | 
| 11 11 |  | 
| 12 12 | 
             
                context "initialize" do
         | 
| 13 | 
            -
                   | 
| 13 | 
            +
                  before do
         | 
| 14 14 | 
             
                    @exception = StandardError.new("something went wrong")
         | 
| 15 15 | 
             
                    @timestamp = Time.now
         | 
| 16 16 | 
             
                    @controller = Object.new
         | 
| 17 17 | 
             
                  end
         | 
| 18 18 |  | 
| 19 19 | 
             
                  context "controller_from_context" do
         | 
| 20 | 
            -
                     | 
| 20 | 
            +
                    it "extract controller from context when not specified explicitly" do
         | 
| 21 21 | 
             
                      exception_context = {
         | 
| 22 22 | 
             
                        "action_controller.instance" => @controller
         | 
| 23 23 | 
             
                      }
         | 
| 24 24 | 
             
                      exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp)
         | 
| 25 | 
            -
                       | 
| 25 | 
            +
                      expect(exception_info.controller).to eq(@controller)
         | 
| 26 26 | 
             
                    end
         | 
| 27 27 |  | 
| 28 | 
            -
                     | 
| 28 | 
            +
                    it "prefer the explicit controller over the one from context" do
         | 
| 29 29 | 
             
                      exception_context = {
         | 
| 30 30 | 
             
                        "action_controller.instance" => Object.new
         | 
| 31 31 | 
             
                      }
         | 
| 32 32 | 
             
                      exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp, controller: @controller)
         | 
| 33 | 
            -
                       | 
| 34 | 
            -
                       | 
| 33 | 
            +
                      expect(exception_info.controller).to eq(@controller)
         | 
| 34 | 
            +
                      expect(exception_info.controller).not_to eq(exception_context["action_controller.instance"])
         | 
| 35 35 | 
             
                    end
         | 
| 36 36 |  | 
| 37 | 
            -
                     | 
| 37 | 
            +
                    it "leave controller unset when not included in the context hash" do
         | 
| 38 38 | 
             
                      exception_info = ExceptionInfo.new(@exception, {}, @timestamp)
         | 
| 39 | 
            -
                       | 
| 39 | 
            +
                      expect(exception_info.controller).to be_nil
         | 
| 40 40 | 
             
                    end
         | 
| 41 41 |  | 
| 42 | 
            -
                     | 
| 42 | 
            +
                    it "leave controller unset when context is not in hash format" do
         | 
| 43 43 | 
             
                      exception_info = ExceptionInfo.new(@exception, "string context", @timestamp)
         | 
| 44 | 
            -
                       | 
| 44 | 
            +
                      expect(exception_info.controller).to be_nil
         | 
| 45 45 | 
             
                    end
         | 
| 46 46 | 
             
                  end
         | 
| 47 47 | 
             
                end
         | 
| 48 48 |  | 
| 49 49 | 
             
                context "data" do
         | 
| 50 | 
            -
                   | 
| 50 | 
            +
                  before do
         | 
| 51 51 | 
             
                    @exception = StandardError.new("something went wrong")
         | 
| 52 52 | 
             
                    @timestamp = Time.now
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 |  | 
| 55 | 
            -
                   | 
| 55 | 
            +
                  it "return a hash with exception specific data including context hash" do
         | 
| 56 56 | 
             
                    exception_context = {
         | 
| 57 57 | 
             
                      "rack.session" => {
         | 
| 58 58 | 
             
                        user_id: 23,
         | 
| @@ -73,17 +73,17 @@ module ExceptionHandling | |
| 73 73 | 
             
                      }
         | 
| 74 74 | 
             
                    }
         | 
| 75 75 |  | 
| 76 | 
            -
                     | 
| 76 | 
            +
                    expect(exception_info.data).to eq(expected_data)
         | 
| 77 77 | 
             
                  end
         | 
| 78 78 |  | 
| 79 | 
            -
                   | 
| 79 | 
            +
                  it "generate exception data appropriately if exception message is nil" do
         | 
| 80 80 | 
             
                    exception_info = ExceptionInfo.new(exception_with_nil_message, "custom context data", @timestamp)
         | 
| 81 81 | 
             
                    exception_data = exception_info.data
         | 
| 82 | 
            -
                     | 
| 83 | 
            -
                     | 
| 82 | 
            +
                    expect(exception_data["error_string"]).to eq("RuntimeError: ")
         | 
| 83 | 
            +
                    expect(exception_data["error"]).to eq("RuntimeError: : custom context data")
         | 
| 84 84 | 
             
                  end
         | 
| 85 85 |  | 
| 86 | 
            -
                   | 
| 86 | 
            +
                  it "return a hash with exception specific data including context string" do
         | 
| 87 87 | 
             
                    exception_context = "custom context data"
         | 
| 88 88 | 
             
                    exception_info = ExceptionInfo.new(@exception, exception_context, @timestamp)
         | 
| 89 89 | 
             
                    expected_data = {
         | 
| @@ -96,11 +96,10 @@ module ExceptionHandling | |
| 96 96 | 
             
                        "message" => "custom context data"
         | 
| 97 97 | 
             
                      }
         | 
| 98 98 | 
             
                    }
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                    assert_equal_with_diff expected_data, exception_info.data
         | 
| 99 | 
            +
                    expect(exception_info.data).to eq(expected_data)
         | 
| 101 100 | 
             
                  end
         | 
| 102 101 |  | 
| 103 | 
            -
                   | 
| 102 | 
            +
                  it "not include enhanced data from controller or custom data callback" do
         | 
| 104 103 | 
             
                    env = { server: "fe98" }
         | 
| 105 104 | 
             
                    parameters = { advertiser_id: 435 }
         | 
| 106 105 | 
             
                    session = { username: "jsmith" }
         | 
| @@ -109,8 +108,8 @@ module ExceptionHandling | |
| 109 108 | 
             
                    data_callback = ->(data) { data[:custom_section] = "value" }
         | 
| 110 109 | 
             
                    exception_info = ExceptionInfo.new(@exception, "custom context data", @timestamp, controller: controller, data_callback: data_callback)
         | 
| 111 110 |  | 
| 112 | 
            -
                     | 
| 113 | 
            -
                     | 
| 111 | 
            +
                    expect(exception_info).to_not receive(:extract_and_merge_controller_data)
         | 
| 112 | 
            +
                    expect(exception_info).to_not receive(:customize_from_data_callback)
         | 
| 114 113 | 
             
                    expected_data = {
         | 
| 115 114 | 
             
                      "error_class" => "StandardError",
         | 
| 116 115 | 
             
                      "error_string" => "StandardError: something went wrong",
         | 
| @@ -122,12 +121,12 @@ module ExceptionHandling | |
| 122 121 | 
             
                      }
         | 
| 123 122 | 
             
                    }
         | 
| 124 123 |  | 
| 125 | 
            -
                     | 
| 124 | 
            +
                    expect(exception_info.data).to eq(expected_data)
         | 
| 126 125 | 
             
                  end
         | 
| 127 126 | 
             
                end
         | 
| 128 127 |  | 
| 129 128 | 
             
                context "enhanced_data" do
         | 
| 130 | 
            -
                   | 
| 129 | 
            +
                  before do
         | 
| 131 130 | 
             
                    @exception = StandardError.new("something went wrong")
         | 
| 132 131 | 
             
                    @timestamp = Time.now
         | 
| 133 132 | 
             
                    @exception_context = {
         | 
| @@ -145,13 +144,13 @@ module ExceptionHandling | |
| 145 144 | 
             
                    @data_callback = ->(data) { data[:custom_section] = "check this out" }
         | 
| 146 145 | 
             
                  end
         | 
| 147 146 |  | 
| 148 | 
            -
                   | 
| 147 | 
            +
                  it "not return a mutable object for the session" do
         | 
| 149 148 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 150 149 | 
             
                    exception_info.enhanced_data["session"]["hello"] = "world"
         | 
| 151 | 
            -
                     | 
| 150 | 
            +
                    expect(@controller.session["hello"]).to be_nil
         | 
| 152 151 | 
             
                  end
         | 
| 153 152 |  | 
| 154 | 
            -
                   | 
| 153 | 
            +
                  it "return a hash with generic exception attributes as well as context data" do
         | 
| 155 154 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 156 155 | 
             
                    expected_data = {
         | 
| 157 156 | 
             
                      "error_class" => "StandardError",
         | 
| @@ -164,19 +163,19 @@ module ExceptionHandling | |
| 164 163 | 
             
                      "location" => { "file" => "<no backtrace>", "line" => nil }
         | 
| 165 164 | 
             
                    }
         | 
| 166 165 |  | 
| 167 | 
            -
                     | 
| 166 | 
            +
                    expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
         | 
| 168 167 | 
             
                  end
         | 
| 169 168 |  | 
| 170 | 
            -
                   | 
| 169 | 
            +
                  it "generate exception data appropriately if exception message is nil" do
         | 
| 171 170 | 
             
                    exception_with_nil_message = RuntimeError.new(nil)
         | 
| 172 | 
            -
                     | 
| 171 | 
            +
                    allow(exception_with_nil_message).to receive(:message).and_return(nil)
         | 
| 173 172 | 
             
                    exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp)
         | 
| 174 173 | 
             
                    exception_data = exception_info.enhanced_data
         | 
| 175 | 
            -
                     | 
| 176 | 
            -
                     | 
| 174 | 
            +
                    expect(exception_data["error_string"]).to eq("RuntimeError: ")
         | 
| 175 | 
            +
                    expect(exception_data["error"]).to eq("RuntimeError: ")
         | 
| 177 176 | 
             
                  end
         | 
| 178 177 |  | 
| 179 | 
            -
                   | 
| 178 | 
            +
                  it "include controller data when available" do
         | 
| 180 179 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
         | 
| 181 180 | 
             
                    expected_data = {
         | 
| 182 181 | 
             
                      "error_class" => "StandardError",
         | 
| @@ -194,10 +193,10 @@ module ExceptionHandling | |
| 194 193 | 
             
                      "location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
         | 
| 195 194 | 
             
                    }
         | 
| 196 195 |  | 
| 197 | 
            -
                     | 
| 196 | 
            +
                    expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
         | 
| 198 197 | 
             
                  end
         | 
| 199 198 |  | 
| 200 | 
            -
                   | 
| 199 | 
            +
                  it "extract controller from rack specific exception context when not provided explicitly" do
         | 
| 201 200 | 
             
                    @exception_context["action_controller.instance"] = @controller
         | 
| 202 201 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 203 202 | 
             
                    expected_data = {
         | 
| @@ -216,10 +215,10 @@ module ExceptionHandling | |
| 216 215 | 
             
                      "location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
         | 
| 217 216 | 
             
                    }
         | 
| 218 217 |  | 
| 219 | 
            -
                     | 
| 218 | 
            +
                    expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
         | 
| 220 219 | 
             
                  end
         | 
| 221 220 |  | 
| 222 | 
            -
                   | 
| 221 | 
            +
                  it "add to_s attribute to specific sections that have their content in hash format" do
         | 
| 223 222 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
         | 
| 224 223 | 
             
                    expected_data = {
         | 
| 225 224 | 
             
                      "error_class" => "StandardError",
         | 
| @@ -245,10 +244,10 @@ module ExceptionHandling | |
| 245 244 | 
             
                      "location" => { "controller" => "dummy", "action" => "fail", "file" => "<no backtrace>", "line" => nil }
         | 
| 246 245 | 
             
                    }
         | 
| 247 246 |  | 
| 248 | 
            -
                     | 
| 247 | 
            +
                    expect(exception_info.enhanced_data).to eq(expected_data)
         | 
| 249 248 | 
             
                  end
         | 
| 250 249 |  | 
| 251 | 
            -
                   | 
| 250 | 
            +
                  it "filter out sensitive parameters like passwords" do
         | 
| 252 251 | 
             
                    @controller.request.parameters[:password] = "super_secret"
         | 
| 253 252 | 
             
                    @controller.request.parameters[:user] = { "password" => "also super secret", "password_confirmation" => "also super secret" }
         | 
| 254 253 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: @controller)
         | 
| @@ -261,10 +260,10 @@ module ExceptionHandling | |
| 261 260 | 
             
                        "password_confirmation" => "[FILTERED]"
         | 
| 262 261 | 
             
                      }
         | 
| 263 262 | 
             
                    }
         | 
| 264 | 
            -
                     | 
| 263 | 
            +
                    expect(exception_info.enhanced_data["request"]["params"]).to eq(expected_params)
         | 
| 265 264 | 
             
                  end
         | 
| 266 265 |  | 
| 267 | 
            -
                   | 
| 266 | 
            +
                  it "include the changes from the custom data callback" do
         | 
| 268 267 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: nil, data_callback: @data_callback)
         | 
| 269 268 | 
             
                    expected_data = {
         | 
| 270 269 | 
             
                      "error_class" => "StandardError",
         | 
| @@ -278,11 +277,11 @@ module ExceptionHandling | |
| 278 277 | 
             
                      "location" => { "file" => "<no backtrace>", "line" => nil }
         | 
| 279 278 | 
             
                    }
         | 
| 280 279 |  | 
| 281 | 
            -
                     | 
| 280 | 
            +
                    expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
         | 
| 282 281 | 
             
                  end
         | 
| 283 282 |  | 
| 284 | 
            -
                   | 
| 285 | 
            -
                     | 
| 283 | 
            +
                  it "apply the custom_data_hook results" do
         | 
| 284 | 
            +
                    allow(ExceptionHandling).to receive(:custom_data_hook).and_return(->(data) { data[:custom_hook] = "changes from custom hook" })
         | 
| 286 285 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 287 286 | 
             
                    expected_data = {
         | 
| 288 287 | 
             
                      "error_class" => "StandardError",
         | 
| @@ -296,35 +295,35 @@ module ExceptionHandling | |
| 296 295 | 
             
                      "location" => { "file" => "<no backtrace>", "line" => nil }
         | 
| 297 296 | 
             
                    }
         | 
| 298 297 |  | 
| 299 | 
            -
                     | 
| 298 | 
            +
                    expect(prepare_data(exception_info.enhanced_data)).to eq(expected_data)
         | 
| 300 299 | 
             
                  end
         | 
| 301 300 |  | 
| 302 | 
            -
                   | 
| 301 | 
            +
                  it "log info if the custom data hook results in a nil message exception" do
         | 
| 303 302 | 
             
                    ExceptionHandling.custom_data_hook = ->(_data) do
         | 
| 304 303 | 
             
                      raise_exception_with_nil_message
         | 
| 305 304 | 
             
                    end
         | 
| 306 305 | 
             
                    log_info_messages = []
         | 
| 307 | 
            -
                     | 
| 306 | 
            +
                    allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
         | 
| 308 307 | 
             
                      log_info_messages << message
         | 
| 309 308 | 
             
                    end
         | 
| 310 309 |  | 
| 311 310 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 312 311 | 
             
                    exception_info.enhanced_data
         | 
| 313 | 
            -
                     | 
| 312 | 
            +
                    expect(log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }).to be_truthy
         | 
| 314 313 | 
             
                    ExceptionHandling.custom_data_hook = nil
         | 
| 315 314 | 
             
                  end
         | 
| 316 315 | 
             
                end
         | 
| 317 316 |  | 
| 318 317 | 
             
                context "exception_description" do
         | 
| 319 | 
            -
                   | 
| 318 | 
            +
                  it "return the exception description from the global exception filter list" do
         | 
| 320 319 | 
             
                    exception = StandardError.new("No route matches")
         | 
| 321 320 | 
             
                    exception_info = ExceptionInfo.new(exception, {}, Time.now)
         | 
| 322 321 | 
             
                    description = exception_info.exception_description
         | 
| 323 | 
            -
                     | 
| 324 | 
            -
                     | 
| 322 | 
            +
                    expect(description).to_not be_nil
         | 
| 323 | 
            +
                    expect(description.filter_name).to eq(:NoRoute)
         | 
| 325 324 | 
             
                  end
         | 
| 326 325 |  | 
| 327 | 
            -
                   | 
| 326 | 
            +
                  it "find the description when filter criteria includes section in hash format" do
         | 
| 328 327 | 
             
                    env = { server: "fe98" }
         | 
| 329 328 | 
             
                    parameters = { advertiser_id: 435, controller: "sessions", action: "fail" }
         | 
| 330 329 | 
             
                    session = { username: "jsmith", id: "session_key" }
         | 
| @@ -332,25 +331,25 @@ module ExceptionHandling | |
| 332 331 | 
             
                    controller = create_dummy_controller(env, parameters, session, request_uri)
         | 
| 333 332 | 
             
                    exception = StandardError.new("Request to click domain rejected")
         | 
| 334 333 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now, controller: controller)
         | 
| 335 | 
            -
                     | 
| 334 | 
            +
                    expect(exception_info.enhanced_data[:request].is_a?(Hash)).to eq(true)
         | 
| 336 335 | 
             
                    description = exception_info.exception_description
         | 
| 337 | 
            -
                     | 
| 338 | 
            -
                     | 
| 336 | 
            +
                    expect(description).to_not be_nil
         | 
| 337 | 
            +
                    expect(description.filter_name).to eq(:"Click Request Rejected")
         | 
| 339 338 | 
             
                  end
         | 
| 340 339 |  | 
| 341 | 
            -
                   | 
| 340 | 
            +
                  it "return same description object for related errors (avoid reloading exception catalog from disk)" do
         | 
| 342 341 | 
             
                    exception = StandardError.new("No route matches")
         | 
| 343 342 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now)
         | 
| 344 343 | 
             
                    description = exception_info.exception_description
         | 
| 345 344 |  | 
| 346 345 | 
             
                    repeat_ex = StandardError.new("No route matches 2")
         | 
| 347 346 | 
             
                    repeat_ex_info = ExceptionInfo.new(repeat_ex, nil, Time.now)
         | 
| 348 | 
            -
                     | 
| 347 | 
            +
                    expect(repeat_ex_info.exception_description.object_id).to eq(description.object_id)
         | 
| 349 348 | 
             
                  end
         | 
| 350 349 | 
             
                end
         | 
| 351 350 |  | 
| 352 351 | 
             
                context "controller_name" do
         | 
| 353 | 
            -
                   | 
| 352 | 
            +
                  before do
         | 
| 354 353 | 
             
                    @exception = StandardError.new('something went wrong')
         | 
| 355 354 | 
             
                    @timestamp = Time.now
         | 
| 356 355 | 
             
                    @exception_context = {
         | 
| @@ -362,7 +361,7 @@ module ExceptionHandling | |
| 362 361 | 
             
                    }
         | 
| 363 362 | 
             
                  end
         | 
| 364 363 |  | 
| 365 | 
            -
                   | 
| 364 | 
            +
                  it "return controller_name when controller is present" do
         | 
| 366 365 | 
             
                    env         = { server:     'fe98' }
         | 
| 367 366 | 
             
                    parameters  = { controller: 'some_controller' }
         | 
| 368 367 | 
             
                    session     = { username:   'smith' }
         | 
| @@ -370,65 +369,85 @@ module ExceptionHandling | |
| 370 369 | 
             
                    controller  = create_dummy_controller(env, parameters, session, request_uri)
         | 
| 371 370 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp, controller: controller)
         | 
| 372 371 |  | 
| 373 | 
            -
                     | 
| 372 | 
            +
                    expect(exception_info.controller_name).to eq('some_controller')
         | 
| 374 373 | 
             
                  end
         | 
| 375 374 |  | 
| 376 | 
            -
                   | 
| 375 | 
            +
                  it "return an empty string when controller is not present" do
         | 
| 377 376 | 
             
                    exception_info = ExceptionInfo.new(@exception, @exception_context, @timestamp)
         | 
| 378 377 |  | 
| 379 | 
            -
                     | 
| 378 | 
            +
                    expect(exception_info.controller_name).to eq('')
         | 
| 380 379 | 
             
                  end
         | 
| 381 380 | 
             
                end
         | 
| 382 381 |  | 
| 383 382 | 
             
                context "send_to_honeybadger?" do
         | 
| 384 | 
            -
                   | 
| 385 | 
            -
                     | 
| 383 | 
            +
                  it "be enabled when Honeybadger is defined and exception is not in the filter list" do
         | 
| 384 | 
            +
                    allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
         | 
| 386 385 | 
             
                    exception = StandardError.new("something went wrong")
         | 
| 387 386 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now)
         | 
| 388 | 
            -
                     | 
| 389 | 
            -
                     | 
| 387 | 
            +
                    expect(exception_info.exception_description).to be_nil
         | 
| 388 | 
            +
                    expect(exception_info.send_to_honeybadger?).to eq(true)
         | 
| 390 389 | 
             
                  end
         | 
| 391 390 |  | 
| 392 | 
            -
                   | 
| 393 | 
            -
                     | 
| 391 | 
            +
                  it "be enabled when Honeybadger is defined and exception is on the filter list with the flag turned on" do
         | 
| 392 | 
            +
                    allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
         | 
| 394 393 | 
             
                    exception = StandardError.new("No route matches")
         | 
| 395 394 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now)
         | 
| 396 | 
            -
                     | 
| 397 | 
            -
                     | 
| 398 | 
            -
                     | 
| 395 | 
            +
                    expect(exception_info.exception_description).to_not be_nil
         | 
| 396 | 
            +
                    expect(exception_info.exception_description.send_to_honeybadger).to eq(true)
         | 
| 397 | 
            +
                    expect(exception_info.send_to_honeybadger?).to eq(true)
         | 
| 399 398 | 
             
                  end
         | 
| 400 399 |  | 
| 401 | 
            -
                   | 
| 402 | 
            -
                     | 
| 400 | 
            +
                  it "be disabled when Honeybadger is defined and exception is on the filter list with the flag turned off" do
         | 
| 401 | 
            +
                    allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(true)
         | 
| 403 402 | 
             
                    exception = StandardError.new("No route matches")
         | 
| 404 403 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now)
         | 
| 405 | 
            -
                     | 
| 406 | 
            -
                     | 
| 407 | 
            -
                     | 
| 404 | 
            +
                    expect(exception_info.exception_description).to_not be_nil
         | 
| 405 | 
            +
                    allow(exception_info.exception_description).to receive(:send_to_honeybadger).and_return(false)
         | 
| 406 | 
            +
                    expect(exception_info.send_to_honeybadger?).to eq(false)
         | 
| 408 407 | 
             
                  end
         | 
| 409 408 |  | 
| 410 | 
            -
                   | 
| 411 | 
            -
                     | 
| 409 | 
            +
                  it "be disabled when Honeybadger is not defined" do
         | 
| 410 | 
            +
                    allow(ExceptionHandling).to receive(:honeybadger_defined?).and_return(false)
         | 
| 412 411 | 
             
                    exception = StandardError.new("something went wrong")
         | 
| 413 412 | 
             
                    exception_info = ExceptionInfo.new(exception, nil, Time.now)
         | 
| 414 | 
            -
                     | 
| 415 | 
            -
                     | 
| 413 | 
            +
                    expect(exception_info.exception_description).to be_nil
         | 
| 414 | 
            +
                    expect(exception_info.send_to_honeybadger?).to eq(false)
         | 
| 416 415 | 
             
                  end
         | 
| 417 416 | 
             
                end
         | 
| 418 417 |  | 
| 419 418 | 
             
                context "honeybadger_context_data" do
         | 
| 420 | 
            -
                   | 
| 419 | 
            +
                  before do
         | 
| 420 | 
            +
                    allow(ExceptionHandling.logger).to receive(:current_context_for_thread).and_return({ cuid: 'ABCD' })
         | 
| 421 | 
            +
                  end
         | 
| 422 | 
            +
             | 
| 423 | 
            +
                  it "include thread_context when log_context: is nil" do
         | 
| 424 | 
            +
                    exception_with_nil_message = RuntimeError.new(nil)
         | 
| 425 | 
            +
                    allow(exception_with_nil_message).to receive(:message).and_return(nil)
         | 
| 426 | 
            +
                    exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp)
         | 
| 427 | 
            +
                    honeybadger_context_data = exception_info.honeybadger_context_data
         | 
| 428 | 
            +
                    expect(honeybadger_context_data[:log_context]).to eq({ "cuid" => 'ABCD' })
         | 
| 429 | 
            +
                  end
         | 
| 430 | 
            +
             | 
| 431 | 
            +
                  it "include thread context merged with log_context:" do
         | 
| 432 | 
            +
                    exception_with_nil_message = RuntimeError.new(nil)
         | 
| 433 | 
            +
                    allow(exception_with_nil_message).to receive(:message).and_return(nil)
         | 
| 434 | 
            +
                    exception_info = ExceptionInfo.new(exception_with_nil_message, @exception_context, @timestamp, log_context: { url: 'http://example.com' })
         | 
| 435 | 
            +
                    honeybadger_context_data = exception_info.honeybadger_context_data
         | 
| 436 | 
            +
                    expect(honeybadger_context_data[:log_context]).to eq({ "cuid" => 'ABCD', "url" => 'http://example.com' })
         | 
| 437 | 
            +
                  end
         | 
| 438 | 
            +
             | 
| 439 | 
            +
                  it "return the error details and relevant context data to be used as honeybadger notification context while filtering sensitive data" do
         | 
| 421 440 | 
             
                    env = { server: "fe98" }
         | 
| 422 441 | 
             
                    parameters = { advertiser_id: 435 }
         | 
| 423 442 | 
             
                    session = { username: "jsmith" }
         | 
| 424 443 | 
             
                    request_uri = "host/path"
         | 
| 425 444 | 
             
                    controller = create_dummy_controller(env, parameters, session, request_uri)
         | 
| 426 | 
            -
                     | 
| 445 | 
            +
                    allow(ExceptionHandling).to receive(:server_name).and_return("invoca_fe98")
         | 
| 427 446 |  | 
| 428 447 | 
             
                    exception = StandardError.new("Some Exception")
         | 
| 429 448 | 
             
                    exception.set_backtrace([
         | 
| 430 | 
            -
                                              " | 
| 431 | 
            -
                                              " | 
| 449 | 
            +
                                              "spec/unit/exception_handling_test.rb:847:in `exception_1'",
         | 
| 450 | 
            +
                                              "spec/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
         | 
| 432 451 | 
             
                                            ])
         | 
| 433 452 | 
             
                    exception_context = { "SERVER_NAME" => "exceptional.com" }
         | 
| 434 453 | 
             
                    data_callback = ->(data) do
         | 
| @@ -446,7 +465,6 @@ module ExceptionHandling | |
| 446 465 | 
             
                      exception_context: { "SERVER_NAME" => "exceptional.com" },
         | 
| 447 466 | 
             
                      server: "invoca_fe98",
         | 
| 448 467 | 
             
                      scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
         | 
| 449 | 
            -
                      notes: "this is used by a test",
         | 
| 450 468 | 
             
                      user_details: { "username" => "jsmith" },
         | 
| 451 469 | 
             
                      request: {
         | 
| 452 470 | 
             
                        "params" => { "advertiser_id" => 435 },
         | 
| @@ -461,28 +479,30 @@ module ExceptionHandling | |
| 461 479 | 
             
                        "SERVER_NAME" => "exceptional.com"
         | 
| 462 480 | 
             
                      },
         | 
| 463 481 | 
             
                      backtrace: [
         | 
| 464 | 
            -
                        " | 
| 465 | 
            -
                        " | 
| 482 | 
            +
                        "spec/unit/exception_handling_test.rb:847:in `exception_1'",
         | 
| 483 | 
            +
                        "spec/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
         | 
| 466 484 | 
             
                      ],
         | 
| 467 | 
            -
                      event_response: "Event successfully received"
         | 
| 485 | 
            +
                      event_response: "Event successfully received",
         | 
| 486 | 
            +
                      log_context: { "cuid" => "ABCD" },
         | 
| 487 | 
            +
                      notes: "this is used by a test"
         | 
| 468 488 | 
             
                    }
         | 
| 469 | 
            -
                     | 
| 489 | 
            +
                    expect(exception_info.honeybadger_context_data).to eq(expected_data)
         | 
| 470 490 | 
             
                  end
         | 
| 471 491 |  | 
| 472 492 | 
             
                  [['Hash',   { 'cookie' => 'cookie_context' }],
         | 
| 473 493 | 
             
                   ['String', 'Entering Error State'],
         | 
| 474 494 | 
             
                   ['Array',  ['Error1', 'Error2']]].each do |klass, value|
         | 
| 475 | 
            -
                     | 
| 495 | 
            +
                    it "extract context from exception_context when it is a #{klass}" do
         | 
| 476 496 | 
             
                      exception = StandardError.new("Exception")
         | 
| 477 497 | 
             
                      exception_context = value
         | 
| 478 498 | 
             
                      exception_info = ExceptionInfo.new(exception, exception_context, Time.now)
         | 
| 479 499 |  | 
| 480 | 
            -
                       | 
| 481 | 
            -
                       | 
| 500 | 
            +
                      expect(value.class.name).to eq(klass)
         | 
| 501 | 
            +
                      expect(exception_info.honeybadger_context_data[:exception_context]).to eq(value)
         | 
| 482 502 | 
             
                    end
         | 
| 483 503 | 
             
                  end
         | 
| 484 504 |  | 
| 485 | 
            -
                   | 
| 505 | 
            +
                  it "filter out sensitive data from exception context such as [password, password_confirmation, oauth_token]" do
         | 
| 486 506 | 
             
                    sensitive_data = {
         | 
| 487 507 | 
             
                      "password" => "super_secret",
         | 
| 488 508 | 
             
                      "password_confirmation" => "super_secret_confirmation",
         | 
| @@ -517,15 +537,14 @@ module ExceptionHandling | |
| 517 537 | 
             
                      }
         | 
| 518 538 | 
             
                    }.merge(expected_sensitive_data)
         | 
| 519 539 |  | 
| 520 | 
            -
                     | 
| 540 | 
            +
                    expect(exception_info.honeybadger_context_data[:exception_context]).to eq(expected_exception_context)
         | 
| 521 541 | 
             
                  end
         | 
| 522 542 |  | 
| 523 | 
            -
                   | 
| 543 | 
            +
                  it "omit context if exception_context is empty" do
         | 
| 524 544 | 
             
                    exception = StandardError.new("Exception")
         | 
| 525 545 | 
             
                    exception_context = ""
         | 
| 526 546 | 
             
                    exception_info = ExceptionInfo.new(exception, exception_context, Time.now)
         | 
| 527 | 
            -
             | 
| 528 | 
            -
                    assert_nil exception_info.honeybadger_context_data[:exception_context]
         | 
| 547 | 
            +
                    expect(exception_info.honeybadger_context_data[:exception_context]).to be_nil
         | 
| 529 548 | 
             
                  end
         | 
| 530 549 | 
             
                end
         | 
| 531 550 |  |