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,51 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require File.expand_path('../../spec_helper',  __dir__)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ExceptionHandling
         | 
| 6 | 
            +
              describe Sensu do
         | 
| 7 | 
            +
                context "#generate_event" do
         | 
| 8 | 
            +
                  it "create an event" do
         | 
| 9 | 
            +
                    expect(ExceptionHandling::Sensu).to receive(:send_event).with(name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye")
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it "add the sensu prefix" do
         | 
| 15 | 
            +
                    ExceptionHandling.sensu_prefix = "cnn_"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    expect(ExceptionHandling::Sensu).to receive(:send_event).with(name: "cnn_world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye")
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  it "allow the level to be set to critical" do
         | 
| 23 | 
            +
                    expect(ExceptionHandling::Sensu).to receive(:send_event).with(name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 2)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye", :critical)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  it "error if an invalid level is supplied" do
         | 
| 29 | 
            +
                    expect(ExceptionHandling::Sensu).to_not receive(:send_event)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    expect do
         | 
| 32 | 
            +
                      ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye", :hair_on_fire)
         | 
| 33 | 
            +
                    end.to raise_exception(RuntimeError, /Invalid alert level/)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                context "#send_event" do
         | 
| 38 | 
            +
                  before do
         | 
| 39 | 
            +
                    @event = { name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1 }
         | 
| 40 | 
            +
                    @socket = SocketStub.new
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  it "send event json to sensu client" do
         | 
| 44 | 
            +
                    expect_any_instance_of(Addrinfo).to receive(:connect).with(any_args) { @socket }
         | 
| 45 | 
            +
                    ExceptionHandling::Sensu.send_event(@event)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    expect(@socket.sent.first).to eq(@event.to_json)
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -1,14 +1,14 @@ | |
| 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 | 
            +
            describe ExceptionHandling do
         | 
| 8 8 | 
             
              include ControllerHelpers
         | 
| 9 9 | 
             
              include ExceptionHelpers
         | 
| 10 10 |  | 
| 11 | 
            -
               | 
| 11 | 
            +
              before do
         | 
| 12 12 | 
             
                @fail_count = 0
         | 
| 13 13 | 
             
              end
         | 
| 14 14 |  | 
| @@ -108,194 +108,228 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 108 108 | 
             
              end
         | 
| 109 109 |  | 
| 110 110 | 
             
              context "with warn and honeybadger notify stubbed" do
         | 
| 111 | 
            -
                 | 
| 112 | 
            -
                   | 
| 113 | 
            -
                   | 
| 111 | 
            +
                before do
         | 
| 112 | 
            +
                  allow(ExceptionHandling).to receive(:warn).with(anything)
         | 
| 113 | 
            +
                  allow(Honeybadger).to receive(:notify).with(anything)
         | 
| 114 114 | 
             
                end
         | 
| 115 115 |  | 
| 116 116 | 
             
                context "with logger stashed" do
         | 
| 117 | 
            -
                   | 
| 118 | 
            -
                   | 
| 117 | 
            +
                  before { @original_logger = ExceptionHandling.logger }
         | 
| 118 | 
            +
                  after { ExceptionHandling.logger = @original_logger }
         | 
| 119 119 |  | 
| 120 | 
            -
                   | 
| 120 | 
            +
                  it "store logger as-is if it has ContextualLogger::Mixin" do
         | 
| 121 121 | 
             
                    logger = Logger.new('/dev/null')
         | 
| 122 122 | 
             
                    logger.extend(ContextualLogger::LoggerMixin)
         | 
| 123 123 | 
             
                    ancestors = logger.singleton_class.ancestors.*.name
         | 
| 124 124 |  | 
| 125 125 | 
             
                    ExceptionHandling.logger = logger
         | 
| 126 | 
            -
                     | 
| 126 | 
            +
                    expect(ExceptionHandling.logger.singleton_class.ancestors.*.name).to eq(ancestors)
         | 
| 127 127 | 
             
                  end
         | 
| 128 128 |  | 
| 129 | 
            -
                   | 
| 130 | 
            -
                     | 
| 129 | 
            +
                  it "allow logger = nil (no deprecation warning)" do
         | 
| 130 | 
            +
                    expect(STDERR).to receive(:puts).with(/DEPRECATION WARNING/).never
         | 
| 131 131 | 
             
                    ExceptionHandling.logger = nil
         | 
| 132 132 | 
             
                  end
         | 
| 133 133 |  | 
| 134 | 
            -
                   | 
| 135 | 
            -
                     | 
| 134 | 
            +
                  it "[deprecated] mix in ContextualLogger::Mixin if not there" do
         | 
| 135 | 
            +
                    expect(STDERR).to receive(:puts).with(/DEPRECATION WARNING: implicit extend with ContextualLogger::LoggerMixin is deprecated and will be removed from exception_handling 3\.0/)
         | 
| 136 136 | 
             
                    logger = Logger.new('/dev/null')
         | 
| 137 137 | 
             
                    ancestors = logger.singleton_class.ancestors.*.name
         | 
| 138 138 |  | 
| 139 139 | 
             
                    ExceptionHandling.logger = logger
         | 
| 140 | 
            -
                     | 
| 141 | 
            -
                     | 
| 140 | 
            +
                    expect(ExceptionHandling.logger.singleton_class.ancestors.*.name).to_not eq(ancestors)
         | 
| 141 | 
            +
                    expect(ExceptionHandling.logger).to be_kind_of(ContextualLogger::LoggerMixin)
         | 
| 142 142 | 
             
                  end
         | 
| 143 143 | 
             
                end
         | 
| 144 144 |  | 
| 145 145 | 
             
                context "#log_error" do
         | 
| 146 | 
            -
                   | 
| 146 | 
            +
                  it "take in additional logging context hash and pass it to the logger" do
         | 
| 147 147 | 
             
                    ExceptionHandling.log_error('This is an Error', 'This is the prefix context', service_name: 'exception_handling')
         | 
| 148 | 
            -
                     | 
| 149 | 
            -
                     | 
| 150 | 
            -
                     | 
| 148 | 
            +
                    expect(logged_excluding_reload_filter.last[:message]).to match(/This is an Error/)
         | 
| 149 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
         | 
| 150 | 
            +
                    expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  it "log with Severity::FATAL" do
         | 
| 154 | 
            +
                    ExceptionHandling.log_error('This is a Warning', service_name: 'exception_handling')
         | 
| 155 | 
            +
                    expect('FATAL').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 151 156 | 
             
                  end
         | 
| 152 157 | 
             
                end
         | 
| 153 158 |  | 
| 154 159 | 
             
                context "#log_warning" do
         | 
| 155 | 
            -
                   | 
| 156 | 
            -
                     | 
| 157 | 
            -
                       | 
| 160 | 
            +
                  it "have empty array as a backtrace" do
         | 
| 161 | 
            +
                    expect(ExceptionHandling).to receive(:log_error).with((ExceptionHandling::Warning), anything) do |error|
         | 
| 162 | 
            +
                      expect(error.backtrace).to eq([])
         | 
| 158 163 | 
             
                    end
         | 
| 159 164 | 
             
                    ExceptionHandling.log_warning('Now with empty array as a backtrace!')
         | 
| 160 165 | 
             
                  end
         | 
| 161 166 |  | 
| 162 | 
            -
                   | 
| 167 | 
            +
                  it "take in additional key word args as logging context and pass them to the logger" do
         | 
| 168 | 
            +
                    ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
         | 
| 169 | 
            +
                    expect(logged_excluding_reload_filter.last[:message]).to match(/This is a Warning/)
         | 
| 170 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
         | 
| 171 | 
            +
                    expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  it "log with Severity::WARN" do
         | 
| 163 175 | 
             
                    ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
         | 
| 164 | 
            -
                     | 
| 165 | 
            -
                    assert_not_empty logged_excluding_reload_filter.last[:context]
         | 
| 166 | 
            -
                    assert_equal logged_excluding_reload_filter.last[:context], service_name: 'exception_handling'
         | 
| 176 | 
            +
                    expect('WARN').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 167 177 | 
             
                  end
         | 
| 168 178 | 
             
                end
         | 
| 169 179 |  | 
| 170 180 | 
             
                context "#log_info" do
         | 
| 171 | 
            -
                   | 
| 172 | 
            -
                    ExceptionHandling. | 
| 173 | 
            -
                     | 
| 174 | 
            -
                     | 
| 175 | 
            -
                     | 
| 181 | 
            +
                  it "take in additional key word args as logging context and pass them to the logger" do
         | 
| 182 | 
            +
                    ExceptionHandling.log_info('This is an Info', service_name: 'exception_handling')
         | 
| 183 | 
            +
                    expect(logged_excluding_reload_filter.last[:message]).to match(/This is an Info/)
         | 
| 184 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
         | 
| 185 | 
            +
                    expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  it "log with Severity::INFO" do
         | 
| 189 | 
            +
                    ExceptionHandling.log_info('This is a Warning', service_name: 'exception_handling')
         | 
| 190 | 
            +
                    expect('INFO').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 176 191 | 
             
                  end
         | 
| 177 192 | 
             
                end
         | 
| 178 193 |  | 
| 179 194 | 
             
                context "#log_debug" do
         | 
| 180 | 
            -
                   | 
| 181 | 
            -
                    ExceptionHandling. | 
| 182 | 
            -
                     | 
| 183 | 
            -
                     | 
| 184 | 
            -
                     | 
| 195 | 
            +
                  it "take in additional key word args as logging context and pass them to the logger" do
         | 
| 196 | 
            +
                    ExceptionHandling.log_debug('This is a Debug', service_name: 'exception_handling')
         | 
| 197 | 
            +
                    expect(logged_excluding_reload_filter.last[:message]).to match(/This is a Debug/)
         | 
| 198 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
         | 
| 199 | 
            +
                    expect(service_name: 'exception_handling').to eq(logged_excluding_reload_filter.last[:context])
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  it "log with Severity::DEBUG" do
         | 
| 203 | 
            +
                    ExceptionHandling.log_debug('This is a Warning', service_name: 'exception_handling')
         | 
| 204 | 
            +
                    expect('DEBUG').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                context "#write_exception_to_log" do
         | 
| 209 | 
            +
                  it "log warnings with Severity::WARN" do
         | 
| 210 | 
            +
                    warning = ExceptionHandling::Warning.new('This is a Warning')
         | 
| 211 | 
            +
                    ExceptionHandling.write_exception_to_log(warning, '', Time.now.to_i, service_name: 'exception_handling')
         | 
| 212 | 
            +
                    expect('WARN').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  it "log everything else with Severity::FATAL" do
         | 
| 216 | 
            +
                    error = RuntimeError.new('This is a runtime error')
         | 
| 217 | 
            +
                    ExceptionHandling.write_exception_to_log(error, '', Time.now.to_i, service_name: 'exception_handling')
         | 
| 218 | 
            +
                    expect('FATAL').to eq(logged_excluding_reload_filter.last[:severity])
         | 
| 185 219 | 
             
                  end
         | 
| 186 220 | 
             
                end
         | 
| 187 221 |  | 
| 188 222 | 
             
                context "configuration with custom_data_hook or post_log_error_hook" do
         | 
| 189 | 
            -
                   | 
| 223 | 
            +
                  after do
         | 
| 190 224 | 
             
                    ExceptionHandling.custom_data_hook = nil
         | 
| 191 225 | 
             
                    ExceptionHandling.post_log_error_hook = nil
         | 
| 192 226 | 
             
                  end
         | 
| 193 227 |  | 
| 194 | 
            -
                   | 
| 228 | 
            +
                  it "support a custom_data_hook" do
         | 
| 195 229 | 
             
                    capture_notifications
         | 
| 196 230 |  | 
| 197 231 | 
             
                    ExceptionHandling.custom_data_hook = method(:append_organization_info_config)
         | 
| 198 232 | 
             
                    ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
         | 
| 199 233 |  | 
| 200 | 
            -
                     | 
| 234 | 
            +
                    expect(sent_notifications.last.enhanced_data['user_details'].to_s).to match(/Invoca Engineering Dept./)
         | 
| 201 235 | 
             
                  end
         | 
| 202 236 |  | 
| 203 | 
            -
                   | 
| 237 | 
            +
                  it "support a log_error hook, and pass exception_data, treat_like_warning, and logged_to_honeybadger to it" do
         | 
| 204 238 | 
             
                    @honeybadger_status = nil
         | 
| 205 239 | 
             
                    ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 206 240 |  | 
| 207 241 | 
             
                    notify_args = []
         | 
| 208 | 
            -
                     | 
| 242 | 
            +
                    expect(Honeybadger).to receive(:notify).with(any_args) { |info| notify_args << info; '06220c5a-b471-41e5-baeb-de247da45a56' }
         | 
| 209 243 | 
             
                    ExceptionHandling.ensure_safe("context") { raise "Some Exception" }
         | 
| 210 | 
            -
                     | 
| 211 | 
            -
                     | 
| 212 | 
            -
                     | 
| 244 | 
            +
                    expect(@fail_count).to eq(1)
         | 
| 245 | 
            +
                    expect(@treat_like_warning).to eq(false)
         | 
| 246 | 
            +
                    expect(@honeybadger_status).to eq(:success)
         | 
| 213 247 |  | 
| 214 | 
            -
                     | 
| 215 | 
            -
                     | 
| 216 | 
            -
                     | 
| 248 | 
            +
                    expect(@callback_data["notes"]).to eq("this is used by a test")
         | 
| 249 | 
            +
                    expect(notify_args.size).to eq(1), notify_args.inspect
         | 
| 250 | 
            +
                    expect(notify_args.last[:context].to_s).to match(/this is used by a test/)
         | 
| 217 251 | 
             
                  end
         | 
| 218 252 |  | 
| 219 | 
            -
                   | 
| 253 | 
            +
                  it "plumb treat_like_warning and logged_to_honeybadger to log error hook" do
         | 
| 220 254 | 
             
                    @honeybadger_status = nil
         | 
| 221 255 | 
             
                    ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 222 256 | 
             
                    ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true)
         | 
