diff --git a/app/controllers/admin/connected_apps_controller.rb b/app/controllers/admin/connected_apps_controller.rb index a5308d4984..a89743079e 100644 --- a/app/controllers/admin/connected_apps_controller.rb +++ b/app/controllers/admin/connected_apps_controller.rb @@ -77,7 +77,7 @@ module Admin def log_and_notify_exception(exception) Rails.logger.error exception.inspect - Bugsnag.notify(exception) + Alert.raise(exception) end def vine_params_empty? diff --git a/app/controllers/api/v0/base_controller.rb b/app/controllers/api/v0/base_controller.rb index 40bc17a06a..f3e44a29b2 100644 --- a/app/controllers/api/v0/base_controller.rb +++ b/app/controllers/api/v0/base_controller.rb @@ -66,7 +66,7 @@ module Api end def error_during_processing(exception) - Bugsnag.notify(exception) + Alert.raise(exception) render(json: { exception: exception.message }, status: :unprocessable_entity) && return diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 2cf59888fb..f1e5e992fb 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -52,7 +52,7 @@ module Api end def error_during_processing(exception) - Bugsnag.notify(exception) + Alert.raise(exception) if Rails.env.development? || Rails.env.test? render status: :unprocessable_entity, diff --git a/app/controllers/concerns/order_completion.rb b/app/controllers/concerns/order_completion.rb index d98faf149b..ca67d4a84d 100644 --- a/app/controllers/concerns/order_completion.rb +++ b/app/controllers/concerns/order_completion.rb @@ -50,9 +50,7 @@ module OrderCompletion end def order_invalid! - Bugsnag.notify("Notice: invalid order loaded during checkout") do |payload| - payload.add_metadata :order, :order, @order - end + Alert.raise_with_record("Notice: invalid order loaded during checkout", @order) flash[:error] = t('checkout.order_not_loaded') redirect_to main_app.shop_path @@ -92,9 +90,7 @@ module OrderCompletion end def notify_failure(error = RuntimeError.new(order_processing_error)) - Bugsnag.notify(error) do |payload| - payload.add_metadata :order, @order - end + Alert.raise_with_record(error, @order) flash[:error] = order_processing_error if flash.blank? end diff --git a/app/controllers/concerns/order_stock_check.rb b/app/controllers/concerns/order_stock_check.rb index 7b94876533..911bedb124 100644 --- a/app/controllers/concerns/order_stock_check.rb +++ b/app/controllers/concerns/order_stock_check.rb @@ -20,9 +20,7 @@ module OrderStockCheck def check_order_cycle_expiry return unless current_order_cycle&.closed? - Bugsnag.notify("Notice: order cycle closed during checkout completion") do |payload| - payload.add_metadata :order, :order, current_order - end + Alert.raise_with_record("Notice: order cycle closed during checkout completion", current_order) current_order.empty! current_order.set_order_cycle! nil diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 262325f029..f071ae64f5 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -4,11 +4,6 @@ class ErrorsController < ApplicationController layout "errors" def not_found - Bugsnag.notify("404") do |event| - event.severity = "info" - - event.add_metadata(:request, :env, request.env) - end render status: :not_found, formats: :html end diff --git a/app/controllers/spree/admin/payments_controller.rb b/app/controllers/spree/admin/payments_controller.rb index 0afaa5227c..f2709a3f85 100644 --- a/app/controllers/spree/admin/payments_controller.rb +++ b/app/controllers/spree/admin/payments_controller.rb @@ -60,7 +60,7 @@ module Spree end rescue StandardError => e logger.error e.message - Bugsnag.notify(e) + Alert.raise(e) flash[:error] = e.message ensure redirect_to request.referer diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index 788d019dce..e00fce21c0 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -213,7 +213,7 @@ module Spree end def notify_bugsnag(error, product, variant) - Bugsnag.notify(error) do |report| + Alert.raise(error) do |report| report.add_metadata(:product, { product: product.attributes, variant: variant.attributes }) report.add_metadata(:product, :product_error, product.errors.first) unless product.valid? diff --git a/app/controllers/user_registrations_controller.rb b/app/controllers/user_registrations_controller.rb index 3593b20b5a..01e819af3d 100644 --- a/app/controllers/user_registrations_controller.rb +++ b/app/controllers/user_registrations_controller.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require 'open_food_network/error_logger' require "spree/core/controller_helpers/auth" require "spree/core/controller_helpers/common" require "spree/core/controller_helpers/order" @@ -37,7 +36,7 @@ class UserRegistrationsController < Devise::RegistrationsController end end rescue StandardError => e - OpenFoodNetwork::ErrorLogger.notify(e) + Alert.raise(e) render_error(message: I18n.t('unknown_error', scope: I18N_SCOPE)) end diff --git a/app/jobs/backorder_job.rb b/app/jobs/backorder_job.rb index a4648e930e..e55a312ec8 100644 --- a/app/jobs/backorder_job.rb +++ b/app/jobs/backorder_job.rb @@ -19,9 +19,7 @@ class BackorderJob < ApplicationJob rescue StandardError => e # Errors here shouldn't affect the checkout. So let's report them # separately: - Bugsnag.notify(e) do |payload| - payload.add_metadata(:order, :order, order) - end + Alert.raise_with_record(e, order) end def perform(order) diff --git a/app/jobs/report_job.rb b/app/jobs/report_job.rb index 0367ddd259..58c6142386 100644 --- a/app/jobs/report_job.rb +++ b/app/jobs/report_job.rb @@ -22,11 +22,7 @@ class ReportJob < ApplicationJob broadcast_result(channel, format, blob) if channel rescue StandardError => e - Bugsnag.notify(e) do |payload| - payload.add_metadata :report, { - report_class:, user:, params:, format: - } - end + Alert.raise(e, { report: { report_class:, user:, params:, format: } }) broadcast_error(channel) end diff --git a/app/jobs/stock_sync_job.rb b/app/jobs/stock_sync_job.rb index cea16a8d14..d4df9c3c61 100644 --- a/app/jobs/stock_sync_job.rb +++ b/app/jobs/stock_sync_job.rb @@ -16,9 +16,7 @@ class StockSyncJob < ApplicationJob rescue StandardError => e # Errors here shouldn't affect the shopping. So let's report them # separately: - Bugsnag.notify(e) do |payload| - payload.add_metadata(:order, :order, order) - end + Alert.raise_with_record(e, order) end def self.sync_linked_catalogs_now(order) @@ -29,9 +27,7 @@ class StockSyncJob < ApplicationJob rescue StandardError => e # Errors here shouldn't affect the shopping. So let's report them # separately: - Bugsnag.notify(e) do |payload| - payload.add_metadata(:order, :order, order) - end + Alert.raise_with_record(e, order) end def self.catalog_ids(order) diff --git a/app/jobs/subscription_confirm_job.rb b/app/jobs/subscription_confirm_job.rb index 2182671049..16f1fd0e6c 100644 --- a/app/jobs/subscription_confirm_job.rb +++ b/app/jobs/subscription_confirm_job.rb @@ -55,9 +55,7 @@ class SubscriptionConfirmJob < ApplicationJob if order.errors.any? send_failed_payment_email(order) else - Bugsnag.notify(e) do |payload| - payload.add_metadata :order, :order, order - end + Alert.raise_with_record(e, order) send_failed_payment_email(order, e.message) end end @@ -108,8 +106,6 @@ class SubscriptionConfirmJob < ApplicationJob record_and_log_error(:failed_payment, order, error_message) SubscriptionMailer.failed_payment_email(order).deliver_now rescue StandardError => e - Bugsnag.notify(e) do |payload| - payload.add_metadata :subscription_data, { order:, error_message: } - end + Alert.raise(e, { subscription_data: { order:, error_message: } }) end end diff --git a/app/models/calculator/default_tax.rb b/app/models/calculator/default_tax.rb index b6c8b1d3c2..c1cb7a4bcd 100644 --- a/app/models/calculator/default_tax.rb +++ b/app/models/calculator/default_tax.rb @@ -28,7 +28,7 @@ module Calculator # In theory it should never be called any more after this has been deployed. # If the message below doesn't show up in Bugsnag, we can safely delete this method and all # the related methods below it. - Bugsnag.notify("Calculator::DefaultTax was called with legacy tax calculations") + Alert.raise("Calculator::DefaultTax was called with legacy tax calculations") calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor, order.order_cycle) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index dfee85fef6..15d7fb33f2 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -481,7 +481,7 @@ class Enterprise < ApplicationRecord image_variant_url_for(image.variant(name)) rescue StandardError => e - Bugsnag.notify "Enterprise ##{id} #{image.try(:name)} error: #{e.message}" + Alert.raise "Enterprise ##{id} #{image.try(:name)} error: #{e.message}" Rails.logger.error(e.message) nil diff --git a/app/models/spree/image.rb b/app/models/spree/image.rb index 45859a6b45..350b1c5b4c 100644 --- a/app/models/spree/image.rb +++ b/app/models/spree/image.rb @@ -34,7 +34,7 @@ module Spree image_variant_url_for(variant(size)) rescue StandardError => e - Bugsnag.notify "Product ##{viewable_id} Image ##{id} error: #{e.message}" + Alert.raise "Product ##{viewable_id} Image ##{id} error: #{e.message}" Rails.logger.error(e.message) self.class.default_image_url(size) diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index d1951db458..81bee889d3 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -535,7 +535,7 @@ module Spree # because an outdated shipping fee is not as bad as a lost payment. # And the shipping fee is already up-to-date when this error occurs. # https://github.com/openfoodfoundation/openfoodnetwork/issues/3924 - Bugsnag.notify(e) do |report| + Alert.raise(e) do |report| report.add_metadata(:order, attributes) report.add_metadata(:shipment, shipment.attributes) report.add_metadata(:shipment_in_db, Spree::Shipment.find_by(id: shipment.id).attributes) diff --git a/app/models/spree/tax_rate.rb b/app/models/spree/tax_rate.rb index 588fe91985..239a6d9072 100644 --- a/app/models/spree/tax_rate.rb +++ b/app/models/spree/tax_rate.rb @@ -106,7 +106,7 @@ module Spree calculator.compute(item) else # Tax refund should not be possible with the way our production server are configured - Bugsnag.notify( + Alert.raise( "Notice: Tax refund should not be possible, please check the default zone and " \ "the tax rate zone configuration" ) do |payload| diff --git a/app/models/spree/variant.rb b/app/models/spree/variant.rb index 72a180c7ed..acb430ecb6 100644 --- a/app/models/spree/variant.rb +++ b/app/models/spree/variant.rb @@ -293,7 +293,7 @@ module Spree end def ensure_unit_value - Bugsnag.notify("Trying to set unit_value to NaN") if unit_value&.nan? + Alert.raise("Trying to set unit_value to NaN") if unit_value&.nan? return unless (variant_unit == "items" && unit_value.nil?) || unit_value&.nan? self.unit_value = 1.0 diff --git a/app/models/stripe_account.rb b/app/models/stripe_account.rb index c0b507f316..260a29270e 100644 --- a/app/models/stripe_account.rb +++ b/app/models/stripe_account.rb @@ -15,7 +15,7 @@ class StripeAccount < ApplicationRecord destroy && Stripe::OAuth.deauthorize(stripe_user_id:) rescue Stripe::OAuth::OAuthError => e - Bugsnag.notify( + Alert.raise( e, stripe_account: stripe_user_id, enterprise_id: diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb index 79a28508f2..16a668700e 100644 --- a/app/models/variant_override.rb +++ b/app/models/variant_override.rb @@ -48,8 +48,8 @@ class VariantOverride < ApplicationRecord def move_stock!(quantity) unless stock_overridden? - Bugsnag.notify RuntimeError.new "Attempting to move stock of a VariantOverride " \ - "without a count_on_hand specified." + Alert.raise "Attempting to move stock of a VariantOverride " \ + "without a count_on_hand specified." return end @@ -73,8 +73,8 @@ class VariantOverride < ApplicationRecord self.attributes = { on_demand: false, count_on_hand: default_stock } save else - Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant " \ - "with no default stock level." + Alert.raise "Attempting to reset stock level for a variant " \ + "with no default stock level." end end self diff --git a/app/services/alert.rb b/app/services/alert.rb new file mode 100644 index 0000000000..d030554298 --- /dev/null +++ b/app/services/alert.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# A handy wrapper around error reporting libraries like Bugsnag. +# +# Bugsnag's API is great for general purpose but overly complex for our use. +# It also changes over time and we often make mistakes using it. So this class +# aims at: +# +# * Abstracting from Bugsnag, open for other services. +# * Simpler interface to reduce user error. +# * Central place to update Bugsnag API usage when it changes. +# +class Alert + # Alert Bugsnag with additional metadata to appear in tabs. + # + # Alert.raise( + # "Invalid order during checkout", + # { + # order: { number: "ABC123", state: "awaiting_return" }, + # env: { referer: "example.com" } + # } + # ) + def self.raise(error, metadata = {}, &block) + Bugsnag.notify(error) do |payload| + metadata.each do |name, data| + payload.add_metadata(name, data) + end + block.call(payload) + end + end + + def self.raise_with_record(error, record, &) + metadata = { + record.class.name => record&.attributes || { record_was_nil: true } + } + self.raise(error, metadata, &) + end +end diff --git a/app/services/place_proxy_order.rb b/app/services/place_proxy_order.rb index c6f942d3b0..ed3c24dc14 100644 --- a/app/services/place_proxy_order.rb +++ b/app/services/place_proxy_order.rb @@ -24,9 +24,7 @@ class PlaceProxyOrder send_placement_email rescue StandardError => e summarizer.record_and_log_error(:processing, order, e.message) - Bugsnag.notify(e) do |payload| - payload.add_metadata :order, :order, order - end + Alert.raise_with_record(e, order) end private @@ -56,9 +54,7 @@ class PlaceProxyOrder true rescue StandardError => e - Bugsnag.notify(e) do |payload| - payload.add_metadata(:proxy_order, { subscription:, proxy_order: }) - end + Alert.raise(e, { proxy_order: { subscription:, proxy_order: } }) false end diff --git a/app/services/sets/product_set.rb b/app/services/sets/product_set.rb index c6a4521ab7..156f73f875 100644 --- a/app/services/sets/product_set.rb +++ b/app/services/sets/product_set.rb @@ -145,7 +145,7 @@ module Sets end def notify_bugsnag(error, product, variant, variant_attributes) - Bugsnag.notify(error) do |report| + Alert.raise(error) do |report| report.add_metadata( :product_set, { product: product.attributes, variant_attributes:, variant: variant.attributes } ) diff --git a/config/initializers/bugsnag.rb b/config/initializers/bugsnag.rb index bd90c70699..572681f1db 100644 --- a/config/initializers/bugsnag.rb +++ b/config/initializers/bugsnag.rb @@ -6,5 +6,9 @@ Bugsnag.configure do |config| config.logger = Logger.new(STDOUT) config.logger.level = Logger::ERROR end - config.notify_release_stages = %w(production staging) + + # If you want to notify Bugsnag in dev or test then set the env var: + # spring stop + # BUGSNAG=true ./bin/rails console + config.notify_release_stages = %w(production staging) unless ENV["BUGSNAG"] end diff --git a/lib/open_food_network/error_logger.rb b/lib/open_food_network/error_logger.rb deleted file mode 100644 index 51ea1e715e..0000000000 --- a/lib/open_food_network/error_logger.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -# Our error logging API currently wraps Bugsnag. -# It makes us more flexible if we wanted to replace Bugsnag or change logging -# behaviour. -module OpenFoodNetwork - module ErrorLogger - # Tries to escalate the error to a developer. - # If Bugsnag is configured, it will notify it. It would be nice to implement - # some kind of fallback. - def self.notify(error) - Bugsnag.notify(error) - end - end -end diff --git a/spec/controllers/user_registrations_controller_spec.rb b/spec/controllers/user_registrations_controller_spec.rb index 23aa1e220a..da2c29cc45 100644 --- a/spec/controllers/user_registrations_controller_spec.rb +++ b/spec/controllers/user_registrations_controller_spec.rb @@ -27,7 +27,7 @@ RSpec.describe UserRegistrationsController, type: :controller do it "returns error when emailing fails" do allow(Spree::UserMailer).to receive(:confirmation_instructions).and_raise("Some error") - expect(OpenFoodNetwork::ErrorLogger).to receive(:notify) + expect(Alert).to receive(:raise) post :create, xhr: true, params: { spree_user: user_params, use_route: :spree } diff --git a/spec/fixtures/vcr_cassettes/Alert/reaches_the_Bugsnag_service_for_real.yml b/spec/fixtures/vcr_cassettes/Alert/reaches_the_Bugsnag_service_for_real.yml new file mode 100644 index 0000000000..7f541b8a07 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/Alert/reaches_the_Bugsnag_service_for_real.yml @@ -0,0 +1,291 @@ +--- +http_interactions: +- request: + method: post + uri: https://notify.bugsnag.com/ + body: + encoding: UTF-8 + string: '{"apiKey":"","notifier":{"name":"Ruby Bugsnag + Notifier","version":"6.26.4","url":"https://www.bugsnag.com"},"payloadVersion":"4.0","events":[{"app":{"version":null,"releaseStage":"test","type":"rails"},"breadcrumbs":[{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.385Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.387Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.389Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.390Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.437Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.492Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.527Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.546Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.585Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.593Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.796Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.814Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.886Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"Spree::Country Exists?","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","binds":"{\"name\":\"?\",\"LIMIT\":\"?\"}","connection_id":42100},"timestamp":"2024-11-20T04:51:11.888Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"Spree::Country Exists?","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","binds":"{\"name\":\"?\",\"LIMIT\":\"?\"}","connection_id":42100},"timestamp":"2024-11-20T04:51:11.890Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.890Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"Spree::Country Load","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","binds":"{\"name\":\"?\",\"LIMIT\":\"?\"}","connection_id":42100},"timestamp":"2024-11-20T04:51:11.891Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.894Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.901Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"SCHEMA","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.904Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"Spree::Preference Load","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","binds":"{\"key\":\"?\",\"LIMIT\":\"?\"}","connection_id":42100},"timestamp":"2024-11-20T04:51:11.905Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"Spree::Country Load","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","binds":"{\"iso\":\"?\",\"LIMIT\":\"?\"}","connection_id":42100},"timestamp":"2024-11-20T04:51:11.933Z"},{"name":"Read + cache","type":"process","metaData":{"hit":true,"event_name":"cache_read.active_support","event_id":"4779ed9ba580ae3d3290"},"timestamp":"2024-11-20T04:51:11.933Z"},{"name":"Read + cache","type":"process","metaData":{"hit":true,"event_name":"cache_read.active_support","event_id":"4779ed9ba580ae3d3290"},"timestamp":"2024-11-20T04:51:11.933Z"},{"name":"ActiveRecord + SQL query","type":"process","metaData":{"name":"TRANSACTION","event_name":"sql.active_record","event_id":"4779ed9ba580ae3d3290","connection_id":42100},"timestamp":"2024-11-20T04:51:11.970Z"}],"device":{"hostname":"blackbox","runtimeVersions":{"ruby":"3.1.4","rails":"7.0.8","rack":"2.2.9"},"time":"2024-11-20T04:51:11.978Z"},"exceptions":[{"errorClass":"RuntimeError","message":"Testing + Bugsnag from RSpec","stacktrace":[{"lineNumber":129,"file":"gems/bugsnag-6.26.4/lib/bugsnag/report.rb","method":"initialize","code":{"126":" self.configuration + = passed_configuration","127":"","128":" @original_error = exception","129":" self.raw_exceptions + = generate_raw_exceptions(exception)","130":" self.exceptions = generate_exception_list","131":" @errors + = generate_error_list","132":""}},{"lineNumber":90,"file":"gems/bugsnag-6.26.4/lib/bugsnag.rb","method":"new","code":{"87":"","88":" exception + = NIL_EXCEPTION_DESCRIPTION if exception.nil?","89":"","90":" report + = Report.new(exception, configuration, auto_notify)","91":"","92":" # + If this is an auto_notify we yield the block before the any middleware is + run","93":" begin"}},{"lineNumber":90,"file":"gems/bugsnag-6.26.4/lib/bugsnag.rb","method":"notify","code":{"87":"","88":" exception + = NIL_EXCEPTION_DESCRIPTION if exception.nil?","89":"","90":" report + = Report.new(exception, configuration, auto_notify)","91":"","92":" # + If this is an auto_notify we yield the block before the any middleware is + run","93":" begin"}},{"lineNumber":29,"inProject":true,"file":"app/services/alert.rb","method":"raise","code":{"26":" # }","27":" # )","28":" def + self.raise(error, metadata = {}, &block)","29":" Bugsnag.notify(error) + do |payload|","30":" metadata.each do |name, data|","31":" payload.add_metadata(name, + data)","32":" end"}},{"lineNumber":34,"inProject":true,"file":"spec/services/alert_spec.rb","method":"block + (2 levels) in
","code":{"31":" config.delivery_method = :synchronous","32":" end","33":"","34":" Alert.raise(","35":" \"Testing + Bugsnag from RSpec\",","36":" { RSpec: { file: __FILE__ }, env: { BUGSNAG: + ENV[\"BUGSNAG\"] } }","37":" )"}},{"lineNumber":263,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"260":" begin","261":" run_before_example","262":" RSpec.current_scope + = :example","263":" @example_group_instance.instance_exec(self, + &@example_block)","264":"","265":" if pending?","266":" Pending.mark_fixed! + self"}},{"lineNumber":263,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"block + in run","code":{"260":" begin","261":" run_before_example","262":" RSpec.current_scope + = :example","263":" @example_group_instance.instance_exec(self, + &@example_block)","264":"","265":" if pending?","266":" Pending.mark_fixed! + self"}},{"lineNumber":511,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"block + in with_around_and_singleton_context_hooks","code":{"508":" def with_around_and_singleton_context_hooks","509":" singleton_context_hooks_host + = example_group_instance.singleton_class","510":" singleton_context_hooks_host.run_before_context_hooks(example_group_instance)","511":" with_around_example_hooks + { yield }","512":" ensure","513":" singleton_context_hooks_host.run_after_context_hooks(example_group_instance)","514":" end"}},{"lineNumber":468,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"block + in with_around_example_hooks","code":{"465":"","466":" def with_around_example_hooks","467":" RSpec.current_scope + = :before_example_hook","468":" hooks.run(:around, :example, self) + { yield }","469":" rescue Support::AllExceptionsExceptOnesWeMustNotRescue + => e","470":" set_exception(e)","471":" end"}},{"lineNumber":486,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + in run","code":{"483":" case position","484":" when + :before then run_example_hooks_for(example_or_group, :before, :reverse_each)","485":" when + :after then run_example_hooks_for(example_or_group, :after, :each)","486":" when + :around then run_around_example_hooks_for(example_or_group) { yield }","487":" end","488":" end","489":" end"}},{"lineNumber":626,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + in run_around_example_hooks_for","code":{"623":"","624":" return + yield if hooks.empty? # exit early to avoid the extra allocation cost of `Example::Procsy`","625":"","626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call"}},{"lineNumber":352,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"call","code":{"349":" # + Calls the proc and notes that the example has been executed.","350":" def + call(*args, &block)","351":" @executed = true","352":" @proc.call(*args, + &block)","353":" end","354":" alias run call","355":""}},{"lineNumber":75,"file":"gems/rspec-rails-6.1.2/lib/rspec/rails/adapters.rb","method":"block + (2 levels) in ","code":{"72":"","73":" group.around + do |example|","74":" before_setup","75":" example.run","76":" after_teardown","77":" end","78":" end"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":390,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"execute_with","code":{"387":" # + @private","388":" class AroundHook < Hook","389":" def execute_with(example, + procsy)","390":" example.instance_exec(procsy, &block)","391":" return + if procsy.executed?","392":" Pending.mark_skipped!(example,","393":" \"#{hook_description} + did not execute the example\")"}},{"lineNumber":628,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + (2 levels) in run_around_example_hooks_for","code":{"625":"","626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call","630":" end","631":""}},{"lineNumber":352,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"call","code":{"349":" # + Calls the proc and notes that the example has been executed.","350":" def + call(*args, &block)","351":" @executed = true","352":" @proc.call(*args, + &block)","353":" end","354":" alias run call","355":""}},{"lineNumber":153,"inProject":true,"file":"spec/base_spec_helper.rb","method":"block + (3 levels) in
","code":{"150":"","151":" # Reset locale for all specs.","152":" config.around(:each) + do |example|","153":" I18n.with_locale(:en_AU) { example.run }","154":" end","155":"","156":" # + Reset all feature toggles to prevent leaking."}},{"lineNumber":351,"file":"gems/i18n-1.14.5/lib/i18n.rb","method":"with_locale","code":{"348":" current_locale + = self.locale","349":" self.locale = tmp_locale","350":" begin","351":" yield","352":" ensure","353":" self.locale + = current_locale","354":" end"}},{"lineNumber":153,"inProject":true,"file":"spec/base_spec_helper.rb","method":"block + (2 levels) in
","code":{"150":"","151":" # Reset locale for all specs.","152":" config.around(:each) + do |example|","153":" I18n.with_locale(:en_AU) { example.run }","154":" end","155":"","156":" # + Reset all feature toggles to prevent leaking."}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":390,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"execute_with","code":{"387":" # + @private","388":" class AroundHook < Hook","389":" def execute_with(example, + procsy)","390":" example.instance_exec(procsy, &block)","391":" return + if procsy.executed?","392":" Pending.mark_skipped!(example,","393":" \"#{hook_description} + did not execute the example\")"}},{"lineNumber":628,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + (2 levels) in run_around_example_hooks_for","code":{"625":"","626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call","630":" end","631":""}},{"lineNumber":352,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"call","code":{"349":" # + Calls the proc and notes that the example has been executed.","350":" def + call(*args, &block)","351":" @executed = true","352":" @proc.call(*args, + &block)","353":" end","354":" alias run call","355":""}},{"lineNumber":39,"file":"gems/webmock-3.23.1/lib/webmock/rspec.rb","method":"block + (2 levels) in
","code":{"36":" end","37":"","38":" config.around(:each) + do |example|","39":" example.run","40":" WebMock.reset!","41":" end","42":"}"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":390,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"execute_with","code":{"387":" # + @private","388":" class AroundHook < Hook","389":" def execute_with(example, + procsy)","390":" example.instance_exec(procsy, &block)","391":" return + if procsy.executed?","392":" Pending.mark_skipped!(example,","393":" \"#{hook_description} + did not execute the example\")"}},{"lineNumber":628,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + (2 levels) in run_around_example_hooks_for","code":{"625":"","626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call","630":" end","631":""}},{"lineNumber":352,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"call","code":{"349":" # + Calls the proc and notes that the example has been executed.","350":" def + call(*args, &block)","351":" @executed = true","352":" @proc.call(*args, + &block)","353":" end","354":" alias run call","355":""}},{"lineNumber":124,"file":"gems/rspec-retry-0.6.2/lib/rspec/retry.rb","method":"block + in run","code":{"121":" example.metadata[:retry_exceptions] ||= []","122":"","123":" example.clear_exception","124":" ex.run","125":"","126":" self.attempts + += 1","127":""}},{"lineNumber":110,"file":"gems/rspec-retry-0.6.2/lib/rspec/retry.rb","method":"loop","code":{"107":" def + run","108":" example = current_example","109":"","110":" loop do","111":" if + attempts > 0","112":" RSpec.configuration.formatters.each { |f| f.retry(example) + if f.respond_to? :retry }","113":" if verbose_retry?"}},{"lineNumber":110,"file":"gems/rspec-retry-0.6.2/lib/rspec/retry.rb","method":"run","code":{"107":" def + run","108":" example = current_example","109":"","110":" loop do","111":" if + attempts > 0","112":" RSpec.configuration.formatters.each { |f| f.retry(example) + if f.respond_to? :retry }","113":" if verbose_retry?"}},{"lineNumber":12,"file":"gems/rspec-retry-0.6.2/lib/rspec_ext/rspec_ext.rb","method":"run_with_retry","code":{"9":"","10":" class + Procsy","11":" def run_with_retry(opts = {})","12":" RSpec::Retry.new(self, + opts).run","13":" end","14":"","15":" def attempts"}},{"lineNumber":37,"file":"gems/rspec-retry-0.6.2/lib/rspec/retry.rb","method":"block + (2 levels) in setup","code":{"34":" config.add_setting :retry_callback, + :default => nil","35":"","36":" config.around(:each) do |ex|","37":" ex.run_with_retry","38":" end","39":" end","40":" end"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":457,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"instance_exec","code":{"454":"","455":" # + @private","456":" def instance_exec(*args, &block)","457":" @example_group_instance.instance_exec(*args, + &block)","458":" end","459":"","460":" private"}},{"lineNumber":390,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"execute_with","code":{"387":" # + @private","388":" class AroundHook < Hook","389":" def execute_with(example, + procsy)","390":" example.instance_exec(procsy, &block)","391":" return + if procsy.executed?","392":" Pending.mark_skipped!(example,","393":" \"#{hook_description} + did not execute the example\")"}},{"lineNumber":628,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"block + (2 levels) in run_around_example_hooks_for","code":{"625":"","626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call","630":" end","631":""}},{"lineNumber":352,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"call","code":{"349":" # + Calls the proc and notes that the example has been executed.","350":" def + call(*args, &block)","351":" @executed = true","352":" @proc.call(*args, + &block)","353":" end","354":" alias run call","355":""}},{"lineNumber":629,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"run_around_example_hooks_for","code":{"626":" initial_procsy + = Example::Procsy.new(example) { yield }","627":" hooks.inject(initial_procsy) + do |procsy, around_hook|","628":" procsy.wrap { around_hook.execute_with(example, + procsy) }","629":" end.call","630":" end","631":"","632":" if + respond_to?(:singleton_class) && singleton_class.ancestors.include?(singleton_class)"}},{"lineNumber":486,"file":"gems/rspec-core-3.13.0/lib/rspec/core/hooks.rb","method":"run","code":{"483":" case + position","484":" when :before then run_example_hooks_for(example_or_group, + :before, :reverse_each)","485":" when :after then run_example_hooks_for(example_or_group, + :after, :each)","486":" when :around then run_around_example_hooks_for(example_or_group) + { yield }","487":" end","488":" end","489":" end"}},{"lineNumber":468,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"with_around_example_hooks","code":{"465":"","466":" def + with_around_example_hooks","467":" RSpec.current_scope = :before_example_hook","468":" hooks.run(:around, + :example, self) { yield }","469":" rescue Support::AllExceptionsExceptOnesWeMustNotRescue + => e","470":" set_exception(e)","471":" end"}},{"lineNumber":511,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"with_around_and_singleton_context_hooks","code":{"508":" def + with_around_and_singleton_context_hooks","509":" singleton_context_hooks_host + = example_group_instance.singleton_class","510":" singleton_context_hooks_host.run_before_context_hooks(example_group_instance)","511":" with_around_example_hooks + { yield }","512":" ensure","513":" singleton_context_hooks_host.run_after_context_hooks(example_group_instance)","514":" end"}},{"lineNumber":259,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example.rb","method":"run","code":{"256":" if + skipped?","257":" Pending.mark_pending! self, skip","258":" elsif + !RSpec.configuration.dry_run?","259":" with_around_and_singleton_context_hooks + do","260":" begin","261":" run_before_example","262":" RSpec.current_scope + = :example"}},{"lineNumber":646,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb","method":"block + in run_examples","code":{"643":" next if RSpec.world.wants_to_quit","644":" instance + = new(example.inspect_output)","645":" set_ivars(instance, before_context_ivars)","646":" succeeded + = example.run(instance, reporter)","647":" if !succeeded && reporter.fail_fast_limit_met?","648":" RSpec.world.wants_to_quit + = true","649":" end"}},{"lineNumber":642,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb","method":"map","code":{"639":"","640":" # + @private","641":" def self.run_examples(reporter)","642":" ordering_strategy.order(filtered_examples).map + do |example|","643":" next if RSpec.world.wants_to_quit","644":" instance + = new(example.inspect_output)","645":" set_ivars(instance, before_context_ivars)"}},{"lineNumber":642,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb","method":"run_examples","code":{"639":"","640":" # + @private","641":" def self.run_examples(reporter)","642":" ordering_strategy.order(filtered_examples).map + do |example|","643":" next if RSpec.world.wants_to_quit","644":" instance + = new(example.inspect_output)","645":" set_ivars(instance, before_context_ivars)"}},{"lineNumber":607,"file":"gems/rspec-core-3.13.0/lib/rspec/core/example_group.rb","method":"run","code":{"604":" begin","605":" RSpec.current_scope + = :before_context_hook","606":" run_before_context_hooks(new(''before(:context) + hook'')) if should_run_context_hooks","607":" result_for_this_group + = run_examples(reporter)","608":" results_for_descendants = ordering_strategy.order(children).map + { |child| child.run(reporter) }.all?","609":" result_for_this_group + && results_for_descendants","610":" rescue Pending::SkipDeclaredInExample + => ex"}},{"lineNumber":121,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"block + (3 levels) in run_specs","code":{"118":" return @configuration.failure_exit_code","119":" end","120":"","121":" example_groups.map + { |g| g.run(reporter) }.all?","122":" end","123":" end","124":""}},{"lineNumber":121,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"map","code":{"118":" return + @configuration.failure_exit_code","119":" end","120":"","121":" example_groups.map + { |g| g.run(reporter) }.all?","122":" end","123":" end","124":""}},{"lineNumber":121,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"block + (2 levels) in run_specs","code":{"118":" return @configuration.failure_exit_code","119":" end","120":"","121":" example_groups.map + { |g| g.run(reporter) }.all?","122":" end","123":" end","124":""}},{"lineNumber":2091,"file":"gems/rspec-core-3.13.0/lib/rspec/core/configuration.rb","method":"with_suite_hooks","code":{"2088":" begin","2089":" RSpec.current_scope + = :before_suite_hook","2090":" run_suite_hooks(\"a `before(:suite)` + hook\", @before_suite_hooks)","2091":" yield","2092":" ensure","2093":" RSpec.current_scope + = :after_suite_hook","2094":" run_suite_hooks(\"an `after(:suite)` + hook\", @after_suite_hooks)"}},{"lineNumber":116,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"block + in run_specs","code":{"113":" def run_specs(example_groups)","114":" examples_count + = @world.example_count(example_groups)","115":" examples_passed = @configuration.reporter.report(examples_count) + do |reporter|","116":" @configuration.with_suite_hooks do","117":" if + examples_count == 0 && @configuration.fail_if_no_examples","118":" return + @configuration.failure_exit_code","119":" end"}},{"lineNumber":74,"file":"gems/rspec-core-3.13.0/lib/rspec/core/reporter.rb","method":"report","code":{"71":" def + report(expected_example_count)","72":" start(expected_example_count)","73":" begin","74":" yield + self","75":" ensure","76":" finish","77":" end"}},{"lineNumber":115,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"run_specs","code":{"112":" # failed.","113":" def + run_specs(example_groups)","114":" examples_count = @world.example_count(example_groups)","115":" examples_passed + = @configuration.reporter.report(examples_count) do |reporter|","116":" @configuration.with_suite_hooks + do","117":" if examples_count == 0 && @configuration.fail_if_no_examples","118":" return + @configuration.failure_exit_code"}},{"lineNumber":89,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"run","code":{"86":" setup(err, + out)","87":" return @configuration.reporter.exit_early(exit_code) if + RSpec.world.wants_to_quit","88":"","89":" run_specs(@world.ordered_example_groups).tap + do","90":" persist_example_statuses","91":" end","92":" end"}},{"lineNumber":71,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"run","code":{"68":" if + options.options[:runner]","69":" options.options[:runner].call(options, + err, out)","70":" else","71":" new(options).run(err, out)","72":" end","73":" end","74":""}},{"lineNumber":45,"file":"gems/rspec-core-3.13.0/lib/rspec/core/runner.rb","method":"invoke","code":{"42":" # + code.","43":" def self.invoke","44":" disable_autorun!","45":" status + = run(ARGV, $stderr, $stdout).to_i","46":" exit(status) if status != + 0","47":" end","48":""}},{"lineNumber":4,"file":"gems/rspec-core-3.13.0/exe/rspec","method":"
","code":{"1":"#!/usr/bin/env + ruby","2":"","3":"require ''rspec/core''","4":"RSpec::Core::Runner.invoke"}},{"lineNumber":18,"file":"gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb","method":"load","code":{"15":"","16":" def + call","17":" ::RSpec.configuration.start_time = Time.now if defined?(::RSpec.configuration.start_time)","18":" load + Gem.bin_path(gem_name, exec_name)","19":" end","20":" end","21":""}},{"lineNumber":18,"file":"gems/spring-commands-rspec-1.0.4/lib/spring/commands/rspec.rb","method":"call","code":{"15":"","16":" def + call","17":" ::RSpec.configuration.start_time = Time.now if defined?(::RSpec.configuration.start_time)","18":" load + Gem.bin_path(gem_name, exec_name)","19":" end","20":" end","21":""}},{"lineNumber":38,"file":"gems/spring-4.2.1/lib/spring/command_wrapper.rb","method":"call","code":{"35":"","36":" def + call","37":" if command.respond_to?(:call)","38":" command.call","39":" else","40":" load + exec","41":" end"}},{"lineNumber":226,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"block + in serve","code":{"223":" invoke_after_fork_callbacks","224":" shush_backtraces","225":"","226":" command.call","227":" }","228":"","229":" disconnect_database"}},{"lineNumber":190,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"fork","code":{"187":" Rails.application.reloader.reload!","188":" end","189":"","190":" pid + = fork {","191":" # Make sure to close other clients otherwise their + graceful termination","192":" # will be impossible due to reference + from this fork.","193":" @clients.each_key { |c| c.close if c != client + }"}},{"lineNumber":190,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"serve","code":{"187":" Rails.application.reloader.reload!","188":" end","189":"","190":" pid + = fork {","191":" # Make sure to close other clients otherwise their + graceful termination","192":" # will be impossible due to reference + from this fork.","193":" @clients.each_key { |c| c.close if c != client + }"}},{"lineNumber":153,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"block + in run","code":{"150":" if terminating? || watcher_stale? || preload_failed?","151":" exit","152":" else","153":" serve + manager.recv_io(UNIXSocket)","154":" end","155":" end","156":" end"}},{"lineNumber":147,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"loop","code":{"144":" state + :running","145":" manager.puts","146":"","147":" loop do","148":" IO.select + [manager, @interrupt.first]","149":"","150":" if terminating? || watcher_stale? + || preload_failed?"}},{"lineNumber":147,"file":"gems/spring-4.2.1/lib/spring/application.rb","method":"run","code":{"144":" state + :running","145":" manager.puts","146":"","147":" loop do","148":" IO.select + [manager, @interrupt.first]","149":"","150":" if terminating? || watcher_stale? + || preload_failed?"}},{"lineNumber":25,"file":"gems/spring-4.2.1/lib/spring/application/boot.rb","method":"","code":{"19":" app.spawn_env,","20":" ].compact","21":" \"spring + app | #{attributes.join(\" | \")}\"","22":"end","23":"","24":"app.eager_preload + if ENV.delete(\"SPRING_PRELOAD\") == \"1\"","25":"app.run"}},{"lineNumber":1,"file":"-e","method":"
","code":null}]}],"featureFlags":[],"metaData":{"RSpec":{"file":"/home/maikel/code/openfoodnetwork/spec/services/alert_spec.rb"},"env":{}},"severity":"warning","severityReason":{"type":"handledException"},"unhandled":false,"user":{}}]}' + headers: + Bugsnag-Api-Key: + - "" + Bugsnag-Payload-Version: + - '4.0' + Bugsnag-Sent-At: + - '2024-11-20T04:51:11.998Z' + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - "*" + Bugsnag-Event-Id: + - 673d6ac0010cfda42d8e0000 + Date: + - Wed, 20 Nov 2024 04:51:12 GMT + Content-Length: + - '2' + Content-Type: + - text/plain; charset=utf-8 + Via: + - 1.1 google + Alt-Svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + body: + encoding: UTF-8 + string: OK + recorded_at: Wed, 20 Nov 2024 04:51:12 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/lib/open_food_network/error_logger_spec.rb b/spec/lib/open_food_network/error_logger_spec.rb deleted file mode 100644 index a2335ee1d9..0000000000 --- a/spec/lib/open_food_network/error_logger_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require 'open_food_network/error_logger' - -module OpenFoodNetwork - RSpec.describe ErrorLogger do - let(:error) { StandardError.new("Test") } - - it "notifies Bugsnag" do - expect(Bugsnag).to receive(:notify).with(error) - - ErrorLogger.notify(error) - end - end -end diff --git a/spec/services/alert_spec.rb b/spec/services/alert_spec.rb new file mode 100644 index 0000000000..2e8723fd8f --- /dev/null +++ b/spec/services/alert_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Alert do + around do |example| + original_config = nil + Bugsnag.configure do |config| + original_config = config.dup + config.api_key ||= "dummy-key" + config.notify_release_stages = ["test"] + config.delivery_method = :synchronous + end + + example.run + + Bugsnag.configure do |config| + config.api_key ||= original_config.api_key + config.notify_release_stages = original_config.notify_release_stages + config.delivery_method = original_config.delivery_method + end + end + + it "notifies Bugsnag" do + expect(Bugsnag).to receive(:notify).with("hey") + + Alert.raise("hey") + end + + it "adds context" do + pending "Bugsnag calls in CI" if ENV.fetch("CI", false) + + expect_any_instance_of(Bugsnag::Report).to receive(:add_metadata).with( + :order, { number: "ABC123" } + ) + expect_any_instance_of(Bugsnag::Report).to receive(:add_metadata).with( + :env, { referer: "example.com" } + ) + + Alert.raise( + "hey", + { order: { number: "ABC123" }, env: { referer: "example.com" } } + ) + end + + it "is compatible with Bugsnag API" do + pending "Bugsnag calls in CI" if ENV.fetch("CI", false) + + expect_any_instance_of(Bugsnag::Report).to receive(:add_metadata).with( + :order, { number: "ABC123" } + ) + + Alert.raise("hey") do |payload| + payload.add_metadata(:order, { number: "ABC123" }) + end + end + + it "sends ActiveRecord objects" do + pending "Bugsnag calls in CI" if ENV.fetch("CI", false) + + order = Spree::Order.new(number: "ABC123") + + expect_any_instance_of(Bugsnag::Report).to receive(:add_metadata).with( + "Spree::Order", hash_including("number" => "ABC123") + ) + + Alert.raise_with_record("Wrong order", order) + end + + it "notifies Bugsnag when ActiveRecord object is missing" do + pending "Bugsnag calls in CI" if ENV.fetch("CI", false) + + expect_any_instance_of(Bugsnag::Report).to receive(:add_metadata).with( + "NilClass", { record_was_nil: true } + ) + Alert.raise_with_record("Wrong order", nil) + end + + it "reaches the Bugsnag service for real", :vcr do + # You need to have a valid Bugsnag API key to record this test. + # And after recording, you need to check the Bugsnag account for the right + # data. + Alert.raise( + "Testing Bugsnag from RSpec", + { RSpec: { file: __FILE__ }, env: { BUGSNAG: ENV.fetch("BUGSNAG", nil) } } + ) + end +end diff --git a/spec/support/vcr_setup.rb b/spec/support/vcr_setup.rb index d9958751a6..ad44f367fa 100644 --- a/spec/support/vcr_setup.rb +++ b/spec/support/vcr_setup.rb @@ -11,6 +11,7 @@ VCR.configure do |config| # Filter sensitive environment variables %w[ + BUGSNAG_API_KEY STRIPE_INSTANCE_SECRET_KEY STRIPE_CUSTOMER STRIPE_ACCOUNT