| 223 | 
            -
                     | 
| 224 | 
            -
                     | 
| 225 | 
            -
                     | 
| 257 | 
            +
                    expect(@fail_count).to eq(1)
         | 
| 258 | 
            +
                    expect(@treat_like_warning).to eq(true)
         | 
| 259 | 
            +
                    expect(@honeybadger_status).to eq(:skipped)
         | 
| 226 260 | 
             
                  end
         | 
| 227 261 |  | 
| 228 | 
            -
                   | 
| 262 | 
            +
                  it "include logging context in the exception data" do
         | 
| 229 263 | 
             
                    ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 230 264 | 
             
                    ExceptionHandling.log_error(StandardError.new("Some Exception"), "mooo", treat_like_warning: true, log_context_test: "contextual_logging")
         | 
| 231 265 |  | 
| 232 266 | 
             
                    expected_log_context = {
         | 
| 233 267 | 
             
                      "log_context_test" => "contextual_logging"
         | 
| 234 268 | 
             
                    }
         | 
| 235 | 
            -
                     | 
| 269 | 
            +
                    expect(@callback_data[:log_context]).to eq(expected_log_context)
         | 
| 236 270 | 
             
                  end
         | 
| 237 271 |  | 
| 238 | 
            -
                   | 
| 272 | 
            +
                  it "support rescue exceptions from a log_error hook" do
         | 
| 239 273 | 
             
                    ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
         | 
| 240 274 | 
             
                    log_info_messages = []
         | 
| 241 | 
            -
                     | 
| 275 | 
            +
                    allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
         | 
| 242 276 | 
             
                      log_info_messages << message
         | 
| 243 277 | 
             
                    end
         | 
| 244 | 
            -
                     | 
| 245 | 
            -
                     | 
| 278 | 
            +
                    expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.to_not raise_error
         | 
| 279 | 
            +
                    expect(log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }).to be_truthy
         | 
| 246 280 | 
             
                  end
         | 
| 247 281 |  | 
| 248 | 
            -
                   | 
| 282 | 
            +
                  it "handle nil message exceptions resulting from the log_error hook" do
         | 
| 249 283 | 
             
                    ExceptionHandling.post_log_error_hook = method(:log_error_callback_returns_nil_message_exception)
         | 
| 250 284 | 
             
                    log_info_messages = []
         | 
| 251 | 
            -
                     | 
| 285 | 
            +
                    allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
         | 
| 252 286 | 
             
                      log_info_messages << message
         | 
| 253 287 | 
             
                    end
         | 
| 254 | 
            -
                     | 
| 255 | 
            -
                     | 
| 288 | 
            +
                    expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.to_not raise_error
         | 
| 289 | 
            +
                    expect(log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }).to be_truthy
         | 
| 256 290 | 
             
                  end
         | 
| 257 291 |  | 
| 258 | 
            -
                   | 
| 292 | 
            +
                  it "handle nil message exceptions resulting from the custom data hook" do
         | 
| 259 293 | 
             
                    ExceptionHandling.custom_data_hook = method(:custom_data_callback_returns_nil_message_exception)
         | 
| 260 294 | 
             
                    log_info_messages = []
         | 
| 261 | 
            -
                     | 
| 295 | 
            +
                    allow(ExceptionHandling.logger).to receive(:info).with(any_args) do |message, _|
         | 
| 262 296 | 
             
                      log_info_messages << message
         | 
| 263 297 | 
             
                    end
         | 
| 264 | 
            -
                     | 
| 265 | 
            -
                     | 
| 298 | 
            +
                    expect { ExceptionHandling.ensure_safe("mooo") { raise "Some Exception" } }.not_to raise_error
         | 
| 299 | 
            +
                    expect(log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }).to be_truthy
         | 
| 266 300 | 
             
                  end
         | 
| 267 301 | 
             
                end
         | 
| 268 302 |  | 
| 269 303 | 
             
                context "Exception Handling" do
         | 
| 270 304 | 
             
                  context "default_metric_name" do
         | 
| 271 305 | 
             
                    context "when metric_name is present in exception_data" do
         | 
| 272 | 
            -
                       | 
| 306 | 
            +
                      it "include metric_name in resulting metric name" do
         | 
| 273 307 | 
             
                        exception = StandardError.new('this is an exception')
         | 
| 274 308 | 
             
                        metric    = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true)
         | 
| 275 | 
            -
                         | 
| 309 | 
            +
                        expect(metric).to eq('exception_handling.special_metric')
         | 
| 276 310 | 
             
                      end
         | 
| 277 311 | 
             
                    end
         | 
| 278 312 |  | 
| 279 313 | 
             
                    context "when metric_name is not present in exception_data" do
         | 
| 280 | 
            -
                       | 
| 314 | 
            +
                      it "return exception_handling.warning when using log warning" do
         | 
| 281 315 | 
             
                        warning = ExceptionHandling::Warning.new('this is a warning')
         | 
| 282 316 | 
             
                        metric  = ExceptionHandling.default_metric_name({}, warning, false)
         | 
| 283 | 
            -
                         | 
| 317 | 
            +
                        expect(metric).to eq('exception_handling.warning')
         | 
| 284 318 | 
             
                      end
         | 
| 285 319 |  | 
| 286 | 
            -
                       | 
| 320 | 
            +
                      it "return exception_handling.exception when using log error" do
         | 
| 287 321 | 
             
                        exception = StandardError.new('this is an exception')
         | 
| 288 322 | 
             
                        metric    = ExceptionHandling.default_metric_name({}, exception, false)
         | 
| 289 | 
            -
                         | 
| 323 | 
            +
                        expect(metric).to eq('exception_handling.exception')
         | 
| 290 324 | 
             
                      end
         | 
| 291 325 |  | 
| 292 326 | 
             
                      context "when using log error with treat_like_warning" do
         | 
| 293 | 
            -
                         | 
| 327 | 
            +
                        it "return exception_handling.unforwarded_exception when exception not present" do
         | 
| 294 328 | 
             
                          metric = ExceptionHandling.default_metric_name({}, nil, true)
         | 
| 295 | 
            -
                           | 
| 329 | 
            +
                          expect(metric).to eq('exception_handling.unforwarded_exception')
         | 
| 296 330 | 
             
                        end
         | 
| 297 331 |  | 
| 298 | 
            -
                         | 
| 332 | 
            +
                        it "return exception_handling.unforwarded_exception with exception classname when exception is present" do
         | 
| 299 333 | 
             
                          module SomeModule
         | 
| 300 334 | 
             
                            class SomeException < StandardError
         | 
| 301 335 | 
             
                            end
         | 
| @@ -303,43 +337,43 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 303 337 |  | 
| 304 338 | 
             
                          exception = SomeModule::SomeException.new('this is an exception')
         | 
| 305 339 | 
             
                          metric    = ExceptionHandling.default_metric_name({}, exception, true)
         | 
| 306 | 
            -
                           | 
| 340 | 
            +
                          expect(metric).to eq('exception_handling.unforwarded_exception_SomeException')
         | 
| 307 341 | 
             
                        end
         | 
| 308 342 | 
             
                      end
         | 
| 309 343 | 
             
                    end
         | 
| 310 344 | 
             
                  end
         | 
| 311 345 |  | 
| 312 346 | 
             
                  context "default_honeybadger_metric_name" do
         | 
| 313 | 
            -
                     | 
| 347 | 
            +
                    it "return exception_handling.honeybadger.success when status is :success" do
         | 
| 314 348 | 
             
                      metric = ExceptionHandling.default_honeybadger_metric_name(:success)
         | 
| 315 | 
            -
                       | 
| 349 | 
            +
                      expect(metric).to eq('exception_handling.honeybadger.success')
         | 
| 316 350 | 
             
                    end
         | 
| 317 351 |  | 
| 318 | 
            -
                     | 
| 352 | 
            +
                    it "return exception_handling.honeybadger.failure when status is :failure" do
         | 
| 319 353 | 
             
                      metric = ExceptionHandling.default_honeybadger_metric_name(:failure)
         | 
| 320 | 
            -
                       | 
| 354 | 
            +
                      expect(metric).to eq('exception_handling.honeybadger.failure')
         | 
| 321 355 | 
             
                    end
         | 
| 322 356 |  | 
| 323 | 
            -
                     | 
| 357 | 
            +
                    it "return exception_handling.honeybadger.skipped when status is :skipped" do
         | 
| 324 358 | 
             
                      metric = ExceptionHandling.default_honeybadger_metric_name(:skipped)
         | 
| 325 | 
            -
                       | 
| 359 | 
            +
                      expect(metric).to eq('exception_handling.honeybadger.skipped')
         | 
| 326 360 | 
             
                    end
         | 
| 327 361 |  | 
| 328 | 
            -
                     | 
| 362 | 
            +
                    it "return exception_handling.honeybadger.unknown_status when status is not recognized" do
         | 
| 329 363 | 
             
                      metric = ExceptionHandling.default_honeybadger_metric_name(nil)
         | 
| 330 | 
            -
                       | 
| 364 | 
            +
                      expect(metric).to eq('exception_handling.honeybadger.unknown_status')
         | 
| 331 365 | 
             
                    end
         | 
| 332 366 | 
             
                  end
         | 
| 333 367 |  | 
| 334 368 | 
             
                  context "ExceptionHandling.ensure_safe" do
         | 
| 335 | 
            -
                     | 
| 336 | 
            -
                       | 
| 369 | 
            +
                    it "log an exception with call stack if an exception is raised." do
         | 
| 370 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 337 371 | 
             
                      ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
         | 
| 338 372 | 
             
                    end
         | 
| 339 373 |  | 
| 340 374 | 
             
                    if ActionView::VERSION::MAJOR >= 5
         | 
| 341 | 
            -
                       | 
| 342 | 
            -
                         | 
| 375 | 
            +
                      it "log an exception with call stack if an ActionView template exception is raised." do
         | 
| 376 | 
            +
                        expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, anything)
         | 
| 343 377 | 
             
                        ExceptionHandling.ensure_safe do
         | 
| 344 378 | 
             
                          begin
         | 
| 345 379 | 
             
                            # Rails 5 made the switch from ActionView::TemplateError taking in the original exception
         | 
| @@ -351,297 +385,292 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 351 385 | 
             
                        end
         | 
| 352 386 | 
             
                      end
         | 
| 353 387 | 
             
                    else
         | 
| 354 | 
            -
                       | 
| 355 | 
            -
                         | 
| 388 | 
            +
                      it "log an exception with call stack if an ActionView template exception is raised." do
         | 
| 389 | 
            +
                        expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:\d+\) \nActionView::Template::Error: \(blah\):\n /, anything)
         | 
| 356 390 | 
             
                        ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, ArgumentError.new("blah")) }
         | 
| 357 391 | 
             
                      end
         | 
| 358 392 | 
             
                    end
         | 
| 359 393 |  | 
| 360 | 
            -
                     | 
| 361 | 
            -
                       | 
| 394 | 
            +
                    it "should not log an exception if an exception is not raised." do
         | 
| 395 | 
            +
                      expect(ExceptionHandling.logger).to_not receive(:fatal)
         | 
| 362 396 | 
             
                      ExceptionHandling.ensure_safe { ; }
         | 
| 363 397 | 
             
                    end
         | 
| 364 398 |  | 
| 365 | 
            -
                     | 
| 366 | 
            -
                       | 
| 399 | 
            +
                    it "return its value if used during an assignment" do
         | 
| 400 | 
            +
                      expect(ExceptionHandling.logger).to_not receive(:fatal)
         | 
| 367 401 | 
             
                      b = ExceptionHandling.ensure_safe { 5 }
         | 
| 368 | 
            -
                       | 
| 402 | 
            +
                      expect(b).to eq(5)
         | 
| 369 403 | 
             
                    end
         | 
| 370 404 |  | 
| 371 | 
            -
                     | 
| 372 | 
            -
                       | 
| 405 | 
            +
                    it "return nil if an exception is raised during an assignment" do
         | 
| 406 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 373 407 | 
             
                      b = ExceptionHandling.ensure_safe { raise ArgumentError, "blah" }
         | 
| 374 | 
            -
                       | 
| 408 | 
            +
                      expect(b).to be_nil
         | 
| 375 409 | 
             
                    end
         | 
| 376 410 |  | 
| 377 | 
            -
                     | 
| 378 | 
            -
                       | 
| 411 | 
            +
                    it "allow a message to be appended to the error when logged." do
         | 
| 412 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 379 413 | 
             
                      b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError, "blah" }
         | 
| 380 | 
            -
                       | 
| 414 | 
            +
                      expect(b).to be_nil
         | 
| 381 415 | 
             
                    end
         | 
| 382 416 |  | 
| 383 | 
            -
                     | 
| 384 | 
            -
                       | 
| 417 | 
            +
                    it "only rescue StandardError and descendents" do
         | 
| 418 | 
            +
                      expect { ExceptionHandling.ensure_safe("mooo") { raise Exception } }.to raise_exception(Exception)
         | 
| 385 419 |  | 
| 386 | 
            -
                       | 
| 420 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nStandardError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 387 421 |  | 
| 388 422 | 
             
                      b = ExceptionHandling.ensure_safe("mooo") { raise StandardError, "blah" }
         | 
| 389 | 
            -
                       | 
| 423 | 
            +
                      expect(b).to be_nil
         | 
| 390 424 | 
             
                    end
         | 
| 391 425 | 
             
                  end
         | 
| 392 426 |  | 
| 393 427 | 
             
                  context "ExceptionHandling.ensure_completely_safe" do
         | 
| 394 | 
            -
                     | 
| 395 | 
            -
                       | 
| 428 | 
            +
                    it "log an exception if an exception is raised." do
         | 
| 429 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 396 430 | 
             
                      ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
         | 
| 397 431 | 
             
                    end
         | 
| 398 432 |  | 
| 399 | 
            -
                     | 
| 400 | 
            -
                       | 
| 433 | 
            +
                    it "should not log an exception if an exception is not raised." do
         | 
| 434 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
         | 
| 401 435 | 
             
                      ExceptionHandling.ensure_completely_safe { ; }
         | 
| 402 436 | 
             
                    end
         | 
| 403 437 |  | 
| 404 | 
            -
                     | 
| 405 | 
            -
                       | 
| 438 | 
            +
                    it "return its value if used during an assignment" do
         | 
| 439 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).exactly(0)
         | 
| 406 440 | 
             
                      b = ExceptionHandling.ensure_completely_safe { 5 }
         | 
| 407 | 
            -
                       | 
| 441 | 
            +
                      expect(b).to eq(5)
         | 
| 408 442 | 
             
                    end
         | 
| 409 443 |  | 
| 410 | 
            -
                     | 
| 411 | 
            -
                       | 
| 444 | 
            +
                    it "return nil if an exception is raised during an assignment" do
         | 
| 445 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything) { nil }
         | 
| 412 446 | 
             
                      b = ExceptionHandling.ensure_completely_safe { raise ArgumentError, "blah" }
         | 
| 413 | 
            -
                       | 
| 447 | 
            +
                      expect(b).to be_nil
         | 
| 414 448 | 
             
                    end
         | 
| 415 449 |  | 
| 416 | 
            -
                     | 
| 417 | 
            -
                       | 
| 450 | 
            +
                    it "allow a message to be appended to the error when logged." do
         | 
| 451 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/mooo\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 418 452 | 
             
                      b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError, "blah" }
         | 
| 419 | 
            -
                       | 
| 453 | 
            +
                      expect(b).to be_nil
         | 
| 420 454 | 
             
                    end
         | 
| 421 455 |  | 
| 422 | 
            -
                     | 
| 423 | 
            -
                       | 
| 456 | 
            +
                    it "rescue any instance or child of Exception" do
         | 
| 457 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 424 458 | 
             
                      ExceptionHandling.ensure_completely_safe { raise Exception, "blah" }
         | 
| 425 459 | 
             
                    end
         | 
| 426 460 |  | 
| 427 | 
            -
                     | 
| 461 | 
            +
                    it "not rescue the special exceptions that Ruby uses" do
         | 
| 428 462 | 
             
                      [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
         | 
| 429 | 
            -
                         | 
| 463 | 
            +
                        expect do
         | 
| 430 464 | 
             
                          ExceptionHandling.ensure_completely_safe do
         | 
| 431 465 | 
             
                            raise exception
         | 
| 432 466 | 
             
                          end
         | 
| 433 | 
            -
                        end
         | 
| 467 | 
            +
                        end.to raise_exception(exception)
         | 
| 434 468 | 
             
                      end
         | 
| 435 469 | 
             
                    end
         | 
| 436 470 | 
             
                  end
         | 
| 437 471 |  | 
| 438 472 | 
             
                  context "ExceptionHandling.ensure_escalation" do
         | 
| 439 | 
            -
                     | 
| 473 | 
            +
                    before do
         | 
| 440 474 | 
             
                      capture_notifications
         | 
| 441 475 | 
             
                      ActionMailer::Base.deliveries.clear
         | 
| 442 476 | 
             
                    end
         | 
| 443 477 |  | 
| 444 | 
            -
                     | 
| 445 | 
            -
                       | 
| 478 | 
            +
                    it "log the exception as usual and send the proper email" do
         | 
| 479 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 446 480 | 
             
                      ExceptionHandling.ensure_escalation("Favorite Feature") { raise ArgumentError, "blah" }
         | 
| 447 | 
            -
                       | 
| 448 | 
            -
                       | 
| 481 | 
            +
                      expect(ActionMailer::Base.deliveries.count).to eq(1)
         | 
| 482 | 
            +
                      expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 449 483 |  | 
| 450 484 | 
             
                      email = ActionMailer::Base.deliveries.last
         | 
| 451 | 
            -
                       | 
| 452 | 
            -
                       | 
| 453 | 
            -
                       | 
| 485 | 
            +
                      expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
         | 
| 486 | 
            +
                      expect(email.body.to_s).to match('ArgumentError: blah')
         | 
| 487 | 
            +
                      expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
         | 
| 454 488 | 
             
                    end
         | 
| 455 489 |  | 
| 456 | 
            -
                     | 
| 457 | 
            -
                       | 
| 490 | 
            +
                    it "should not escalate if an exception is not raised." do
         | 
| 491 | 
            +
                      expect(ExceptionHandling.logger).to_not receive(:fatal)
         | 
| 458 492 | 
             
                      ExceptionHandling.ensure_escalation('Ignored') { ; }
         | 
| 459 | 
            -
                       | 
| 493 | 
            +
                      expect(ActionMailer::Base.deliveries.count).to eq(0)
         | 
| 460 494 | 
             
                    end
         | 
| 461 495 |  | 
| 462 | 
            -
                     | 
| 463 | 
            -
                       | 
| 464 | 
            -
                        mock(message).deliver { raise RuntimeError.new, "Delivery Error" }
         | 
| 465 | 
            -
                      end
         | 
| 496 | 
            +
                    it "log if the escalation email cannot be sent" do
         | 
| 497 | 
            +
                      expect_any_instance_of(Mail::Message).to receive(:deliver).and_raise(RuntimeError.new, "Delivery Error")
         | 
| 466 498 | 
             
                      log_fatals = []
         | 
| 467 | 
            -
                       | 
| 468 | 
            -
                         | 
| 499 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(any_args).at_least(:once) do |*args|
         | 
| 500 | 
            +
                        log_fatals << args
         | 
| 469 501 | 
             
                      end
         | 
| 470 502 |  | 
| 471 503 | 
             
                      ExceptionHandling.ensure_escalation("ensure context") { raise ArgumentError, "first_test_exception" }
         | 
| 504 | 
            +
                      expect(log_fatals[0].first).to match(/ArgumentError.*first_test_exception/)
         | 
| 505 | 
            +
                      expect(log_fatals[1].first).to match(/safe_email_deliver.*Delivery Error/m)
         | 
| 472 506 |  | 
| 473 | 
            -
                       | 
| 474 | 
            -
                      assert_match(/safe_email_deliver.*Delivery Error/, log_fatals[1].first)
         | 
| 475 | 
            -
             | 
| 476 | 
            -
                      assert_equal 2, log_fatals.size, log_fatals.inspect
         | 
| 507 | 
            +
                      expect(log_fatals.size).to eq(2), log_fatals.inspect
         | 
| 477 508 |  | 
| 478 | 
            -
                       | 
| 509 | 
            +
                      expect(sent_notifications.size).to eq(1), sent_notifications.inspect # still sent to honeybadger
         | 
| 479 510 | 
             
                    end
         | 
| 480 511 |  | 
| 481 | 
            -
                     | 
| 512 | 
            +
                    it "allow the caller to specify custom recipients" do
         | 
| 482 513 | 
             
                      custom_recipients = ['something@invoca.com']
         | 
| 483 | 
            -
                       | 
| 514 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 484 515 | 
             
                      ExceptionHandling.ensure_escalation("Favorite Feature", custom_recipients) { raise ArgumentError, "blah" }
         | 
| 485 | 
            -
                       | 
| 486 | 
            -
                       | 
| 516 | 
            +
                      expect(ActionMailer::Base.deliveries.count).to eq(1)
         | 
| 517 | 
            +
                      expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 487 518 |  | 
| 488 519 | 
             
                      email = ActionMailer::Base.deliveries.last
         | 
| 489 | 
            -
                       | 
| 490 | 
            -
                       | 
| 491 | 
            -
                       | 
| 492 | 
            -
                       | 
| 520 | 
            +
                      expect(email.subject).to eq("#{ExceptionHandling.email_environment} Escalation: Favorite Feature")
         | 
| 521 | 
            +
                      expect(email.body.to_s).to match('ArgumentError: blah')
         | 
| 522 | 
            +
                      expect(email.body.to_s).to match(ExceptionHandling.last_exception_timestamp.to_s)
         | 
| 523 | 
            +
                      expect(email.to).to eq(custom_recipients)
         | 
| 493 524 | 
             
                    end
         | 
| 494 525 | 
             
                  end
         | 
| 495 526 |  | 
| 496 527 | 
             
                  context "ExceptionHandling.ensure_alert" do
         | 
| 497 | 
            -
                     | 
| 498 | 
            -
                       | 
| 499 | 
            -
                       | 
| 528 | 
            +
                    it "log the exception as usual and fire a sensu event" do
         | 
| 529 | 
            +
                      expect(ExceptionHandling::Sensu).to receive(:generate_event).with("Favorite Feature", "test context\nblah")
         | 
| 530 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 500 531 | 
             
                      ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError, "blah" }
         | 
| 501 532 | 
             
                    end
         | 
| 502 533 |  | 
| 503 | 
            -
                     | 
| 504 | 
            -
                       | 
| 505 | 
            -
                       | 
| 534 | 
            +
                    it "should not send sensu event if an exception is not raised." do
         | 
| 535 | 
            +
                      expect(ExceptionHandling.logger).to_not receive(:fatal)
         | 
| 536 | 
            +
                      expect(ExceptionHandling::Sensu).to_not receive(:generate_event)
         | 
| 506 537 | 
             
                      ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
         | 
| 507 538 | 
             
                    end
         | 
| 508 539 |  | 
| 509 | 
            -
                     | 
| 510 | 
            -
                       | 
| 511 | 
            -
                       | 
| 512 | 
            -
             | 
| 513 | 
            -
                        logger.fatal(/Failed to send/, anything)
         | 
| 514 | 
            -
                      end
         | 
| 540 | 
            +
                    it "log if the sensu event could not be sent" do
         | 
| 541 | 
            +
                      expect(ExceptionHandling::Sensu).to receive(:send_event).with(anything) { raise "Failed to send" }
         | 
| 542 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/first_test_exception/, anything)
         | 
| 543 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to send/, anything)
         | 
| 515 544 | 
             
                      ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError, "first_test_exception" }
         | 
| 516 545 | 
             
                    end
         | 
| 517 546 |  | 
| 518 | 
            -
                     | 
| 519 | 
            -
                       | 
| 547 | 
            +
                    it "log if the exception message is nil" do
         | 
| 548 | 
            +
                      expect(ExceptionHandling::Sensu).to receive(:generate_event).with("some alert", "test context\n")
         | 
| 520 549 | 
             
                      ExceptionHandling.ensure_alert('some alert', 'test context') { raise_exception_with_nil_message }
         | 
| 521 550 | 
             
                    end
         | 
| 522 551 | 
             
                  end
         | 
| 523 552 |  | 
| 524 553 | 
             
                  context "ExceptionHandling.escalate_to_production_support" do
         | 
| 525 | 
            -
                     | 
| 554 | 
            +
                    it "notify production support" do
         | 
| 526 555 | 
             
                      subject = "Runtime Error found!"
         | 
| 527 556 | 
             
                      exception = RuntimeError.new("Test")
         | 
| 528 557 | 
             
                      recipients = ["prodsupport@example.com"]
         | 
| 529 558 |  | 
| 530 | 
            -
                       | 
| 531 | 
            -
                       | 
| 559 | 
            +
                      expect(ExceptionHandling).to receive(:production_support_recipients).and_return(recipients).exactly(2)
         | 
| 560 | 
            +
                      expect(ExceptionHandling).to receive(:escalate).with(subject, exception, ExceptionHandling.last_exception_timestamp, recipients)
         | 
| 532 561 | 
             
                      ExceptionHandling.escalate_to_production_support(exception, subject)
         | 
| 533 562 | 
             
                    end
         | 
| 534 563 | 
             
                  end
         | 
| 535 564 |  | 
| 536 565 | 
             
                  context "exception timestamp" do
         | 
| 537 | 
            -
                     | 
| 566 | 
            +
                    before do
         | 
| 538 567 | 
             
                      Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
         | 
| 539 568 | 
             
                    end
         | 
| 540 569 |  | 
| 541 | 
            -
                     | 
| 570 | 
            +
                    it "include the timestamp when the exception is logged" do
         | 
| 542 571 | 
             
                      capture_notifications
         | 
| 543 572 |  | 
| 544 | 
            -
                       | 
| 573 | 
            +
                      expect(ExceptionHandling.logger).to receive(:fatal).with(/\(Error:517033020\) context\nArgumentError: \(blah\):\n.*exception_handling_spec\.rb/, anything)
         | 
| 545 574 | 
             
                      b = ExceptionHandling.ensure_safe("context") { raise ArgumentError, "blah" }
         | 
| 546 | 
            -
                       | 
| 575 | 
            +
                      expect(b).to be_nil
         | 
| 547 576 |  | 
| 548 | 
            -
                       | 
| 577 | 
            +
                      expect(ExceptionHandling.last_exception_timestamp).to eq(517_033_020)
         | 
| 549 578 |  | 
| 550 | 
            -
                       | 
| 579 | 
            +
                      expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 551 580 |  | 
| 552 | 
            -
                       | 
| 581 | 
            +
                      expect(sent_notifications.last.enhanced_data['timestamp']).to eq(517_033_020)
         | 
| 553 582 | 
             
                    end
         | 
| 554 583 | 
             
                  end
         | 
| 555 584 |  | 
| 556 | 
            -
                   | 
| 585 | 
            +
                  it "log the error if the exception message is nil" do
         | 
| 557 586 | 
             
                    capture_notifications
         | 
| 558 587 |  | 
| 559 588 | 
             
                    ExceptionHandling.log_error(exception_with_nil_message)
         | 
| 560 589 |  | 
| 561 | 
            -
                     | 
| 562 | 
            -
                     | 
| 590 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 591 | 
            +
                    expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
         | 
| 563 592 | 
             
                  end
         | 
| 564 593 |  | 
| 565 | 
            -
                   | 
| 594 | 
            +
                  it "log the error if the exception message is nil and the exception context is a hash" do
         | 
| 566 595 | 
             
                    capture_notifications
         | 
| 567 596 |  | 
| 568 597 | 
             
                    ExceptionHandling.log_error(exception_with_nil_message, "SERVER_NAME" => "exceptional.com")
         | 
| 569 598 |  | 
| 570 | 
            -
                     | 
| 571 | 
            -
                     | 
| 599 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 600 | 
            +
                    expect(sent_notifications.last.enhanced_data['error_string']).to eq('RuntimeError: ')
         | 
| 572 601 | 
             
                  end
         | 
| 573 602 |  | 
| 574 603 | 
             
                  context "Honeybadger integration" do
         | 
| 575 604 | 
             
                    context "with Honeybadger not defined" do
         | 
| 576 | 
            -
                       | 
| 577 | 
            -
                         | 
| 605 | 
            +
                      before do
         | 
| 606 | 
            +
                        allow(ExceptionHandling).to receive(:honeybadger_defined?) { false }
         | 
| 578 607 | 
             
                      end
         | 
| 579 608 |  | 
| 580 | 
            -
                       | 
| 581 | 
            -
                         | 
| 609 | 
            +
                      it "not invoke send_exception_to_honeybadger when log_error is executed" do
         | 
| 610 | 
            +
                        expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
         | 
| 582 611 | 
             
                        ExceptionHandling.log_error(exception_1)
         | 
| 583 612 | 
             
                      end
         | 
| 584 613 |  | 
| 585 | 
            -
                       | 
| 586 | 
            -
                         | 
| 614 | 
            +
                      it "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
         | 
| 615 | 
            +
                        expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
         | 
| 587 616 | 
             
                        ExceptionHandling.ensure_safe { raise exception_1 }
         | 
| 588 617 | 
             
                      end
         | 
| 589 618 | 
             
                    end
         | 
| 590 619 |  | 
| 591 620 | 
             
                    context "with Honeybadger defined" do
         | 
| 592 | 
            -
                       | 
| 593 | 
            -
                         | 
| 621 | 
            +
                      it "not send_exception_to_honeybadger when log_warning is executed" do
         | 
| 622 | 
            +
                        expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
         | 
| 594 623 | 
             
                        ExceptionHandling.log_warning("This should not go to honeybadger")
         | 
| 595 624 | 
             
                      end
         | 
| 596 625 |  | 
| 597 | 
            -
                       | 
| 598 | 
            -
                         | 
| 626 | 
            +
                      it "not send_exception_to_honeybadger when log_error is called with a Warning" do
         | 
| 627 | 
            +
                        expect(ExceptionHandling).to_not receive(:send_exception_to_honeybadger)
         | 
| 599 628 | 
             
                        ExceptionHandling.log_error(ExceptionHandling::Warning.new("This should not go to honeybadger"))
         | 
| 600 629 | 
             
                      end
         | 
| 601 630 |  | 
| 602 | 
            -
                       | 
| 603 | 
            -
                         | 
| 631 | 
            +
                      it "invoke send_exception_to_honeybadger when log_error is executed" do
         | 
| 632 | 
            +
                        expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
         | 
| 604 633 | 
             
                        ExceptionHandling.log_error(exception_1)
         | 
| 605 634 | 
             
                      end
         | 
| 606 635 |  | 
| 607 | 
            -
                       | 
| 608 | 
            -
                         | 
| 636 | 
            +
                      it "invoke send_exception_to_honeybadger when log_error_rack is executed" do
         | 
| 637 | 
            +
                        expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
         | 
| 609 638 | 
             
                        ExceptionHandling.log_error_rack(exception_1, {}, nil)
         | 
| 610 639 | 
             
                      end
         | 
| 611 640 |  | 
| 612 | 
            -
                       | 
| 613 | 
            -
                         | 
| 641 | 
            +
                      it "invoke send_exception_to_honeybadger when ensure_safe is executed" do
         | 
| 642 | 
            +
                        expect(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(any_args).and_call_original
         | 
| 614 643 | 
             
                        ExceptionHandling.ensure_safe { raise exception_1 }
         | 
| 615 644 | 
             
                      end
         | 
| 616 645 |  | 
| 617 | 
            -
                       | 
| 618 | 
            -
                         | 
| 619 | 
            -
                           | 
| 646 | 
            +
                      it "specify error message as an empty string when notifying honeybadger if exception message is nil" do
         | 
| 647 | 
            +
                        expect(Honeybadger).to receive(:notify).with(any_args) do |args|
         | 
| 648 | 
            +
                          expect(args[:error_message]).to eq("")
         | 
| 620 649 | 
             
                        end
         | 
| 621 650 | 
             
                        ExceptionHandling.log_error(exception_with_nil_message)
         | 
| 622 651 | 
             
                      end
         | 
| 623 652 |  | 
| 624 653 | 
             
                      context "with stubbed values" do
         | 
| 625 | 
            -
                         | 
| 654 | 
            +
                        before do
         | 
| 626 655 | 
             
                          Time.now_override = Time.now
         | 
| 627 656 | 
             
                          @env = { server: "fe98" }
         | 
| 628 657 | 
             
                          @parameters = { advertiser_id: 435, controller: "some_controller" }
         | 
| 629 658 | 
             
                          @session = { username: "jsmith" }
         | 
| 630 659 | 
             
                          @request_uri = "host/path"
         | 
| 631 660 | 
             
                          @controller = create_dummy_controller(@env, @parameters, @session, @request_uri)
         | 
| 632 | 
            -
                           | 
| 661 | 
            +
                          allow(ExceptionHandling).to receive(:server_name) { "invoca_fe98" }
         | 
| 633 662 |  | 
| 634 663 | 
             
                          @exception = StandardError.new("Some Exception")
         | 
| 635 664 | 
             
                          @exception.set_backtrace([
         | 
| 636 | 
            -
                                                     " | 
| 637 | 
            -
                                                     " | 
| 665 | 
            +
                                                     "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
         | 
| 666 | 
            +
                                                     "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
         | 
| 638 667 | 
             
                                                   ])
         | 
| 639 668 | 
             
                          @exception_context = { "SERVER_NAME" => "exceptional.com" }
         | 
| 640 669 | 
             
                        end
         | 
| 641 670 |  | 
| 642 | 
            -
                         | 
| 671 | 
            +
                        it "send error details and relevant context data to Honeybadger with log_context" do
         | 
| 643 672 | 
             
                          honeybadger_data = nil
         | 
| 644 | 
            -
                           | 
| 673 | 
            +
                          expect(Honeybadger).to receive(:notify).with(any_args) do |data|
         | 
| 645 674 | 
             
                            honeybadger_data = data
         | 
| 646 675 | 
             
                          end
         | 
| 647 676 | 
             
                          ExceptionHandling.logger.global_context = { service_name: "rails", region: "AWS-us-east-1" }
         | 
| @@ -679,19 +708,19 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 679 708 | 
             
                                "SERVER_NAME" => "exceptional.com"
         | 
| 680 709 | 
             
                              },
         | 
| 681 710 | 
             
                              backtrace: [
         | 
| 682 | 
            -
                                " | 
| 683 | 
            -
                                " | 
| 711 | 
            +
                                "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
         | 
| 712 | 
            +
                                "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
         | 
| 684 713 | 
             
                              ],
         | 
| 685 714 | 
             
                              event_response: "Event successfully received",
         | 
| 686 715 | 
             
                              log_context: { "service_name" => "bin/console", "region" => "AWS-us-east-1", "log_source" => "gem/listen" }
         | 
| 687 716 | 
             
                            }
         | 
| 688 717 | 
             
                          }
         | 
| 689 | 
            -
                           | 
| 718 | 
            +
                          expect(honeybadger_data).to eq(expected_data)
         | 
| 690 719 | 
             
                        end
         | 
| 691 720 |  | 
| 692 | 
            -
                         | 
| 721 | 
            +
                        it "send error details and relevant context data to Honeybadger with empty log_context" do
         | 
| 693 722 | 
             
                          honeybadger_data = nil
         | 
| 694 | 
            -
                           | 
| 723 | 
            +
                          expect(Honeybadger).to receive(:notify).with(any_args) do |data|
         | 
| 695 724 | 
             
                            honeybadger_data = data
         | 
| 696 725 | 
             
                          end
         | 
| 697 726 | 
             
                          ExceptionHandling.logger.global_context = {}
         | 
| @@ -729,22 +758,22 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 729 758 | 
             
                                "SERVER_NAME" => "exceptional.com"
         | 
| 730 759 | 
             
                              },
         | 
| 731 760 | 
             
                              backtrace: [
         | 
| 732 | 
            -
                                           " | 
| 733 | 
            -
                                           " | 
| 761 | 
            +
                                           "spec/unit/exception_handling_spec.rb:847:in `exception_1'",
         | 
| 762 | 
            +
                                           "spec/unit/exception_handling_spec.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"
         | 
| 734 763 | 
             
                                         ],
         | 
| 735 764 | 
             
                              event_response: "Event successfully received"
         | 
| 736 765 | 
             
                            }
         | 
| 737 766 | 
             
                          }
         | 
| 738 | 
            -
                           | 
| 767 | 
            +
                          expect(honeybadger_data).to eq(expected_data)
         | 
| 739 768 | 
             
                        end
         | 
| 740 769 | 
             
                      end
         | 
| 741 770 |  | 
| 742 771 | 
             
                      context "with post_log_error_hook set" do
         | 
| 743 | 
            -
                         | 
| 772 | 
            +
                        after do
         | 
| 744 773 | 
             
                          ExceptionHandling.post_log_error_hook = nil
         | 
| 745 774 | 
             
                        end
         | 
| 746 775 |  | 
| 747 | 
            -
                         | 
| 776 | 
            +
                        it "not send notification to honeybadger when exception description has the flag turned off and call log error callback with logged_to_honeybadger set to nil" do
         | 
| 748 777 | 
             
                          @honeybadger_status = nil
         | 
| 749 778 | 
             
                          ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 750 779 | 
             
                          filter_list = {
         | 
| @@ -753,37 +782,37 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 753 782 | 
             
                              send_to_honeybadger: false
         | 
| 754 783 | 
             
                            }
         | 
| 755 784 | 
             
                          }
         | 
| 756 | 
            -
                           | 
| 757 | 
            -
                           | 
| 785 | 
            +
                          allow(File).to receive(:mtime) { incrementing_mtime }
         | 
| 786 | 
            +
                          expect(YAML).to receive(:load_file).with(any_args) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }.at_least(1)
         | 
| 758 787 |  | 
| 759 | 
            -
                           | 
| 760 | 
            -
                           | 
| 788 | 
            +
                          expect(ExceptionHandling).to receive(:send_exception_to_honeybadger_unless_filtered).with(any_args).exactly(1).and_call_original
         | 
| 789 | 
            +
                          expect(Honeybadger).to_not receive(:notify)
         | 
| 761 790 | 
             
                          ExceptionHandling.log_error(StandardError.new("suppress Honeybadger notification"))
         | 
| 762 | 
            -
                           | 
| 791 | 
            +
                          expect(@honeybadger_status).to eq(:skipped)
         | 
| 763 792 | 
             
                        end
         | 
| 764 793 |  | 
| 765 | 
            -
                         | 
| 794 | 
            +
                        it "call log error callback with logged_to_honeybadger set to false if an error occurs while attempting to notify honeybadger" do
         | 
| 766 795 | 
             
                          @honeybadger_status = nil
         | 
| 767 796 | 
             
                          ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 768 | 
            -
                           | 
| 797 | 
            +
                          expect(Honeybadger).to receive(:notify).with(any_args) { raise "Honeybadger Notification Failure" }
         | 
| 769 798 | 
             
                          ExceptionHandling.log_error(exception_1)
         | 
| 770 | 
            -
                           | 
| 799 | 
            +
                          expect(@honeybadger_status).to eq(:failure)
         | 
| 771 800 | 
             
                        end
         | 
| 772 801 |  | 
| 773 | 
            -
                         | 
| 802 | 
            +
                        it "call log error callback with logged_to_honeybadger set to false on unsuccessful honeybadger notification" do
         | 
| 774 803 | 
             
                          @honeybadger_status = nil
         | 
| 775 804 | 
             
                          ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 776 | 
            -
                           | 
| 805 | 
            +
                          expect(Honeybadger).to receive(:notify).with(any_args) { false }
         | 
| 777 806 | 
             
                          ExceptionHandling.log_error(exception_1)
         | 
| 778 | 
            -
                           | 
| 807 | 
            +
                          expect(@honeybadger_status).to eq(:failure)
         | 
| 779 808 | 
             
                        end
         | 
| 780 809 |  | 
| 781 | 
            -
                         | 
| 810 | 
            +
                        it "call log error callback with logged_to_honeybadger set to true on successful honeybadger notification" do
         | 
| 782 811 | 
             
                          @honeybadger_status = nil
         | 
| 783 812 | 
             
                          ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
         | 
| 784 | 
            -
                           | 
| 813 | 
            +
                          expect(Honeybadger).to receive(:notify).with(any_args) { '06220c5a-b471-41e5-baeb-de247da45a56' }
         | 
| 785 814 | 
             
                          ExceptionHandling.log_error(exception_1)
         | 
| 786 | 
            -
                           | 
| 815 | 
            +
                          expect(@honeybadger_status).to eq(:success)
         | 
| 787 816 | 
             
                        end
         | 
| 788 817 | 
             
                      end
         | 
| 789 818 | 
             
                    end
         | 
| @@ -795,33 +824,33 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 795 824 | 
             
                    end
         | 
| 796 825 | 
             
                  end
         | 
| 797 826 |  | 
| 798 | 
            -
                   | 
| 827 | 
            +
                  it "allow sections to have data with just a to_s method" do
         | 
| 799 828 | 
             
                    capture_notifications
         | 
| 800 829 |  | 
| 801 830 | 
             
                    ExceptionHandling.log_error("This is my RingSwitch example.") do |data|
         | 
| 802 831 | 
             
                      data.merge!(event_response: EventResponse.new)
         | 
| 803 832 | 
             
                    end
         | 
| 804 833 |  | 
| 805 | 
            -
                     | 
| 806 | 
            -
                     | 
| 834 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 835 | 
            +
                    expect(sent_notifications.last.enhanced_data['event_response'].to_s).to match(/message from to_s!/)
         | 
| 807 836 | 
             
                  end
         | 
| 808 837 | 
             
                end
         | 
| 809 838 |  | 
| 810 | 
            -
                 | 
| 839 | 
            +
                it "return the error ID (timestamp)" do
         | 
| 811 840 | 
             
                  result = ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
         | 
| 812 | 
            -
                   | 
| 841 | 
            +
                  expect(result).to eq(ExceptionHandling.last_exception_timestamp)
         | 
| 813 842 | 
             
                end
         | 
| 814 843 |  | 
| 815 | 
            -
                 | 
| 816 | 
            -
                   | 
| 817 | 
            -
                   | 
| 844 | 
            +
                it "rescue exceptions that happen in log_error" do
         | 
| 845 | 
            +
                  allow(ExceptionHandling).to receive(:make_exception) { raise ArgumentError, "Bad argument" }
         | 
| 846 | 
            +
                  expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
         | 
| 818 847 | 
             
                                                                 satisfy { |context| context['ExceptionHandlingError: log_error rescued exception while logging Runtime message'] },
         | 
| 819 848 | 
             
                                                                 anything)
         | 
| 820 849 | 
             
                  ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
         | 
| 821 850 | 
             
                end
         | 
| 822 851 |  | 
| 823 | 
            -
                 | 
| 824 | 
            -
                   | 
| 852 | 
            +
                it "rescue exceptions that happen when log_error yields" do
         | 
| 853 | 
            +
                  expect(ExceptionHandling).to receive(:write_exception_to_log).with(satisfy { |ex| ex.to_s['Bad argument'] },
         | 
| 825 854 | 
             
                                                                 satisfy { |context| context['Context message'] },
         | 
| 826 855 | 
             
                                                                 anything,
         | 
| 827 856 | 
             
                                                                 anything)
         | 
| @@ -829,38 +858,38 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 829 858 | 
             
                end
         | 
| 830 859 |  | 
| 831 860 | 
             
                context "Exception Filtering" do
         | 
| 832 | 
            -
                   | 
| 861 | 
            +
                  before do
         | 
| 833 862 | 
             
                    filter_list = { exception1: { 'error' => "my error message" },
         | 
| 834 863 | 
             
                                    exception2: { 'error' => "some other message", :session => "misc data" } }
         | 
| 835 | 
            -
                     | 
| 864 | 
            +
                    allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
         | 
| 836 865 |  | 
| 837 866 | 
             
                    # bump modified time up to get the above filter loaded
         | 
| 838 | 
            -
                     | 
| 867 | 
            +
                    allow(File).to receive(:mtime) { incrementing_mtime }
         | 
| 839 868 | 
             
                  end
         | 
| 840 869 |  | 
| 841 | 
            -
                   | 
| 842 | 
            -
                     | 
| 870 | 
            +
                  it "handle case where filter list is not found" do
         | 
| 871 | 
            +
                    allow(YAML).to receive(:load_file) { raise Errno::ENOENT, "File not found" }
         | 
| 843 872 |  | 
| 844 873 | 
             
                    capture_notifications
         | 
| 845 874 |  | 
| 846 875 | 
             
                    ExceptionHandling.log_error("My error message is in list")
         | 
| 847 | 
            -
                     | 
| 876 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 848 877 | 
             
                  end
         | 
| 849 878 |  | 
| 850 | 
            -
                   | 
| 879 | 
            +
                  it "log exception and suppress email when exception is on filter list" do
         | 
| 851 880 | 
             
                    capture_notifications
         | 
| 852 881 |  | 
| 853 882 | 
             
                    ExceptionHandling.log_error("Error message is not in list")
         | 
| 854 | 
            -
                     | 
| 883 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 855 884 |  | 
| 856 885 | 
             
                    sent_notifications.clear
         | 
| 857 886 | 
             
                    ExceptionHandling.log_error("My error message is in list")
         | 
| 858 | 
            -
                     | 
| 887 | 
            +
                    expect(sent_notifications.size).to eq(0), sent_notifications.inspect
         | 
| 859 888 | 
             
                  end
         | 
| 860 889 |  | 
| 861 | 
            -
                   | 
| 890 | 
            +
                  it "allow filtering exception on any text in exception data" do
         | 
| 862 891 | 
             
                    filters = { exception1: { session: "data: my extra session data" } }
         | 
| 863 | 
            -
                     | 
| 892 | 
            +
                    allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filters) }
         | 
| 864 893 |  | 
| 865 894 | 
             
                    capture_notifications
         | 
| 866 895 |  | 
| @@ -870,7 +899,7 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 870 899 | 
             
                        data: "my extra session data"
         | 
| 871 900 | 
             
                      }
         | 
| 872 901 | 
             
                    end
         | 
| 873 | 
            -
                     | 
| 902 | 
            +
                    expect(sent_notifications.size).to eq(0), sent_notifications.inspect
         | 
| 874 903 |  | 
| 875 904 | 
             
                    ExceptionHandling.log_error("No match here") do |data|
         | 
| 876 905 | 
             
                      data[:session] = {
         | 
| @@ -878,86 +907,86 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 878 907 | 
             
                        data: "my extra session <no match!> data"
         | 
| 879 908 | 
             
                      }
         | 
| 880 909 | 
             
                    end
         | 
| 881 | 
            -
                     | 
| 910 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 882 911 | 
             
                  end
         | 
| 883 912 |  | 
| 884 | 
            -
                   | 
| 913 | 
            +
                  it "reload filter list on the next exception if file was modified" do
         | 
| 885 914 | 
             
                    capture_notifications
         | 
| 886 915 |  | 
| 887 916 | 
             
                    ExceptionHandling.log_error("Error message is not in list")
         | 
| 888 | 
            -
                     | 
| 917 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 889 918 |  | 
| 890 919 | 
             
                    filter_list = { exception1: { 'error' => "Error message is not in list" } }
         | 
| 891 | 
            -
                     | 
| 892 | 
            -
                     | 
| 920 | 
            +
                    allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
         | 
| 921 | 
            +
                    allow(File).to receive(:mtime) { incrementing_mtime }
         | 
| 893 922 |  | 
| 894 923 | 
             
                    sent_notifications.clear
         | 
| 895 924 | 
             
                    ExceptionHandling.log_error("Error message is not in list")
         | 
| 896 | 
            -
                     | 
| 925 | 
            +
                    expect(sent_notifications.size).to eq(0), sent_notifications.inspect
         | 
| 897 926 | 
             
                  end
         | 
| 898 927 |  | 
| 899 | 
            -
                   | 
| 928 | 
            +
                  it "not consider filter if both error message and body do not match" do
         | 
| 900 929 | 
             
                    capture_notifications
         | 
| 901 930 |  | 
| 902 931 | 
             
                    # error message matches, but not full text
         | 
| 903 932 | 
             
                    ExceptionHandling.log_error("some other message")
         | 
| 904 | 
            -
                     | 
| 933 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 905 934 |  | 
| 906 935 | 
             
                    # now both match
         | 
| 907 936 | 
             
                    sent_notifications.clear
         | 
| 908 937 | 
             
                    ExceptionHandling.log_error("some other message") do |data|
         | 
| 909 938 | 
             
                      data[:session] = { some_random_key: "misc data" }
         | 
| 910 939 | 
             
                    end
         | 
| 911 | 
            -
                     | 
| 940 | 
            +
                    expect(sent_notifications.size).to eq(0), sent_notifications.inspect
         | 
| 912 941 | 
             
                  end
         | 
| 913 942 |  | 
| 914 | 
            -
                   | 
| 943 | 
            +
                  it "skip environment keys not on whitelist" do
         | 
| 915 944 | 
             
                    capture_notifications
         | 
| 916 945 |  | 
| 917 946 | 
             
                    ExceptionHandling.log_error("some message") do |data|
         | 
| 918 947 | 
             
                      data[:environment] = { SERVER_PROTOCOL: "HTTP/1.0", RAILS_SECRETS_YML_CONTENTS: 'password: VERY_SECRET_PASSWORD' }
         | 
| 919 948 | 
             
                    end
         | 
| 920 | 
            -
                     | 
| 949 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 921 950 |  | 
| 922 951 | 
             
                    mail = sent_notifications.last
         | 
| 923 952 | 
             
                    environment = mail.enhanced_data['environment']
         | 
| 924 953 |  | 
| 925 | 
            -
                     | 
| 926 | 
            -
                     | 
| 954 | 
            +
                    expect(environment["RAILS_SECRETS_YML_CONTENTS"]).to be_nil, environment.inspect # this is not on whitelist).to be_nil
         | 
| 955 | 
            +
                    expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this is
         | 
| 927 956 | 
             
                  end
         | 
| 928 957 |  | 
| 929 | 
            -
                   | 
| 958 | 
            +
                  it "omit environment defaults" do
         | 
| 930 959 | 
             
                    capture_notifications
         | 
| 931 960 |  | 
| 932 | 
            -
                     | 
| 961 | 
            +
                    allow(ExceptionHandling).to receive(:send_exception_to_honeybadger).with(anything) { |exception_info| sent_notifications << exception_info }
         | 
| 933 962 |  | 
| 934 963 | 
             
                    ExceptionHandling.log_error("some message") do |data|
         | 
| 935 964 | 
             
                      data[:environment] = { SERVER_PORT: '80', SERVER_PROTOCOL: "HTTP/1.0" }
         | 
| 936 965 | 
             
                    end
         | 
| 937 | 
            -
                     | 
| 966 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 938 967 | 
             
                    mail = sent_notifications.last
         | 
| 939 968 | 
             
                    environment = mail.enhanced_data['environment']
         | 
| 940 969 |  | 
| 941 | 
            -
                     | 
| 942 | 
            -
                     | 
| 970 | 
            +
                    expect(environment["SERVER_PORT"]).to be_nil, environment.inspect # this was default).to be_nil
         | 
| 971 | 
            +
                    expect(environment["SERVER_PROTOCOL"]).to be_truthy, environment.inspect # this was not
         | 
| 943 972 | 
             
                  end
         | 
| 944 973 |  | 
| 945 | 
            -
                   | 
| 974 | 
            +
                  it "reject the filter file if any contain all empty regexes" do
         | 
| 946 975 | 
             
                    filter_list = { exception1: { 'error' => "", :session => "" },
         | 
| 947 976 | 
             
                                    exception2: { 'error' => "is not in list", :session => "" } }
         | 
| 948 | 
            -
                     | 
| 949 | 
            -
                     | 
| 977 | 
            +
                    allow(YAML).to receive(:load_file) { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
         | 
| 978 | 
            +
                    allow(File).to receive(:mtime) { incrementing_mtime }
         | 
| 950 979 |  | 
| 951 980 | 
             
                    capture_notifications
         | 
| 952 981 |  | 
| 953 982 | 
             
                    ExceptionHandling.log_error("Error message is not in list")
         | 
| 954 | 
            -
                     | 
| 983 | 
            +
                    expect(sent_notifications.size).to eq(1), sent_notifications.inspect
         | 
| 955 984 | 
             
                  end
         | 
| 956 985 |  | 
| 957 | 
            -
                   | 
| 986 | 
            +
                  it "reload filter file if filename changes" do
         | 
| 958 987 | 
             
                    catalog = ExceptionHandling.exception_catalog
         | 
| 959 988 | 
             
                    ExceptionHandling.filter_list_filename = "./config/other_exception_filters.yml"
         | 
| 960 | 
            -
                     | 
| 989 | 
            +
                    expect(ExceptionHandling.exception_catalog).to_not eq(catalog)
         | 
| 961 990 | 
             
                  end
         | 
| 962 991 |  | 
| 963 992 | 
             
                  context "Exception Handling Mailer" do
         | 
| @@ -971,7 +1000,7 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 971 1000 |  | 
| 972 1001 | 
             
                    [[true, false], [true, true]].each do |em_flag, synchrony_flag|
         | 
| 973 1002 | 
             
                      context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
         | 
| 974 | 
            -
                         | 
| 1003 | 
            +
                        before do
         | 
| 975 1004 | 
             
                          ExceptionHandling.eventmachine_safe       = em_flag
         | 
| 976 1005 | 
             
                          ExceptionHandling.eventmachine_synchrony  = synchrony_flag
         | 
| 977 1006 | 
             
                          EventMachineStub.block = nil
         | 
| @@ -981,61 +1010,60 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 981 1010 | 
             
                          set_test_const('EventMachine::DNS::Resolver', DNSResolvStub)
         | 
| 982 1011 | 
             
                        end
         | 
| 983 1012 |  | 
| 984 | 
            -
                         | 
| 1013 | 
            +
                        after do
         | 
| 985 1014 | 
             
                          ExceptionHandling.eventmachine_safe       = false
         | 
| 986 1015 | 
             
                          ExceptionHandling.eventmachine_synchrony  = false
         | 
| 987 1016 | 
             
                        end
         | 
| 988 1017 |  | 
| 989 | 
            -
                         | 
| 1018 | 
            +
                        it "schedule EventMachine STMP when EventMachine defined" do
         | 
| 990 1019 | 
             
                          ActionMailer::Base.deliveries.clear
         | 
| 991 1020 |  | 
| 992 1021 | 
             
                          set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
         | 
| 993 1022 |  | 
| 994 1023 | 
             
                          ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
         | 
| 995 | 
            -
                           | 
| 1024 | 
            +
                          expect(EventMachineStub.block).to be_truthy
         | 
| 996 1025 | 
             
                          EventMachineStub.block.call
         | 
| 997 | 
            -
                           | 
| 1026 | 
            +
                          expect(DNSResolvStub.callback_block).to be_truthy
         | 
| 998 1027 | 
             
                          DNSResolvStub.callback_block.call ['127.0.0.1']
         | 
| 999 | 
            -
                           | 
| 1000 | 
            -
                           | 
| 1001 | 
            -
                           | 
| 1028 | 
            +
                          expect((SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }) .to eq(EXPECTED_SMTP_HASH), SmtpClientStub.send_hash.inspect
         | 
| 1029 | 
            +
                          expect(SmtpClientStub.last_method).to eq((synchrony_flag ? :asend : :send))
         | 
| 1030 | 
            +
                          expect(SmtpClientStub.send_hash[:content]).to match(/Exception to escalate/)
         | 
| 1002 1031 | 
             
                          assert_emails 0, ActionMailer::Base.deliveries.*.to_s
         | 
| 1003 1032 | 
             
                        end
         | 
| 1004 1033 |  | 
| 1005 | 
            -
                         | 
| 1034 | 
            +
                        it "pass the content as a proper rfc 2822 message" do
         | 
| 1006 1035 | 
             
                          set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
         | 
| 1007 1036 | 
             
                          ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
         | 
| 1008 | 
            -
                           | 
| 1037 | 
            +
                          expect(EventMachineStub.block).to be_truthy
         | 
| 1009 1038 | 
             
                          EventMachineStub.block.call
         | 
| 1010 | 
            -
                           | 
| 1039 | 
            +
                          expect(DNSResolvStub.callback_block).to be_truthy
         | 
| 1011 1040 | 
             
                          DNSResolvStub.callback_block.call ['127.0.0.1']
         | 
| 1012 | 
            -
                           | 
| 1013 | 
            -
                           | 
| 1014 | 
            -
                           | 
| 1041 | 
            +
                          expect(content = SmtpClientStub.send_hash[:content]).to be_truthy
         | 
| 1042 | 
            +
                          expect(content).to match(/Content-Transfer-Encoding: 7bit/)
         | 
| 1043 | 
            +
                          expect(content).to match(/\r\n\.\r\n\z/)
         | 
| 1015 1044 | 
             
                        end
         | 
| 1016 1045 |  | 
| 1017 | 
            -
                         | 
| 1046 | 
            +
                        it "log fatal on EventMachine STMP errback" do
         | 
| 1018 1047 | 
             
                          ActionMailer::Base.deliveries.clear
         | 
| 1019 1048 |  | 
| 1020 1049 | 
             
                          set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
         | 
| 1021 | 
            -
                           | 
| 1022 | 
            -
                           | 
| 1050 | 
            +
                          expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, anything)
         | 
| 1051 | 
            +
                          expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to email by SMTP: "credential mismatch"/)
         | 
| 1023 1052 |  | 
| 1024 1053 | 
             
                          ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
         | 
| 1025 | 
            -
                           | 
| 1054 | 
            +
                          expect(EventMachineStub.block).to be_truthy
         | 
| 1026 1055 | 
             
                          EventMachineStub.block.call
         | 
| 1027 | 
            -
                           | 
| 1056 | 
            +
                          expect(DNSResolvStub.callback_block).to be_truthy
         | 
| 1028 1057 | 
             
                          DNSResolvStub.callback_block.call(['127.0.0.1'])
         | 
| 1029 1058 | 
             
                          SmtpClientErrbackStub.block.call("credential mismatch")
         | 
| 1030 | 
            -
                           | 
| 1031 | 
            -
                        end
         | 
| 1059 | 
            +
                          expect((SmtpClientErrbackStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |_k, v| v.to_s }).to eq(EXPECTED_SMTP_HASH), SmtpClientErrbackStub.send_hash.inspect            end
         | 
| 1032 1060 |  | 
| 1033 | 
            -
                         | 
| 1034 | 
            -
                           | 
| 1035 | 
            -
                           | 
| 1061 | 
            +
                        it "log fatal on EventMachine dns resolver errback" do
         | 
| 1062 | 
            +
                          expect(ExceptionHandling.logger).to receive(:fatal).with(/Exception to escalate/, anything)
         | 
| 1063 | 
            +
                          expect(ExceptionHandling.logger).to receive(:fatal).with(/Failed to resolv DNS for localhost: "softlayer sucks"/)
         | 
| 1036 1064 |  | 
| 1037 1065 | 
             
                          ExceptionHandling.ensure_escalation("ensure message") { raise 'Exception to escalate!' }
         | 
| 1038 | 
            -
                           | 
| 1066 | 
            +
                          expect(EventMachineStub.block).to be_truthy
         | 
| 1039 1067 | 
             
                          EventMachineStub.block.call
         | 
| 1040 1068 | 
             
                          DNSResolvStub.errback_block.call("softlayer sucks")
         | 
| 1041 1069 | 
             
                        end
         | 
| @@ -1045,7 +1073,7 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 1045 1073 | 
             
                end
         | 
| 1046 1074 |  | 
| 1047 1075 | 
             
                context "Exception mapping" do
         | 
| 1048 | 
            -
                   | 
| 1076 | 
            +
                  before do
         | 
| 1049 1077 | 
             
                    @data = {
         | 
| 1050 1078 | 
             
                      environment: {
         | 
| 1051 1079 | 
             
                        'HTTP_HOST' => "localhost",
         | 
| @@ -1070,17 +1098,17 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 1070 1098 | 
             
                    }
         | 
| 1071 1099 | 
             
                  end
         | 
| 1072 1100 |  | 
| 1073 | 
            -
                   | 
| 1101 | 
            +
                  it "clean backtraces" do
         | 
| 1074 1102 | 
             
                    begin
         | 
| 1075 1103 | 
             
                      raise "test exception"
         | 
| 1076 1104 | 
             
                    rescue => ex
         | 
| 1077 1105 | 
             
                      backtrace = ex.backtrace
         | 
| 1078 1106 | 
             
                    end
         | 
| 1079 1107 | 
             
                    result = ExceptionHandling.send(:clean_backtrace, ex).to_s
         | 
| 1080 | 
            -
                     | 
| 1108 | 
            +
                    expect(backtrace).to_not eq(result)
         | 
| 1081 1109 | 
             
                  end
         | 
| 1082 1110 |  | 
| 1083 | 
            -
                   | 
| 1111 | 
            +
                  it "return entire backtrace if cleaned is emtpy" do
         | 
| 1084 1112 | 
             
                    begin
         | 
| 1085 1113 | 
             
                      backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
         | 
| 1086 1114 | 
             
                                   "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
         | 
| @@ -1118,12 +1146,14 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 1118 1146 | 
             
                        end
         | 
| 1119 1147 | 
             
                      end
         | 
| 1120 1148 |  | 
| 1121 | 
            -
                       | 
| 1149 | 
            +
                      rails = double(Rails)
         | 
| 1150 | 
            +
                      expect(rails).to receive(:backtrace_cleaner) { Rails::BacktraceCleaner.new }
         | 
| 1151 | 
            +
                      rails.backtrace_cleaner
         | 
| 1122 1152 |  | 
| 1123 1153 | 
             
                      ex = Exception.new
         | 
| 1124 1154 | 
             
                      ex.set_backtrace(backtrace)
         | 
| 1125 1155 | 
             
                      result = ExceptionHandling.send(:clean_backtrace, ex)
         | 
| 1126 | 
            -
                       | 
| 1156 | 
            +
                      expect(result).to eq(backtrace)
         | 
| 1127 1157 | 
             
                    ensure
         | 
| 1128 1158 | 
             
                      Object.send(:remove_const, :Rails)
         | 
| 1129 1159 | 
             
                    end
         | 
| @@ -1131,36 +1161,36 @@ class ExceptionHandlingTest < ActiveSupport::TestCase | |
| 1131 1161 | 
             
                end
         | 
| 1132 1162 |  | 
| 1133 1163 | 
             
                context "log_perodically" do
         | 
| 1134 | 
            -
                   | 
| 1164 | 
            +
                  before do
         | 
| 1135 1165 | 
             
                    Time.now_override = Time.now # Freeze time
         | 
| 1136 1166 | 
             
                    ExceptionHandling.logger.clear
         | 
| 1137 1167 | 
             
                  end
         | 
| 1138 1168 |  | 
| 1139 | 
            -
                   | 
| 1169 | 
            +
                  after do
         | 
| 1140 1170 | 
             
                    Time.now_override = nil
         | 
| 1141 1171 | 
             
                  end
         | 
| 1142 1172 |  | 
| 1143 | 
            -
                   | 
| 1173 | 
            +
                  it "take in additional logging context and pass them to the logger" do
         | 
| 1144 1174 | 
             
                    ExceptionHandling.log_periodically(:test_context_with_periodic, 30.minutes, "this will be written", service_name: 'exception_handling')
         | 
| 1145 | 
            -
                     | 
| 1146 | 
            -
                     | 
| 1175 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to_not be_empty
         | 
| 1176 | 
            +
                    expect(logged_excluding_reload_filter.last[:context]).to eq({ service_name: 'exception_handling' })
         | 
| 1147 1177 | 
             
                  end
         | 
| 1148 1178 |  | 
| 1149 | 
            -
                   | 
| 1179 | 
            +
                  it "log immediately when we are expected to log" do
         | 
| 1150 1180 | 
             
                    ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
         | 
| 1151 | 
            -
                     | 
| 1181 | 
            +
                    expect(logged_excluding_reload_filter.size).to eq(1)
         | 
| 1152 1182 |  | 
| 1153 1183 | 
             
                    Time.now_override = Time.now + 5.minutes
         | 
| 1154 1184 | 
             
                    ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
         | 
| 1155 | 
            -
                     | 
| 1185 | 
            +
                    expect(logged_excluding_reload_filter.size).to eq(1)
         | 
| 1156 1186 |  | 
| 1157 1187 | 
             
                    ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
         | 
| 1158 | 
            -
                     | 
| 1188 | 
            +
                    expect(logged_excluding_reload_filter.size).to eq(2)
         | 
| 1159 1189 |  | 
| 1160 1190 | 
             
                    Time.now_override = Time.now + 26.minutes
         | 
| 1161 1191 |  | 
| 1162 1192 | 
             
                    ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
         | 
| 1163 | 
            -
                     | 
| 1193 | 
            +
                    expect(logged_excluding_reload_filter.size).to eq(3)
         | 
| 1164 1194 | 
             
                  end
         | 
| 1165 1195 | 
             
                end
         | 
| 1166 1196 | 
             
              end
         |